memhouse 0.4.3 → 0.7.0

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.
Files changed (64) hide show
  1. package/README.md +244 -114
  2. package/bin/memhouse.js +1169 -233
  3. package/editors/base.js +6 -0
  4. package/editors/claude.js +44 -9
  5. package/editors/goose.js +4 -0
  6. package/editors/index.js +0 -0
  7. package/editors/opencode.js +3 -0
  8. package/{mem-house → memhouse}/COMPETITION.md +5 -5
  9. package/{mem-house → memhouse}/DESIGN.md +18 -12
  10. package/memhouse/RESUME-AND-UPDATE.md +185 -0
  11. package/{mem-house → memhouse}/delivery/AGENT-INSTALL-PROMPT.md +1 -1
  12. package/{mem-house → memhouse}/delivery/AGENT-INSTALL.md +9 -7
  13. package/{mem-house → memhouse}/delivery/PROMPT.md +8 -8
  14. package/{mem-house → memhouse}/delivery/kernel-install.md +3 -3
  15. package/{mem-house → memhouse}/delivery/plugin/.claude-plugin/plugin.json +1 -1
  16. package/{mem-house/delivery/skills/memhouse-search → memhouse/delivery/plugin/skills/search}/SKILL.md +60 -18
  17. package/memhouse/delivery/plugin/skills/sessions/SKILL.md +177 -0
  18. package/memhouse/delivery/plugin/skills/sql/SKILL.md +206 -0
  19. package/memhouse/host.js +101 -0
  20. package/{mem-house → memhouse}/per-member/INSTALL.md +59 -14
  21. package/{mem-house → memhouse}/per-member/SCHEMA.md +47 -0
  22. package/memhouse/per-member/provision.js +265 -0
  23. package/memhouse/per-member/rooms.js +235 -0
  24. package/memhouse/per-member/schema-member.sql.tpl +125 -0
  25. package/{mem-house → memhouse}/per-member/schema-merge.sql.tpl +3 -3
  26. package/memhouse/resume.js +150 -0
  27. package/memhouse/self-update.js +150 -0
  28. package/{mem-house → memhouse}/server/queries.js +80 -34
  29. package/memhouse/server/server.js +451 -0
  30. package/{mem-house → memhouse}/service.js +48 -29
  31. package/memhouse/shipper/ship.js +949 -0
  32. package/package.json +5 -20
  33. package/public/assets/{index-CtH-5i95.js → index-CnWzrjF3.js} +10 -7
  34. package/public/index.html +2 -2
  35. package/ui/index.html +1 -1
  36. package/ui/src/App.jsx +1 -1
  37. package/ui/src/lib/api.js +29 -13
  38. package/ui/src/pages/Dashboard.jsx +8 -5
  39. package/ui/src/pages/Sessions.jsx +3 -1
  40. package/ui/src/pages/SqlViewer.jsx +6 -23
  41. package/cache.js +0 -850
  42. package/ch-ingest.js +0 -329
  43. package/ch-schema.js +0 -152
  44. package/clickhouse.js +0 -87
  45. package/index.js +0 -425
  46. package/mcp-server.js +0 -294
  47. package/mem-house/delivery/install.sh +0 -183
  48. package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +0 -125
  49. package/mem-house/delivery/skills/memhouse-sql/SKILL.md +0 -146
  50. package/mem-house/per-member/provision.js +0 -142
  51. package/mem-house/per-member/rooms.js +0 -132
  52. package/mem-house/per-member/schema-member.sql.tpl +0 -85
  53. package/mem-house/server/server.js +0 -228
  54. package/mem-house/shipper/ship.js +0 -504
  55. package/relay-client.js +0 -306
  56. package/relay-server.js +0 -577
  57. package/server.js +0 -860
  58. package/share-image.js +0 -367
  59. /package/{mem-house → memhouse}/deploy.js +0 -0
  60. /package/{mem-house → memhouse}/envfile.js +0 -0
  61. /package/{mem-house → memhouse}/per-member/PLAN.md +0 -0
  62. /package/{mem-house → memhouse}/per-member/PROVISIONING.md +0 -0
  63. /package/{mem-house → memhouse}/per-member/README.md +0 -0
  64. /package/{mem-house → memhouse}/per-member/SHARING.md +0 -0
