memhouse 0.0.1 → 0.3.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 (96) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +59 -3
  3. package/bin/memhouse.js +487 -0
  4. package/cache.js +850 -0
  5. package/ch-ingest.js +329 -0
  6. package/ch-schema.js +152 -0
  7. package/clickhouse.js +87 -0
  8. package/editors/antigravity.js +1019 -0
  9. package/editors/base.js +321 -0
  10. package/editors/claude.js +386 -0
  11. package/editors/codebuff.js +338 -0
  12. package/editors/codex.js +537 -0
  13. package/editors/commandcode.js +166 -0
  14. package/editors/copilot-jetbrains.js +207 -0
  15. package/editors/copilot.js +260 -0
  16. package/editors/cursor-agent.js +202 -0
  17. package/editors/cursor.js +438 -0
  18. package/editors/gemini.js +231 -0
  19. package/editors/goose.js +358 -0
  20. package/editors/gsd.js +366 -0
  21. package/editors/index.js +179 -0
  22. package/editors/kiro.js +315 -0
  23. package/editors/opencode.js +374 -0
  24. package/editors/vscode.js +413 -0
  25. package/editors/windsurf.js +634 -0
  26. package/editors/zed.js +213 -0
  27. package/index.js +424 -2
  28. package/mcp-server.js +294 -0
  29. package/mem-house/DESIGN.md +119 -0
  30. package/mem-house/delivery/AGENT-INSTALL.md +75 -0
  31. package/mem-house/delivery/PROMPT.md +42 -0
  32. package/mem-house/delivery/install.sh +181 -0
  33. package/mem-house/delivery/kernel-install.md +76 -0
  34. package/mem-house/delivery/plugin/.claude-plugin/plugin.json +8 -0
  35. package/mem-house/delivery/skills/memhouse-search/SKILL.md +104 -0
  36. package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +94 -0
  37. package/mem-house/delivery/skills/memhouse-sql/SKILL.md +115 -0
  38. package/mem-house/rls.sql +17 -0
  39. package/mem-house/schema.sql +145 -0
  40. package/mem-house/server/queries.js +878 -0
  41. package/mem-house/server/server.js +228 -0
  42. package/mem-house/shipper/ship.js +402 -0
  43. package/misc/syntax-check.js +38 -0
  44. package/package.json +56 -7
  45. package/pricing.js +88 -0
  46. package/pricing.json +992 -0
  47. package/public/assets/index-BsgjOSTv.js +75 -0
  48. package/public/assets/index-DV6ONi_F.css +2 -0
  49. package/public/index.html +16 -0
  50. package/relay-client.js +306 -0
  51. package/relay-server.js +577 -0
  52. package/server.js +860 -0
  53. package/share-image.js +367 -0
  54. package/ui/eslint.config.js +29 -0
  55. package/ui/index.html +15 -0
  56. package/ui/package-lock.json +4229 -0
  57. package/ui/package.json +38 -0
  58. package/ui/src/App.jsx +364 -0
  59. package/ui/src/components/ActivityHeatmap.jsx +206 -0
  60. package/ui/src/components/AiAuditCard.jsx +254 -0
  61. package/ui/src/components/AnimatedLoader.jsx +14 -0
  62. package/ui/src/components/AnimatedLogo.jsx +96 -0
  63. package/ui/src/components/ChatSidebar.jsx +226 -0
  64. package/ui/src/components/DateRangePicker.jsx +104 -0
  65. package/ui/src/components/EditorDot.jsx +11 -0
  66. package/ui/src/components/EditorIcon.jsx +117 -0
  67. package/ui/src/components/KpiCard.jsx +9 -0
  68. package/ui/src/components/LiveFeed.jsx +128 -0
  69. package/ui/src/components/LoginScreen.jsx +81 -0
  70. package/ui/src/components/MessageRenderer.jsx +148 -0
  71. package/ui/src/components/PageHeader.jsx +11 -0
  72. package/ui/src/components/SectionTitle.jsx +3 -0
  73. package/ui/src/components/ShareModal.jsx +305 -0
  74. package/ui/src/components/TokenTimeline.jsx +258 -0
  75. package/ui/src/hooks/useLive.jsx +15 -0
  76. package/ui/src/index.css +83 -0
  77. package/ui/src/lib/api.js +343 -0
  78. package/ui/src/lib/constants.js +88 -0
  79. package/ui/src/lib/theme.jsx +23 -0
  80. package/ui/src/main.jsx +16 -0
  81. package/ui/src/pages/Artifacts.jsx +575 -0
  82. package/ui/src/pages/Compare.jsx +232 -0
  83. package/ui/src/pages/CostAnalysis.jsx +354 -0
  84. package/ui/src/pages/Dashboard.jsx +529 -0
  85. package/ui/src/pages/DeepAnalysis.jsx +495 -0
  86. package/ui/src/pages/GSD.jsx +726 -0
  87. package/ui/src/pages/MCPs.jsx +744 -0
  88. package/ui/src/pages/ProjectDetail.jsx +325 -0
  89. package/ui/src/pages/Projects.jsx +249 -0
  90. package/ui/src/pages/RelayDashboard.jsx +590 -0
  91. package/ui/src/pages/RelayUserDetail.jsx +466 -0
  92. package/ui/src/pages/Sessions.jsx +535 -0
  93. package/ui/src/pages/Settings.jsx +241 -0
  94. package/ui/src/pages/SqlViewer.jsx +344 -0
  95. package/ui/src/pages/Subscriptions.jsx +411 -0
  96. package/ui/vite.config.js +18 -0
