clay-server 2.19.0 → 2.20.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/README.md CHANGED
@@ -1,10 +1,12 @@
1
- # Clay
1
+ <p align="center">
2
+ <img src="media/logo/icon-full-banded-256-transparent.png" alt="Clay" />
3
+ </p>
2
4
 
3
- <h3 align="center">Claude Code in your browser. Bring your team, or build one.</h3>
5
+ <h3 align="center">Your AI dev team. Not another coding agent.</h3>
4
6
 
5
7
  [![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)
6
8
 
7
- Everything Claude Code does, Clay does in your browser. Plus multi-session, file browser, scheduled agents, mobile notifications, and more. Invite your team to work together in the same session, or build an AI team from scratch. **Your machine is the server.** No cloud relay, no middleman.
9
+ AI teammates who remember your decisions, challenge your thinking, and grow with your codebase. Runs on your machine.
8
10
 
9
11
  ```bash
10
12
  npx clay-server
@@ -15,78 +17,64 @@ npx clay-server
15
17
 
16
18
  ## What you get
17
19
 
18
- ### Everything the CLI does
19
-
20
- Your CLI sessions, your CLAUDE.md rules, your MCP servers. **All of it works in Clay as-is.** Pick up a CLI session in the browser, or continue a browser session in the CLI. Same SDK, same tools, same results.
21
-
22
- <p align="center">
23
- <img src="media/split.gif" alt="split-screen workflow" width="700">
24
- </p>
25
-
26
- ### Everything the CLI doesn't
20
+ ### Debate before you decide
27
21
 
28
- **Multiple agents, multiple projects, at the same time.** Switch between them in the sidebar. Browse project files live while the agent works, with syntax highlighting for 180+ languages. Mermaid diagrams render as diagrams. Tables render as tables.
22
+ <!-- TODO: debate.gif -->
29
23
 
30
- **Schedule agents with cron**, or let them run autonomously with **Ralph Loop**. Your phone buzzes when Claude needs approval, finishes a task, or hits an error. Install as a **PWA for push notifications**. Close your laptop, sessions keep running.
24
+ Let your Mates challenge each other. Set up a debate. Pick a moderator and panelists, give them a topic, and let them go. You can raise your hand to interject.
31
25
 
32
- <p align="center">
33
- <img src="media/phone.gif" alt="Clay on phone" width="280">
34
- </p>
26
+ "REST vs GraphQL for the new API?" "Monorepo or separate repos?" "This architecture won't scale past 10k users. Here's why." Get opposing perspectives before you commit.
35
27
 
36
- ### Bring your whole team
28
+ ### Build your team with Mates
37
29
 
38
- **One API key runs the whole workspace.** Invite teammates, set permissions per person, per project, per session. A designer reports a bug in plain language. A junior dev works with guardrails. If someone gets stuck, **jump into their session** to help in real time.
30
+ <!-- TODO: mates.gif -->
39
31
 
40
- Add a CLAUDE.md and the AI operates within those rules: explains technical terms simply, escalates risky operations to seniors, summarizes changes in plain words. Real-time presence shows who's where.
32
+ Mates are AI teammates shaped through real conversation, built to hold their own perspective. Give them a name, avatar, expertise, and working style. **They don't flatter you. They push back.**
41
33
 
42
- ### Build your team with Mates
34
+ @mention them in any project session, DM them directly, or bring multiple into the same conversation. Each Mate builds persistent knowledge over time, remembering past decisions, project context, and how you work together.
43
35
 
44
- Not *"act like a design expert."* Mates are AI teammates shaped through real conversation, trained with your context, and built to hold their own perspective. Give them a name, avatar, expertise, and working style. **They don't flatter you. They push back.**
36
+ ### Drop-in replacement
45
37
 
46
- They live in your sidebar next to your human teammates. @mention them in any project session when you need their take, DM them directly, or bring multiple into the same conversation. Each Mate builds persistent knowledge over time, remembering past decisions, project context, and how you work together.
38
+ Your existing agent setup works as-is. CLI sessions, CLAUDE.md rules, MCP servers. **No migration needed.** Pick up a CLI session in the browser, or continue a browser session in the CLI.
47
39
 
48
- #### Debate before you decide
40
+ ### Your machine, your server
49
41
 
50
- Let your Mates challenge each other. Set up a debate. Pick a moderator and panelists, give them a topic, and let them go. You can raise your hand to interject. When it wraps up, you get opposing perspectives from every angle.
42
+ Clay runs as a daemon on your machine. **No cloud relay, no intermediary service.** Schedule agents with cron, get push notifications on your phone, close your laptop. Sessions keep running.
51
43
 
52
- "Should we rewrite this in Rust?" "Should we delay the launch to fix onboarding?" "Should we position this as enterprise-first or PLG?" Get real opposing perspectives before you commit.
44
+ ### Bring your whole team
53
45
 
54
- ### Your machine, your server
46
+ **One API key runs the whole workspace.** Invite teammates, set permissions per person, per project, per session. If someone gets stuck, **jump into their session** to help in real time.
55
47
 
56
- Clay runs as a daemon on your machine. **No cloud relay, no intermediary service** between your browser and your code. Data flows directly to the Anthropic API, exactly as it does from the CLI.
48
+ ### Before vs Clay
57
49
 
58
- PIN authentication, per-project permissions, and HTTPS are built in. For remote access, use a VPN like Tailscale.
50
+ | Before | With Clay |
51
+ |---|---|
52
+ | One CLI session at a time | Multiple agents across projects, persistent |
53
+ | No shared context across teammates | Shared workspace with roles and permissions |
54
+ | No memory between decisions | AI that remembers past decisions and challenges new ones |
55
+ | No teammates, just prompts | Mates with names, knowledge, and opinions |
59
56
 
60
- ---
57
+ ### Why not just use Claude Code directly?
61
58
 
62
- ## How a bug gets fixed
59
+ Claude Code is excellent for solo coding. Clay is for when you need more than a single agent session. A team that debates your architecture decisions. AI colleagues who remember what you decided last month. A workspace where your human teammates and AI teammates sit side by side.
63
60
 
64
- **Without Clay:**
65
- Designer finds a bug → writes up a ticket on Asana → dev asks clarifying questions → PM prioritizes → dev opens terminal, fixes it → shares a preview → QA checks → deploy
66
- <br>*7 steps. 3 people. 2 days.*
61
+ If Claude Code is a solo instrument, Clay is the band.
67
62
 
68
- **With Clay:**
69
- Designer opens Clay in the browser, describes the bug in plain language → senior joins the same session, reviews the fix together → merge
70
- <br>*2 steps. 2 people. Minutes. The designer never touched a terminal.*
63
+ ## Who is Clay for
71
64
 
72
- ---
65
+ - **Solo dev juggling multiple roles.** You need a code reviewer, a marketing lead, a writing partner, but it's just you. Build them as Mates.
66
+ - **Small team sharing one AI workflow.** One API key, everyone in the browser, no terminal knowledge required.
67
+ - **Founder doing dev + product + ops.** Run agents overnight, get notified on your phone, review in the morning.
73
68
 
74
- ## How Clay compares
69
+ ## Why I built Clay
75
70
 
76
- *As of March 2026.*
71
+ I wanted AI teammates, not just a coding agent. The underlying agent is the best foundation for a personal AI I've found. I wanted to turn it into my own AI assistant, one that knows my context, remembers my decisions, and works the way I work.
77
72
 
78
- | | CLI | Remote Control | Channels | **Clay** |
79
- |---|---|---|---|---|
80
- | Multi-user with roles | – | – | Platform-dependent | **Accounts + RBAC** |
81
- | AI teammates (Mates + Debates) | – | – | – | **Yes** |
82
- | Join teammate's session | – | – | – | **Yes** |
83
- | Persistent daemon | – | Session-based | – | **Yes** |
84
- | Native mobile app | – | **Yes** | **Platform app** | PWA |
85
- | Official support | **Anthropic** | **Anthropic** | **Anthropic** | Community |
73
+ That started as a browser interface so I could access it from anywhere. Then I added multi-user so my team could use it too. Then I started building the AI teammates themselves.
86
74
 
87
- Clay is a community project, not affiliated with Anthropic. Official tools receive guaranteed support and updates.
75
+ Most AI agent projects go for full autonomy. Let the AI loose, give it all the permissions, let it run. I wanted the opposite: **AI that works as part of a team.** Visible, controllable, accountable. Your teammates can see what the agent is doing, jump in when it needs help, and set the rules it operates under.
88
76
 
89
- ---
77
+ That's Clay now. A workspace where AI teammates have names, persistent memory, and their own perspective. Not "act like an expert" prompting. Actual teammates that push back, remember last week, and sit in your sidebar next to your human colleagues.
90
78
 
91
79
  ## Getting Started
92
80
 
@@ -96,21 +84,25 @@ Clay is a community project, not affiliated with Anthropic. Official tools recei
96
84
  npx clay-server
97
85
  ```
98
86
 
99
- On first run, it walks you through port and PIN setup.
87
+ On first run, it asks for a port number and whether you're using it solo or with a team.
100
88
  Scan the QR code to connect from your phone instantly.
101
89
 
102
90
  For remote access, use a VPN like Tailscale.
103
91
 
104
- <p align="center">
105
- <img src="media/start.gif" alt="Clay starting from CLI" width="600">
106
- </p>
92
+ ## Philosophy
107
93
 
108
- ---
94
+ **AI is a teammate, not a tool.** A tool gets used once and forgotten. A teammate accumulates your history, your decisions, your working style. Give them a specialty, let them build context over time, and bring them into any project as a colleague.
95
+
96
+ **AI should understand you first.** When you create a Mate, set up a scheduled task, or start a Ralph Loop, Clay interviews you. Not to save time, but to use AI's capability to understand what you actually want. "Just do it for me" is a trap. The better AI understands you, the better the output.
97
+
98
+ **Your data is yours.** Sessions are JSONL files. Settings are JSON. Knowledge is Markdown. Everything lives on your machine in formats you can read, move, and back up. No proprietary database, no cloud lock-in. If you stop using Clay tomorrow, your data doesn't disappear.
99
+
100
+ **Friction is a feature.** The goal of AI is not to remove all friction. It's to free you to focus on the friction that matters. Reviewing a critical decision, shaping the direction, catching what the agent missed. Clay keeps those moments in, on purpose.
109
101
 
110
102
  ## FAQ
111
103
 
112
104
  **"Is this just a terminal wrapper?"**
113
- No. Clay runs on the Claude Agent SDK. It doesn't wrap terminal output. It communicates directly with the agent through the SDK.
105
+ No. Clay is a team workspace that happens to use Claude as its engine. It manages AI teammates, persistent knowledge, and structured debates. The agent runtime is an implementation detail.
114
106
 
115
107
  **"Does my code leave my machine?"**
116
108
  The Clay server runs locally. Files stay local. Only Claude API calls go out, which is the same as using the CLI.
@@ -127,36 +119,8 @@ No. Teammates share the Claude Code session logged in on the server. You can als
127
119
  **"Does it work with MCP servers?"**
128
120
  Yes. MCP configurations from the CLI carry over as-is.
129
121
 
130
- ---
131
-
132
- ## HTTPS
133
-
134
- 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:
135
-
136
- ```
137
- https://192-168-1-50.d.clay.studio:2633
138
- ```
139
-
140
- The domain resolves to your local IP. All traffic stays on your network. See [clay-dns](clay-dns/) for details on how this works.
141
-
142
- 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.
143
-
144
- <details>
145
- <summary><strong>Alternative: local certificate with mkcert</strong></summary>
146
-
147
- If you prefer to use a locally generated certificate (e.g. air-gapped environments where DNS is unavailable):
148
-
149
- ```bash
150
- brew install mkcert
151
- mkcert -install
152
- npx clay-server --local-cert
153
- ```
154
-
155
- This generates a self-signed certificate trusted by your machine. The setup wizard will guide you through installing the CA on other devices.
156
-
157
- </details>
158
-
159
- ---
122
+ **"What is d.clay.studio in my browser URL?"**
123
+ It's a DNS-only service that resolves to your local IP for HTTPS certificate validation. No data passes through it. All traffic stays between your browser and your machine. See [clay-dns](clay-dns/) for details.
160
124
 
161
125
  ## CLI Options
162
126
 
@@ -180,11 +144,9 @@ npx clay-server --dangerously-skip-permissions
180
144
  npx clay-server --dev # Dev mode (foreground, auto-restart on lib/ changes, port 2635)
181
145
  ```
182
146
 
183
- ---
184
-
185
147
  ## Architecture
186
148
 
187
- Clay drives Claude Code execution through the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) and streams it to the browser over WebSocket.
149
+ Clay drives agent execution through the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) and streams it to the browser over WebSocket.
188
150
 
189
151
  ```mermaid
190
152
  graph LR
@@ -207,8 +169,6 @@ graph LR
207
169
 
208
170
  For detailed sequence diagrams, daemon architecture, and design decisions, see [docs/architecture.md](docs/architecture.md).
209
171
 
210
- ---
211
-
212
172
  ## Contributors
213
173
 
214
174
  <a href="https://github.com/chadbyte/clay/graphs/contributors">
package/bin/cli.js CHANGED
@@ -33,7 +33,7 @@ if (_isDev || process.argv.includes("--debug")) {
33
33
  }
34
34
 
35
35
  var crypto = require("crypto");
36
- var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo } = require("../lib/config");
36
+ var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo, REAL_HOME } = require("../lib/config");
37
37
  var { sendIPCCommand } = require("../lib/ipc");
