clay-server 2.42.0 → 2.43.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -37,6 +37,7 @@ var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir,
37
37
  var { sendIPCCommand } = require("../lib/ipc");
38
38
  var { generateAuthToken } = require("../lib/server");
39
39
  var { enableMultiUser, disableMultiUser, hasAdmin, isMultiUser, getSetupCode } = require("../lib/users");
40
+ var clayStudioCert = require("../lib/clay-studio-cert");
40
41
 
41
42
  function openUrl(url) {
42
43
  try {
@@ -567,6 +568,12 @@ function getAllIPs() {
567
568
 
568
569
  function getBuiltinCert() {
569
570
  try {
571
+ // Prefer the cert fetched at runtime from the clay.studio endpoint (always
572
+ // current) over the copy baked into the package (expires ~90 days after
573
+ // publish). forkDaemon refreshes this cache before we get here.
574
+ var fetched = clayStudioCert.cachedCertFiles();
575
+ if (fetched) return { key: fetched.key, cert: fetched.cert, caRoot: null, builtin: true };
576
+
570
577
  var certDir = path.join(__dirname, "..", "lib", "certs");
571
578
  var keyPath = path.join(certDir, "privkey.pem");
572
579
  var certPath = path.join(certDir, "fullchain.pem");
@@ -1458,13 +1465,19 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
1458
1465
  var mkcertDetected = false;
1459
1466
 
1460
1467
  if (useHttps) {
1468
+ // Refresh the runtime cert cache from the clay.studio endpoint (best effort,
1469
+ // short timeout) so getBuiltinCert picks up the current auto-renewed cert
1470
+ // instead of the stale baked copy. Skipped when the user forces mkcert.
1471
+ if (!forceMkcert) {
1472
+ try { await clayStudioCert.refreshCache(5000); } catch (e) {}
1473
+ }
1461
1474
  var certPaths = ensureCerts(ip);
1462
1475
  if (certPaths) {
1463
1476
  hasTls = true;
1464
1477
  if (certPaths.builtin) hasBuiltinCert = true;
1465
1478
  if (certPaths.mkcertDetected) mkcertDetected = true;
1466
1479
  } else {
1467
- log(sym.warn + " " + a.yellow + "HTTPS unavailable" + a.reset + a.dim + " · mkcert not installed" + a.reset);
1480
+ log(sym.warn + " " + a.yellow + "HTTPS unavailable" + a.reset + a.dim + " · could not obtain a certificate" + a.reset);
1468
1481
  }
1469
1482
  }
1470
1483
 
@@ -1640,6 +1653,12 @@ async function devMode(mode, keepAwake, existingPinHash, wantOsUsers) {
1640
1653
  var mkcertDetected = false;
1641
1654
 
1642
1655
  if (useHttps) {
1656
+ // Refresh the runtime cert cache from the clay.studio endpoint (best effort,
1657
+ // short timeout) so getBuiltinCert picks up the current auto-renewed cert
1658
+ // instead of the stale baked copy. Skipped when the user forces mkcert.
1659
+ if (!forceMkcert) {
1660
+ try { await clayStudioCert.refreshCache(5000); } catch (e) {}
1661
+ }
1643
1662
  var certPaths = ensureCerts(ip);
1644
1663
  if (certPaths) {
1645
1664
  hasTls = true;
@@ -1851,6 +1870,9 @@ async function devMode(mode, keepAwake, existingPinHash, wantOsUsers) {
1851
1870
  // ==============================
1852
1871
  async function restartDaemonWithTLS(config, callback) {
1853
1872
  var ip = getLocalIP();
1873
+ if (!forceMkcert) {
1874
+ try { await clayStudioCert.refreshCache(5000); } catch (e) {}
1875
+ }
1854
1876
  var certPaths = ensureCerts(ip);
1855
1877
  if (!certPaths) {
1856
1878
  callback(config);
@@ -1,47 +1,81 @@
1
1
  -----BEGIN CERTIFICATE-----
2
- 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
- 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
- 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
- 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
- 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
- Gz8+XEewGw6jggIUMIICEDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB
8
- 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
- +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
- JjAkMCIGCCsGAQUFBzAChhZodHRwOi8vZTguaS5sZW5jci5vcmcvMBoGA1UdEQQT
11
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
21
+ AQ==
21
22
  -----END CERTIFICATE-----
22
23
  -----BEGIN CERTIFICATE-----
23
- MIIEVjCCAj6gAwIBAgIQY5WTY8JOcIJxWRi/w9ftVjANBgkqhkiG9w0BAQsFADBP
24
+ MIICizCCAhGgAwIBAgIQXd1w3TH4AchcGGp6BLgK/jAKBggqhkjOPQQDAzAuMQsw
25
+ CQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTAeFw0y
26
+ NTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYwFAYD
27
+ VQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZRTEwdjAQBgcqhkjOPQIBBgUr
28
+ gQQAIgNiAAQHZVB1/mimla2hfSurylScjPMZaOJXLz/NnAc2sylm8WDyhU9Ccp+z
29
+ ASQi5vSwGGJjSGklkD9fdPR8GpyDIOIjCEfrnbt/v+ZSEPLLEGbaM6EccDbN7p9x
30
+ teIm2Avf+ryjge4wgeswDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF
31
+ BwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLsgykcL/tflnPmPCSqj
32
+ jDdFsbzYMB8GA1UdIwQYMBaAFKPIJlqOoUzQNWP8myPIOq5W809WMDIGCCsGAQUF
33
+ BwEBBCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3llLmkubGVuY3Iub3JnLzATBgNV
34
+ HSAEDDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veWUuYy5s
35
+ ZW5jci5vcmcvMAoGCCqGSM49BAMDA2gAMGUCMQDgjUEahFT/h3DRakqiPZpLvPgf
36
+ Zwkt6K2EOMmh1nvEzl83eMLYcod4GCl3b0J1Nn0CMBNYmEQJb4CEG5WoOe7aRn/L
37
+ VKu6saHmHEynI7ysIPd8zQsK1HdmhlHKlw9Z5GpGvA==
38
+ -----END CERTIFICATE-----
39
+ -----BEGIN CERTIFICATE-----
40
+ MIICpjCCAiugAwIBAgIRAIchZfw0tuX7qK3Vs3BftTowCgYIKoZIzj0EAwMwTzEL
41
+ MAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNo
42
+ IEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDIwHhcNMjYwNTEzMDAwMDAwWhcN
43
+ MzIwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4G
44
+ A1UEAxMHUm9vdCBZRTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDwS/6vhrcVqcbBo
45
+ +wgdI3fwn9x7DNJJOY/lTOti0vkwuRN87RhEhTH17E7XyFjWsPYhIPt/wzOqxTd2
46
+ b+4ZJNy9ID04YywF9U5zasDVyGSNErVNtz8uSGh5izW87j77GaOB6zCB6DAOBgNV
47
+ HQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB
48
+ /zAdBgNVHQ4EFgQUo8gmWo6hTNA1Y/ybI8g6rlbzT1YwHwYDVR0jBBgwFoAUfEKW
49
+ rt5LSDv6kviejM9ti6lyN5UwMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAChhZo
50
+ dHRwOi8veDIuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcGA1Ud
51
+ HwQgMB4wHKAaoBiGFmh0dHA6Ly94Mi5jLmxlbmNyLm9yZy8wCgYIKoZIzj0EAwMD
52
+ aQAwZgIxAMU19WCtmxVND8UHBZRoma49Z7jPs64Dma0eTu1OChVbB/2J7GV3nvYK
53
+ Ax54uk1G9QIxAO0miLVJu8PLNiXXXkiE/gsK3CTRTF/aeo4bMX42Zw40csRU6AC2
54
+ 6hSW1/IWaas6dg==
55
+ -----END CERTIFICATE-----
56
+ -----BEGIN CERTIFICATE-----
57
+ MIIEcDCCAligAwIBAgIQbI8dxyfHEX97r4U6yYD5zTANBgkqhkiG9w0BAQsFADBP
24
58
  MQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFy
25
- Y2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNDAzMTMwMDAwMDBa
26
- Fw0yNzAzMTIyMzU5NTlaMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBF
27
- bmNyeXB0MQswCQYDVQQDEwJFODB2MBAGByqGSM49AgEGBSuBBAAiA2IABNFl8l7c
28
- S7QMApzSsvru6WyrOq44ofTUOTIzxULUzDMMNMchIJBwXOhiLxxxs0LXeb5GDcHb
29
- R6EToMffgSZjO9SNHfY9gjMy9vQr5/WWOrQTZxh7az6NSNnq3u2ubT6HTKOB+DCB
30
- 9TAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB
31
- MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFI8NE6L2Ln7RUGwzGDhdWY4j
32
- cpHKMB8GA1UdIwQYMBaAFHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEB
33
- BCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzATBgNVHSAE
34
- DDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veDEuYy5sZW5j
35
- ci5vcmcvMA0GCSqGSIb3DQEBCwUAA4ICAQBnE0hGINKsCYWi0Xx1ygxD5qihEjZ0
36
- RI3tTZz1wuATH3ZwYPIp97kWEayanD1j0cDhIYzy4CkDo2jB8D5t0a6zZWzlr98d
37
- AQFNh8uKJkIHdLShy+nUyeZxc5bNeMp1Lu0gSzE4McqfmNMvIpeiwWSYO9w82Ob8
38
- otvXcO2JUYi3svHIWRm3+707DUbL51XMcY2iZdlCq4Wa9nbuk3WTU4gr6LY8MzVA
39
- aDQG2+4U3eJ6qUF10bBnR1uuVyDYs9RhrwucRVnfuDj29CMLTsplM5f5wSV5hUpm
40
- Uwp/vV7M4w4aGunt74koX71n4EdagCsL/Yk5+mAQU0+tue0JOfAV/R6t1k+Xk9s2
41
- HMQFeoxppfzAVC04FdG9M+AC2JWxmFSt6BCuh3CEey3fE52Qrj9YM75rtvIjsm/1
42
- Hl+u//Wqxnu1ZQ4jpa+VpuZiGOlWrqSP9eogdOhCGisnyewWJwRQOqK16wiGyZeR
43
- xs/Bekw65vwSIaVkBruPiTfMOo0Zh4gVa8/qJgMbJbyrwwG97z/PRgmLKCDl8z3d
44
- tA0Z7qq7fta0Gl24uyuB05dqI5J1LvAzKuWdIjT1tP8qCoxSE/xpix8hX2dt3h+/
45
- jujUgFPFZ0EVZ0xSyBNRF3MboGZnYXFUxpNjTWPKpagDHJQmqrAcDmWJnMsFY3jS
46
- u1igv3OefnWjSQ==
59
+ Y2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNjA1MTMwMDAwMDBa
60
+ Fw0zMjA5MDIyMzU5NTlaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5l
61
+ dCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgy
62
+ MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H
63
+ ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7
64
+ AlF9ItgKbppbd9/w+kHsOdx1ymgHDB/qo4H1MIHyMA4GA1UdDwEB/wQEAwIBBjAd
65
+ BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAd
66
+ BgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwHwYDVR0jBBgwFoAUebRZ5nu2
67
+ 5eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAChhZodHRw
68
+ Oi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcGA1UdHwQg
69
+ MB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcNAQELBQAD
70
+ ggIBAD2/e9frmMxNpCV03qUHegg+MV2wz9644YoXdqtH8RyWYcBO7xfjjGEXdU1e
71
+ /o0OkEFiynUCOSIk/vLLo7ttz6CPAeNlWfC0XNkoGeWgK6jjXvozBaGuGH5n0Ufo
72
+ shMeWTuURqNN5G00sSXDTBrpp2+mgvdZQjb8K11TYMA25QA+YHNfbIEL0BniAhKS
73
+ 2gsnJjSzrdZLI+EZ7SEyqdR2rkjd1KutLDU+n3TFyxjniZVGur4YlhMP3mY/dV95
74
+ IruAkkjOZier6hGBdEgZXXvaCz9u9iVEadsIE75pAGL8oHV5vxdARDiotRpul1IN
75
+ /UZwzAbrfUFcw1HkAcYD/mlZfnQ2ieCF2MS7j3Vhv7JPDKp45fmykmzYNSrumRW0
76
+ upFFKDBOoF7hsOb7oLyHS+Uft6jOUfOrogj8YUx38hKb2K20r42OgsSdDdxdeYWc
77
+ MS3Sb6mwJeSZEYxJ2gaXnDSPaKhhrNkYwljyVQyr4Nq+MEJytXNTnHqaAcrNwZlV
78
+ pcJL1KBnMrMjP7eanvUwL3FYj3cF17jtboLt7gLoi4+2rWZFvn+w54jmd/FIuhhZ
79
+ cEaU/wvU6BUNMtcVquVGHp7itQeDth5j+XL3j4WJ2SABwzUl6OeYdgpIt/ITZa+p
80
+ TT0mQ/r5XyA4MEAiabn7XJjvCERlF2dcn2wqJw+CreTkkQ2R
47
81
  -----END CERTIFICATE-----
@@ -1,5 +1,5 @@
1
1
  -----BEGIN PRIVATE KEY-----
2
- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgtwBpQgcr1N5kEPEv
3
- Rz5JCBMpDQHD8U44HlzB9bLvQAihRANCAARIQA9YbakYPJ+3r35QWdV+1GgnqgRn
4
- LxvzDbZQCndVVX1Rhbmp8lI5LFffvkdRLii4RDzN5aC/9xs/PlxHsBsO
2
+ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgeObHdfttml/RvJo1
3
+ pfW5sNawWozxsJYt7wLg6CpXM8KhRANCAASrxxs5PE+8vMltWI5D+dynbZexsgeg
4
+ G4LmDP2+ZRt4OIWJA4e8qR39LP0W8IRtvhpjM0ifyImZOFfgxX/NgtCB
5
5
  -----END PRIVATE KEY-----
@@ -0,0 +1,112 @@
1
+ // Runtime fetch of the *.d.clay.studio wildcard certificate.
2
+ //
3
+ // Clay serves browser-trusted HTTPS for local instances via a Let's Encrypt
4
+ // wildcard cert for *.d.clay.studio. Historically that cert was baked into the
5
+ // npm package (lib/certs/), so every ~90 days the shipped copy expired and all
6
+ // installed clients silently dropped to HTTP until a new release. This module
7
+ // fetches the CURRENT cert from a small endpoint (served off the same VM that
8
+ // auto-renews it) and caches it under ~/.clay/certs, decoupling cert freshness
9
+ // from npm releases. The baked cert remains a fallback for offline starts.
10
+ //
11
+ // The wildcard private key is public by design: every client needs it to
12
+ // terminate TLS for its own <dashed-ip>.d.clay.studio hostname. This is the
13
+ // same trade-off sslip.io-style zero-config HTTPS already accepts, so fetching
14
+ // it at runtime exposes nothing that the npm tarball did not already.
15
+
16
+ var fs = require("fs");
17
+ var path = require("path");
18
+ var https = require("https");
19
+ var { execFileSync } = require("child_process");
20
+ var { REAL_HOME } = require("./config");
21
+
22
+ var DEFAULT_URL = "https://cert.d.clay.studio/clay-cert.json";
23
+ var EXPIRY_SKEW_MS = 7 * 24 * 60 * 60 * 1000;
24
+
25
+ function certUrl() {
26
+ return process.env.CLAY_CERT_URL || DEFAULT_URL;
27
+ }
28
+
29
+ function cacheDir() {
30
+ var home = process.env.CLAY_HOME || process.env.CLAUDE_RELAY_HOME || path.join(REAL_HOME, ".clay");
31
+ return path.join(home, "certs");
32
+ }
33
+
34
+ function fullchainPath() { return path.join(cacheDir(), "clay-studio-fullchain.pem"); }
35
+ function privkeyPath() { return path.join(cacheDir(), "clay-studio-privkey.pem"); }
36
+
37
+ // notAfter (ms) of a PEM file, or 0 if it can't be read.
38
+ function certExpiryMs(certPath) {
39
+ try {
40
+ var out = execFileSync("openssl", ["x509", "-in", certPath, "-noout", "-enddate"], { encoding: "utf8" });
41
+ var m = out.match(/notAfter=(.+)/);
42
+ if (m) return new Date(m[1]).getTime();
43
+ } catch (e) {}
44
+ return 0;
45
+ }
46
+
47
+ // Returns { key, cert } cached file paths if a previously-fetched cert exists
48
+ // and is not expiring within 7 days; otherwise null. Synchronous.
49
+ function cachedCertFiles() {
50
+ var cert = fullchainPath();
51
+ var key = privkeyPath();
52
+ if (!fs.existsSync(cert) || !fs.existsSync(key)) return null;
53
+ var expiry = certExpiryMs(cert);
54
+ if (expiry && expiry - Date.now() < EXPIRY_SKEW_MS) return null;
55
+ return { key: key, cert: cert };
56
+ }
57
+
58
+ function isPem(s) {
59
+ return typeof s === "string" && s.indexOf("-----BEGIN") !== -1;
60
+ }
61
+
62
+ // Best-effort fetch of the current cert from the endpoint, written atomically
63
+ // to the cache. Resolves true on success, false on any failure. Never rejects,
64
+ // so callers can `await` it unconditionally on startup.
65
+ function refreshCache(timeoutMs) {
66
+ return new Promise(function (resolve) {
67
+ var done = false;
68
+ function finish(ok) { if (!done) { done = true; resolve(ok); } }
69
+ var req;
70
+ try {
71
+ req = https.get(certUrl(), { timeout: timeoutMs || 5000 }, function (res) {
72
+ if (res.statusCode !== 200) { res.resume(); return finish(false); }
73
+ var body = "";
74
+ res.setEncoding("utf8");
75
+ res.on("data", function (c) {
76
+ body += c;
77
+ if (body.length > 1024 * 1024) { req.destroy(); finish(false); }
78
+ });
79
+ res.on("end", function () {
80
+ try {
81
+ var obj = JSON.parse(body);
82
+ if (!isPem(obj.fullchain) || !isPem(obj.privkey)) return finish(false);
83
+ var dir = cacheDir();
84
+ fs.mkdirSync(dir, { recursive: true });
85
+ var ctmp = fullchainPath() + ".tmp";
86
+ var ktmp = privkeyPath() + ".tmp";
87
+ fs.writeFileSync(ctmp, obj.fullchain);
88
+ fs.writeFileSync(ktmp, obj.privkey);
89
+ try { fs.chmodSync(ktmp, 0o600); } catch (e) {}
90
+ // Don't overwrite a good cache with a near-expired cert.
91
+ var expiry = certExpiryMs(ctmp);
92
+ if (expiry && expiry - Date.now() < EXPIRY_SKEW_MS) {
93
+ try { fs.unlinkSync(ctmp); fs.unlinkSync(ktmp); } catch (e) {}
94
+ return finish(false);
95
+ }
96
+ fs.renameSync(ctmp, fullchainPath());
97
+ fs.renameSync(ktmp, privkeyPath());
98
+ finish(true);
99
+ } catch (e) { finish(false); }
100
+ });
101
+ });
102
+ req.on("timeout", function () { req.destroy(); finish(false); });
103
+ req.on("error", function () { finish(false); });
104
+ } catch (e) { finish(false); }
105
+ });
106
+ }
107
+
108
+ module.exports = {
109
+ certUrl: certUrl,
110
+ cachedCertFiles: cachedCertFiles,
111
+ refreshCache: refreshCache,
112
+ };
package/lib/daemon.js CHANGED
@@ -78,8 +78,17 @@ if (config.tls) {
78
78
  var userKeyPath = path.join(certDir, "key.pem");
79
79
  var userCertPath = path.join(certDir, "cert.pem");
80
80
 
81
+ // Cert fetched at runtime from the clay.studio endpoint (always current),
82
+ // cached by the CLI before forking us. Prefer it over the baked copy.
83
+ var fetched = null;
84
+ try { fetched = require("./clay-studio-cert").cachedCertFiles(); } catch (e) {}
85
+
81
86
  var keyPath, certPath;
82
- if (config.builtinCert !== false && fs.existsSync(builtinKeyPath) && fs.existsSync(builtinCertPath)) {
87
+ if (config.builtinCert !== false && fetched) {
88
+ keyPath = fetched.key;
89
+ certPath = fetched.cert;
90
+ config.builtinCert = true;
91
+ } else if (config.builtinCert !== false && fs.existsSync(builtinKeyPath) && fs.existsSync(builtinCertPath)) {
83
92
  keyPath = builtinKeyPath;
84
93
  certPath = builtinCertPath;
85
94
  config.builtinCert = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.42.0",
3
+ "version": "2.43.0-beta.1",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",