clay-server 2.17.0-beta.1 → 2.17.0-beta.3

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 CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/clay-server)](https://www.npmjs.com/package/clay-server) [![npm downloads](https://img.shields.io/npm/dw/clay-server)](https://www.npmjs.com/package/clay-server) [![GitHub stars](https://img.shields.io/github/stars/chadbyte/clay)](https://github.com/chadbyte/clay) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/chadbyte/clay/blob/main/LICENSE)
10
10
 
11
- Clay gives Claude Code a browser UI that runs on any device. Use it from your phone, run it on macOS, Windows, or Linux. Invite teammates, manage multiple projects from one sidebar, and get push notifications when Claude needs you. Built on the official Claude Agent SDK, not a terminal parser. Your machine is the server. No cloud relay in between, no extra network surface.
11
+ Clay gives Claude Code a browser UI that runs on any device. Use it from your phone, run it on macOS, Windows, or Linux. Invite teammates, manage multiple projects from one sidebar, and get push notifications when Claude needs you. HTTPS and push notifications work out of the box with zero config. Built on the official Claude Agent SDK, not a terminal parser. Your machine is the server. No cloud relay in between, no extra network surface.
12
12
 
13
13
  ---
14
14
 
@@ -98,7 +98,7 @@ Take it further with Ralph Loop, an autonomous coding loop built into Clay. The
98
98
 
99
99
  Your data flows directly from your machine to the Anthropic API, exactly as it does when you use the CLI. Clay adds a browser layer on top, not a middleman.
100
100
 
101
- PIN authentication, per-project/session permissions, and HTTPS are supported by default. For local network use, this is sufficient. For remote access, we recommend a VPN like Tailscale.
101
+ HTTPS is enabled by default with a builtin certificate. PIN authentication and per-project/session permissions are built in. For local network use, this is sufficient. For remote access, we recommend a VPN like Tailscale.
102
102
 
103
103
  ---
104
104
 
@@ -166,20 +166,32 @@ Yes. Create as many as you need. A code reviewer, a writing partner, a project m
166
166
 
167
167
  ---
168
168
 
169
- ## HTTPS for Push
169
+ ## HTTPS
170
170
 
171
- Everything works out of the box. Only push notifications require HTTPS.
171
+ HTTPS is enabled by default using a builtin wildcard certificate for `*.d.clay.studio`. No setup required. Available from `v2.17.0-beta.2`. Your browser connects to a URL like:
172
172
 
173
- Set it up once with [mkcert](https://github.com/FiloSottile/mkcert):
173
+ ```
174
+ https://192-168-1-50.d.clay.studio:2633
175
+ ```
176
+
177
+ The domain resolves to your local IP. All traffic stays on your network. See [clay-dns](clay-dns/) for details on how this works.
178
+
179
+ Push notifications require HTTPS, so they work out of the box with this setup. Install Clay as a PWA on your device to receive them.
180
+
181
+ <details>
182
+ <summary><strong>Alternative: local certificate with mkcert</strong></summary>
183
+
184
+ If you prefer to use a locally generated certificate (e.g. air-gapped environments where DNS is unavailable):
174
185
 
175
186
  ```bash
176
187
  brew install mkcert
177
188
  mkcert -install
189
+ npx clay-server --local-cert
178
190
  ```
179
191
 
180
- Certificates are auto-generated. The setup wizard handles the rest.
192
+ This generates a self-signed certificate trusted by your machine. The setup wizard will guide you through installing the CA on other devices.
181
193
 
182
- If push registration fails: check that your browser trusts the HTTPS certificate and that your phone can reach the server address.
194
+ </details>
183
195
 
184
196
  ---
185
197
 
@@ -192,6 +204,7 @@ npx clay-server --yes # Skip interactive prompts (use defaults)
192
204
  npx clay-server -y --pin 123456
193
205
  # Non-interactive + PIN (for scripts/CI)
194
206
  npx clay-server --no-https # Disable HTTPS
207
+ npx clay-server --local-cert # Use local certificate (mkcert) instead of builtin
195
208
  npx clay-server --no-update # Skip update check
196
209
  npx clay-server --debug # Enable debug panel
197
210
  npx clay-server --add . # Add current directory to running daemon
package/bin/cli.js CHANGED
@@ -599,12 +599,6 @@ function toClayStudioUrl(ip, port, protocol) {
599
599
  }
600
600
 
601
601
  function ensureCerts(ip) {
602
- // Check builtin cert first (unless --local-cert flag is set)
603
- if (!forceMkcert) {
604
- var builtin = getBuiltinCert();
605
- if (builtin) return builtin;
606
- }
607
-
608
602
  var homeDir = os.homedir();
609
603
  var certDir = path.join(process.env.CLAY_HOME || path.join(homeDir, ".clay"), "certs");
610
604
  var keyPath = path.join(certDir, "key.pem");
@@ -619,14 +613,18 @@ function ensureCerts(ip) {
619
613
  fs.copyFileSync(legacyCert, certPath);
620
614
  }
621
615
 
616
+ var mkcertInstalled = hasMkcert();
617
+
622
618
  var caRoot = null;
623
- try {
624
- caRoot = path.join(
625
- execSync("mkcert -CAROOT", { encoding: "utf8" }).trim(),
626
- "rootCA.pem"
627
- );
628
- if (!fs.existsSync(caRoot)) caRoot = null;
629
- } catch (e) {}
619
+ if (mkcertInstalled) {
620
+ try {
621
+ caRoot = path.join(
622
+ execSync("mkcert -CAROOT", { encoding: "utf8" }).trim(),
623
+ "rootCA.pem"
624
+ );
625
+ if (!fs.existsSync(caRoot)) caRoot = null;
626
+ } catch (e) {}
627
+ }
630
628
 
631
629
  // Collect all IPv4 addresses (Tailscale + LAN)
632
630
  var allIPs = getAllIPs();
@@ -644,24 +642,39 @@ function ensureCerts(ip) {
644
642
  }
645
643
  }
646
644
  } catch (e) { needRegen = true; }
647
- if (!needRegen) return { key: keyPath, cert: certPath, caRoot: caRoot };
645
+ if (!needRegen) {
646
+ return { key: keyPath, cert: certPath, caRoot: caRoot, mkcertDetected: mkcertInstalled && !forceMkcert };
647
+ }
648
648
  }