38
38
  var { generateAuthToken } = require("../lib/server");
39
39
  var { enableMultiUser, disableMultiUser, hasAdmin, isMultiUser } = require("../lib/users");
@@ -610,8 +610,7 @@ function ensureCerts(ip) {
610
610
  return null;
611
611
  }
612
612
 
613
- var homeDir = os.homedir();
614
- var certDir = path.join(process.env.CLAY_HOME || path.join(homeDir, ".clay"), "certs");
613
+ var certDir = path.join(process.env.CLAY_HOME || path.join(REAL_HOME, ".clay"), "certs");
615
614
  var keyPath = path.join(certDir, "key.pem");
616
615
  var certPath = path.join(certDir, "cert.pem");
617
616
 
@@ -891,7 +890,7 @@ function promptText(title, placeholder, callback, opts) {
891
890
 
892
891
  // Resolve ~ to home
893
892
  if (current.charAt(0) === "~") {
894
- current = os.homedir() + current.substring(1);
893
+ current = REAL_HOME + current.substring(1);
895
894
  }
896
895
 
897
896
  var resolved = path.resolve(current);
@@ -1605,14 +1604,11 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
1605
1604
  }
1606
1605
 
1607
1606
  // Enable/disable multi-user mode based on startup config
1607
+ var _pendingSetupCode = null;
1608
1608
  if (config.mode === "multi") {
1609
1609
  var muResult = enableMultiUser();
1610
1610
  if (muResult.setupCode) {
1611
- log("");
1612
- log(sym.done + " " + a.green + "Multi-user mode enabled." + a.reset);
1613
- log(sym.bar + " Setup code: " + a.bold + muResult.setupCode + a.reset);
1614
- log(sym.bar + " Open Clay in your browser and enter this code to create the admin account.");
1615
- log("");
1611
+ _pendingSetupCode = muResult.setupCode;
1616
1612
  }
1617
1613
  } else if (isMultiUser()) {
1618
1614
  disableMultiUser();
@@ -1628,13 +1624,19 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
1628
1624
  console.log(" " + sym.done + " " + url);
1629
1625
  if (config.builtinCert) console.log(" " + sym.done + " d.clay.studio provides HTTPS certificates only. Your traffic never leaves your network.");
1630
1626
  if (config.mkcertDetected) console.log(" " + sym.warn + " Clay now ships with a builtin HTTPS certificate. To use it, pass --builtin-cert or uninstall mkcert.");
1627
+ if (_pendingSetupCode) {
1628
+ console.log("");
1629
+ console.log(" " + sym.done + " " + a.green + "Multi-user mode enabled." + a.reset);
1630
+ console.log(" " + sym.bar + " Setup code: " + a.bold + _pendingSetupCode + a.reset);
1631
+ console.log(" " + sym.bar + " Open Clay in your browser and enter this code to create the admin account.");
1632
+ }
1631
1633
  console.log(" " + sym.done + " Headless mode — exiting CLI");
1632
1634
  process.exit(0);
1633
1635
  return;
1634
1636
  }