@@ -1,228 +0,0 @@
1
- // mem-house server — the dashboard/API entrypoint.
2
- //
3
- // Speaks the agentlytics REST contract (same routes, same response shapes) over the
4
- // mem-house typed schema, so the UNMODIFIED agentlytics React SPA is the dashboard:
5
- // queries.js computes every response; this file is routing + config + stubs. Serves
6
- // the built SPA from the repo root's public/.
7
- //
8
- // node mem-house/server/server.js (env: MEMHOUSE_* per DESIGN.md; port 4640)
9
-
10
- const express = require('express');
11
- const path = require('path');
12
- const fs = require('fs');
13
- const os = require('os');
14
- const qy = require('./queries');
15
-
16
- const PORT = parseInt(process.env.MEMHOUSE_PORT || '4640', 10);
17
- // Localhost by default: the dashboard exposes full transcripts and a SQL console
18
- // with no auth. Remote access is an explicit opt-in (MEMHOUSE_HOST=0.0.0.0).
19
- const HOST = process.env.MEMHOUSE_HOST || '127.0.0.1';
20
- const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
21
- const CONFIG_PATH = path.join(os.homedir(), '.memhouse', 'config.json');
22
-
23
- const app = express();
24
- app.use(express.json());
25
- if (fs.existsSync(PUBLIC_DIR)) app.use(express.static(PUBLIC_DIR));
26
-
27
- function readConfig() {
28
- try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); } catch { return {}; }
29
- }
30
- function writeConfig(config) {
31
- fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
32
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
33
- }
34
- const hiddenFolders = () => readConfig().hiddenProjects || [];
35
-
36
- function parseDateOpts(query) {
37
- const opts = {};
38
- if (query.dateFrom) opts.dateFrom = parseInt(query.dateFrom) || null;
39
- if (query.dateTo) opts.dateTo = parseInt(query.dateTo) || null;
40
- return opts;
41
- }
42
-
43
- // Route helper: async handler with the uniform 500 shape.
44
- const route = (fn) => async (req, res) => {
45
- try { await fn(req, res); } catch (err) { res.status(500).json({ error: err.message }); }
46
- };
47
-
48
- app.get('/api/ping', (req, res) => res.json({ app: 'agentlytics', pid: process.pid }));
49
- app.get('/api/mode', (req, res) => res.json({ mode: 'local' }));
50
-
51
- app.get('/api/overview', route(async (req, res) => {
52
- res.json(await qy.getOverview({ editor: req.query.editor || null, ...parseDateOpts(req.query), hiddenFolders: hiddenFolders() }));
53
- }));
54
-
55
- app.get('/api/daily-activity', route(async (req, res) => {
56
- res.json(await qy.getDailyActivity({ editor: req.query.editor || null, ...parseDateOpts(req.query), hiddenFolders: hiddenFolders() }));
57
- }));
58
-
59
- app.get('/api/dashboard-stats', route(async (req, res) => {
60
- res.json(await qy.getDashboardStats({ editor: req.query.editor || null, ...parseDateOpts(req.query), hiddenFolders: hiddenFolders() }));
61
- }));
62
-
63
- app.get('/api/chats', route(async (req, res) => {
64
- const opts = {
65
- editor: req.query.editor || null,
66
- folder: req.query.folder || null,
67
- named: req.query.named !== 'false',
68
- limit: req.query.limit ? parseInt(req.query.limit) : 200,
69
- offset: req.query.offset ? parseInt(req.query.offset) : 0,
70
- ...parseDateOpts(req.query),
71
- hiddenFolders: hiddenFolders(),
72
- };
73
- const [total, chats] = await Promise.all([qy.countChats(opts), qy.getChats(opts)]);
74
- res.json({ total, chats });
75
- }));
76
-
77
- app.get('/api/chats/:id', route(async (req, res) => {
78
- const result = await qy.getChat(req.params.id);
79
- if (!result) return res.status(404).json({ error: 'Chat not found' });
80
- res.json(result);
81
- }));
82
-
83
- app.get('/api/chats/:id/markdown', route(async (req, res) => {
84
- const result = await qy.getChat(req.params.id);
85
- if (!result) return res.status(404).json({ error: 'Chat not found' });
86
- const lines = [];
87
- const title = result.name || 'Untitled Session';
88
- lines.push(`# ${title}\n`);
89
- const meta = [];
90
- if (result.source) meta.push(`**Editor:** ${result.source}`);
91
- if (result.mode) meta.push(`**Mode:** ${result.mode}`);
92
- if (result.folder) meta.push(`**Project:** ${result.folder}`);
93
- if (result.createdAt) meta.push(`**Created:** ${new Date(result.createdAt).toISOString()}`);
94
- if (result.lastUpdatedAt) meta.push(`**Updated:** ${new Date(result.lastUpdatedAt).toISOString()}`);
95
- if (result.stats) {
96
- meta.push(`**Messages:** ${result.stats.totalMessages}`);
97
- if (result.stats.totalInputTokens) meta.push(`**Input Tokens:** ${result.stats.totalInputTokens}`);
98
- if (result.stats.totalOutputTokens) meta.push(`**Output Tokens:** ${result.stats.totalOutputTokens}`);
99
- const models = [...new Set(result.stats.models || [])];
100
- if (models.length > 0) meta.push(`**Models:** ${models.join(', ')}`);
101
- }
102
- if (meta.length > 0) lines.push(meta.join(' \n') + '\n');
103
- lines.push('---\n');
104
- for (const msg of result.messages) {
105
- const label = msg.role === 'user' ? '## User' : msg.role === 'assistant' ? '## Assistant' : `## ${msg.role.charAt(0).toUpperCase() + msg.role.slice(1)}`;
106
- const modelTag = msg.model ? ` *(${msg.model})*` : '';
107
- lines.push(`${label}${modelTag}\n`);
108
- lines.push(msg.content + '\n');
109
- }
110
- const filename = title.replace(/[^a-zA-Z0-9_-]/g, '_').substring(0, 80) + '.md';
111
- res.setHeader('Content-Type', 'text/markdown; charset=utf-8');
112
- res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
113
- res.send(lines.join('\n'));
114
- }));
115
-
116
- app.get('/api/projects', route(async (req, res) => {
117
- res.json(await qy.getProjects({ ...parseDateOpts(req.query), hiddenFolders: hiddenFolders() }));
118
- }));
119
-
120
- app.get('/api/all-projects', route(async (req, res) => {
121
- res.json(await qy.getProjects({ ...parseDateOpts(req.query), includeHidden: true }));
122
- }));
123
-
124
- app.get('/api/deep-analytics', route(async (req, res) => {
125
- res.json(await qy.getDeepAnalytics({
126
- editor: req.query.editor || null,
127
- folder: req.query.folder || null,
128
- limit: Math.min(parseInt(req.query.limit) || 500, 5000),
129
- ...parseDateOpts(req.query),
130
- hiddenFolders: hiddenFolders(),
131
- }));
132
- }));
133
-
134
- app.get('/api/costs', route(async (req, res) => {
135
- res.json(await qy.estimateCosts({
136
- editor: req.query.editor || null,
137
- folder: req.query.folder || null,
138
- chatId: req.query.chatId || null,
139
- ...parseDateOpts(req.query),
140
- hiddenFolders: hiddenFolders(),
141
- }));
142
- }));
143
-
144
- app.get('/api/cost-analytics', route(async (req, res) => {
145
- res.json(await qy.getCostAnalytics({ editor: req.query.editor || null, ...parseDateOpts(req.query), hiddenFolders: hiddenFolders() }));
146
- }));
147
-
148
- app.get('/api/tool-calls', route(async (req, res) => {
149
- const name = req.query.name;
150
- if (!name) return res.status(400).json({ error: 'name query param required' });
151
- // Same filter set as the analytics the drill-down is opened from.
152
- res.json(await qy.getToolCalls(name, {
153
- limit: Math.min(parseInt(req.query.limit) || 200, 1000),
154
- folder: req.query.folder || null,
155
- editor: req.query.editor || null,
156
- ...parseDateOpts(req.query),
157
- hiddenFolders: hiddenFolders(),
158
- }));
159
- }));
160
-
161
- app.post('/api/query', route(async (req, res) => {
162
- const { sql } = req.body;
163
- if (!sql || typeof sql !== 'string') return res.status(400).json({ error: 'sql string required' });
164
- const first = sql.trim().replace(/^--.*$/gm, '').trim().split(/\s+/)[0].toUpperCase();
165
- if (!['SELECT', 'WITH', 'EXPLAIN', 'DESCRIBE', 'SHOW'].includes(first)) {
166
- return res.status(403).json({ error: 'Only SELECT queries are allowed' });
167
- }
168
- try { res.json(await qy.rawQuery(sql)); }
169
- catch (err) { res.status(400).json({ error: err.message }); }
170
- }));
171
-
172
- app.get('/api/schema', route(async (req, res) => res.json(await qy.schema())));
173
-
174
- app.get('/api/config', (req, res) => res.json(readConfig()));
175
- app.put('/api/config', route(async (req, res) => {
176
- const config = readConfig();
177
- Object.assign(config, req.body);
178
- writeConfig(config);
179
- res.json(config);
180
- }));
181
-
182
- // Real refetch: run one full shipper pass, streaming SSE like the root contract.
183
- app.get('/api/refetch', async (req, res) => {
184
- res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' });
185
- try {
186
- const { runShip } = require('../shipper/ship');
187
- res.write(`data: ${JSON.stringify({ scanned: 0, analyzed: 0, skipped: 0, total: 0 })}\n\n`);
188
- const r = await runShip(qy.getClient(), { full: true });
189
- res.write(`data: ${JSON.stringify({ done: true, total: r.sessions + r.skipped, analyzed: r.sessions })}\n\n`);
190
- } catch (err) {
191
- res.write(`data: ${JSON.stringify({ error: err.message })}\n\n`);
192
- }
193
- res.end();
194
- });
195
-
196
- // Not applicable to mem-house (dashboard renders these pages empty/absent).
197
- app.get('/api/usage', (req, res) => res.json([]));
198
- app.get('/api/artifacts', (req, res) => res.json([]));
199
- app.get('/api/artifact-content', (req, res) => res.status(404).json({ error: 'not available in mem-house' }));
200
- app.get('/api/mcps', (req, res) => res.json({
201
- servers: [], toolCalls: [], matchedTools: {}, topSessions: [], projectMcps: [],
202
- summary: { totalServers: 0, totalToolCalls: 0, uniqueTools: 0, sessionsWithTools: 0, editorsWithServers: [] },
203
- }));
204
- app.get('/api/gsd/projects', (req, res) => res.json([]));
205
- app.get('/api/gsd/phases', (req, res) => res.json([]));
206
- app.get('/api/gsd/plan', (req, res) => res.status(404).json({ error: 'not available in mem-house' }));
207
- app.get('/api/gsd/overview', (req, res) => res.json({ totalProjects: 0, totalPhases: 0, completedPhases: 0, activePhases: [], executingPhases: 0, plannedPhases: 0 }));
208
- app.get('/api/gsd/config', (req, res) => res.json(null));
209
- app.get('/api/gsd/phase-tokens', (req, res) => res.json([]));
210
- app.get('/api/gsd/file', (req, res) => res.json({ content: null }));
211
- app.get('/api/share-image', (req, res) => res.status(501).json({ error: 'not available in mem-house' }));
212
- app.get('/api/check-ai', (req, res) => res.status(501).json({ error: 'not available in mem-house' }));
213
-
214
- // SPA fallback
215
- app.get('*', (req, res) => {
216
- const index = path.join(PUBLIC_DIR, 'index.html');
217
- if (fs.existsSync(index)) res.sendFile(index);
218
- else res.status(503).json({ error: 'UI not built — run: cd ui && npm install && npm run build' });
219
- });
220
-
221
- if (require.main === module) {
222
- app.listen(PORT, HOST, () => {
223
- console.log(`[mem-house] dashboard → http://localhost:${PORT} (house '${qy.config.database}' @ ${qy.config.url.replace(/\/\/.*@/, '//')})`
224
- + (HOST !== '127.0.0.1' ? ` [bound to ${HOST} — remotely reachable]` : ''));
225
- });
226
- }
227
-
228
- module.exports = app;