clay-server 2.14.0 → 2.15.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 +25 -40
- package/lib/mates.js +159 -0
- package/lib/project.js +93 -8
- package/lib/public/app.js +5 -11
- package/lib/public/css/input.css +9 -1
- package/lib/public/css/mates.css +167 -13
- package/lib/public/css/session-search.css +9 -0
- package/lib/public/css/sidebar.css +0 -17
- package/lib/public/index.html +15 -1
- package/lib/public/modules/command-palette.js +47 -100
- package/lib/public/modules/mate-knowledge.js +375 -121
- package/lib/public/modules/mate-sidebar.js +2 -6
- package/lib/public/modules/sidebar.js +0 -196
- package/lib/sessions.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,18 +4,11 @@
|
|
|
4
4
|
<img src="media/phone.gif" alt="Clay on phone" width="300">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
<h3 align="center">
|
|
7
|
+
<h3 align="center">Turn Claude Code into a team workspace. Any device, one command.</h3>
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/clay-server) [](https://www.npmjs.com/package/clay-server) [](https://github.com/chadbyte/clay) [](https://github.com/chadbyte/clay/blob/main/LICENSE)
|
|
10
10
|
|
|
11
|
-
Clay
|
|
12
|
-
|
|
13
|
-
No relay server in the cloud. Your machine is the server. Zero install — one command:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npx clay-server
|
|
17
|
-
# Scan the QR code to connect from any device
|
|
18
|
-
```
|
|
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.
|
|
19
12
|
|
|
20
13
|
---
|
|
21
14
|
|
|
@@ -65,6 +58,18 @@ If someone gets stuck, join their session to unblock them in real time. Permissi
|
|
|
65
58
|
|
|
66
59
|
---
|
|
67
60
|
|
|
61
|
+
## Mates
|
|
62
|
+
|
|
63
|
+
Build your team, even if you're solo.
|
|
64
|
+
|
|
65
|
+
Mates are AI teammates you create through conversation. Interview them, give them a name, avatar, and role. A code reviewer, a marketing lead, a writing partner. They remember how you work together and carry context across sessions.
|
|
66
|
+
|
|
67
|
+
They sit in your sidebar next to your human teammates. DM them, bring them into projects, let them work autonomously. In a multi-user workspace, your whole team, human and AI, works in one place.
|
|
68
|
+
|
|
69
|
+
<!-- screenshot: sidebar with Mates visible -->
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
68
73
|
## Mobile & notifications
|
|
69
74
|
|
|
70
75
|
Phone, tablet, couch. All you need is a browser.
|
|
@@ -85,15 +90,13 @@ The scheduler kicks off agents at set times.
|
|
|
85
90
|
Have it check open issues and submit PRs every morning at 8 AM.
|
|
86
91
|
Or compile world news and email you a digest every day.
|
|
87
92
|
|
|
88
|
-
Take it further with Ralph Loop
|
|
93
|
+
Take it further with Ralph Loop, an autonomous coding loop built into Clay. The agent works, commits, and a judge evaluates. If it fails, a fresh session starts over with no memory of the previous attempt. Only the code carries over. Based on [Geoffrey Huntley's Ralph Wiggum technique](https://ghuntley.com/loop/).
|
|
89
94
|
|
|
90
95
|
---
|
|
91
96
|
|
|
92
97
|
## Security & Privacy
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
The best relay server to trust is the one that doesn't exist. Clay has none. Your data flows directly from your machine to the Anthropic API — exactly as it does when you use the CLI. No one intercepts, collects, or reroutes it. Clay adds a browser layer on top, not a middleman.
|
|
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.
|
|
97
100
|
|
|
98
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.
|
|
99
102
|
|
|
@@ -119,11 +122,12 @@ PIN authentication, per-project/session permissions, and HTTPS are supported by
|
|
|
119
122
|
|
|
120
123
|
## Key Features
|
|
121
124
|
|
|
125
|
+
* **Mates** - AI teammates with persistent identity, context, and memory. Create through an interview, DM them, collaborate across sessions.
|
|
122
126
|
* **Multi-user** - Accounts, invitations, per-project/session permissions, real-time presence.
|
|
123
127
|
* **Multi-agent** - Parallel agents per project, sidebar switching.
|
|
124
128
|
* **Push notifications** - Approval, completion, error. Native-like PWA experience.
|
|
125
129
|
* **Scheduler** - Cron-based automatic agent execution.
|
|
126
|
-
* **Ralph Loop** - Autonomous coding loop
|
|
130
|
+
* **Ralph Loop** - Autonomous coding loop. The agent works, a judge evaluates, and it iterates until it passes.
|
|
127
131
|
* **File browser** - File exploration, syntax highlighting, live reload.
|
|
128
132
|
* **Built-in terminal** - Multi-tab terminal, mobile keyboard support.
|
|
129
133
|
* **Session search** - Full-text search across all conversation history.
|
|
@@ -136,47 +140,29 @@ PIN authentication, per-project/session permissions, and HTTPS are supported by
|
|
|
136
140
|
**"Is this just a terminal wrapper?"**
|
|
137
141
|
No. Clay runs on the Claude Agent SDK. It doesn't wrap terminal output. It communicates directly with the agent through the SDK.
|
|
138
142
|
|
|
139
|
-
**"Is it free? Open source?"**
|
|
140
|
-
Free. MIT-licensed open source. All code is public.
|
|
141
|
-
|
|
142
|
-
**"Do I need to install anything?"**
|
|
143
|
-
No. `npx clay-server` runs it directly. No global install, no build step, no Docker. Node.js and Claude Code are the only prerequisites.
|
|
144
|
-
|
|
145
143
|
**"Does my code leave my machine?"**
|
|
146
144
|
The Clay server runs locally. Files stay local. Only Claude API calls go out, which is the same as using the CLI.
|
|
147
145
|
|
|
148
|
-
**"Is it secure?"**
|
|
149
|
-
PIN authentication, per-project/session permissions, and HTTPS are supported by default. See [Security & Privacy](#security--privacy) for details.
|
|
150
|
-
|
|
151
|
-
**"What OS does it run on?"**
|
|
152
|
-
Windows, Linux, and macOS are all supported.
|
|
153
|
-
|
|
154
146
|
**"Can I continue a CLI session?"**
|
|
155
147
|
Yes. Pick up a CLI session in the browser, or continue a browser session in the CLI.
|
|
156
148
|
|
|
157
149
|
**"Does my existing CLAUDE.md work?"**
|
|
158
150
|
Yes. If your project has a CLAUDE.md, it works in Clay as-is.
|
|
159
151
|
|
|
160
|
-
**"Can I use it as an app?"**
|
|
161
|
-
PWA is supported. On mobile, tap the download icon in the top-left corner to open the PWA install guide. Once installed, it provides a native-like experience.
|
|
162
|
-
|
|
163
|
-
**"Can I use the terminal on mobile?"**
|
|
164
|
-
Yes. Clay provides a built-in terminal with mobile keyboard support.
|
|
165
|
-
|
|
166
152
|
**"Does each teammate need their own API key?"**
|
|
167
153
|
No. Teammates share the Claude Code session logged in on the server. If needed, you can configure per-project environment variables to use different API keys.
|
|
168
154
|
|
|
169
155
|
**"Does it work with MCP servers?"**
|
|
170
156
|
Yes. MCP configurations from the CLI carry over as-is.
|
|
171
157
|
|
|
172
|
-
**"
|
|
173
|
-
|
|
158
|
+
**"What are Mates?"**
|
|
159
|
+
AI teammates you create through a conversation. Each Mate has a name, avatar, personality, and persistent memory. They live in your sidebar and you can DM them or bring them into projects.
|
|
174
160
|
|
|
175
|
-
**"
|
|
176
|
-
|
|
161
|
+
**"How is a Mate different from Claude Projects?"**
|
|
162
|
+
Claude Projects save prompts and files as context. A Mate is a teammate. It has its own identity formed through an interview, remembers how you work together across sessions, and exists alongside your human teammates in the workspace. You're not organizing prompts. You're building a team.
|
|
177
163
|
|
|
178
|
-
**"Can I
|
|
179
|
-
|
|
164
|
+
**"Can I create multiple Mates?"**
|
|
165
|
+
Yes. Create as many as you need. A code reviewer, a writing partner, a project manager. Each one is independent.
|
|
180
166
|
|
|
181
167
|
---
|
|
182
168
|
|
|
@@ -222,8 +208,7 @@ npx clay-server --dev # Dev mode (foreground, auto-restart on lib/ change
|
|
|
222
208
|
|
|
223
209
|
## Architecture
|
|
224
210
|
|
|
225
|
-
Clay
|
|
226
|
-
It's a local relay server that 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.
|
|
211
|
+
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.
|
|
227
212
|
|
|
228
213
|
```mermaid
|
|
229
214
|
graph LR
|
package/lib/mates.js
CHANGED
|
@@ -98,6 +98,7 @@ function createMate(ctx, seedData) {
|
|
|
98
98
|
avatarStyle: "bottts",
|
|
99
99
|
avatarSeed: crypto.randomBytes(4).toString("hex"),
|
|
100
100
|
},
|
|
101
|
+
bio: null,
|
|
101
102
|
status: "interviewing",
|
|
102
103
|
interviewProjectPath: null,
|
|
103
104
|
};
|
|
@@ -133,6 +134,7 @@ function createMate(ctx, seedData) {
|
|
|
133
134
|
claudeMd += "- Communication: " + seedData.communicationStyle.join(", ") + "\n";
|
|
134
135
|
}
|
|
135
136
|
claudeMd += "- Autonomy: " + (seedData.autonomy || "always_ask") + "\n";
|
|
137
|
+
claudeMd += TEAM_SECTION;
|
|
136
138
|
claudeMd += crisisSafety.getSection();
|
|
137
139
|
fs.writeFileSync(path.join(mateDir, "CLAUDE.md"), claudeMd);
|
|
138
140
|
|
|
@@ -264,6 +266,155 @@ function migrateLegacyMates() {
|
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
268
|
|
|
269
|
+
// --- Team awareness ---
|
|
270
|
+
|
|
271
|
+
var TEAM_MARKER = "<!-- TEAM_AWARENESS_MANAGED_BY_SYSTEM -->";
|
|
272
|
+
|
|
273
|
+
var TEAM_SECTION =
|
|
274
|
+
"\n\n" + TEAM_MARKER + "\n" +
|
|
275
|
+
"## Your Team\n\n" +
|
|
276
|
+
"**This section is managed by the system and cannot be removed.**\n\n" +
|
|
277
|
+
"You are one of several AI Mates in this workspace. Your teammates and their profiles are listed in `../mates.json`. " +
|
|
278
|
+
"Each teammate's identity and working style is described in their own directory:\n\n" +
|
|
279
|
+
"- `../{mate_id}/CLAUDE.md` — their identity, personality, and working style\n" +
|
|
280
|
+
"- `../{mate_id}/mate.yaml` — their metadata (name, role, status, activities)\n" +
|
|
281
|
+
"- `../common-knowledge.json` — shared knowledge registry; files listed here are readable by all mates\n\n" +
|
|
282
|
+
"Check the team registry when it would be relevant to know who else is available or what they do. " +
|
|
283
|
+
"You cannot message other Mates directly yet, but knowing your team helps you work with the user more effectively.\n";
|
|
284
|
+
|
|
285
|
+
function hasTeamSection(content) {
|
|
286
|
+
return content.indexOf(TEAM_MARKER) !== -1;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Enforce the team awareness section on a mate's CLAUDE.md.
|
|
291
|
+
* Inserts before the crisis safety section (if present), or appends at the end.
|
|
292
|
+
* Returns true if the file was modified.
|
|
293
|
+
*/
|
|
294
|
+
function enforceTeamAwareness(filePath) {
|
|
295
|
+
if (!fs.existsSync(filePath)) return false;
|
|
296
|
+
|
|
297
|
+
var content = fs.readFileSync(filePath, "utf8");
|
|
298
|
+
|
|
299
|
+
// Check if already present and correct
|
|
300
|
+
var teamIdx = content.indexOf(TEAM_MARKER);
|
|
301
|
+
if (teamIdx !== -1) {
|
|
302
|
+
// Extract existing team section (up to next system marker or ## heading)
|
|
303
|
+
var afterTeam = content.substring(teamIdx);
|
|
304
|
+
var crisisIdx = afterTeam.indexOf(crisisSafety.MARKER);
|
|
305
|
+
var existing;
|
|
306
|
+
if (crisisIdx !== -1) {
|
|
307
|
+
existing = afterTeam.substring(0, crisisIdx).trimEnd();
|
|
308
|
+
} else {
|
|
309
|
+
existing = afterTeam.trimEnd();
|
|
310
|
+
}
|
|
311
|
+
if (existing === TEAM_SECTION.trimStart().trimEnd()) return false; // already correct
|
|
312
|
+
|
|
313
|
+
// Strip the existing team section
|
|
314
|
+
var endOfTeam = crisisIdx !== -1 ? teamIdx + crisisIdx : content.length;
|
|
315
|
+
content = content.substring(0, teamIdx).trimEnd() + content.substring(endOfTeam);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Insert before crisis safety section if present, otherwise append
|
|
319
|
+
var crisisPos = content.indexOf(crisisSafety.MARKER);
|
|
320
|
+
if (crisisPos !== -1) {
|
|
321
|
+
content = content.substring(0, crisisPos).trimEnd() + TEAM_SECTION + "\n\n" + content.substring(crisisPos);
|
|
322
|
+
} else {
|
|
323
|
+
content = content.trimEnd() + TEAM_SECTION;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// --- Common knowledge registry ---
|
|
331
|
+
|
|
332
|
+
function commonKnowledgePath(ctx) {
|
|
333
|
+
return path.join(resolveMatesRoot(ctx), "common-knowledge.json");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function loadCommonKnowledge(ctx) {
|
|
337
|
+
try {
|
|
338
|
+
var raw = fs.readFileSync(commonKnowledgePath(ctx), "utf8");
|
|
339
|
+
return JSON.parse(raw);
|
|
340
|
+
} catch (e) {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function saveCommonKnowledge(ctx, entries) {
|
|
346
|
+
var filePath = commonKnowledgePath(ctx);
|
|
347
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
348
|
+
var tmpPath = filePath + ".tmp";
|
|
349
|
+
fs.writeFileSync(tmpPath, JSON.stringify(entries, null, 2));
|
|
350
|
+
fs.renameSync(tmpPath, filePath);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function promoteKnowledge(ctx, mateId, mateName, fileName) {
|
|
354
|
+
var entries = loadCommonKnowledge(ctx);
|
|
355
|
+
// Check if already promoted (same mateId + name)
|
|
356
|
+
for (var i = 0; i < entries.length; i++) {
|
|
357
|
+
if (entries[i].mateId === mateId && entries[i].name === fileName) {
|
|
358
|
+
return entries; // already promoted
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
entries.push({
|
|
362
|
+
name: fileName,
|
|
363
|
+
mateId: mateId,
|
|
364
|
+
mateName: mateName || null,
|
|
365
|
+
promotedAt: Date.now()
|
|
366
|
+
});
|
|
367
|
+
saveCommonKnowledge(ctx, entries);
|
|
368
|
+
return entries;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function depromoteKnowledge(ctx, mateId, fileName) {
|
|
372
|
+
var entries = loadCommonKnowledge(ctx);
|
|
373
|
+
entries = entries.filter(function (e) {
|
|
374
|
+
return !(e.mateId === mateId && e.name === fileName);
|
|
375
|
+
});
|
|
376
|
+
saveCommonKnowledge(ctx, entries);
|
|
377
|
+
return entries;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function getCommonKnowledgeForMate(ctx, mateId) {
|
|
381
|
+
var entries = loadCommonKnowledge(ctx);
|
|
382
|
+
var root = resolveMatesRoot(ctx);
|
|
383
|
+
var result = [];
|
|
384
|
+
for (var i = 0; i < entries.length; i++) {
|
|
385
|
+
var e = entries[i];
|
|
386
|
+
var filePath = path.join(root, e.mateId, "knowledge", e.name);
|
|
387
|
+
try {
|
|
388
|
+
var stat = fs.statSync(filePath);
|
|
389
|
+
result.push({
|
|
390
|
+
name: e.name,
|
|
391
|
+
size: stat.size,
|
|
392
|
+
mtime: stat.mtimeMs,
|
|
393
|
+
common: true,
|
|
394
|
+
ownMateId: e.mateId,
|
|
395
|
+
ownerName: e.mateName
|
|
396
|
+
});
|
|
397
|
+
} catch (err) {
|
|
398
|
+
// Source file deleted, skip (could clean up registry but not critical)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function readCommonKnowledgeFile(ctx, mateId, fileName) {
|
|
405
|
+
var root = resolveMatesRoot(ctx);
|
|
406
|
+
var filePath = path.join(root, mateId, "knowledge", path.basename(fileName));
|
|
407
|
+
return fs.readFileSync(filePath, "utf8");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function isPromoted(ctx, mateId, fileName) {
|
|
411
|
+
var entries = loadCommonKnowledge(ctx);
|
|
412
|
+
for (var i = 0; i < entries.length; i++) {
|
|
413
|
+
if (entries[i].mateId === mateId && entries[i].name === fileName) return true;
|
|
414
|
+
}
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
267
418
|
// Format seed data as a human-readable context string
|
|
268
419
|
function formatSeedContext(seedData) {
|
|
269
420
|
if (!seedData) return "";
|
|
@@ -318,4 +469,12 @@ module.exports = {
|
|
|
318
469
|
getMateDir: getMateDir,
|
|
319
470
|
migrateLegacyMates: migrateLegacyMates,
|
|
320
471
|
formatSeedContext: formatSeedContext,
|
|
472
|
+
enforceTeamAwareness: enforceTeamAwareness,
|
|
473
|
+
TEAM_MARKER: TEAM_MARKER,
|
|
474
|
+
loadCommonKnowledge: loadCommonKnowledge,
|
|
475
|
+
promoteKnowledge: promoteKnowledge,
|
|
476
|
+
depromoteKnowledge: depromoteKnowledge,
|
|
477
|
+
getCommonKnowledgeForMate: getCommonKnowledgeForMate,
|
|
478
|
+
readCommonKnowledgeFile: readCommonKnowledgeFile,
|
|
479
|
+
isPromoted: isPromoted,
|
|
321
480
|
};
|
package/lib/project.js
CHANGED
|
@@ -12,6 +12,7 @@ var { createLoopRegistry } = require("./scheduler");
|
|
|
12
12
|
var usersModule = require("./users");
|
|
13
13
|
var { resolveOsUserInfo, fsAsUser } = require("./os-users");
|
|
14
14
|
var crisisSafety = require("./crisis-safety");
|
|
15
|
+
var matesModule = require("./mates");
|
|
15
16
|
var MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
16
17
|
|
|
17
18
|
// Validate environment variable string (KEY=VALUE per line)
|
|
@@ -1287,11 +1288,30 @@ function createProjectContext(opts) {
|
|
|
1287
1288
|
for (var ki = 0; ki < entries.length; ki++) {
|
|
1288
1289
|
if (entries[ki].endsWith(".md") || entries[ki].endsWith(".jsonl")) {
|
|
1289
1290
|
var stat = fs.statSync(path.join(knowledgeDir, entries[ki]));
|
|
1290
|
-
files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs });
|
|
1291
|
+
files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs, common: false });
|
|
1291
1292
|
}
|
|
1292
1293
|
}
|
|
1293
1294
|
} catch (e) { /* dir may not exist */ }
|
|
1294
1295
|
files.sort(function (a, b) { return b.mtime - a.mtime; });
|
|
1296
|
+
|
|
1297
|
+
// For mate projects, check which files are promoted and include common files from other mates
|
|
1298
|
+
if (isMate) {
|
|
1299
|
+
var mateCtx = matesModule.buildMateCtx(opts.ownerId || null);
|
|
1300
|
+
var thisMateId = path.basename(cwd);
|
|
1301
|
+
// Tag promoted files
|
|
1302
|
+
for (var pi = 0; pi < files.length; pi++) {
|
|
1303
|
+
files[pi].promoted = matesModule.isPromoted(mateCtx, thisMateId, files[pi].name);
|
|
1304
|
+
}
|
|
1305
|
+
// Get common files from other mates
|
|
1306
|
+
var commonFiles = matesModule.getCommonKnowledgeForMate(mateCtx, thisMateId);
|
|
1307
|
+
// Filter out entries that belong to THIS mate (those are already in the list as promoted)
|
|
1308
|
+
for (var ci = 0; ci < commonFiles.length; ci++) {
|
|
1309
|
+
if (commonFiles[ci].ownMateId !== thisMateId) {
|
|
1310
|
+
files.push(commonFiles[ci]);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1295
1315
|
sendTo(ws, { type: "knowledge_list", files: files });
|
|
1296
1316
|
return;
|
|
1297
1317
|
}
|
|
@@ -1299,12 +1319,24 @@ function createProjectContext(opts) {
|
|
|
1299
1319
|
if (msg.type === "knowledge_read") {
|
|
1300
1320
|
if (!msg.name) return;
|
|
1301
1321
|
var safeName = path.basename(msg.name);
|
|
1302
|
-
var filePath
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1322
|
+
var filePath;
|
|
1323
|
+
if (msg.common && msg.ownMateId && isMate) {
|
|
1324
|
+
// Reading a common file from another mate
|
|
1325
|
+
var mateCtx = matesModule.buildMateCtx(opts.ownerId || null);
|
|
1326
|
+
try {
|
|
1327
|
+
var content = matesModule.readCommonKnowledgeFile(mateCtx, msg.ownMateId, safeName);
|
|
1328
|
+
sendTo(ws, { type: "knowledge_content", name: safeName, content: content, common: true, ownMateId: msg.ownMateId });
|
|
1329
|
+
} catch (e) {
|
|
1330
|
+
sendTo(ws, { type: "knowledge_content", name: safeName, content: "", error: "File not found", common: true });
|
|
1331
|
+
}
|
|
1332
|
+
} else {
|
|
1333
|
+
filePath = path.join(cwd, "knowledge", safeName);
|
|
1334
|
+
try {
|
|
1335
|
+
var content = fs.readFileSync(filePath, "utf8");
|
|
1336
|
+
sendTo(ws, { type: "knowledge_content", name: safeName, content: content });
|
|
1337
|
+
} catch (e) {
|
|
1338
|
+
sendTo(ws, { type: "knowledge_content", name: safeName, content: "", error: "File not found" });
|
|
1339
|
+
}
|
|
1308
1340
|
}
|
|
1309
1341
|
return;
|
|
1310
1342
|
}
|
|
@@ -1328,6 +1360,19 @@ function createProjectContext(opts) {
|
|
|
1328
1360
|
}
|
|
1329
1361
|
} catch (e) {}
|
|
1330
1362
|
files.sort(function (a, b) { return b.mtime - a.mtime; });
|
|
1363
|
+
// Tag files for mate projects
|
|
1364
|
+
if (isMate) {
|
|
1365
|
+
var mateCtx = matesModule.buildMateCtx(opts.ownerId || null);
|
|
1366
|
+
var thisMateId = path.basename(cwd);
|
|
1367
|
+
for (var pi = 0; pi < files.length; pi++) {
|
|
1368
|
+
files[pi].common = false;
|
|
1369
|
+
files[pi].promoted = matesModule.isPromoted(mateCtx, thisMateId, files[pi].name);
|
|
1370
|
+
}
|
|
1371
|
+
var commonFiles = matesModule.getCommonKnowledgeForMate(mateCtx, thisMateId);
|
|
1372
|
+
for (var ci = 0; ci < commonFiles.length; ci++) {
|
|
1373
|
+
if (commonFiles[ci].ownMateId !== thisMateId) files.push(commonFiles[ci]);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1331
1376
|
sendTo(ws, { type: "knowledge_saved", name: safeName });
|
|
1332
1377
|
sendTo(ws, { type: "knowledge_list", files: files });
|
|
1333
1378
|
return;
|
|
@@ -1351,11 +1396,49 @@ function createProjectContext(opts) {
|
|
|
1351
1396
|
}
|
|
1352
1397
|
} catch (e) {}
|
|
1353
1398
|
files.sort(function (a, b) { return b.mtime - a.mtime; });
|
|
1399
|
+
// Tag files for mate projects
|
|
1400
|
+
if (isMate) {
|
|
1401
|
+
var mateCtx = matesModule.buildMateCtx(opts.ownerId || null);
|
|
1402
|
+
var thisMateId = path.basename(cwd);
|
|
1403
|
+
for (var pi = 0; pi < files.length; pi++) {
|
|
1404
|
+
files[pi].common = false;
|
|
1405
|
+
files[pi].promoted = matesModule.isPromoted(mateCtx, thisMateId, files[pi].name);
|
|
1406
|
+
}
|
|
1407
|
+
var commonFiles = matesModule.getCommonKnowledgeForMate(mateCtx, thisMateId);
|
|
1408
|
+
for (var ci = 0; ci < commonFiles.length; ci++) {
|
|
1409
|
+
if (commonFiles[ci].ownMateId !== thisMateId) files.push(commonFiles[ci]);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1354
1412
|
sendTo(ws, { type: "knowledge_deleted", name: safeName });
|
|
1355
1413
|
sendTo(ws, { type: "knowledge_list", files: files });
|
|
1356
1414
|
return;
|
|
1357
1415
|
}
|
|
1358
1416
|
|
|
1417
|
+
if (msg.type === "knowledge_promote") {
|
|
1418
|
+
if (!isMate || !msg.name) return;
|
|
1419
|
+
var safeName = path.basename(msg.name);
|
|
1420
|
+
var mateCtx = matesModule.buildMateCtx(opts.ownerId || null);
|
|
1421
|
+
var thisMateId = path.basename(cwd);
|
|
1422
|
+
var mate = matesModule.getMate(mateCtx, thisMateId);
|
|
1423
|
+
var mateName = (mate && mate.name) || null;
|
|
1424
|
+
matesModule.promoteKnowledge(mateCtx, thisMateId, mateName, safeName);
|
|
1425
|
+
sendTo(ws, { type: "knowledge_promoted", name: safeName });
|
|
1426
|
+
// Re-send updated list (reuse knowledge_list logic)
|
|
1427
|
+
handleMessage(ws, { type: "knowledge_list" });
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
if (msg.type === "knowledge_depromote") {
|
|
1432
|
+
if (!isMate || !msg.name) return;
|
|
1433
|
+
var safeName = path.basename(msg.name);
|
|
1434
|
+
var mateCtx = matesModule.buildMateCtx(opts.ownerId || null);
|
|
1435
|
+
var thisMateId = path.basename(cwd);
|
|
1436
|
+
matesModule.depromoteKnowledge(mateCtx, thisMateId, safeName);
|
|
1437
|
+
sendTo(ws, { type: "knowledge_depromoted", name: safeName });
|
|
1438
|
+
handleMessage(ws, { type: "knowledge_list" });
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1359
1442
|
if (msg.type === "push_subscribe") {
|
|
1360
1443
|
if (pushModule && msg.subscription) pushModule.addSubscription(msg.subscription, msg.replaceEndpoint);
|
|
1361
1444
|
return;
|
|
@@ -3911,10 +3994,11 @@ function createProjectContext(opts) {
|
|
|
3911
3994
|
icon = newIcon || null;
|
|
3912
3995
|
}
|
|
3913
3996
|
|
|
3914
|
-
// Mate projects: watch CLAUDE.md and enforce
|
|
3997
|
+
// Mate projects: watch CLAUDE.md and enforce system-managed sections
|
|
3915
3998
|
if (isMate) {
|
|
3916
3999
|
var claudeMdPath = path.join(cwd, "CLAUDE.md");
|
|
3917
4000
|
// Enforce immediately on startup
|
|
4001
|
+
try { matesModule.enforceTeamAwareness(claudeMdPath); } catch (e) {}
|
|
3918
4002
|
try { crisisSafety.enforce(claudeMdPath); } catch (e) {}
|
|
3919
4003
|
// Watch for changes
|
|
3920
4004
|
try {
|
|
@@ -3922,6 +4006,7 @@ function createProjectContext(opts) {
|
|
|
3922
4006
|
if (crisisDebounce) clearTimeout(crisisDebounce);
|
|
3923
4007
|
crisisDebounce = setTimeout(function () {
|
|
3924
4008
|
crisisDebounce = null;
|
|
4009
|
+
try { matesModule.enforceTeamAwareness(claudeMdPath); } catch (e) {}
|
|
3925
4010
|
try { crisisSafety.enforce(claudeMdPath); } catch (e) {}
|
|
3926
4011
|
}, 500);
|
|
3927
4012
|
});
|
package/lib/public/app.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
|
|
2
2
|
import { refreshIcons, iconHtml, randomThinkingVerb } from './modules/icons.js';
|
|
3
3
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal, parseEmojis } from './modules/markdown.js';
|
|
4
|
-
import { initSidebar, renderSessionList, handleSearchResults,
|
|
4
|
+
import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge, updateSessionBadge, updateProjectBadge, closeDmUserPicker, spawnDustParticles } from './modules/sidebar.js';
|
|
5
5
|
import { initMateSidebar, showMateSidebar, hideMateSidebar, renderMateSessionList, updateMateSidebarProfile, handleMateSearchResults } from './modules/mate-sidebar.js';
|
|
6
6
|
import { initMateKnowledge, requestKnowledgeList, renderKnowledgeList, handleKnowledgeContent, hideKnowledge } from './modules/mate-knowledge.js';
|
|
7
7
|
import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton } from './modules/rewind.js';
|
|
@@ -638,6 +638,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
638
638
|
// Apply mate color to chat title bar and panels
|
|
639
639
|
var mateColor = (targetUser.profile && targetUser.profile.avatarColor) || targetUser.avatarColor || "#7c3aed";
|
|
640
640
|
document.body.style.setProperty("--mate-color", mateColor);
|
|
641
|
+
document.body.style.setProperty("--mate-color-tint", mateColor + "0a");
|
|
641
642
|
document.body.classList.add("mate-dm-active");
|
|
642
643
|
// Build mate avatar URL for DM bubble injection
|
|
643
644
|
var mp = targetUser.profile || {};
|
|
@@ -741,6 +742,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
741
742
|
}
|
|
742
743
|
// Reset chat title bar and mate color
|
|
743
744
|
document.body.style.removeProperty("--mate-color");
|
|
745
|
+
document.body.style.removeProperty("--mate-color-tint");
|
|
744
746
|
document.body.classList.remove("mate-dm-active");
|
|
745
747
|
delete document.body.dataset.mateAvatarUrl;
|
|
746
748
|
delete document.body.dataset.myAvatarUrl;
|
|
@@ -851,7 +853,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
851
853
|
handleKnowledgeContent(msg);
|
|
852
854
|
return;
|
|
853
855
|
}
|
|
854
|
-
if (msg.type === "knowledge_saved" || msg.type === "knowledge_deleted") {
|
|
856
|
+
if (msg.type === "knowledge_saved" || msg.type === "knowledge_deleted" || msg.type === "knowledge_promoted" || msg.type === "knowledge_depromoted") {
|
|
855
857
|
return; // list update follows separately
|
|
856
858
|
}
|
|
857
859
|
// On done: scan DOM for [[MATE_READY: name]], update name, strip marker
|
|
@@ -3258,7 +3260,6 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
3258
3260
|
isUserScrolledUp = false;
|
|
3259
3261
|
newMsgBtn.classList.add("hidden");
|
|
3260
3262
|
setRewindMode(false);
|
|
3261
|
-
removeSearchTimeline();
|
|
3262
3263
|
setActivity(null);
|
|
3263
3264
|
setStatus("connected");
|
|
3264
3265
|
if (!loopActive) enableMainInput();
|
|
@@ -3484,10 +3485,6 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
3484
3485
|
finalizeAssistantBlock();
|
|
3485
3486
|
stopUrgentBlink();
|
|
3486
3487
|
scrollToBottom();
|
|
3487
|
-
var pendingQuery = getActiveSearchQuery();
|
|
3488
|
-
if (pendingQuery) {
|
|
3489
|
-
requestAnimationFrame(function() { buildSearchTimeline(pendingQuery); });
|
|
3490
|
-
}
|
|
3491
3488
|
// Scroll to tool element if navigating from file edit history
|
|
3492
3489
|
var nav = getPendingNavigate();
|
|
3493
3490
|
if (nav && (nav.toolId || nav.assistantUuid)) {
|
|
@@ -3732,8 +3729,6 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
3732
3729
|
case "search_content_results":
|
|
3733
3730
|
if (msg.source === "find_in_session") {
|
|
3734
3731
|
handleFindInSessionResults(msg);
|
|
3735
|
-
} else {
|
|
3736
|
-
handleSearchContentResults(msg);
|
|
3737
3732
|
}
|
|
3738
3733
|
break;
|
|
3739
3734
|
|
|
@@ -4603,8 +4598,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
4603
4598
|
updateHistorySentinel();
|
|
4604
4599
|
}
|
|
4605
4600
|
|
|
4606
|
-
// Notify
|
|
4607
|
-
onHistoryPrepended();
|
|
4601
|
+
// Notify in-session search that history was prepended (for pending scroll targets)
|
|
4608
4602
|
onSessionSearchHistoryPrepended();
|
|
4609
4603
|
}
|
|
4610
4604
|
|
package/lib/public/css/input.css
CHANGED
|
@@ -270,6 +270,7 @@
|
|
|
270
270
|
#input-area {
|
|
271
271
|
flex-shrink: 0;
|
|
272
272
|
padding: 8px 16px calc(var(--safe-bottom) + 8px);
|
|
273
|
+
background: transparent;
|
|
273
274
|
}
|
|
274
275
|
|
|
275
276
|
#input-wrapper {
|
|
@@ -452,7 +453,13 @@
|
|
|
452
453
|
display: flex;
|
|
453
454
|
flex-wrap: wrap;
|
|
454
455
|
gap: 6px;
|
|
455
|
-
padding:
|
|
456
|
+
padding: 8px 6px;
|
|
457
|
+
position: absolute;
|
|
458
|
+
bottom: 100%;
|
|
459
|
+
left: 0;
|
|
460
|
+
right: 0;
|
|
461
|
+
z-index: 5;
|
|
462
|
+
background: transparent;
|
|
456
463
|
}
|
|
457
464
|
|
|
458
465
|
#suggestion-chips.hidden { display: none; }
|
|
@@ -472,6 +479,7 @@
|
|
|
472
479
|
text-align: left;
|
|
473
480
|
max-width: 100%;
|
|
474
481
|
line-height: 1.3;
|
|
482
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
475
483
|
}
|
|
476
484
|
|
|
477
485
|
.suggestion-chip:hover {
|