1635
1637
 
1636
1638
  // Show success + QR
1637
- showServerStarted(config, ip);
1639
+ showServerStarted(config, ip, _pendingSetupCode);
1638
1640
  }
1639
1641
 
1640
1642
  // ==============================
@@ -1959,14 +1961,14 @@ async function restartDaemonWithTLS(config, callback) {
1959
1961
  // ==============================
1960
1962
  // Show server started info
1961
1963
  // ==============================
1962
- function showServerStarted(config, ip) {
1963
- showMainMenu(config, ip);
1964
+ function showServerStarted(config, ip, setupCode) {
1965
+ showMainMenu(config, ip, setupCode);
1964
1966
  }
1965
1967
 
1966
1968
  // ==============================
1967
1969
  // Main management menu
1968
1970
  // ==============================
1969
- function showMainMenu(config, ip) {
1971
+ function showMainMenu(config, ip, setupCode) {
1970
1972
  startDaemonWatcher();
1971
1973
  var protocol = config.tls ? "https" : "http";
1972
1974
  var url = config.builtinCert
@@ -2007,6 +2009,12 @@ function showMainMenu(config, ip) {
2007
2009
  log("");
2008
2010
  }
2009
2011
 
2012
+ if (setupCode) {
2013
+ log(" " + a.yellow + sym.warn + " Setup code: " + a.bold + setupCode + a.reset);
2014
+ log(" " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
2015
+ log("");
2016
+ }
2017
+
2010
2018
  showMenuItems();
2011
2019
  }
2012
2020
 
@@ -2507,7 +2515,19 @@ function showSettingsMenu(config, ip) {
2507
2515
  ], function (confirmChoice) {
2508
2516
  if (confirmChoice === "confirm") {
2509
2517
  sendIPCCommand(socketPath(), { cmd: "set_os_users", value: true }).then(function (res) {
2510
- if (res.ok) {
2518
+ if (res.error === "acl_not_installed") {
2519
+ log(sym.bar);
2520
+ log(sym.bar + " " + a.red + sym.warn + " setfacl is not installed." + a.reset);
2521
+ log(sym.bar);
2522
+ log(sym.bar + " OS user isolation requires the ACL (Access Control List) package");
2523
+ log(sym.bar + " to manage per-user file permissions on shared projects.");
2524
+ log(sym.bar);
2525
+ log(sym.bar + " " + a.bold + "Install it:" + a.reset);
2526
+ log(sym.bar + " " + a.cyan + res.installCmd + a.reset);
2527
+ log(sym.bar);
2528
+ log(sym.bar + " " + a.dim + "Then try enabling OS user isolation again." + a.reset);
2529
+ log(sym.bar);
2530
+ } else if (res.ok) {
2511
2531
  config.osUsers = true;
2512
2532
  log(sym.bar);
2513
2533
  log(sym.done + " " + a.green + "OS-level user isolation enabled." + a.reset);
@@ -2858,6 +2878,21 @@ var currentVersion = require("../package.json").version;
2858
2878
  return;
2859
2879
  }
2860
2880
 
2881
+ // os-users requires setfacl (ACL package)
2882
+ if (savedOsUsers && process.platform === "linux") {
2883
+ var { checkAclSupport } = require("../lib/os-users");
2884
+ var aclCheck = checkAclSupport();
2885
+ if (!aclCheck.available) {
2886
+ console.error(a.red + "OS user isolation requires the 'acl' package (setfacl)." + a.reset);
2887
+ console.error("");
2888
+ console.error("Install it: " + a.bold + aclCheck.installCmd + a.reset);
2889
+ console.error("");
2890
+ console.error("Then restart Clay.");
2891
+ process.exit(1);
2892
+ return;
2893
+ }
2894
+ }
2895
+
2861
2896
  if (savedConfig && savedConfig.port) port = savedConfig.port;
2862
2897
  if (savedConfig && savedConfig.host) host = savedConfig.host;
2863
2898
  if (savedConfig && savedConfig.dangerouslySkipPermissions) dangerouslySkipPermissions = true;
@@ -1,8 +1,8 @@
1
1
  var fs = require("fs");
2
2
  var path = require("path");
3
- var os = require("os");
4
3
  var readline = require("readline");
5
4
  var utils = require("./utils");
5
+ var { REAL_HOME } = require("./config");
6
6
 
7
7
  var encodeCwd = utils.encodeCwd;
8
8
 
@@ -108,7 +108,7 @@ function parseSessionFile(filePath, maxLines) {
108
108
  */
109
109
  function listCliSessions(cwd) {
110
110
  var encoded = encodeCwd(cwd);
111
- var projectDir = path.join(os.homedir(), ".claude", "projects", encoded);
111
+ var projectDir = path.join(REAL_HOME, ".claude", "projects", encoded);
112
112
 
113
113
  return new Promise(function (resolve) {
114
114
  fs.readdir(projectDir, { withFileTypes: true }, function (err, entries) {
@@ -177,7 +177,7 @@ function extractText(content) {
177
177
  */
178
178
  function readCliSessionHistory(cwd, sessionId) {
179
179
  var encoded = encodeCwd(cwd);
180
- var filePath = path.join(os.homedir(), ".claude", "projects", encoded, sessionId + ".jsonl");
180
+ var filePath = path.join(REAL_HOME, ".claude", "projects", encoded, sessionId + ".jsonl");
181
181
 
182
182
  return new Promise(function (resolve) {
183
183
  var history = [];
package/lib/config.js CHANGED
@@ -3,9 +3,34 @@ var path = require("path");
3
3
  var os = require("os");
4
4
  var net = require("net");
5
5
 
6
+ // When running under sudo, resolve the real user's home directory
7
+ // so that ~/.clay/ points to the original user's data, not /root/.clay/
8
+ function getRealHome() {
9
+ if (process.env.SUDO_USER) {
10
+ try {
11
+ var entry = require("child_process")
12
+ .execSync("getent passwd " + process.env.SUDO_USER, { encoding: "utf8" })
13
+ .trim();
14
+ var home = entry.split(":")[5];
15
+ if (home) return home;
16
+ } catch (e) {
17
+ // getent failed (e.g. macOS), try user home dir from ~SUDO_USER
18
+ try {
19
+ var home = require("child_process")
20
+ .execSync("eval echo ~" + process.env.SUDO_USER, { encoding: "utf8" })
21
+ .trim();
22
+ if (home && home !== "~" + process.env.SUDO_USER) return home;
23
+ } catch (e2) {}
24
+ }
25
+ }
26
+ return os.homedir();
27
+ }
28
+
29
+ var REAL_HOME = getRealHome();
30
+
6
31
  // v3: ~/.clay/ (v2 was ~/.claude-relay/, v1 was {cwd}/.claude-relay/)
7
- var CLAY_HOME = process.env.CLAY_HOME || path.join(os.homedir(), ".clay");
8
- var LEGACY_HOME = path.join(os.homedir(), ".claude-relay");
32
+ var CLAY_HOME = process.env.CLAY_HOME || path.join(REAL_HOME, ".clay");
33
+ var LEGACY_HOME = path.join(REAL_HOME, ".claude-relay");
9
34
 
10
35
  // Auto-migrate v2 -> v3: rename ~/.claude-relay/ to ~/.clay/ (once, before anything reads)
11
36
  if (!fs.existsSync(CLAY_HOME) && fs.existsSync(LEGACY_HOME)) {
@@ -19,7 +44,7 @@ if (!fs.existsSync(CLAY_HOME) && fs.existsSync(LEGACY_HOME)) {
19
44
  }
20
45
 
21
46
  // Auto-migrate dev sessions: merge ~/.clay-dev/sessions/ into ~/.clay/sessions/
22
- var CLAY_DEV_HOME = path.join(os.homedir(), ".clay-dev");
47
+ var CLAY_DEV_HOME = path.join(REAL_HOME, ".clay-dev");
23
48
  var devSessionsRoot = path.join(CLAY_DEV_HOME, "sessions");
24
49
  if (fs.existsSync(devSessionsRoot)) {
25
50
  try {
@@ -67,7 +92,7 @@ if (fs.existsSync(devSessionsRoot)) {
67
92
  }
68
93
 
69
94
  var CONFIG_DIR = CLAY_HOME;
70
- var CLAYRC_PATH = path.join(os.homedir(), ".clayrc");
95
+ var CLAYRC_PATH = path.join(REAL_HOME, ".clayrc");
71
96
  var CRASH_INFO_PATH = path.join(CONFIG_DIR, "crash.json");
72
97
 
73
98
  // Dev mode uses separate daemon files so dev and prod can run simultaneously
@@ -336,4 +361,5 @@ module.exports = {
336
361
  removeFromClayrc: removeFromClayrc,
337
362
  chmodSafe: chmodSafe,
338
363
  isDevMode: _devMode,
364
+ REAL_HOME: REAL_HOME,
339
365
  };
package/lib/daemon.js CHANGED
@@ -25,10 +25,10 @@ delete process.env.CLAUDECODE;
25
25
 
26
26
  var fs = require("fs");
27
27
  var path = require("path");
28
- var { loadConfig, saveConfig, socketPath, generateSlug, syncClayrc, removeFromClayrc, writeCrashInfo, readCrashInfo, clearCrashInfo, isPidAlive, clearStaleConfig } = require("./config");
28
+ var { loadConfig, saveConfig, socketPath, generateSlug, syncClayrc, removeFromClayrc, writeCrashInfo, readCrashInfo, clearCrashInfo, isPidAlive, clearStaleConfig, REAL_HOME } = require("./config");
29
29
  var { createIPCServer } = require("./ipc");
30
30
  var { createServer, generateAuthToken } = require("./server");
31
- var { grantProjectAccess, revokeProjectAccess, provisionAllUsers, provisionLinuxUser, grantAllUsersAccess, deactivateLinuxUser, ensureProjectsDir } = require("./os-users");
31
+ var { checkAclSupport, grantProjectAccess, revokeProjectAccess, provisionAllUsers, provisionLinuxUser, grantAllUsersAccess, deactivateLinuxUser, ensureProjectsDir } = require("./os-users");
32
32
  var usersModule = require("./users");
33
33
  var { scanWorktrees, createWorktree, removeWorktree, isWorktree } = require("./worktree");
34
34
  var mates = require("./mates");
@@ -66,7 +66,7 @@ if (config.tls) {
66
66
 
67
67
  // 2. User cert (mkcert, etc.)
68
68
  var os = require("os");
69
- var certDir = path.join(process.env.CLAY_HOME || process.env.CLAUDE_RELAY_HOME || path.join(os.homedir(), ".clay"), "certs");
69
+ var certDir = path.join(process.env.CLAY_HOME || process.env.CLAUDE_RELAY_HOME || path.join(REAL_HOME, ".clay"), "certs");
70
70
  var userKeyPath = path.join(certDir, "key.pem");
71
71
  var userCertPath = path.join(certDir, "cert.pem");
72
72
 
@@ -195,7 +195,7 @@ var relay = createServer({
195
195
  if (config.osUsers) {
196
196
  baseDir = "/var/clay/projects";
197
197
  } else {
198
- baseDir = config.projectsDir || path.join(os.homedir(), "clay-projects");
198
+ baseDir = config.projectsDir || path.join(REAL_HOME, "clay-projects");
199
199
  }
200
200
  try { fs.mkdirSync(baseDir, { recursive: true }); } catch (e) {}
201
201
  // Generate slug and deduplicate
@@ -266,7 +266,7 @@ var relay = createServer({
266
266
  if (config.osUsers) {
267
267
  baseDir = "/var/clay/projects";
268
268
  } else {
269
- baseDir = config.projectsDir || path.join(os.homedir(), "clay-projects");
269
+ baseDir = config.projectsDir || path.join(REAL_HOME, "clay-projects");
270
270
  }
271
271
  try { fs.mkdirSync(baseDir, { recursive: true }); } catch (e) {}
272
272
  // Derive slug from repo URL
@@ -659,6 +659,7 @@ var relay = createServer({
659
659
  tls: !!tlsOptions,
660
660
  debug: !!config.debug,
661
661
  keepAwake: !!config.keepAwake,
662
+ autoContinueOnRateLimit: !!config.autoContinueOnRateLimit,
662
663
  pinEnabled: !!config.pinHash,
663
664
  platform: process.platform,
664
665
  hostname: os2.hostname(),
@@ -698,6 +699,13 @@ var relay = createServer({
698
699
  saveConfig(config);
699
700
  console.log("[daemon] PIN hash auto-upgraded to scrypt");
700
701
  },
702
+ onSetAutoContinue: function (value) {
703
+ var want = !!value;
704
+ config.autoContinueOnRateLimit = want;
705
+ saveConfig(config);
706
+ console.log("[daemon] Auto-continue on rate limit:", want, "(web)");
707
+ return { ok: true, autoContinueOnRateLimit: want };
708
+ },
701
709
  onSetKeepAwake: function (value) {
702
710
  var want = !!value;
703
711
  config.keepAwake = want;
@@ -1111,6 +1119,13 @@ var ipc = createIPCServer(socketPath(), function (msg) {
1111
1119
 
1112
1120
  case "set_os_users": {
1113
1121
  var enableOsUsers = !!msg.value;
1122
+ if (enableOsUsers) {
1123
+ // Pre-flight: check if setfacl is available
1124
+ var aclCheck = checkAclSupport();
1125
+ if (!aclCheck.available) {
1126
+ return { error: "acl_not_installed", installCmd: aclCheck.installCmd };
1127
+ }
1128
+ }
1114
1129
  config.osUsers = enableOsUsers;
1115
1130
  saveConfig(config);
1116
1131
  console.log("[daemon] OS users:", enableOsUsers);
@@ -1156,6 +1171,14 @@ var ipc = createIPCServer(socketPath(), function (msg) {
1156
1171
  return { ok: true };
1157
1172
  }
1158
1173
 
1174
+ case "set_auto_continue": {
1175
+ var acWant = !!msg.value;
1176
+ config.autoContinueOnRateLimit = acWant;
1177
+ saveConfig(config);
1178
+ console.log("[daemon] Auto-continue on rate limit:", acWant, "(cli)");
1179
+ return { ok: true, autoContinueOnRateLimit: acWant };
1180
+ }
1181
+
1159
1182
  case "set_keep_awake": {
1160
1183
  var want = !!msg.value;
1161
1184
  config.keepAwake = want;
package/lib/mates.js CHANGED
@@ -351,13 +351,12 @@ var SESSION_MEMORY_SECTION =
351
351
  "\n\n" + SESSION_MEMORY_MARKER + "\n" +
352
352
  "## Session Memory\n\n" +
353
353
  "**This section is managed by the system and cannot be removed.**\n\n" +
354
- "Your `knowledge/session-digests.jsonl` file may contain summaries of your participation " +
355
- "in project sessions via @mentions. Each line is a JSON object recording your perspective " +
356
- "from that session, including your positions, disagreements with other mates, decisions made, " +
357
- "and open items.\n\n" +
358
- "When a user references a previous conversation, asks what you discussed before, or when " +
359
- "continuity with a past session is relevant, read this file to recall context. " +
360
- "Do not read this file proactively on every conversation start.\n";
354
+ "Your `knowledge/memory-summary.md` file contains your compressed long-term memory, " +
355
+ "automatically maintained across sessions. Refer to it for context about past " +
356
+ "interactions, decisions, and patterns.\n\n" +
357
+ "Your `knowledge/session-digests.jsonl` file contains raw session logs as an archive. " +
358
+ "You do not need to read it routinely. Only access it when you need to look up " +
359
+ "specific details from a past session that are not in the summary.\n";
361
360
 
362
361
  function hasSessionMemory(content) {
363
362
  return content.indexOf(SESSION_MEMORY_MARKER) !== -1;
@@ -404,6 +403,59 @@ function enforceSessionMemory(filePath) {
404
403
  return true;
405
404
  }
406
405
 
406
+ // --- Sticky notes pointer in CLAUDE.md ---
407
+
408
+ var STICKY_NOTES_MARKER = "<!-- STICKY_NOTES_MANAGED_BY_SYSTEM -->";
409
+
410
+ var STICKY_NOTES_SECTION =
411
+ "\n\n" + STICKY_NOTES_MARKER + "\n" +
412
+ "## Sticky Notes\n\n" +
413
+ "**This section is managed by the system and cannot be removed.**\n\n" +
414
+ "Your `knowledge/sticky-notes.md` file contains sticky notes left by the user. " +
415
+ "Read this file when starting a conversation for important context. " +
416
+ "These notes are read-only. You cannot create, update, or delete them.\n";
417
+
418
+ function hasStickyNotesSection(content) {
419
+ return content.indexOf(STICKY_NOTES_MARKER) !== -1;
420
+ }
421
+
422
+ /**
423
+ * Enforce the sticky notes pointer section in a mate's CLAUDE.md.
424
+ * Inserts after session memory and before crisis safety.
425
+ * Returns true if the file was modified.
426
+ */
427
+ function enforceStickyNotes(filePath) {
428
+ if (!fs.existsSync(filePath)) return false;
429
+
430
+ var content = fs.readFileSync(filePath, "utf8");
431
+
432
+ var markerIdx = content.indexOf(STICKY_NOTES_MARKER);
433
+ if (markerIdx !== -1) {
434
+ var afterMarker = content.substring(markerIdx);
435
+ var crisisIdx = afterMarker.indexOf(crisisSafety.MARKER);
436
+ var existing;
437
+ if (crisisIdx !== -1) {
438
+ existing = afterMarker.substring(0, crisisIdx).trimEnd();
439
+ } else {
440
+ existing = afterMarker.trimEnd();
441
+ }
442
+ if (existing === STICKY_NOTES_SECTION.trimStart().trimEnd()) return false;
443
+
444
+ var endOfSection = crisisIdx !== -1 ? markerIdx + crisisIdx : content.length;
445
+ content = content.substring(0, markerIdx).trimEnd() + content.substring(endOfSection);
446
+ }
447
+
448
+ var crisisPos = content.indexOf(crisisSafety.MARKER);
449
+ if (crisisPos !== -1) {
450
+ content = content.substring(0, crisisPos).trimEnd() + STICKY_NOTES_SECTION + "\n\n" + content.substring(crisisPos);
451
+ } else {
452
+ content = content.trimEnd() + STICKY_NOTES_SECTION;
453
+ }
454
+
455
+ fs.writeFileSync(filePath, content, "utf8");
456
+ return true;
457
+ }
458
+
407
459
  // --- Common knowledge registry ---
408
460
 
409
461
  function commonKnowledgePath(ctx) {
@@ -556,4 +608,6 @@ module.exports = {
556
608
  getCommonKnowledgeForMate: getCommonKnowledgeForMate,
557
609
  readCommonKnowledgeFile: readCommonKnowledgeFile,
558
610
  isPromoted: isPromoted,
611
+ enforceStickyNotes: enforceStickyNotes,
612
+ STICKY_NOTES_MARKER: STICKY_NOTES_MARKER,
559
613
  };