649
649
 
650
- fs.mkdirSync(certDir, { recursive: true });
650
+ // mkcert installed: generate local cert (legacy behavior)
651
+ if (mkcertInstalled) {
652
+ fs.mkdirSync(certDir, { recursive: true });
653
+
654
+ var domains = ["localhost", "127.0.0.1", "::1"];
655
+ for (var i = 0; i < allIPs.length; i++) {
656
+ if (domains.indexOf(allIPs[i]) === -1) domains.push(allIPs[i]);
657
+ }
651
658
 
652
- var domains = ["localhost", "127.0.0.1", "::1"];
653
- for (var i = 0; i < allIPs.length; i++) {
654
- if (domains.indexOf(allIPs[i]) === -1) domains.push(allIPs[i]);
659
+ try {
660
+ var mkcertArgs = ["-key-file", keyPath, "-cert-file", certPath].concat(domains);
661
+ execFileSync("mkcert", mkcertArgs, { stdio: "pipe" });
662
+ } catch (err) {
663
+ // mkcert generation failed, fall through to builtin
664
+ }
665
+
666
+ if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
667
+ return { key: keyPath, cert: certPath, caRoot: caRoot, mkcertDetected: !forceMkcert };
668
+ }
655
669
  }
656
670
 
657
- try {
658
- var mkcertArgs = ["-key-file", keyPath, "-cert-file", certPath].concat(domains);
659
- execFileSync("mkcert", mkcertArgs, { stdio: "pipe" });
660
- } catch (err) {
661
- return null;
671
+ // Fallback: builtin cert (unless --local-cert forces mkcert-only)
672
+ if (!forceMkcert) {
673
+ var builtin = getBuiltinCert();
674
+ if (builtin) return builtin;
662
675
  }
663
676
 
664
- return { key: keyPath, cert: certPath, caRoot: caRoot };
677
+ return null;
665
678
  }
666
679
 
667
680
  // --- Logo ---
@@ -1436,12 +1449,14 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
1436
1449
  var ip = getLocalIP();
1437
1450
  var hasTls = false;
1438
1451
  var hasBuiltinCert = false;
1452
+ var mkcertDetected = false;
1439
1453
 
