memhouse 0.4.4 → 0.7.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 +244 -114
- package/bin/memhouse.js +1177 -231
- package/editors/base.js +6 -0
- package/editors/claude.js +44 -9
- package/editors/goose.js +4 -0
- package/editors/index.js +0 -0
- package/editors/opencode.js +3 -0
- package/{mem-house → memhouse}/COMPETITION.md +5 -5
- package/{mem-house → memhouse}/DESIGN.md +18 -12
- package/memhouse/RESUME-AND-UPDATE.md +185 -0
- package/{mem-house → memhouse}/delivery/AGENT-INSTALL-PROMPT.md +1 -1
- package/{mem-house → memhouse}/delivery/AGENT-INSTALL.md +9 -7
- package/{mem-house → memhouse}/delivery/PROMPT.md +8 -8
- package/{mem-house → memhouse}/delivery/kernel-install.md +3 -3
- package/{mem-house → memhouse}/delivery/plugin/.claude-plugin/plugin.json +1 -1
- package/{mem-house/delivery/skills/memhouse-search → memhouse/delivery/plugin/skills/search}/SKILL.md +60 -18
- package/memhouse/delivery/plugin/skills/sessions/SKILL.md +177 -0
- package/memhouse/delivery/plugin/skills/sql/SKILL.md +206 -0
- package/memhouse/host.js +101 -0
- package/{mem-house → memhouse}/per-member/INSTALL.md +59 -14
- package/{mem-house → memhouse}/per-member/SCHEMA.md +47 -0
- package/{mem-house → memhouse}/per-member/SHARING.md +47 -0
- package/memhouse/per-member/provision.js +297 -0
- package/memhouse/per-member/rooms.js +252 -0
- package/{mem-house → memhouse}/per-member/schema-member.sql.tpl +18 -14
- package/{mem-house → memhouse}/per-member/schema-merge.sql.tpl +3 -3
- package/memhouse/resume.js +150 -0
- package/memhouse/self-update.js +150 -0
- package/{mem-house → memhouse}/server/queries.js +80 -34
- package/memhouse/server/server.js +451 -0
- package/{mem-house → memhouse}/service.js +48 -29
- package/memhouse/shipper/ship.js +949 -0
- package/package.json +5 -20
- package/public/assets/{index-CtH-5i95.js → index-CnWzrjF3.js} +10 -7
- package/public/index.html +2 -2
- package/ui/index.html +1 -1
- package/ui/src/App.jsx +1 -1
- package/ui/src/lib/api.js +29 -13
- package/ui/src/pages/Dashboard.jsx +8 -5
- package/ui/src/pages/Sessions.jsx +3 -1
- package/ui/src/pages/SqlViewer.jsx +6 -23
- package/cache.js +0 -850
- package/ch-ingest.js +0 -329
- package/ch-schema.js +0 -152
- package/clickhouse.js +0 -87
- package/index.js +0 -425
- package/mcp-server.js +0 -294
- package/mem-house/delivery/install.sh +0 -183
- package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +0 -125
- package/mem-house/delivery/skills/memhouse-sql/SKILL.md +0 -146
- package/mem-house/per-member/provision.js +0 -142
- package/mem-house/per-member/rooms.js +0 -132
- package/mem-house/server/server.js +0 -228
- package/mem-house/shipper/ship.js +0 -566
- package/relay-client.js +0 -306
- package/relay-server.js +0 -577
- package/server.js +0 -860
- package/share-image.js +0 -367
- /package/{mem-house → memhouse}/deploy.js +0 -0
- /package/{mem-house → memhouse}/envfile.js +0 -0
- /package/{mem-house → memhouse}/per-member/PLAN.md +0 -0
- /package/{mem-house → memhouse}/per-member/PROVISIONING.md +0 -0
- /package/{mem-house → memhouse}/per-member/README.md +0 -0
package/relay-client.js
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const http = require('http');
|
|
3
|
-
const https = require('https');
|
|
4
|
-
const readline = require('readline');
|
|
5
|
-
const crypto = require('crypto');
|
|
6
|
-
|
|
7
|
-
const cache = require('./cache');
|
|
8
|
-
|
|
9
|
-
const SYNC_INTERVAL_MS = 30000; // 30 seconds
|
|
10
|
-
|
|
11
|
-
const EDITOR_LABELS = {
|
|
12
|
-
'cursor': 'Cursor',
|
|
13
|
-
'devin': 'Devin',
|
|
14
|
-
'devin-next': 'Devin Next',
|
|
15
|
-
'antigravity': 'Antigravity',
|
|
16
|
-
'claude-code': 'Claude Code',
|
|
17
|
-
'claude': 'Claude Code',
|
|
18
|
-
'vscode': 'VS Code',
|
|
19
|
-
'vscode-insiders': 'VS Code Insiders',
|
|
20
|
-
'zed': 'Zed',
|
|
21
|
-
'opencode': 'OpenCode',
|
|
22
|
-
'codex': 'Codex CLI',
|
|
23
|
-
'gemini-cli': 'Gemini CLI',
|
|
24
|
-
'copilot-cli': 'GitHub Copilot',
|
|
25
|
-
'copilot-jetbrains': 'GitHub Copilot - JetBrains',
|
|
26
|
-
'cursor-agent': 'Cursor (Background Agent)',
|
|
27
|
-
'commandcode': 'CommandCode',
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Interactive project picker using readline (no external deps beyond Node built-ins).
|
|
32
|
-
* Returns an array of selected folder paths.
|
|
33
|
-
*/
|
|
34
|
-
async function pickProjects() {
|
|
35
|
-
cache.initDb();
|
|
36
|
-
|
|
37
|
-
// Scan to populate cache
|
|
38
|
-
console.log(chalk.dim(' Scanning local sessions...'));
|
|
39
|
-
cache.scanAll(() => {});
|
|
40
|
-
|
|
41
|
-
const db = cache.getDb();
|
|
42
|
-
const projects = db.prepare(`
|
|
43
|
-
SELECT folder, COUNT(*) as count
|
|
44
|
-
FROM chats WHERE folder IS NOT NULL
|
|
45
|
-
GROUP BY folder ORDER BY count DESC
|
|
46
|
-
`).all();
|
|
47
|
-
|
|
48
|
-
if (projects.length === 0) {
|
|
49
|
-
console.log(chalk.yellow(' No projects found in local cache.'));
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const cwd = process.cwd();
|
|
54
|
-
const cwdMatch = projects.find(p => p.folder === cwd);
|
|
55
|
-
|
|
56
|
-
// If cwd is a known project, offer quick share
|
|
57
|
-
if (cwdMatch) {
|
|
58
|
-
const name = cwdMatch.folder.split('/').pop();
|
|
59
|
-
const editors = db.prepare(`
|
|
60
|
-
SELECT source, COUNT(*) as count FROM chats
|
|
61
|
-
WHERE folder = ? AND source IS NOT NULL
|
|
62
|
-
GROUP BY source ORDER BY count DESC
|
|
63
|
-
`).all(cwdMatch.folder);
|
|
64
|
-
console.log('');
|
|
65
|
-
console.log(chalk.cyan(` ${name}`) + chalk.dim(` — ${cwdMatch.count} sessions`));
|
|
66
|
-
for (const e of editors) {
|
|
67
|
-
console.log(chalk.yellow(` • ${EDITOR_LABELS[e.source] || e.source}`) + chalk.dim(` (${e.count} sessions)`));
|
|
68
|
-
}
|
|
69
|
-
console.log('');
|
|
70
|
-
|
|
71
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
72
|
-
const answer = await new Promise(r => {
|
|
73
|
-
rl.question(chalk.bold(' Share your sessions with your team? ') + chalk.dim('(Y/n) '), r);
|
|
74
|
-
});
|
|
75
|
-
rl.close();
|
|
76
|
-
|
|
77
|
-
const trimmed = answer.trim().toLowerCase();
|
|
78
|
-
if (trimmed === '' || trimmed === 'y' || trimmed === 'yes') {
|
|
79
|
-
return [cwdMatch.folder];
|
|
80
|
-
}
|
|
81
|
-
// Fall through to full picker
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
console.log('');
|
|
85
|
-
console.log(chalk.bold(' Select projects to share (comma-separated numbers, or "all"):'));
|
|
86
|
-
console.log('');
|
|
87
|
-
projects.forEach((p, i) => {
|
|
88
|
-
const name = p.folder.split('/').pop();
|
|
89
|
-
console.log(chalk.cyan(` ${i + 1}.`) + ` ${name} ${chalk.dim(`(${p.count} sessions) — ${p.folder}`)}`);
|
|
90
|
-
});
|
|
91
|
-
console.log('');
|
|
92
|
-
|
|
93
|
-
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
94
|
-
|
|
95
|
-
return new Promise((resolve) => {
|
|
96
|
-
rl2.question(chalk.bold(' > '), (answer) => {
|
|
97
|
-
rl2.close();
|
|
98
|
-
const trimmed = answer.trim().toLowerCase();
|
|
99
|
-
if (trimmed === 'all' || trimmed === '*') {
|
|
100
|
-
resolve(projects.map(p => p.folder));
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const indices = trimmed.split(/[,\s]+/).map(s => parseInt(s.trim()) - 1).filter(i => i >= 0 && i < projects.length);
|
|
104
|
-
if (indices.length === 0) {
|
|
105
|
-
console.log(chalk.red(' No valid selection. Exiting.'));
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
resolve(indices.map(i => projects[i].folder));
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Collect data for selected projects from local cache DB.
|
|
115
|
-
*/
|
|
116
|
-
function collectProjectData(selectedFolders) {
|
|
117
|
-
const db = cache.getDb();
|
|
118
|
-
if (!db) return { chats: [], messages: [], stats: [] };
|
|
119
|
-
|
|
120
|
-
const allChats = [];
|
|
121
|
-
const allMessages = [];
|
|
122
|
-
const allStats = [];
|
|
123
|
-
|
|
124
|
-
for (const folder of selectedFolders) {
|
|
125
|
-
// Get chats for this project
|
|
126
|
-
const chats = db.prepare(`
|
|
127
|
-
SELECT id, source, name, mode, folder, created_at, last_updated_at, bubble_count
|
|
128
|
-
FROM chats WHERE folder = ?
|
|
129
|
-
`).all(folder);
|
|
130
|
-
|
|
131
|
-
for (const chat of chats) {
|
|
132
|
-
allChats.push({
|
|
133
|
-
id: chat.id,
|
|
134
|
-
source: chat.source,
|
|
135
|
-
name: chat.name,
|
|
136
|
-
mode: chat.mode,
|
|
137
|
-
folder: chat.folder,
|
|
138
|
-
created_at: chat.created_at,
|
|
139
|
-
last_updated_at: chat.last_updated_at,
|
|
140
|
-
bubble_count: chat.bubble_count,
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Get messages
|
|
144
|
-
const messages = db.prepare(
|
|
145
|
-
'SELECT chat_id, seq, role, content, model FROM messages WHERE chat_id = ? ORDER BY seq'
|
|
146
|
-
).all(chat.id);
|
|
147
|
-
for (const m of messages) {
|
|
148
|
-
allMessages.push({
|
|
149
|
-
chat_id: m.chat_id,
|
|
150
|
-
seq: m.seq,
|
|
151
|
-
role: m.role,
|
|
152
|
-
content: m.content,
|
|
153
|
-
model: m.model,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Get stats
|
|
158
|
-
const stat = db.prepare(
|
|
159
|
-
'SELECT * FROM chat_stats WHERE chat_id = ?'
|
|
160
|
-
).get(chat.id);
|
|
161
|
-
if (stat) {
|
|
162
|
-
allStats.push({
|
|
163
|
-
chat_id: stat.chat_id,
|
|
164
|
-
total_messages: stat.total_messages,
|
|
165
|
-
user_messages: stat.user_messages,
|
|
166
|
-
assistant_messages: stat.assistant_messages,
|
|
167
|
-
tool_calls: JSON.parse(stat.tool_calls || '[]'),
|
|
168
|
-
models: JSON.parse(stat.models || '[]'),
|
|
169
|
-
total_input_tokens: stat.total_input_tokens,
|
|
170
|
-
total_output_tokens: stat.total_output_tokens,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return { chats: allChats, messages: allMessages, stats: allStats };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* POST data to relay server.
|
|
181
|
-
*/
|
|
182
|
-
function postToRelay(scheme, host, port, username, data, authToken) {
|
|
183
|
-
return new Promise((resolve, reject) => {
|
|
184
|
-
const payload = JSON.stringify({
|
|
185
|
-
username,
|
|
186
|
-
projects: data.projects,
|
|
187
|
-
chats: data.chats,
|
|
188
|
-
messages: data.messages,
|
|
189
|
-
stats: data.stats,
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const headers = {
|
|
193
|
-
'Content-Type': 'application/json',
|
|
194
|
-
'Content-Length': Buffer.byteLength(payload),
|
|
195
|
-
};
|
|
196
|
-
if (authToken) headers['Authorization'] = `Bearer ${authToken}`;
|
|
197
|
-
|
|
198
|
-
const options = {
|
|
199
|
-
hostname: host,
|
|
200
|
-
port: port,
|
|
201
|
-
path: '/relay/sync',
|
|
202
|
-
method: 'POST',
|
|
203
|
-
headers,
|
|
204
|
-
};
|
|
205
|
-
const client = scheme === 'https' ? https : http
|
|
206
|
-
const req = client.request(options, (res) => {
|
|
207
|
-
let body = '';
|
|
208
|
-
res.on('data', (chunk) => { body += chunk; });
|
|
209
|
-
res.on('end', () => {
|
|
210
|
-
try {
|
|
211
|
-
resolve(JSON.parse(body));
|
|
212
|
-
} catch {
|
|
213
|
-
resolve({ raw: body });
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
req.on('error', reject);
|
|
219
|
-
req.setTimeout(15000, () => {
|
|
220
|
-
req.destroy(new Error('Request timed out'));
|
|
221
|
-
});
|
|
222
|
-
req.write(payload);
|
|
223
|
-
req.end();
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Main join client entry point.
|
|
229
|
-
*/
|
|
230
|
-
async function startJoinClient(relayAddress, username) {
|
|
231
|
-
console.log('');
|
|
232
|
-
console.log(chalk.bold(' ⚡ Agentlytics Relay — Join'));
|
|
233
|
-
console.log(chalk.dim(` Connecting to relay at ${relayAddress}`));
|
|
234
|
-
console.log(chalk.dim(` Username: ${username}`));
|
|
235
|
-
console.log('');
|
|
236
|
-
|
|
237
|
-
// Parse host:port
|
|
238
|
-
const parts = relayAddress.replace(/^https?:\/\//, '').split(':');
|
|
239
|
-
const scheme = relayAddress.startsWith("https://") ? "https" : 'http'
|
|
240
|
-
const host = parts[0] || 'localhost';
|
|
241
|
-
const port = parseInt(parts[1]) || 4638;
|
|
242
|
-
|
|
243
|
-
// Auth token from RELAY_PASSWORD env
|
|
244
|
-
const relayPassword = process.env.RELAY_PASSWORD || null;
|
|
245
|
-
const authToken = relayPassword
|
|
246
|
-
? crypto.createHmac('sha256', 'agentlytics-relay').update(relayPassword).digest('hex')
|
|
247
|
-
: null;
|
|
248
|
-
|
|
249
|
-
// Test connection
|
|
250
|
-
try {
|
|
251
|
-
const testResult = await postToRelay(scheme, host, port, username, { projects: [], chats: [], messages: [], stats: [] }, authToken);
|
|
252
|
-
if (!testResult.ok) {
|
|
253
|
-
const msg = testResult.error || 'unknown error';
|
|
254
|
-
if (msg === 'Unauthorized') {
|
|
255
|
-
console.log(chalk.red(' ✗ Relay requires a password. Set RELAY_PASSWORD env variable.'));
|
|
256
|
-
} else {
|
|
257
|
-
console.log(chalk.red(` ✗ Failed to connect: ${msg}`));
|
|
258
|
-
}
|
|
259
|
-
process.exit(1);
|
|
260
|
-
}
|
|
261
|
-
console.log(chalk.green(' ✓ Connected to relay'));
|
|
262
|
-
} catch (err) {
|
|
263
|
-
console.log(chalk.red(` ✗ Cannot reach relay at ${host}:${port}`));
|
|
264
|
-
console.log(chalk.dim(` ${err.message}`));
|
|
265
|
-
process.exit(1);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Pick projects
|
|
269
|
-
const selectedFolders = await pickProjects();
|
|
270
|
-
console.log('');
|
|
271
|
-
console.log(chalk.green(` ✓ Sharing ${selectedFolders.length} project(s):`));
|
|
272
|
-
for (const f of selectedFolders) {
|
|
273
|
-
console.log(chalk.dim(` • ${f.split('/').pop()} — ${f}`));
|
|
274
|
-
}
|
|
275
|
-
console.log('');
|
|
276
|
-
|
|
277
|
-
// Initial sync
|
|
278
|
-
async function sync() {
|
|
279
|
-
try {
|
|
280
|
-
// Rescan editors to pick up new/updated sessions (reset caches so fresh LS data is obtained)
|
|
281
|
-
cache.scanAll(() => {}, { resetCaches: true });
|
|
282
|
-
const data = collectProjectData(selectedFolders);
|
|
283
|
-
data.projects = selectedFolders;
|
|
284
|
-
const result = await postToRelay(scheme, host, port, username, data, authToken);
|
|
285
|
-
if (result.ok) {
|
|
286
|
-
const s = result.synced || {};
|
|
287
|
-
process.stdout.write(chalk.dim(`\r ⟳ Synced: ${s.chats || 0} chats, ${s.messages || 0} messages — ${new Date().toLocaleTimeString()} `));
|
|
288
|
-
} else {
|
|
289
|
-
process.stdout.write(chalk.yellow(`\r ⚠ Sync issue: ${result.error || 'unknown'} `));
|
|
290
|
-
}
|
|
291
|
-
} catch (err) {
|
|
292
|
-
process.stdout.write(chalk.red(`\r ✗ Sync failed: ${err.message} `));
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
console.log(chalk.cyan(` ⟳ Syncing every ${SYNC_INTERVAL_MS / 1000}s (Ctrl+C to stop)`));
|
|
297
|
-
console.log('');
|
|
298
|
-
|
|
299
|
-
// Do first sync immediately
|
|
300
|
-
await sync();
|
|
301
|
-
|
|
302
|
-
// Then sync periodically
|
|
303
|
-
setInterval(sync, SYNC_INTERVAL_MS);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
module.exports = { startJoinClient };
|