clay-server 2.12.0 → 2.13.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/daemon.js +14 -0
- package/lib/dm.js +3 -3
- package/lib/mates.js +222 -0
- package/lib/project.js +83 -2
- package/lib/public/app.js +324 -16
- package/lib/public/css/base.css +1 -0
- package/lib/public/css/mates.css +1059 -0
- package/lib/public/index.html +237 -0
- package/lib/public/modules/mate-knowledge.js +222 -0
- package/lib/public/modules/mate-sidebar.js +325 -0
- package/lib/public/modules/mate-wizard.js +265 -0
- package/lib/public/modules/profile.js +207 -0
- package/lib/public/modules/sidebar.js +143 -3
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +4 -0
- package/lib/server.js +110 -3
- package/lib/sessions.js +27 -0
- package/package.json +1 -1
package/lib/daemon.js
CHANGED
|
@@ -31,6 +31,7 @@ var { createServer, generateAuthToken } = require("./server");
|
|
|
31
31
|
var { 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
|
+
var mates = require("./mates");
|
|
34
35
|
|
|
35
36
|
var configFile = process.env.CLAY_CONFIG || process.env.CLAUDE_RELAY_CONFIG || require("./config").configPath();
|
|
36
37
|
var config;
|
|
@@ -935,6 +936,19 @@ for (var i = 0; i < projects.length; i++) {
|
|
|
935
936
|
}
|
|
936
937
|
}
|
|
937
938
|
|
|
939
|
+
// Register existing mates as projects
|
|
940
|
+
var allMates = mates.getAllMates();
|
|
941
|
+
for (var mi = 0; mi < allMates.length; mi++) {
|
|
942
|
+
var m = allMates[mi];
|
|
943
|
+
var mateDir = path.join(mates.MATES_DIR, m.id);
|
|
944
|
+
var mateSlug = "mate-" + m.id;
|
|
945
|
+
var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
946
|
+
if (fs.existsSync(mateDir)) {
|
|
947
|
+
console.log("[daemon] Adding mate project:", mateSlug);
|
|
948
|
+
relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true });
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
938
952
|
// Sync ~/.clayrc on startup
|
|
939
953
|
try { syncClayrc(config.projects); } catch (e) {}
|
|
940
954
|
|
package/lib/dm.js
CHANGED
|
@@ -118,10 +118,10 @@ function getDmList(userId) {
|
|
|
118
118
|
return dms;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
//
|
|
122
|
-
// Returns false for now - will be implemented when Mates feature is added
|
|
121
|
+
// Check if a user is a mate (AI persona)
|
|
123
122
|
function isMate(userId) {
|
|
124
|
-
|
|
123
|
+
var mates = require("./mates");
|
|
124
|
+
return mates.isMate(userId);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
module.exports = {
|
package/lib/mates.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
var fs = require("fs");
|
|
2
|
+
var path = require("path");
|
|
3
|
+
var crypto = require("crypto");
|
|
4
|
+
var config = require("./config");
|
|
5
|
+
|
|
6
|
+
var MATES_FILE = path.join(config.CONFIG_DIR, "mates.json");
|
|
7
|
+
var MATES_DIR = path.join(process.cwd(), ".claude", "mates");
|
|
8
|
+
|
|
9
|
+
// --- Default data ---
|
|
10
|
+
|
|
11
|
+
function defaultData() {
|
|
12
|
+
return { mates: [] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// --- Load / Save ---
|
|
16
|
+
|
|
17
|
+
function loadMates() {
|
|
18
|
+
try {
|
|
19
|
+
var raw = fs.readFileSync(MATES_FILE, "utf8");
|
|
20
|
+
var data = JSON.parse(raw);
|
|
21
|
+
if (!data.mates) data.mates = [];
|
|
22
|
+
return data;
|
|
23
|
+
} catch (e) {
|
|
24
|
+
return defaultData();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function saveMates(data) {
|
|
29
|
+
fs.mkdirSync(path.dirname(MATES_FILE), { recursive: true });
|
|
30
|
+
var tmpPath = MATES_FILE + ".tmp";
|
|
31
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
32
|
+
fs.renameSync(tmpPath, MATES_FILE);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --- CRUD ---
|
|
36
|
+
|
|
37
|
+
function generateMateId() {
|
|
38
|
+
return "mate_" + crypto.randomUUID();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createMate(seedData, userId) {
|
|
42
|
+
var data = loadMates();
|
|
43
|
+
var id = generateMateId();
|
|
44
|
+
|
|
45
|
+
// Pick a random avatar color from a pleasant palette
|
|
46
|
+
var colors = ["#6c5ce7", "#00b894", "#e17055", "#0984e3", "#fdcb6e", "#e84393", "#00cec9", "#ff7675"];
|
|
47
|
+
var colorIdx = crypto.randomBytes(1)[0] % colors.length;
|
|
48
|
+
|
|
49
|
+
var mate = {
|
|
50
|
+
id: id,
|
|
51
|
+
name: null,
|
|
52
|
+
createdBy: userId,
|
|
53
|
+
createdAt: Date.now(),
|
|
54
|
+
seedData: seedData || {},
|
|
55
|
+
profile: {
|
|
56
|
+
displayName: null,
|
|
57
|
+
avatarColor: colors[colorIdx],
|
|
58
|
+
avatarStyle: "bottts",
|
|
59
|
+
avatarSeed: crypto.randomBytes(4).toString("hex"),
|
|
60
|
+
},
|
|
61
|
+
status: "interviewing",
|
|
62
|
+
interviewProjectPath: null,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
data.mates.push(mate);
|
|
66
|
+
saveMates(data);
|
|
67
|
+
|
|
68
|
+
// Create the mate's identity directory
|
|
69
|
+
var mateDir = path.join(MATES_DIR, id);
|
|
70
|
+
fs.mkdirSync(mateDir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
// Write initial mate.yaml
|
|
73
|
+
var yaml = "# Mate metadata\n";
|
|
74
|
+
yaml += "id: " + id + "\n";
|
|
75
|
+
yaml += "name: null\n";
|
|
76
|
+
yaml += "status: interviewing\n";
|
|
77
|
+
yaml += "createdBy: " + userId + "\n";
|
|
78
|
+
yaml += "createdAt: " + mate.createdAt + "\n";
|
|
79
|
+
yaml += "relationship: " + (seedData.relationship || "assistant") + "\n";
|
|
80
|
+
yaml += "activities: " + JSON.stringify(seedData.activity || []) + "\n";
|
|
81
|
+
yaml += "autonomy: " + (seedData.autonomy || "always_ask") + "\n";
|
|
82
|
+
fs.writeFileSync(path.join(mateDir, "mate.yaml"), yaml);
|
|
83
|
+
|
|
84
|
+
// Write initial CLAUDE.md (will be replaced by interview)
|
|
85
|
+
var claudeMd = "# Mate Identity\n\n";
|
|
86
|
+
claudeMd += "This mate is currently being interviewed. Identity will be generated after the interview.\n\n";
|
|
87
|
+
claudeMd += "## Seed Data\n\n";
|
|
88
|
+
claudeMd += "- Relationship: " + (seedData.relationship || "assistant") + "\n";
|
|
89
|
+
if (seedData.activity && seedData.activity.length > 0) {
|
|
90
|
+
claudeMd += "- Activities: " + seedData.activity.join(", ") + "\n";
|
|
91
|
+
}
|
|
92
|
+
if (seedData.communicationStyle && seedData.communicationStyle.length > 0) {
|
|
93
|
+
claudeMd += "- Communication: " + seedData.communicationStyle.join(", ") + "\n";
|
|
94
|
+
}
|
|
95
|
+
claudeMd += "- Autonomy: " + (seedData.autonomy || "always_ask") + "\n";
|
|
96
|
+
fs.writeFileSync(path.join(mateDir, "CLAUDE.md"), claudeMd);
|
|
97
|
+
|
|
98
|
+
return mate;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getMate(id) {
|
|
102
|
+
var data = loadMates();
|
|
103
|
+
for (var i = 0; i < data.mates.length; i++) {
|
|
104
|
+
if (data.mates[i].id === id) return data.mates[i];
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function updateMate(id, updates) {
|
|
110
|
+
var data = loadMates();
|
|
111
|
+
for (var i = 0; i < data.mates.length; i++) {
|
|
112
|
+
if (data.mates[i].id === id) {
|
|
113
|
+
var keys = Object.keys(updates);
|
|
114
|
+
for (var j = 0; j < keys.length; j++) {
|
|
115
|
+
data.mates[i][keys[j]] = updates[keys[j]];
|
|
116
|
+
}
|
|
117
|
+
saveMates(data);
|
|
118
|
+
return data.mates[i];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function deleteMate(id) {
|
|
125
|
+
var data = loadMates();
|
|
126
|
+
var before = data.mates.length;
|
|
127
|
+
data.mates = data.mates.filter(function (m) {
|
|
128
|
+
return m.id !== id;
|
|
129
|
+
});
|
|
130
|
+
if (data.mates.length === before) return { error: "Mate not found" };
|
|
131
|
+
saveMates(data);
|
|
132
|
+
|
|
133
|
+
// Remove mate directory
|
|
134
|
+
var mateDir = path.join(MATES_DIR, id);
|
|
135
|
+
try {
|
|
136
|
+
fs.rmSync(mateDir, { recursive: true, force: true });
|
|
137
|
+
} catch (e) {
|
|
138
|
+
// Directory may not exist
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { ok: true };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getAllMates() {
|
|
145
|
+
var data = loadMates();
|
|
146
|
+
return data.mates;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getMatesByUser(userId) {
|
|
150
|
+
var data = loadMates();
|
|
151
|
+
return data.mates.filter(function (m) {
|
|
152
|
+
return m.createdBy === userId;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isMate(id) {
|
|
157
|
+
if (!id) return false;
|
|
158
|
+
if (typeof id === "string" && id.indexOf("mate_") === 0) {
|
|
159
|
+
// Double check it exists in registry
|
|
160
|
+
return !!getMate(id);
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getMateDir(id) {
|
|
166
|
+
return path.join(MATES_DIR, id);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Format seed data as a human-readable context string
|
|
170
|
+
function formatSeedContext(seedData) {
|
|
171
|
+
if (!seedData) return "";
|
|
172
|
+
var parts = [];
|
|
173
|
+
|
|
174
|
+
if (seedData.relationship) {
|
|
175
|
+
parts.push("The user wants a " + seedData.relationship + " relationship.");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (seedData.activity && seedData.activity.length > 0) {
|
|
179
|
+
parts.push("Primary activities: " + seedData.activity.join(", ") + ".");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (seedData.communicationStyle && seedData.communicationStyle.length > 0) {
|
|
183
|
+
var styleLabels = {
|
|
184
|
+
direct_concise: "direct and concise",
|
|
185
|
+
soft_detailed: "soft and detailed",
|
|
186
|
+
witty: "witty",
|
|
187
|
+
encouraging: "encouraging",
|
|
188
|
+
formal: "formal",
|
|
189
|
+
no_nonsense: "no-nonsense",
|
|
190
|
+
};
|
|
191
|
+
var styles = seedData.communicationStyle.map(function (s) { return styleLabels[s] || s.replace(/_/g, " "); });
|
|
192
|
+
parts.push("Communication style: " + styles.join(", ") + ".");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (seedData.autonomy) {
|
|
196
|
+
var autonomyLabels = {
|
|
197
|
+
always_ask: "Always ask before acting",
|
|
198
|
+
minor_stuff_ok: "Handle minor stuff without asking",
|
|
199
|
+
mostly_autonomous: "Mostly autonomous, ask for big decisions",
|
|
200
|
+
fully_autonomous: "Fully autonomous",
|
|
201
|
+
};
|
|
202
|
+
parts.push("Autonomy: " + (autonomyLabels[seedData.autonomy] || seedData.autonomy) + ".");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return parts.join(" ");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = {
|
|
209
|
+
MATES_FILE: MATES_FILE,
|
|
210
|
+
MATES_DIR: MATES_DIR,
|
|
211
|
+
loadMates: loadMates,
|
|
212
|
+
saveMates: saveMates,
|
|
213
|
+
createMate: createMate,
|
|
214
|
+
getMate: getMate,
|
|
215
|
+
updateMate: updateMate,
|
|
216
|
+
deleteMate: deleteMate,
|
|
217
|
+
getAllMates: getAllMates,
|
|
218
|
+
getMatesByUser: getMatesByUser,
|
|
219
|
+
isMate: isMate,
|
|
220
|
+
getMateDir: getMateDir,
|
|
221
|
+
formatSeedContext: formatSeedContext,
|
|
222
|
+
};
|
package/lib/project.js
CHANGED
|
@@ -11,7 +11,6 @@ var { execFileSync, spawn } = require("child_process");
|
|
|
11
11
|
var { createLoopRegistry } = require("./scheduler");
|
|
12
12
|
var usersModule = require("./users");
|
|
13
13
|
var { resolveOsUserInfo, fsAsUser } = require("./os-users");
|
|
14
|
-
|
|
15
14
|
var MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
16
15
|
|
|
17
16
|
// Validate environment variable string (KEY=VALUE per line)
|
|
@@ -114,6 +113,7 @@ function createProjectContext(opts) {
|
|
|
114
113
|
var osUsers = opts.osUsers || false;
|
|
115
114
|
var projectOwnerId = opts.projectOwnerId || null;
|
|
116
115
|
var worktreeMeta = opts.worktreeMeta || null; // { parentSlug, branch, accessible }
|
|
116
|
+
var isMate = opts.isMate || false;
|
|
117
117
|
var onCreateWorktree = opts.onCreateWorktree || null;
|
|
118
118
|
var latestVersion = null;
|
|
119
119
|
|
|
@@ -1214,13 +1214,91 @@ function createProjectContext(opts) {
|
|
|
1214
1214
|
|
|
1215
1215
|
function handleMessage(ws, msg) {
|
|
1216
1216
|
// --- DM messages (delegated to server-level handler) ---
|
|
1217
|
-
if (msg.type === "dm_open" || msg.type === "dm_send" || msg.type === "dm_list" || msg.type === "dm_typing" || msg.type === "dm_add_favorite" || msg.type === "dm_remove_favorite") {
|
|
1217
|
+
if (msg.type === "dm_open" || msg.type === "dm_send" || msg.type === "dm_list" || msg.type === "dm_typing" || msg.type === "dm_add_favorite" || msg.type === "dm_remove_favorite" || msg.type === "mate_create" || msg.type === "mate_list" || msg.type === "mate_delete" || msg.type === "mate_update") {
|
|
1218
1218
|
if (typeof opts.onDmMessage === "function") {
|
|
1219
1219
|
opts.onDmMessage(ws, msg);
|
|
1220
1220
|
}
|
|
1221
1221
|
return;
|
|
1222
1222
|
}
|
|
1223
1223
|
|
|
1224
|
+
// --- Knowledge file management ---
|
|
1225
|
+
if (msg.type === "knowledge_list") {
|
|
1226
|
+
var knowledgeDir = path.join(cwd, "knowledge");
|
|
1227
|
+
var files = [];
|
|
1228
|
+
try {
|
|
1229
|
+
var entries = fs.readdirSync(knowledgeDir);
|
|
1230
|
+
for (var ki = 0; ki < entries.length; ki++) {
|
|
1231
|
+
if (entries[ki].endsWith(".md")) {
|
|
1232
|
+
var stat = fs.statSync(path.join(knowledgeDir, entries[ki]));
|
|
1233
|
+
files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs });
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
} catch (e) { /* dir may not exist */ }
|
|
1237
|
+
files.sort(function (a, b) { return b.mtime - a.mtime; });
|
|
1238
|
+
sendTo(ws, { type: "knowledge_list", files: files });
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (msg.type === "knowledge_read") {
|
|
1243
|
+
if (!msg.name) return;
|
|
1244
|
+
var safeName = path.basename(msg.name);
|
|
1245
|
+
var filePath = path.join(cwd, "knowledge", safeName);
|
|
1246
|
+
try {
|
|
1247
|
+
var content = fs.readFileSync(filePath, "utf8");
|
|
1248
|
+
sendTo(ws, { type: "knowledge_content", name: safeName, content: content });
|
|
1249
|
+
} catch (e) {
|
|
1250
|
+
sendTo(ws, { type: "knowledge_content", name: safeName, content: "", error: "File not found" });
|
|
1251
|
+
}
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
if (msg.type === "knowledge_save") {
|
|
1256
|
+
if (!msg.name || typeof msg.content !== "string") return;
|
|
1257
|
+
var safeName = path.basename(msg.name);
|
|
1258
|
+
if (!safeName.endsWith(".md")) safeName += ".md";
|
|
1259
|
+
var knowledgeDir = path.join(cwd, "knowledge");
|
|
1260
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
1261
|
+
fs.writeFileSync(path.join(knowledgeDir, safeName), msg.content);
|
|
1262
|
+
// Return updated list
|
|
1263
|
+
var files = [];
|
|
1264
|
+
try {
|
|
1265
|
+
var entries = fs.readdirSync(knowledgeDir);
|
|
1266
|
+
for (var ki = 0; ki < entries.length; ki++) {
|
|
1267
|
+
if (entries[ki].endsWith(".md")) {
|
|
1268
|
+
var stat = fs.statSync(path.join(knowledgeDir, entries[ki]));
|
|
1269
|
+
files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs });
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
} catch (e) {}
|
|
1273
|
+
files.sort(function (a, b) { return b.mtime - a.mtime; });
|
|
1274
|
+
sendTo(ws, { type: "knowledge_saved", name: safeName });
|
|
1275
|
+
sendTo(ws, { type: "knowledge_list", files: files });
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if (msg.type === "knowledge_delete") {
|
|
1280
|
+
if (!msg.name) return;
|
|
1281
|
+
var safeName = path.basename(msg.name);
|
|
1282
|
+
var filePath = path.join(cwd, "knowledge", safeName);
|
|
1283
|
+
try { fs.unlinkSync(filePath); } catch (e) {}
|
|
1284
|
+
// Return updated list
|
|
1285
|
+
var knowledgeDir = path.join(cwd, "knowledge");
|
|
1286
|
+
var files = [];
|
|
1287
|
+
try {
|
|
1288
|
+
var entries = fs.readdirSync(knowledgeDir);
|
|
1289
|
+
for (var ki = 0; ki < entries.length; ki++) {
|
|
1290
|
+
if (entries[ki].endsWith(".md")) {
|
|
1291
|
+
var stat = fs.statSync(path.join(knowledgeDir, entries[ki]));
|
|
1292
|
+
files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs });
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
} catch (e) {}
|
|
1296
|
+
files.sort(function (a, b) { return b.mtime - a.mtime; });
|
|
1297
|
+
sendTo(ws, { type: "knowledge_deleted", name: safeName });
|
|
1298
|
+
sendTo(ws, { type: "knowledge_list", files: files });
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1224
1302
|
if (msg.type === "push_subscribe") {
|
|
1225
1303
|
if (pushModule && msg.subscription) pushModule.addSubscription(msg.subscription, msg.replaceEndpoint);
|
|
1226
1304
|
return;
|
|
@@ -3496,6 +3574,9 @@ function createProjectContext(opts) {
|
|
|
3496
3574
|
isProcessing: hasProcessing,
|
|
3497
3575
|
projectOwnerId: projectOwnerId,
|
|
3498
3576
|
};
|
|
3577
|
+
if (isMate) {
|
|
3578
|
+
status.isMate = true;
|
|
3579
|
+
}
|
|
3499
3580
|
if (worktreeMeta) {
|
|
3500
3581
|
status.isWorktree = true;
|
|
3501
3582
|
status.parentSlug = worktreeMeta.parentSlug;
|