1440
1454
  if (useHttps) {
1441
1455
  var certPaths = ensureCerts(ip);
1442
1456
  if (certPaths) {
1443
1457
  hasTls = true;
1444
1458
  if (certPaths.builtin) hasBuiltinCert = true;
1459
+ if (certPaths.mkcertDetected) mkcertDetected = true;
1445
1460
  } else {
1446
1461
  log(sym.warn + " " + a.yellow + "HTTPS unavailable" + a.reset + a.dim + " · mkcert not installed" + a.reset);
1447
1462
  }
@@ -1516,6 +1531,7 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
1516
1531
  pinHash: mode === "multi" && cliPin ? generateAuthToken(cliPin) : null,
1517
1532
  tls: hasTls,
1518
1533
  builtinCert: hasBuiltinCert,
1534
+ mkcertDetected: mkcertDetected,
1519
1535
  debug: debugMode,
1520
1536
  keepAwake: keepAwake,
1521
1537
  dangerouslySkipPermissions: dangerouslySkipPermissions,
@@ -1595,6 +1611,8 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
1595
1611
  : protocol + "://" + ip + ":" + config.port;
1596
1612
  console.log(" " + sym.done + " Daemon started (PID " + config.pid + ")");
1597
1613
  console.log(" " + sym.done + " " + url);
1614
+ if (config.builtinCert) console.log(" " + sym.done + " d.clay.studio is only used for HTTPS certificates. All traffic stays on your local network. https://github.com/chadbyte/clay/tree/main/clay-dns");
1615
+ if (config.mkcertDetected) console.log(" " + sym.warn + " mkcert detected. Uninstall mkcert to use builtin cert, or pass --local-cert to suppress this warning.");
1598
1616
  console.log(" " + sym.done + " Headless mode — exiting CLI");
1599
1617
  process.exit(0);
1600
1618
  return;
@@ -1611,12 +1629,14 @@ async function devMode(mode, keepAwake, existingPinHash) {
1611
1629
  var ip = getLocalIP();
1612
1630
  var hasTls = false;
1613
1631
  var hasBuiltinCert = false;
1632
+ var mkcertDetected = false;
1614
1633
 
1615
1634
  if (useHttps) {
1616
1635
  var certPaths = ensureCerts(ip);
1617
1636
  if (certPaths) {
1618
1637
  hasTls = true;
1619
1638
  if (certPaths.builtin) hasBuiltinCert = true;
1639
+ if (certPaths.mkcertDetected) mkcertDetected = true;
1620
1640
  }
1621
1641
  }
1622
1642
 
@@ -1680,6 +1700,7 @@ async function devMode(mode, keepAwake, existingPinHash) {
1680
1700
  pinHash: existingPinHash || null,
1681
1701
  tls: hasTls,
1682
1702
  builtinCert: hasBuiltinCert,
1703
+ mkcertDetected: mkcertDetected,
1683
1704
  debug: true,
1684
1705
  keepAwake: keepAwake || false,
1685
1706
  dangerouslySkipPermissions: dangerouslySkipPermissions,
@@ -1829,6 +1850,7 @@ async function restartDaemonWithTLS(config, callback) {
1829
1850
  return;
1830
1851
  }
1831
1852
  var hasBuiltinCert = !!(certPaths && certPaths.builtin);
1853
+ var mkcertDetected = !!(certPaths && certPaths.mkcertDetected);
1832
1854
 
1833
1855
  // Shut down old daemon
1834
1856
  stopDaemonWatcher();
@@ -1853,6 +1875,7 @@ async function restartDaemonWithTLS(config, callback) {
1853
1875
  pinHash: config.pinHash || null,
1854
1876
  tls: true,
1855
1877
  builtinCert: hasBuiltinCert,
1878
+ mkcertDetected: mkcertDetected,
1856
1879
  debug: config.debug || false,
1857
1880
  keepAwake: config.keepAwake || false,
1858
1881
  dangerouslySkipPermissions: config.dangerouslySkipPermissions || false,
@@ -1951,6 +1974,7 @@ function showMainMenu(config, ip) {
1951
1974
  function afterQr() {
1952
1975
  // Status line
1953
1976
  log(" " + a.dim + "clay" + a.reset + " " + a.dim + "v" + currentVersion + a.reset + a.dim + " — " + url + a.reset);
1977
+ if (config.builtinCert) log(" " + a.dim + "d.clay.studio is only used for HTTPS certificates. All traffic stays on your local network. https://github.com/chadbyte/clay/tree/main/clay-dns" + a.reset);
1954
1978
  var parts = [];
1955
1979
  parts.push(a.bold + projs.length + a.reset + a.dim + (projs.length === 1 ? " project" : " projects"));
1956
1980
  parts.push(a.reset + a.bold + totalSessions + a.reset + a.dim + (totalSessions === 1 ? " session" : " sessions"));
@@ -1961,6 +1985,15 @@ function showMainMenu(config, ip) {
1961
1985
  log(" Press " + a.bold + "o" + a.reset + " to open in browser");
1962
1986
  log("");
1963
1987
 
1988
+ if (config.mkcertDetected) {
1989
+ log(" " + sym.warn + " " + a.yellow + "mkcert detected." + a.reset + " Clay now ships with a builtin HTTPS certificate.");
1990
+ log(" " + a.dim + "Uninstall mkcert to use it. No more CA setup on each device." + a.reset);
1991
+ log(" " + a.dim + " brew uninstall mkcert (macOS)" + a.reset);
1992
+ log(" " + a.dim + " sudo apt remove mkcert (Linux)" + a.reset);
1993
+ log(" " + a.dim + "Or pass --local-cert to keep using mkcert without this warning." + a.reset);
1994
+ log("");
1995
+ }
1996
+
1964
1997
  showMenuItems();
1965
1998
  }
1966
1999
 
@@ -2725,6 +2758,7 @@ var currentVersion = require("../package.json").version;
2725
2758
  : protocol + "://" + ip + ":" + config.port;
2726
2759
  console.log(" " + sym.done + " Daemon already running (PID " + config.pid + ")");
2727
2760
  console.log(" " + sym.done + " " + url);
2761
+ if (config.builtinCert) console.log(" " + sym.done + " d.clay.studio is only used for HTTPS certificates. All traffic stays on your local network. https://github.com/chadbyte/clay/tree/main/clay-dns");
2728
2762
  process.exit(0);
2729
2763
  return;
2730
2764
  }
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 WebSocket upgrade requests and API/data endpoints
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.17.0-beta.1",
3
+ "version": "2.17.0-beta.3",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",