agentlytics 0.2.7 → 0.2.9

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.
@@ -36,6 +36,38 @@ function queryDb(sql) {
36
36
  }
37
37
  }
38
38
 
39
+ function extractModelInfo(data) {
40
+ let modelValue = null;
41
+ let providerValue = null;
42
+
43
+ if (typeof data?.modelID === 'string') {
44
+ modelValue = data.modelID;
45
+ providerValue = typeof data.providerID === 'string' ? data.providerID : null;
46
+ } else if (data?.model && typeof data.model === 'object') {
47
+ modelValue = typeof data.model.modelID === 'string' ? data.model.modelID : null;
48
+ providerValue = typeof data.providerID === 'string'
49
+ ? data.providerID
50
+ : (typeof data.model.providerID === 'string' ? data.model.providerID : null);
51
+ } else if (typeof data?.model === 'string') {
52
+ modelValue = data.model;
53
+ providerValue = typeof data.providerID === 'string' ? data.providerID : null;
54
+ }
55
+
56
+ return { modelValue, providerValue };
57
+ }
58
+
59
+ function extractTokenInfo(data) {
60
+ const tokens = data?.tokens && typeof data.tokens === 'object' ? data.tokens : null;
61
+ const cache = tokens?.cache && typeof tokens.cache === 'object' ? tokens.cache : null;
62
+
63
+ return {
64
+ inputTokens: tokens?.input,
65
+ outputTokens: tokens?.output,
66
+ cacheRead: cache?.read,
67
+ cacheWrite: cache?.write,
68
+ };
69
+ }
70
+
39
71
  function getSqliteSessions() {
40
72
  return queryDb(
41
73
  `SELECT s.id, s.title, s.directory, s.time_created, s.time_updated,
@@ -94,19 +126,18 @@ function getSqliteMessages(sessionId) {
94
126
  const content = contentParts.join('\n');
95
127
  if (!content) continue;
96
128
 
97
- let modelValue = null;
98
- if (typeof msgData.modelID === 'string') {
99
- modelValue = msgData.modelID;
100
- } else if (msgData.model && typeof msgData.model === 'object' && msgData.model.modelID) {
101
- modelValue = msgData.model.modelID;
102
- } else if (typeof msgData.model === 'string') {
103
- modelValue = msgData.model;
104
- }
129
+ const { modelValue, providerValue } = extractModelInfo(msgData);
130
+ const { inputTokens, outputTokens, cacheRead, cacheWrite } = extractTokenInfo(msgData);
105
131
 
106
132
  result.push({
107
133
  role: role === 'user' ? 'user' : role === 'assistant' ? 'assistant' : role,
108
134
  content,
109
135
  _model: modelValue,
136
+ _provider: providerValue,
137
+ _inputTokens: inputTokens,
138
+ _outputTokens: outputTokens,
139
+ _cacheRead: cacheRead,
140
+ _cacheWrite: cacheWrite,
110
141
  });
111
142
  }
112
143
 
@@ -165,12 +196,19 @@ function getMessagesForSession(sessionId) {
165
196
  let files;
166
197
  try { files = fs.readdirSync(sessionMsgDir).filter(f => f.startsWith('msg_') && f.endsWith('.json')); } catch { return []; }
167
198
 
168
- const messages = [];
199
+ const rawMsgs = [];
169
200
  for (const file of files) {
170
201
  const msgPath = path.join(sessionMsgDir, file);
171
202
  const msg = readJson(msgPath);
172
203
  if (!msg || !msg.id) continue;
204
+ rawMsgs.push(msg);
205
+ }
173
206
 
207
+ // Sort by creation time before building output
208
+ rawMsgs.sort((a, b) => (a.time?.created || 0) - (b.time?.created || 0));
209
+
210
+ const messages = [];
211
+ for (const msg of rawMsgs) {
174
212
  // Get parts for this message
175
213
  const msgPartDir = path.join(PART_DIR, msg.id);
176
214
  const parts = [];
@@ -213,35 +251,24 @@ function getMessagesForSession(sessionId) {
213
251
 
214
252
  const content = contentParts.join('\n');
215
253
  if (content) {
216
- // Extract model value - handle both string and object formats
217
- let modelValue = null;
218
- if (typeof msg.modelID === 'string') {
219
- modelValue = msg.modelID;
220
- } else if (msg.model && typeof msg.model === 'object' && msg.model.modelID) {
221
- modelValue = msg.model.modelID;
222
- } else if (typeof msg.model === 'string') {
223
- modelValue = msg.model;
224
- }
254
+ const { modelValue, providerValue } = extractModelInfo(msg);
255
+ const { inputTokens, outputTokens, cacheRead, cacheWrite } = extractTokenInfo(msg);
225
256
 
226
257
  messages.push({
227
258
  role: msg.role || 'assistant',
228
259
  content,
229
260
  _model: modelValue,
230
- _inputTokens: msg.tokens?.input,
231
- _outputTokens: msg.tokens?.output,
232
- _cacheRead: msg.tokens?.cache?.read,
233
- _cacheWrite: msg.tokens?.cache?.write,
261
+ _provider: providerValue,
262
+ _inputTokens: inputTokens,
263
+ _outputTokens: outputTokens,
264
+ _cacheRead: cacheRead,
265
+ _cacheWrite: cacheWrite,
234
266
  _finish: msg.finish,
235
267
  });
236
268
  }
237
269
  }
238
270
 
239
- // Sort by creation time
240
- return messages.sort((a, b) => {
241
- const aTime = a.time?.created || 0;
242
- const bTime = b.time?.created || 0;
243
- return aTime - bTime;
244
- });
271
+ return messages;
245
272
  }
246
273
 
247
274
  // ============================================================
package/index.js CHANGED
@@ -6,11 +6,44 @@ const path = require('path');
6
6
  const os = require('os');
7
7
  const { execSync } = require('child_process');
8
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
+
9
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';
10
42
  const PORT = process.env.PORT || 4637;
11
43
  const RELAY_PORT = process.env.RELAY_PORT || 4638;
12
44
  const noCache = process.argv.includes('--no-cache');
13
45
  const collectOnly = process.argv.includes('--collect');
46
+ const isUiDev = process.argv.includes('--ui-dev');
14
47
  const isRelay = process.argv.includes('--relay');
15
48
  const joinIndex = process.argv.indexOf('--join');
16
49
  const isJoin = joinIndex !== -1;
@@ -47,7 +80,7 @@ if (isRelay) {
47
80
  console.log('');
48
81
  console.log(chalk.bold(' Share this command with your team:'));
49
82
  console.log('');
50
- console.log(chalk.cyan(` npx agentlytics --join ${localIp}:${RELAY_PORT} --username <name>`));
83
+ console.log(chalk.cyan(` ${PM_RUN} agentlytics --join ${localIp}:${RELAY_PORT} --username <name>`));
51
84
  console.log('');
52
85
  console.log(chalk.bold(' MCP server endpoint (add to your AI client):'));
53
86
  console.log('');
@@ -76,7 +109,7 @@ if (isJoin) {
76
109
  let username = usernameIndex !== -1 ? process.argv[usernameIndex + 1] : null;
77
110
 
78
111
  if (!relayAddress) {
79
- console.error(chalk.red('\n ✗ Missing relay address. Usage: npx agentlytics --join <host:port> --username <name>\n'));
112
+ console.error(chalk.red(`\n ✗ Missing relay address. Usage: ${PM_RUN} agentlytics --join <host:port> --username <name>\n`));
80
113
  process.exit(1);
81
114
  }
82
115
 
@@ -135,15 +168,20 @@ console.log('');
135
168
  const publicIndex = path.join(__dirname, 'public', 'index.html');
136
169
  const uiDir = path.join(__dirname, 'ui');
137
170
 
138
- if (!collectOnly && !fs.existsSync(publicIndex) && fs.existsSync(uiDir)) {
171
+ if (!collectOnly && !isUiDev && !fs.existsSync(publicIndex) && fs.existsSync(uiDir)) {
139
172
  console.log(chalk.cyan(' ⟳ Building dashboard UI (first run)...'));
140
173
  try {
141
174
  const uiModules = path.join(uiDir, 'node_modules');
142
175
  if (fs.existsSync(uiModules)) fs.rmSync(uiModules, { recursive: true, force: true });
143
176
  console.log(chalk.dim(' Installing UI dependencies...'));
144
- execSync('npm install --no-audit --no-fund', { cwd: uiDir, stdio: 'pipe' });
177
+ const installCmd = PM === 'npm' ? 'npm install --no-audit --no-fund'
178
+ : PM === 'pnpm' ? 'pnpm install --no-frozen-lockfile'
179
+ : PM === 'yarn' ? 'yarn install'
180
+ : 'bun install';
181
+ const buildCmd = `${PM} run build`;
182
+ execSync(installCmd, { cwd: uiDir, stdio: 'pipe' });
145
183
  console.log(chalk.dim(' Compiling frontend...'));
146
- execSync('npm run build', { cwd: uiDir, stdio: 'pipe' });
184
+ execSync(buildCmd, { cwd: uiDir, stdio: 'pipe' });
147
185
  console.log(chalk.green(' ✓ UI built successfully'));
148
186
  } catch (err) {
149
187
  console.error(chalk.red(' ✗ UI build failed:'), err.message);
@@ -152,9 +190,9 @@ if (!collectOnly && !fs.existsSync(publicIndex) && fs.existsSync(uiDir)) {
152
190
  console.log('');
153
191
  }
154
192
 
155
- if (!collectOnly && !fs.existsSync(publicIndex)) {
193
+ if (!collectOnly && !isUiDev && !fs.existsSync(publicIndex)) {
156
194
  console.error(chalk.red(' ✗ No built UI found at public/index.html'));
157
- console.error(chalk.dim(' Run: cd ui && npm install && npm run build'));
195
+ console.error(chalk.dim(` Run: cd ui && ${PM} install && ${PM} run build`));
158
196
  process.exit(1);
159
197
  }
160
198
 
@@ -365,12 +403,12 @@ const BOT_STYLES = [
365
403
  }
366
404
 
367
405
  app.listen(port, '0.0.0.0', () => {
406
+ if (isUiDev) return;
368
407
  const url = `http://localhost:${port}`;
369
408
  console.log(chalk.green(` ✓ Dashboard ready at ${chalk.bold.white(url)}`));
370
409
  console.log('');
371
410
  console.log(chalk.dim(' Press Ctrl+C to stop\n'));
372
411
 
373
- // Auto-open browser
374
412
  const open = require('open');
375
413
  open(url).catch(() => {});
376
414
  });