@@ -0,0 +1,358 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const os = require('os');
4
+
5
+ const GOOSE_DIR = path.join(os.homedir(), '.local', 'share', 'goose', 'sessions');
6
+ const DB_PATH = path.join(GOOSE_DIR, 'sessions.db');
7
+ const CONFIG_PATH = path.join(os.homedir(), '.config', 'goose', 'config.yaml');
8
+
9
+ // ============================================================
10
+ // Query SQLite via better-sqlite3 (cross-platform)
11
+ // ============================================================
12
+
13
+ let Database;
14
+ function getDatabase() {
15
+ if (!Database) {
16
+ try {
17
+ Database = require('better-sqlite3');
18
+ } catch {
19
+ // better-sqlite3 not available
20
+ }
21
+ }
22
+ return Database;
23
+ }
24
+
25
+ function queryDb(sql) {
26
+ if (!fs.existsSync(DB_PATH)) return [];
27
+ const Db = getDatabase();
28
+ if (!Db) return []; // Fallback if better-sqlite3 not available
29
+ try {
30
+ const db = new Db(DB_PATH, { readonly: true });
31
+ const rows = db.prepare(sql).all();
32
+ db.close();
33
+ return rows;
34
+ } catch { return []; }
35
+ }
36
+
37
+ // ============================================================
38
+ // Adapter interface
39
+ // ============================================================
40
+
41
+ const name = 'goose';
42
+
43
+ let _configModel = undefined; // lazy-loaded
44
+
45
+ function getConfigModel() {
46
+ if (_configModel !== undefined) return _configModel;
47
+ _configModel = null;
48
+ try {
49
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
50
+ const modelMatch = raw.match(/^GOOSE_MODEL:\s*(.+)$/m);
51
+ if (modelMatch) _configModel = modelMatch[1].trim();
52
+ } catch {}
53
+ return _configModel;
54
+ }
55
+
56
+ function getChats() {
57
+ const chats = [];
58
+
59
+ const configModel = getConfigModel();
60
+
61
+ // --- SQLite sessions (v1.10.0+) ---
62
+ const dbSessions = queryDb(
63
+ `SELECT id, name, description, working_dir, created_at, updated_at,
64
+ total_tokens, input_tokens, output_tokens, provider_name, model_config_json,
65
+ (SELECT count(*) FROM messages m WHERE m.session_id = s.id) as msg_count
66
+ FROM sessions s ORDER BY updated_at DESC`
67
+ );
68
+
69
+ const dbSessionIds = new Set();
70
+ for (const row of dbSessions) {
71
+ dbSessionIds.add(row.id);
72
+ chats.push({
73
+ source: 'goose',
74
+ composerId: row.id,
75
+ name: cleanTitle(row.name || row.description),
76
+ createdAt: parseTimestamp(row.created_at),
77
+ lastUpdatedAt: parseTimestamp(row.updated_at),
78
+ mode: 'goose',
79
+ folder: row.working_dir || null,
80
+ encrypted: false,
81
+ bubbleCount: row.msg_count || 0,
82
+ _storage: 'db',
83
+ _inputTokens: row.input_tokens,
84
+ _outputTokens: row.output_tokens,
85
+ _model: extractSessionModel(row) || configModel,
86
+ });
87
+ }
88
+
89
+ // --- Legacy JSONL files ---
90
+ if (fs.existsSync(GOOSE_DIR)) {
91
+ let files;
92
+ try { files = fs.readdirSync(GOOSE_DIR).filter(f => f.endsWith('.jsonl')); } catch { files = []; }
93
+
94
+ for (const file of files) {
95
+ const sessionId = file.replace('.jsonl', '');
96
+ if (dbSessionIds.has(sessionId)) continue; // already in DB
97
+
98
+ const fullPath = path.join(GOOSE_DIR, file);
99
+ try {
100
+ const stat = fs.statSync(fullPath);
101
+ const meta = peekJsonlMeta(fullPath);
102
+ chats.push({
103
+ source: 'goose',
104
+ composerId: sessionId,
105
+ name: meta.firstPrompt ? cleanTitle(meta.firstPrompt) : null,
106
+ createdAt: meta.timestamp || stat.birthtime.getTime(),
107
+ lastUpdatedAt: stat.mtime.getTime(),
108
+ mode: 'goose',
109
+ folder: meta.workingDir || null,
110
+ encrypted: false,
111
+ _storage: 'jsonl',
112
+ _fullPath: fullPath,
113
+ _model: configModel,
114
+ });
115
+ } catch { /* skip */ }
116
+ }
117
+ }
118
+
119
+ return chats;
120
+ }
121
+
122
+ function parseTimestamp(ts) {
123
+ if (!ts) return null;
124
+ if (typeof ts === 'number') return ts;
125
+ const d = new Date(ts);
126
+ return isNaN(d.getTime()) ? null : d.getTime();
127
+ }
128
+
129
+ function cleanTitle(title) {
130
+ if (!title) return null;
131
+ return title.substring(0, 120).trim() || null;
132
+ }
133
+
134
+ function peekJsonlMeta(filePath) {
135
+ const meta = { firstPrompt: null, workingDir: null, timestamp: null };
136
+ try {
137
+ const buf = fs.readFileSync(filePath, 'utf-8');
138
+ for (const line of buf.split('\n')) {
139
+ if (!line) continue;
140
+ const obj = JSON.parse(line);
141
+
142
+ if (!meta.timestamp && obj.created) {
143
+ meta.timestamp = parseTimestamp(obj.created);
144
+ }
145
+
146
+ if (!meta.workingDir && obj.working_dir) {
147
+ meta.workingDir = obj.working_dir;
148
+ }
149
+
150
+ // First user text message
151
+ if (!meta.firstPrompt && obj.role === 'user' && obj.content) {
152
+ let parts;
153
+ try { parts = typeof obj.content === 'string' ? JSON.parse(obj.content) : obj.content; } catch { continue; }
154
+ if (Array.isArray(parts)) {
155
+ const text = parts.filter(p => p.type === 'text').map(p => p.text).join(' ');
156
+ if (text) meta.firstPrompt = text.substring(0, 200);
157
+ }
158
+ }
159
+
160
+ if (meta.firstPrompt && meta.workingDir) break;
161
+ }
162
+ } catch {}
163
+ return meta;
164
+ }
165
+
166
+ function getMessages(chat) {
167
+ if (chat._storage === 'db') return getMessagesFromDb(chat);
168
+ if (chat._storage === 'jsonl') return getMessagesFromJsonl(chat);
169
+ // Try DB first, then JSONL
170
+ const dbMessages = getMessagesFromDb(chat);
171
+ if (dbMessages.length) return dbMessages;
172
+ return getMessagesFromJsonl(chat);
173
+ }
174
+
175
+ function getMessagesFromDb(chat) {
176
+ const rows = queryDb(
177
+ `SELECT role, content_json, created_timestamp FROM messages
178
+ WHERE session_id = '${chat.composerId}' ORDER BY created_timestamp ASC`
179
+ );
180
+
181
+ const result = [];
182
+ for (const row of rows) {
183
+ let parts;
184
+ try { parts = JSON.parse(row.content_json); } catch { continue; }
185
+ if (!Array.isArray(parts)) continue;
186
+
187
+ const role = row.role;
188
+ const contentParts = [];
189
+ const toolCalls = [];
190
+
191
+ for (const part of parts) {
192
+ if (part.type === 'text' && part.text) {
193
+ contentParts.push(part.text);
194
+ } else if (part.type === 'toolRequest' && part.toolCall) {
195
+ const tc = part.toolCall.value || {};
196
+ const toolName = tc.name || 'tool';
197
+ let argKeys = '';
198
+ try { argKeys = Object.keys(tc.arguments || {}).join(', '); } catch {}
199
+ contentParts.push(`[tool-call: ${toolName}(${argKeys})]`);
200
+ toolCalls.push({ name: toolName, args: tc.arguments || {} });
201
+ } else if (part.type === 'toolResponse' && part.toolResult) {
202
+ const tr = part.toolResult;
203
+ let preview = '';
204
+ if (tr.value && Array.isArray(tr.value)) {
205
+ preview = tr.value
206
+ .filter(v => v.type === 'text')
207
+ .map(v => v.text)
208
+ .join('\n')
209
+ .substring(0, 500);
210
+ }
211
+ contentParts.push(`[tool-result: ${tr.status || 'done'}] ${preview}`);
212
+ }
213
+ }
214
+
215
+ const content = contentParts.join('\n');
216
+ if (!content) continue;
217
+
218
+ const mappedRole = role === 'user' ? 'user' : role === 'assistant' ? 'assistant' : role;
219
+ const msg = { role: mappedRole, content };
220
+ if (mappedRole === 'assistant' && chat._model) msg._model = chat._model;
221
+ if (toolCalls.length) msg._toolCalls = toolCalls;
222
+ result.push(msg);
223
+ }
224
+
225
+ return result;
226
+ }
227
+
228
+ function getMessagesFromJsonl(chat) {
229
+ const filePath = chat._fullPath || path.join(GOOSE_DIR, chat.composerId + '.jsonl');
230
+ if (!fs.existsSync(filePath)) return [];
231
+
232
+ const lines = fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean);
233
+ const result = [];
234
+
235
+ for (const line of lines) {
236
+ let obj;
237
+ try { obj = JSON.parse(line); } catch { continue; }
238
+
239
+ if (!obj.role) continue;
240
+
241
+ let parts;
242
+ try {
243
+ parts = typeof obj.content === 'string' ? JSON.parse(obj.content) : obj.content;
244
+ } catch {
245
+ // content might be plain text
246
+ if (typeof obj.content === 'string' && obj.content) {
247
+ result.push({ role: obj.role, content: obj.content });
248
+ }
249
+ continue;
250
+ }
251
+
252
+ if (!Array.isArray(parts)) continue;
253
+
254
+ const contentParts = [];
255
+ for (const part of parts) {
256
+ if (part.type === 'text' && part.text) {
257
+ contentParts.push(part.text);
258
+ } else if (part.type === 'toolRequest') {
259
+ const tc = part.toolCall?.value || {};
260
+ contentParts.push(`[tool-call: ${tc.name || 'tool'}(${Object.keys(tc.arguments || {}).join(', ')})]`);
261
+ } else if (part.type === 'toolResponse') {
262
+ const tr = part.toolResult || {};
263
+ let preview = '';
264
+ if (Array.isArray(tr.value)) {
265
+ preview = tr.value.filter(v => v.type === 'text').map(v => v.text).join('\n').substring(0, 500);
266
+ }
267
+ contentParts.push(`[tool-result] ${preview}`);
268
+ }
269
+ }
270
+
271
+ const content = contentParts.join('\n');
272
+ if (content) {
273
+ const msg = { role: obj.role, content };
274
+ if (obj.role === 'assistant' && chat._model) msg._model = chat._model;
275
+ result.push(msg);
276
+ }
277
+ }
278
+
279
+ return result;
280
+ }
281
+
282
+ function extractSessionModel(row) {
283
+ if (row.model_config_json) {
284
+ try {
285
+ const cfg = JSON.parse(row.model_config_json);
286
+ if (cfg.model) return cfg.model;
287
+ if (cfg.model_id) return cfg.model_id;
288
+ } catch {}
289
+ }
290
+ return null;
291
+ }
292
+
293
+ function resetCache() {
294
+ _configModel = undefined;
295
+ }
296
+
297
+ const labels = { 'goose': 'Goose' };
298
+
299
+ function getArtifacts(folder) {
300
+ const { scanArtifacts } = require('./base');
301
+ return scanArtifacts(folder, {
302
+ editor: 'goose',
303
+ label: 'Goose',
304
+ files: ['.goosehints'],
305
+ dirs: [],
306
+ });
307
+ }
308
+
309
+ function getMCPServers() {
310
+ const results = [];
311
+ // Goose stores MCP config in ~/.config/goose/config.yaml under extensions
312
+ // Also check profiles.yaml for MCP server entries
313
+ const configFiles = [CONFIG_PATH, path.join(os.homedir(), '.config', 'goose', 'profiles.yaml')];
314
+ for (const cfgPath of configFiles) {
315
+ if (!fs.existsSync(cfgPath)) continue;
316
+ try {
317
+ const raw = fs.readFileSync(cfgPath, 'utf-8');
318
+ // Simple YAML parsing for mcpServers / extensions blocks
319
+ let currentServer = null;
320
+ let inMcp = false;
321
+ for (const line of raw.split('\n')) {
322
+ if (line.match(/^\s*(mcpServers|extensions):/)) { inMcp = true; continue; }
323
+ if (inMcp && line.match(/^\s{2}\w/) && !line.match(/^\s{4}/)) {
324
+ // New top-level key under mcpServers
325
+ if (currentServer) results.push(currentServer);
326
+ const nameMatch = line.match(/^\s{2}(\S+):/);
327
+ if (nameMatch) {
328
+ currentServer = {
329
+ name: nameMatch[1],
330
+ editor: 'goose',
331
+ editorLabel: 'Goose',
332
+ scope: 'global',
333
+ configPath: cfgPath,
334
+ command: null, args: [], env: [], url: null,
335
+ transport: 'stdio', disabled: false, disabledTools: [],
336
+ };
337
+ }
338
+ continue;
339
+ }
340
+ if (inMcp && currentServer) {
341
+ const cmdMatch = line.match(/^\s+command:\s*(.+)/);
342
+ if (cmdMatch) currentServer.command = cmdMatch[1].trim();
343
+ const urlMatch = line.match(/^\s+url:\s*(.+)/);
344
+ if (urlMatch) { currentServer.url = urlMatch[1].trim(); currentServer.transport = 'http'; }
345
+ }
346
+ if (line.match(/^\S/) && !line.match(/^\s/) && inMcp) {
347
+ if (currentServer) results.push(currentServer);
348
+ currentServer = null;
349
+ inMcp = false;
350
+ }
351
+ }
352
+ if (currentServer) results.push(currentServer);
353
+ } catch {}
354
+ }
355
+ return results;
356
+ }
357
+
358
+ module.exports = { name, labels, getChats, getMessages, resetCache, getArtifacts, getMCPServers };