memhouse 0.4.4 → 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.
- package/README.md +244 -114
- package/bin/memhouse.js +1162 -230
- 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/memhouse/per-member/provision.js +265 -0
- package/memhouse/per-member/rooms.js +235 -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/{mem-house → memhouse}/per-member/SHARING.md +0 -0
package/index.js
DELETED
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const { execSync } = require('child_process');
|
|
8
|
-
|
|
9
|
-
// ── Node.js version check ─────────────────────────────────
|
|
10
|
-
const nodeVersion = process.versions.node;
|
|
11
|
-
const [nodeMajor, nodeMinor] = nodeVersion.split('.').map(Number);
|
|
12
|
-
const isNodeSupported =
|
|
13
|
-
(nodeMajor === 20 && nodeMinor >= 19) ||
|
|
14
|
-
(nodeMajor === 22 && nodeMinor >= 12) ||
|
|
15
|
-
nodeMajor === 23 || nodeMajor === 24 || nodeMajor >= 25;
|
|
16
|
-
|
|
17
|
-
if (!isNodeSupported) {
|
|
18
|
-
console.error('');
|
|
19
|
-
console.error(` \x1b[31m✗ Unsupported Node.js version: v${nodeVersion}\x1b[0m`);
|
|
20
|
-
console.error('');
|
|
21
|
-
console.error(` \x1b[1mAgentlytics requires Node.js 20.19+ or 22.12+\x1b[0m`);
|
|
22
|
-
console.error(` \x1b[2mYour current version: v${nodeVersion}\x1b[0m`);
|
|
23
|
-
console.error('');
|
|
24
|
-
console.error(` \x1b[2mTo upgrade, visit: https://nodejs.org\x1b[0m`);
|
|
25
|
-
console.error(` \x1b[2mOr use a version manager: nvm install 22\x1b[0m`);
|
|
26
|
-
console.error('');
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const HOME = os.homedir();
|
|
31
|
-
|
|
32
|
-
// ── Detect package manager ─────────────────────────────────
|
|
33
|
-
function detectPackageManager() {
|
|
34
|
-
const ua = process.env.npm_config_user_agent || '';
|
|
35
|
-
if (ua.startsWith('pnpm/')) return 'pnpm';
|
|
36
|
-
if (ua.startsWith('yarn/')) return 'yarn';
|
|
37
|
-
if (ua.startsWith('bun/')) return 'bun';
|
|
38
|
-
return 'npm';
|
|
39
|
-
}
|
|
40
|
-
const PM = detectPackageManager();
|
|
41
|
-
const PM_RUN = PM === 'npm' ? 'npx' : PM === 'pnpm' ? 'pnpm dlx' : PM === 'yarn' ? 'yarn dlx' : 'bunx';
|
|
42
|
-
const PORT = process.env.PORT || 4637;
|
|
43
|
-
const RELAY_PORT = process.env.RELAY_PORT || 4638;
|
|
44
|
-
const noCache = process.argv.includes('--no-cache');
|
|
45
|
-
const collectOnly = process.argv.includes('--collect');
|
|
46
|
-
const isUiDev = process.argv.includes('--ui-dev');
|
|
47
|
-
const isRelay = process.argv.includes('--relay');
|
|
48
|
-
const joinIndex = process.argv.indexOf('--join');
|
|
49
|
-
const isJoin = joinIndex !== -1;
|
|
50
|
-
|
|
51
|
-
// ── Relay mode ───────────────────────────────────────────────
|
|
52
|
-
if (isRelay) {
|
|
53
|
-
// Loopback by default; exposing to the network is an explicit opt-in AND
|
|
54
|
-
// fail-closed: a non-loopback bind without RELAY_PASSWORD would let any
|
|
55
|
-
// network client read all transcripts, upload/overwrite sessions, and merge
|
|
56
|
-
// users — refuse before touching anything.
|
|
57
|
-
const RELAY_HOST = process.env.RELAY_HOST || '127.0.0.1';
|
|
58
|
-
const relayLoopback = RELAY_HOST === '127.0.0.1' || RELAY_HOST === 'localhost' || RELAY_HOST === '::1';
|
|
59
|
-
if (!relayLoopback && !process.env.RELAY_PASSWORD) {
|
|
60
|
-
console.error(chalk.red(` ✗ Refusing to bind relay to ${RELAY_HOST} without RELAY_PASSWORD.`));
|
|
61
|
-
console.error(chalk.dim(' Set RELAY_PASSWORD, or drop RELAY_HOST to stay loopback-only.'));
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const { initRelayDb, getRelayDb, createRelayApp } = require('./relay-server');
|
|
66
|
-
const { wireMcpToExpress } = require('./mcp-server');
|
|
67
|
-
|
|
68
|
-
console.log('');
|
|
69
|
-
console.log(chalk.bold(' ⚡ Agentlytics Relay'));
|
|
70
|
-
console.log(chalk.dim(' Multi-user context sharing server'));
|
|
71
|
-
console.log('');
|
|
72
|
-
|
|
73
|
-
initRelayDb();
|
|
74
|
-
console.log(chalk.green(' ✓ Relay database initialized'));
|
|
75
|
-
|
|
76
|
-
const app = createRelayApp();
|
|
77
|
-
wireMcpToExpress(app, getRelayDb);
|
|
78
|
-
console.log(chalk.green(' ✓ MCP server registered'));
|
|
79
|
-
|
|
80
|
-
if (process.env.RELAY_PASSWORD) {
|
|
81
|
-
console.log(chalk.green(' ✓ Password protection enabled'));
|
|
82
|
-
} else {
|
|
83
|
-
console.log(chalk.yellow(' ⚠ No password set (loopback-only; set RELAY_PASSWORD before exposing with RELAY_HOST)'));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
app.listen(RELAY_PORT, RELAY_HOST, () => {
|
|
87
|
-
const localIp = relayLoopback ? '127.0.0.1' : getLocalIp();
|
|
88
|
-
const relayUrl = `http://${localIp}:${RELAY_PORT}`;
|
|
89
|
-
|
|
90
|
-
console.log('');
|
|
91
|
-
console.log(chalk.green(` ✓ Relay server running on port ${RELAY_PORT}`));
|
|
92
|
-
console.log('');
|
|
93
|
-
console.log(chalk.bold(' Team sharing:'));
|
|
94
|
-
console.log(chalk.dim(' Relay join is not supported in memhouse — members ship with their own'));
|
|
95
|
-
console.log(chalk.dim(' credentials (kernel-based): see mem-house/delivery/kernel-install.md'));
|
|
96
|
-
console.log('');
|
|
97
|
-
console.log(chalk.bold(' MCP server endpoint (add to your AI client):'));
|
|
98
|
-
console.log('');
|
|
99
|
-
console.log(chalk.cyan(` ${relayUrl}/mcp`));
|
|
100
|
-
if (process.env.RELAY_PASSWORD) {
|
|
101
|
-
console.log(chalk.dim(' (password protection is on — clients must send the bearer token from /api/login)'));
|
|
102
|
-
}
|
|
103
|
-
console.log('');
|
|
104
|
-
console.log(chalk.dim(' REST endpoints:'));
|
|
105
|
-
console.log(chalk.dim(` GET ${relayUrl}/relay/health`));
|
|
106
|
-
console.log(chalk.dim(` GET ${relayUrl}/relay/users`));
|
|
107
|
-
console.log(chalk.dim(` GET ${relayUrl}/relay/search?q=<query>`));
|
|
108
|
-
console.log(chalk.dim(` GET ${relayUrl}/relay/activity/<username>`));
|
|
109
|
-
console.log(chalk.dim(` GET ${relayUrl}/relay/session/<chatId>`));
|
|
110
|
-
console.log('');
|
|
111
|
-
console.log(chalk.dim(' Press Ctrl+C to stop'));
|
|
112
|
-
console.log('');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Skip the rest of the normal flow
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ── Join mode ────────────────────────────────────────────────
|
|
120
|
-
if (isJoin) {
|
|
121
|
-
// Fail fast: the relay join client still targets the removed synchronous SQLite
|
|
122
|
-
// cache (relay-client.js pickProjects → cache.getDb), which the ClickHouse
|
|
123
|
-
// cutover eliminated — it would crash after connecting. In memhouse, team
|
|
124
|
-
// sharing is grants on per-member rooms, not relay.
|
|
125
|
-
console.error(chalk.red('\n ✗ Relay join is not supported in memhouse.'));
|
|
126
|
-
console.error(chalk.dim(' Team sharing here is kernel-based: see mem-house/delivery/kernel-install.md'));
|
|
127
|
-
console.error(chalk.dim(' (register-member + per-member rooms + a GRANT on your own rooms).\n'));
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ── Helper: get local IP for relay ───────────────────────────
|
|
132
|
-
function getLocalIp() {
|
|
133
|
-
const interfaces = os.networkInterfaces();
|
|
134
|
-
for (const name of Object.keys(interfaces)) {
|
|
135
|
-
for (const iface of interfaces[name]) {
|
|
136
|
-
if (iface.family === 'IPv4' && !iface.internal) return iface.address;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return 'localhost';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ── ASCII banner ─────────────────────────────────────────
|
|
143
|
-
const c1 = chalk.hex('#818cf8'), c2 = chalk.hex('#f472b6'), c3 = chalk.hex('#34d399'), c4 = chalk.hex('#fbbf24');
|
|
144
|
-
console.log('');
|
|
145
|
-
console.log(` ${c1('(● ●)')} ${c2('[● ●]')} ${chalk.bold('Agentlytics')}`);
|
|
146
|
-
console.log(` ${c3('{● ●}')} ${c4('<● ●>')} ${chalk.dim('Unified analytics for your AI coding agents')}`);
|
|
147
|
-
if (collectOnly) console.log(chalk.cyan(' ⟳ Collect-only mode (no server)'));
|
|
148
|
-
console.log('');
|
|
149
|
-
|
|
150
|
-
// ── Build UI if not already built ──────────────────────────
|
|
151
|
-
const publicIndex = path.join(__dirname, 'public', 'index.html');
|
|
152
|
-
const uiDir = path.join(__dirname, 'ui');
|
|
153
|
-
|
|
154
|
-
if (!collectOnly && !isUiDev && !fs.existsSync(publicIndex) && fs.existsSync(uiDir)) {
|
|
155
|
-
console.log(chalk.cyan(' ⟳ Building dashboard UI (first run)...'));
|
|
156
|
-
try {
|
|
157
|
-
const uiModules = path.join(uiDir, 'node_modules');
|
|
158
|
-
if (fs.existsSync(uiModules)) fs.rmSync(uiModules, { recursive: true, force: true });
|
|
159
|
-
console.log(chalk.dim(' Installing UI dependencies...'));
|
|
160
|
-
const installCmd = PM === 'npm' ? 'npm install --no-audit --no-fund'
|
|
161
|
-
: PM === 'pnpm' ? 'pnpm install --no-frozen-lockfile'
|
|
162
|
-
: PM === 'yarn' ? 'yarn install'
|
|
163
|
-
: 'bun install';
|
|
164
|
-
const buildCmd = `${PM} run build`;
|
|
165
|
-
execSync(installCmd, { cwd: uiDir, stdio: 'pipe' });
|
|
166
|
-
console.log(chalk.dim(' Compiling frontend...'));
|
|
167
|
-
execSync(buildCmd, { cwd: uiDir, stdio: 'pipe' });
|
|
168
|
-
console.log(chalk.green(' ✓ UI built successfully'));
|
|
169
|
-
} catch (err) {
|
|
170
|
-
console.error(chalk.red(' ✗ UI build failed:'), err.message);
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
console.log('');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (!collectOnly && !isUiDev && !fs.existsSync(publicIndex)) {
|
|
177
|
-
console.error(chalk.red(' ✗ No built UI found at public/index.html'));
|
|
178
|
-
console.error(chalk.dim(` Run: cd ui && ${PM} install && ${PM} run build`));
|
|
179
|
-
process.exit(1);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// ── ClickHouse cache (no native module to build) ────────────
|
|
183
|
-
const cache = require('./cache');
|
|
184
|
-
// --no-cache is handled in the async flow below (ClickHouse truncate needs await).
|
|
185
|
-
|
|
186
|
-
// ── Warn about installed-but-not-running Devin variants (macOS only) ─
|
|
187
|
-
if (process.platform === 'darwin') {
|
|
188
|
-
const DEVIN_DESKTOP_VARIANTS = [
|
|
189
|
-
{
|
|
190
|
-
name: 'Devin',
|
|
191
|
-
apps: ['/Applications/Devin Desktop.app', '/Applications/Devin.app', '/Applications/Windsurf.app'],
|
|
192
|
-
dataDirs: [path.join(HOME, '.windsurf'), path.join(HOME, '.codeium', 'windsurf')],
|
|
193
|
-
ides: ['devin-desktop', 'devin', 'windsurf'],
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
name: 'Devin Next',
|
|
197
|
-
apps: ['/Applications/Devin Desktop Next.app', '/Applications/Devin Next.app', '/Applications/Windsurf Next.app'],
|
|
198
|
-
dataDirs: [path.join(HOME, '.codeium', 'windsurf-next')],
|
|
199
|
-
ides: ['devin-desktop-next', 'devin-next', 'windsurf-next'],
|
|
200
|
-
},
|
|
201
|
-
{ name: 'Antigravity', app: '/Applications/Antigravity.app', dataDir: path.join(HOME, '.codeium', 'antigravity'), ide: 'antigravity' },
|
|
202
|
-
];
|
|
203
|
-
|
|
204
|
-
(() => {
|
|
205
|
-
// Check which language servers are running
|
|
206
|
-
let runningIdes = [];
|
|
207
|
-
try {
|
|
208
|
-
const ps = execSync('ps aux', { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
|
|
209
|
-
for (const line of ps.split('\n')) {
|
|
210
|
-
if (!line.includes('language_server_macos')) continue;
|
|
211
|
-
const ideMatch = line.match(/--ide_name\s+(\S+)/);
|
|
212
|
-
const appDirMatch = line.match(/--app_data_dir\s+(\S+)/);
|
|
213
|
-
if (ideMatch) runningIdes.push(ideMatch[1]);
|
|
214
|
-
if (appDirMatch) runningIdes.push(appDirMatch[1]);
|
|
215
|
-
}
|
|
216
|
-
} catch {}
|
|
217
|
-
|
|
218
|
-
const installedNotRunning = DEVIN_DESKTOP_VARIANTS.filter(v => {
|
|
219
|
-
const apps = v.apps || [v.app];
|
|
220
|
-
const dataDirs = v.dataDirs || [v.dataDir];
|
|
221
|
-
const ides = v.ides || [v.ide];
|
|
222
|
-
const installed = apps.some(app => fs.existsSync(app)) || dataDirs.some(dataDir => fs.existsSync(dataDir));
|
|
223
|
-
const running = runningIdes.some(r => ides.some(ide => r === ide || r.includes(ide)));
|
|
224
|
-
return installed && !running;
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
if (installedNotRunning.length > 0) {
|
|
228
|
-
const names = installedNotRunning.map(v => chalk.bold(v.name)).join(', ');
|
|
229
|
-
console.log(chalk.yellow(` ⚠ ${names} installed but not running`));
|
|
230
|
-
console.log(chalk.dim(' These editors must be open for their sessions to be detected.'));
|
|
231
|
-
console.log('');
|
|
232
|
-
}
|
|
233
|
-
})();
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Cache DB is initialized (and optionally cleared) inside the async flow below.
|
|
237
|
-
|
|
238
|
-
// ── Detect editors & collect sessions ───────────────────────
|
|
239
|
-
const { editors: editorModules, editorLabels } = require('./editors');
|
|
240
|
-
|
|
241
|
-
console.log(chalk.dim(' Looking for AI coding agents...'));
|
|
242
|
-
const allChats = [];
|
|
243
|
-
for (const editor of editorModules) {
|
|
244
|
-
try {
|
|
245
|
-
const chats = editor.getChats();
|
|
246
|
-
allChats.push(...chats);
|
|
247
|
-
} catch { /* skip broken adapters */ }
|
|
248
|
-
}
|
|
249
|
-
allChats.sort((a, b) => (b.lastUpdatedAt || b.createdAt || 0) - (a.lastUpdatedAt || a.createdAt || 0));
|
|
250
|
-
|
|
251
|
-
// Count per source
|
|
252
|
-
const bySource = {};
|
|
253
|
-
for (const chat of allChats) bySource[chat.source] = (bySource[chat.source] || 0) + 1;
|
|
254
|
-
|
|
255
|
-
const displayByLabel = new Map();
|
|
256
|
-
for (const [src, label] of Object.entries(editorLabels)) {
|
|
257
|
-
const existing = displayByLabel.get(label) || { label, count: 0 };
|
|
258
|
-
existing.count += bySource[src] || 0;
|
|
259
|
-
displayByLabel.set(label, existing);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const displayList = Array.from(displayByLabel.values())
|
|
263
|
-
.map(({ label, count }) => [label, count])
|
|
264
|
-
.sort((a, b) => b[1] - a[1]);
|
|
265
|
-
|
|
266
|
-
for (const [label, count] of displayList) {
|
|
267
|
-
if (count > 0) {
|
|
268
|
-
console.log(` ${chalk.green('✓')} ${chalk.bold(label.padEnd(18))} ${chalk.dim(`${count} session${count === 1 ? '' : 's'}`)}`);
|
|
269
|
-
} else {
|
|
270
|
-
console.log(` ${chalk.dim('–')} ${chalk.dim(label.padEnd(18) + '–')}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
console.log('');
|
|
274
|
-
|
|
275
|
-
// ── Analyze sessions with robot animation (async to allow Ctrl+C) ──
|
|
276
|
-
const logUpdate = require('log-update');
|
|
277
|
-
const BOT_STYLES = [
|
|
278
|
-
{ l: '(', r: ')', color: '#818cf8' },
|
|
279
|
-
{ l: '[', r: ']', color: '#f472b6' },
|
|
280
|
-
{ l: '{', r: '}', color: '#34d399' },
|
|
281
|
-
{ l: '<', r: '>', color: '#fbbf24' },
|
|
282
|
-
];
|
|
283
|
-
|
|
284
|
-
(async () => {
|
|
285
|
-
// Initialize the ClickHouse cache (ensure DB + schema); optionally clear it.
|
|
286
|
-
await cache.initDb();
|
|
287
|
-
if (noCache) {
|
|
288
|
-
await cache.clearAll();
|
|
289
|
-
console.log(chalk.yellow(' ⟳ Cache cleared (--no-cache)'));
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ── Ask for subscription access permission (first run only) ──
|
|
293
|
-
const CONFIG_DIR = path.join(os.homedir(), '.agentlytics');
|
|
294
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
295
|
-
let agentConfig = {};
|
|
296
|
-
try { agentConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')); } catch {}
|
|
297
|
-
|
|
298
|
-
if (agentConfig.allowSubscriptionAccess === undefined) {
|
|
299
|
-
console.log(chalk.yellow(' ⚠ Subscription & usage details require access to local auth tokens.'));
|
|
300
|
-
console.log('');
|
|
301
|
-
console.log(chalk.dim(' To show your plan and usage info, Agentlytics needs to read'));
|
|
302
|
-
console.log(chalk.dim(' locally stored tokens from the following sources:'));
|
|
303
|
-
console.log('');
|
|
304
|
-
console.log(chalk.dim(' • Claude Code – macOS Keychain / Linux secret-tool'));
|
|
305
|
-
console.log(chalk.dim(' • Cursor – local SQLite (state.vscdb)'));
|
|
306
|
-
console.log(chalk.dim(' • Copilot – ~/.config/github-copilot/apps.json'));
|
|
307
|
-
console.log(chalk.dim(' • VS Code – ~/.config/github-copilot/apps.json'));
|
|
308
|
-
console.log(chalk.dim(' • Codex – local auth.json (JWT decode only)'));
|
|
309
|
-
console.log(chalk.dim(' • Devin – local SQLite (state.vscdb)'));
|
|
310
|
-
console.log('');
|
|
311
|
-
console.log(chalk.dim(' These tokens are used to query each editor\'s own API for'));
|
|
312
|
-
console.log(chalk.dim(' your plan name and usage limits.'));
|
|
313
|
-
console.log('');
|
|
314
|
-
console.log(chalk.bold.white(' → Tokens are kept in-memory only and never sent to any'));
|
|
315
|
-
console.log(chalk.bold.white(' third-party service. They are discarded after the request.'));
|
|
316
|
-
console.log('');
|
|
317
|
-
const readline = require('readline');
|
|
318
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
319
|
-
const answer = await new Promise(r => {
|
|
320
|
-
rl.question(chalk.bold(' Allow local token inspection for subscription details? (y/N) '), (a) => {
|
|
321
|
-
rl.close();
|
|
322
|
-
r(a.trim().toLowerCase());
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
agentConfig.allowSubscriptionAccess = answer === 'y' || answer === 'yes';
|
|
326
|
-
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
327
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(agentConfig, null, 2));
|
|
328
|
-
if (agentConfig.allowSubscriptionAccess) {
|
|
329
|
-
console.log(chalk.green(' ✓ Subscription access enabled'));
|
|
330
|
-
} else {
|
|
331
|
-
console.log(chalk.dim(' – Subscription access skipped (plan/usage details won\'t be collected)'));
|
|
332
|
-
}
|
|
333
|
-
console.log('');
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
let tick = 0;
|
|
337
|
-
const startTime = Date.now();
|
|
338
|
-
const result = await cache.scanAllAsync((p) => {
|
|
339
|
-
tick++;
|
|
340
|
-
if (tick % 5 !== 0) return;
|
|
341
|
-
const frame = Math.floor(tick / 40);
|
|
342
|
-
const b = BOT_STYLES[frame % 4];
|
|
343
|
-
const dots = '.'.repeat((Math.floor(tick / 10) % 3) + 1).padEnd(3);
|
|
344
|
-
logUpdate(` ${chalk.hex(b.color)(`${b.l}● ●${b.r}`)} ${chalk.dim(`Analyzing${dots} ${p.scanned}/${p.total}`)}`);
|
|
345
|
-
}, { chats: allChats });
|
|
346
|
-
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
347
|
-
const allFaces = BOT_STYLES.map(b => chalk.hex(b.color)(`${b.l}● ●${b.r}`)).join(' ');
|
|
348
|
-
logUpdate(` ${allFaces} ${chalk.green(`✓ ${result.analyzed} analyzed, ${result.skipped} cached (${elapsed}s)`)}`);
|
|
349
|
-
logUpdate.done();
|
|
350
|
-
console.log('');
|
|
351
|
-
|
|
352
|
-
// In collect-only mode, exit after cache is built
|
|
353
|
-
if (collectOnly) {
|
|
354
|
-
const { config } = require('./clickhouse');
|
|
355
|
-
console.log(chalk.dim(` Cache: ClickHouse ${config.database} @ ${config.url}`));
|
|
356
|
-
console.log('');
|
|
357
|
-
process.exit(0);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Start server (kill stale agentlytics or find free port)
|
|
361
|
-
const app = require('./server');
|
|
362
|
-
const http = require('http');
|
|
363
|
-
const net = require('net');
|
|
364
|
-
|
|
365
|
-
// Pre-cache MCP server configs (runs in background, non-blocking). Live tool
|
|
366
|
-
// discovery — which EXECUTES configured servers — only happens when the user
|
|
367
|
-
// opted in via AGENTLYTICS_MCP_DISCOVERY=1 (see server.js initMcpToolsCache).
|
|
368
|
-
const mcpDiscovery = process.env.AGENTLYTICS_MCP_DISCOVERY === '1'
|
|
369
|
-
|| process.env.AGENTLYTICS_MCP_DISCOVERY === 'true';
|
|
370
|
-
app.initMcpToolsCache().then(() => {
|
|
371
|
-
console.log(chalk.green(mcpDiscovery
|
|
372
|
-
? ' ✓ MCP tools cached'
|
|
373
|
-
: ' ✓ MCP configs listed (tool discovery off — enable with AGENTLYTICS_MCP_DISCOVERY=1)'));
|
|
374
|
-
}).catch(() => {});
|
|
375
|
-
|
|
376
|
-
function isPortFree(port) {
|
|
377
|
-
return new Promise((resolve) => {
|
|
378
|
-
const tester = net.createServer()
|
|
379
|
-
.once('error', () => resolve(false))
|
|
380
|
-
.once('listening', () => tester.close(() => resolve(true)))
|
|
381
|
-
.listen(port, '0.0.0.0');
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
async function startServer(port) {
|
|
386
|
-
const free = await isPortFree(port);
|
|
387
|
-
if (!free) {
|
|
388
|
-
// Port in use — check if it's a previous agentlytics instance
|
|
389
|
-
try {
|
|
390
|
-
const data = await new Promise((resolve, reject) => {
|
|
391
|
-
const req = http.get(`http://127.0.0.1:${port}/api/ping`, { timeout: 2000 }, (res) => {
|
|
392
|
-
let body = '';
|
|
393
|
-
res.on('data', (d) => body += d);
|
|
394
|
-
res.on('end', () => { try { resolve(JSON.parse(body)); } catch { reject(); } });
|
|
395
|
-
});
|
|
396
|
-
req.on('error', reject);
|
|
397
|
-
});
|
|
398
|
-
if (data.app === 'agentlytics' && data.pid) {
|
|
399
|
-
console.log(chalk.yellow(` ⟳ Killing previous Agentlytics instance (PID ${data.pid})...`));
|
|
400
|
-
try { process.kill(data.pid, 'SIGTERM'); } catch {}
|
|
401
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
402
|
-
return startServer(port);
|
|
403
|
-
}
|
|
404
|
-
} catch {}
|
|
405
|
-
console.log(chalk.yellow(` ⚠ Port ${port} is in use by another app, trying ${port + 1}...`));
|
|
406
|
-
return startServer(port + 1);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Loopback by default — the dashboard exposes transcripts and a SQL console
|
|
410
|
-
// with no auth. LAN exposure is an explicit opt-in (AGENTLYTICS_HOST=0.0.0.0).
|
|
411
|
-
const HOST = process.env.AGENTLYTICS_HOST || '127.0.0.1';
|
|
412
|
-
app.listen(port, HOST, () => {
|
|
413
|
-
if (isUiDev) return;
|
|
414
|
-
const url = `http://localhost:${port}`;
|
|
415
|
-
console.log(chalk.green(` ✓ Dashboard ready at ${chalk.bold.white(url)}`));
|
|
416
|
-
console.log('');
|
|
417
|
-
console.log(chalk.dim(' Press Ctrl+C to stop\n'));
|
|
418
|
-
|
|
419
|
-
const open = require('open');
|
|
420
|
-
open(url).catch(() => {});
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
startServer(parseInt(PORT));
|
|
425
|
-
})();
|