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.
- package/LICENSE +22 -0
- package/README.md +59 -3
- package/bin/memhouse.js +487 -0
- package/cache.js +850 -0
- package/ch-ingest.js +329 -0
- package/ch-schema.js +152 -0
- package/clickhouse.js +87 -0
- package/editors/antigravity.js +1019 -0
- package/editors/base.js +321 -0
- package/editors/claude.js +386 -0
- package/editors/codebuff.js +338 -0
- package/editors/codex.js +537 -0
- package/editors/commandcode.js +166 -0
- package/editors/copilot-jetbrains.js +207 -0
- package/editors/copilot.js +260 -0
- package/editors/cursor-agent.js +202 -0
- package/editors/cursor.js +438 -0
- package/editors/gemini.js +231 -0
- package/editors/goose.js +358 -0
- package/editors/gsd.js +366 -0
- package/editors/index.js +179 -0
- package/editors/kiro.js +315 -0
- package/editors/opencode.js +374 -0
- package/editors/vscode.js +413 -0
- package/editors/windsurf.js +634 -0
- package/editors/zed.js +213 -0
- package/index.js +424 -2
- package/mcp-server.js +294 -0
- package/mem-house/DESIGN.md +119 -0
- package/mem-house/delivery/AGENT-INSTALL.md +75 -0
- package/mem-house/delivery/PROMPT.md +42 -0
- package/mem-house/delivery/install.sh +181 -0
- package/mem-house/delivery/kernel-install.md +76 -0
- package/mem-house/delivery/plugin/.claude-plugin/plugin.json +8 -0
- package/mem-house/delivery/skills/memhouse-search/SKILL.md +104 -0
- package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +94 -0
- package/mem-house/delivery/skills/memhouse-sql/SKILL.md +115 -0
- package/mem-house/rls.sql +17 -0
- package/mem-house/schema.sql +145 -0
- package/mem-house/server/queries.js +878 -0
- package/mem-house/server/server.js +228 -0
- package/mem-house/shipper/ship.js +402 -0
- package/misc/syntax-check.js +38 -0
- package/package.json +56 -7
- package/pricing.js +88 -0
- package/pricing.json +992 -0
- package/public/assets/index-BsgjOSTv.js +75 -0
- package/public/assets/index-DV6ONi_F.css +2 -0
- package/public/index.html +16 -0
- package/relay-client.js +306 -0
- package/relay-server.js +577 -0
- package/server.js +860 -0
- package/share-image.js +367 -0
- package/ui/eslint.config.js +29 -0
- package/ui/index.html +15 -0
- package/ui/package-lock.json +4229 -0
- package/ui/package.json +38 -0
- package/ui/src/App.jsx +364 -0
- package/ui/src/components/ActivityHeatmap.jsx +206 -0
- package/ui/src/components/AiAuditCard.jsx +254 -0
- package/ui/src/components/AnimatedLoader.jsx +14 -0
- package/ui/src/components/AnimatedLogo.jsx +96 -0
- package/ui/src/components/ChatSidebar.jsx +226 -0
- package/ui/src/components/DateRangePicker.jsx +104 -0
- package/ui/src/components/EditorDot.jsx +11 -0
- package/ui/src/components/EditorIcon.jsx +117 -0
- package/ui/src/components/KpiCard.jsx +9 -0
- package/ui/src/components/LiveFeed.jsx +128 -0
- package/ui/src/components/LoginScreen.jsx +81 -0
- package/ui/src/components/MessageRenderer.jsx +148 -0
- package/ui/src/components/PageHeader.jsx +11 -0
- package/ui/src/components/SectionTitle.jsx +3 -0
- package/ui/src/components/ShareModal.jsx +305 -0
- package/ui/src/components/TokenTimeline.jsx +258 -0
- package/ui/src/hooks/useLive.jsx +15 -0
- package/ui/src/index.css +83 -0
- package/ui/src/lib/api.js +343 -0
- package/ui/src/lib/constants.js +88 -0
- package/ui/src/lib/theme.jsx +23 -0
- package/ui/src/main.jsx +16 -0
- package/ui/src/pages/Artifacts.jsx +575 -0
- package/ui/src/pages/Compare.jsx +232 -0
- package/ui/src/pages/CostAnalysis.jsx +354 -0
- package/ui/src/pages/Dashboard.jsx +529 -0
- package/ui/src/pages/DeepAnalysis.jsx +495 -0
- package/ui/src/pages/GSD.jsx +726 -0
- package/ui/src/pages/MCPs.jsx +744 -0
- package/ui/src/pages/ProjectDetail.jsx +325 -0
- package/ui/src/pages/Projects.jsx +249 -0
- package/ui/src/pages/RelayDashboard.jsx +590 -0
- package/ui/src/pages/RelayUserDetail.jsx +466 -0
- package/ui/src/pages/Sessions.jsx +535 -0
- package/ui/src/pages/Settings.jsx +241 -0
- package/ui/src/pages/SqlViewer.jsx +344 -0
- package/ui/src/pages/Subscriptions.jsx +411 -0
- package/ui/vite.config.js +18 -0
package/editors/goose.js
ADDED
|
@@ -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 };
|