@agent-analytics/cli 0.1.9 → 0.1.10

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 (3) hide show
  1. package/bin/cli.mjs +208 -290
  2. package/lib/api.mjs +16 -25
  3. package/package.json +1 -1
package/bin/cli.mjs CHANGED
@@ -47,6 +47,22 @@ function requireKey() {
47
47
  return new AgentAnalyticsAPI(key, getBaseUrl());
48
48
  }
49
49
 
50
+ function withApi(fn) {
51
+ return async (...args) => {
52
+ const api = requireKey();
53
+ try {
54
+ return await fn(api, ...args);
55
+ } catch (err) {
56
+ error(err.message);
57
+ }
58
+ };
59
+ }
60
+
61
+ function ifEmpty(arr, label) {
62
+ if (!arr || arr.length === 0) { log(` No ${label} found.`); return true; }
63
+ return false;
64
+ }
65
+
50
66
  // ==================== COMMANDS ====================
51
67
 
52
68
  async function cmdLogin(token) {
@@ -82,343 +98,257 @@ async function cmdLogin(token) {
82
98
  }
83
99
  }
84
100
 
85
- async function cmdCreate(name, domain) {
101
+ const cmdCreate = withApi(async (api, name, domain) => {
86
102
  if (!name) error('Usage: npx @agent-analytics/cli create <project-name> --domain https://mysite.com');
87
103
  if (!domain) error('Usage: npx @agent-analytics/cli create <project-name> --domain https://mysite.com\n\nThe domain is required so we can restrict tracking to your site.');
88
104
 
89
- const api = requireKey();
90
-
91
105
  heading(`Creating project: ${name}`);
92
106
 
93
- try {
94
- const data = await api.createProject(name, domain);
95
-
96
- success(data.existing
97
- ? `Found existing project for ${BOLD}${domain}${RESET}!\n`
98
- : `Project created for ${BOLD}${domain}${RESET}!\n`);
107
+ const data = await api.createProject(name, domain);
99
108
 
100
- heading('1. Add this snippet to your site:');
101
- log(`${CYAN}${data.snippet}${RESET}\n`);
109
+ success(data.existing
110
+ ? `Found existing project for ${BOLD}${domain}${RESET}!\n`
111
+ : `Project created for ${BOLD}${domain}${RESET}!\n`);
102
112
 
103
- heading('2. Your agent queries stats with:');
104
- log(`${CYAN}${data.api_example}${RESET}\n`);
113
+ heading('1. Add this snippet to your site:');
114
+ log(`${CYAN}${data.snippet}${RESET}\n`);
105
115
 
106
- heading('Project token (for the snippet):');
107
- log(`${YELLOW}${data.project_token}${RESET}\n`);
116
+ heading('2. Your agent queries stats with:');
117
+ log(`${CYAN}${data.api_example}${RESET}\n`);
108
118
 
109
- } catch (err) {
110
- error(`Failed to create project: ${err.message}`);
111
- }
112
- }
119
+ heading('Project token (for the snippet):');
120
+ log(`${YELLOW}${data.project_token}${RESET}\n`);
121
+ });
113
122
 
114
- async function cmdProjects() {
115
- const api = requireKey();
123
+ const cmdProjects = withApi(async (api) => {
124
+ const data = await api.listProjects();
125
+ const projects = data.projects;
116
126
 
117
- try {
118
- const data = await api.listProjects();
119
- const projects = data.projects;
127
+ if (!projects || projects.length === 0) {
128
+ log('No projects yet. Create one:');
129
+ log(` ${CYAN}npx @agent-analytics/cli create my-site${RESET}`);
130
+ return;
131
+ }
120
132
 
121
- if (!projects || projects.length === 0) {
122
- log('No projects yet. Create one:');
123
- log(` ${CYAN}npx @agent-analytics/cli create my-site${RESET}`);
124
- return;
125
- }
133
+ heading(`Your Projects (${projects.length})`);
134
+ log('');
126
135
 
127
- heading(`Your Projects (${projects.length})`);
136
+ for (const p of projects) {
137
+ const created = new Date(p.created_at).toLocaleDateString();
138
+ log(` ${BOLD}${p.name}${RESET} ${DIM}created ${created}${RESET}`);
139
+ log(` ${DIM}token:${RESET} ${p.project_token}`);
140
+ log(` ${DIM}origins:${RESET} ${p.allowed_origins || '*'}`);
128
141
  log('');
129
-
130
- for (const p of projects) {
131
- const created = new Date(p.created_at).toLocaleDateString();
132
- log(` ${BOLD}${p.name}${RESET} ${DIM}created ${created}${RESET}`);
133
- log(` ${DIM}token:${RESET} ${p.project_token}`);
134
- log(` ${DIM}origins:${RESET} ${p.allowed_origins || '*'}`);
135
- log('');
136
- }
137
- } catch (err) {
138
- error(`Failed to list projects: ${err.message}`);
139
142
  }
140
- }
143
+ });
141
144
 
142
- async function cmdStats(project, days = 7) {
145
+ const cmdStats = withApi(async (api, project, days = 7) => {
143
146
  if (!project) error('Usage: npx @agent-analytics/cli stats <project-name> [--days N]');
144
147
 
145
- const api = requireKey();
148
+ const result = await api.getStats(project, days, { returnHeaders: true });
149
+ const data = result.data;
150
+ const headers = result.headers;
146
151
 
147
- try {
148
- const result = await api.getStats(project, days, { returnHeaders: true });
149
- const data = result.data;
150
- const headers = result.headers;
151
-
152
- heading(`Stats: ${project} (last ${days} days)`);
153
- log('');
154
-
155
- if (data.totals) {
156
- log(` ${BOLD}Total events:${RESET} ${data.totals.total_events || 0}`);
157
- log(` ${BOLD}Unique users:${RESET} ${data.totals.unique_users || 0}`);
158
- }
152
+ heading(`Stats: ${project} (last ${days} days)`);
153
+ log('');
159
154
 
160
- if (data.events && data.events.length > 0) {
161
- log('');
162
- heading('Events:');
163
- for (const e of data.events) {
164
- log(` ${e.event} ${DIM}→${RESET} ${BOLD}${e.count}${RESET} ${DIM}(${e.unique_users} users)${RESET}`);
165
- }
166
- }
155
+ if (data.totals) {
156
+ log(` ${BOLD}Total events:${RESET} ${data.totals.total_events || 0}`);
157
+ log(` ${BOLD}Unique users:${RESET} ${data.totals.unique_users || 0}`);
158
+ }
167
159
 
168
- if (data.daily && data.daily.length > 0) {
169
- log('');
170
- heading('Daily:');
171
- for (const d of data.daily) {
172
- const bar = '█'.repeat(Math.min(Math.ceil(d.total_events / 5), 40));
173
- log(` ${d.date} ${GREEN}${bar}${RESET} ${d.total_events} events`);
174
- }
160
+ if (data.events && data.events.length > 0) {
161
+ log('');
162
+ heading('Events:');
163
+ for (const e of data.events) {
164
+ log(` ${e.event} ${DIM}→${RESET} ${BOLD}${e.count}${RESET} ${DIM}(${e.unique_users} users)${RESET}`);
175
165
  }
166
+ }
176
167
 
177
- // Monthly usage summary from response headers
178
- const monthlyUsage = headers['x-monthly-usage'];
179
- if (monthlyUsage) {
180
- const events = parseInt(monthlyUsage, 10);
181
- const bill = (events / 1000) * 2;
182
- const monthlyLimit = headers['x-monthly-limit'];
183
- const pct = headers['x-monthly-usage-percent'];
184
- log('');
185
- if (monthlyLimit && pct) {
186
- const capDollars = (parseInt(monthlyLimit, 10) / 1000) * 2;
187
- log(` ${DIM}Monthly usage:${RESET} ${events.toLocaleString()} events ($${bill.toFixed(2)}) — ${pct}% of $${capDollars.toFixed(2)} cap`);
188
- } else {
189
- log(` ${DIM}Monthly usage:${RESET} ${events.toLocaleString()} events ($${bill.toFixed(2)})`);
190
- }
168
+ if (data.daily && data.daily.length > 0) {
169
+ log('');
170
+ heading('Daily:');
171
+ for (const d of data.daily) {
172
+ const bar = '█'.repeat(Math.min(Math.ceil(d.total_events / 5), 40));
173
+ log(` ${d.date} ${GREEN}${bar}${RESET} ${d.total_events} events`);
191
174
  }
175
+ }
192
176
 
177
+ // Monthly usage summary from response headers
178
+ const monthlyUsage = headers['x-monthly-usage'];
179
+ if (monthlyUsage) {
180
+ const events = parseInt(monthlyUsage, 10);
181
+ const bill = (events / 1000) * 2;
182
+ const monthlyLimit = headers['x-monthly-limit'];
183
+ const pct = headers['x-monthly-usage-percent'];
193
184
  log('');
194
- } catch (err) {
195
- error(`Failed to get stats: ${err.message}`);
185
+ if (monthlyLimit && pct) {
186
+ const capDollars = (parseInt(monthlyLimit, 10) / 1000) * 2;
187
+ log(` ${DIM}Monthly usage:${RESET} ${events.toLocaleString()} events ($${bill.toFixed(2)}) — ${pct}% of $${capDollars.toFixed(2)} cap`);
188
+ } else {
189
+ log(` ${DIM}Monthly usage:${RESET} ${events.toLocaleString()} events ($${bill.toFixed(2)})`);
190
+ }
196
191
  }
197
- }
198
192
 
199
- async function cmdEvents(project, opts = {}) {
200
- if (!project) error('Usage: npx @agent-analytics/cli events <project-name> [--days N] [--limit N]');
193
+ log('');
194
+ });
201
195
 
202
- const api = requireKey();
196
+ const cmdEvents = withApi(async (api, project, opts = {}) => {
197
+ if (!project) error('Usage: npx @agent-analytics/cli events <project-name> [--days N] [--limit N]');
203
198
 
204
- try {
205
- const data = await api.getEvents(project, opts);
199
+ const data = await api.getEvents(project, opts);
206
200
 
207
- heading(`Events: ${project}`);
208
- log('');
201
+ heading(`Events: ${project}`);
202
+ log('');
209
203
 
210
- if (!data.events || data.events.length === 0) {
211
- log(' No events yet.');
212
- return;
213
- }
204
+ if (ifEmpty(data.events, 'events')) return;
214
205
 
215
- for (const e of data.events) {
216
- const time = new Date(e.timestamp).toLocaleString();
217
- log(` ${DIM}${time}${RESET} ${BOLD}${e.event}${RESET} ${DIM}${e.user_id || ''}${RESET}`);
218
- if (e.properties) {
219
- log(` ${DIM}${JSON.stringify(e.properties)}${RESET}`);
220
- }
206
+ for (const e of data.events) {
207
+ const time = new Date(e.timestamp).toLocaleString();
208
+ log(` ${DIM}${time}${RESET} ${BOLD}${e.event}${RESET} ${DIM}${e.user_id || ''}${RESET}`);
209
+ if (e.properties) {
210
+ log(` ${DIM}${JSON.stringify(e.properties)}${RESET}`);
221
211
  }
222
- log('');
223
- } catch (err) {
224
- error(`Failed to get events: ${err.message}`);
225
212
  }
226
- }
213
+ log('');
214
+ });
227
215
 
228
- async function cmdPropertiesReceived(project, opts = {}) {
216
+ const cmdPropertiesReceived = withApi(async (api, project, opts = {}) => {
229
217
  if (!project) error('Usage: npx @agent-analytics/cli properties-received <project-name> [--since DATE] [--sample N]');
230
218
 
231
- const api = requireKey();
219
+ const data = await api.getPropertiesReceived(project, opts);
232
220
 
233
- try {
234
- const data = await api.getPropertiesReceived(project, opts);
235
-
236
- heading(`Received Properties: ${project}`);
237
- log('');
221
+ heading(`Received Properties: ${project}`);
222
+ log('');
238
223
 
239
- if (!data.properties || data.properties.length === 0) {
240
- log(' No properties found.');
241
- return;
242
- }
224
+ if (ifEmpty(data.properties, 'properties')) return;
243
225
 
244
- // Group by event for display
245
- const byEvent = {};
246
- for (const p of data.properties) {
247
- if (!byEvent[p.event]) byEvent[p.event] = [];
248
- byEvent[p.event].push(p.key);
249
- }
226
+ // Group by event for display
227
+ const byEvent = {};
228
+ for (const p of data.properties) {
229
+ if (!byEvent[p.event]) byEvent[p.event] = [];
230
+ byEvent[p.event].push(p.key);
231
+ }
250
232
 
251
- for (const [event, keys] of Object.entries(byEvent)) {
252
- log(` ${BOLD}${event}${RESET}`);
253
- for (const key of keys) {
254
- log(` ${CYAN}${key}${RESET}`);
255
- }
233
+ for (const [event, keys] of Object.entries(byEvent)) {
234
+ log(` ${BOLD}${event}${RESET}`);
235
+ for (const key of keys) {
236
+ log(` ${CYAN}${key}${RESET}`);
256
237
  }
257
-
258
- log(`\n${DIM}Sampled from last ${data.sample_size} events${RESET}`);
259
- log('');
260
- } catch (err) {
261
- error(`Failed to get properties: ${err.message}`);
262
238
  }
263
- }
264
-
265
- async function cmdInsights(project, period = '7d') {
266
- if (!project) error('Usage: npx @agent-analytics/cli insights <project-name> [--period 7d]');
267
239
 
268
- const api = requireKey();
240
+ log(`\n${DIM}Sampled from last ${data.sample_size} events${RESET}`);
241
+ log('');
242
+ });
269
243
 
270
- try {
271
- const data = await api.getInsights(project, { period });
244
+ const cmdInsights = withApi(async (api, project, period = '7d') => {
245
+ if (!project) error('Usage: npx @agent-analytics/cli insights <project-name> [--period 7d]');
272
246
 
273
- heading(`Insights: ${project} (${period} vs previous)`);
274
- log('');
247
+ const data = await api.getInsights(project, { period });
275
248
 
276
- const m = data.metrics;
277
- for (const [key, metric] of Object.entries(m)) {
278
- const label = key.replace(/_/g, ' ');
279
- const arrow = metric.change > 0 ? `${GREEN}↑` : metric.change < 0 ? `${RED}↓` : `${DIM}—`;
280
- const pct = metric.change_pct !== null ? ` (${metric.change_pct > 0 ? '+' : ''}${metric.change_pct}%)` : '';
281
- log(` ${BOLD}${label}:${RESET} ${metric.current} ${arrow}${pct}${RESET} ${DIM}was ${metric.previous}${RESET}`);
282
- }
249
+ heading(`Insights: ${project} (${period} vs previous)`);
250
+ log('');
283
251
 
284
- log('');
285
- log(` ${BOLD}Trend:${RESET} ${data.trend}`);
286
- log('');
287
- } catch (err) {
288
- error(`Failed to get insights: ${err.message}`);
252
+ const m = data.metrics;
253
+ for (const [key, metric] of Object.entries(m)) {
254
+ const label = key.replace(/_/g, ' ');
255
+ const arrow = metric.change > 0 ? `${GREEN}↑` : metric.change < 0 ? `${RED}↓` : `${DIM}—`;
256
+ const pct = metric.change_pct !== null ? ` (${metric.change_pct > 0 ? '+' : ''}${metric.change_pct}%)` : '';
257
+ log(` ${BOLD}${label}:${RESET} ${metric.current} ${arrow}${pct}${RESET} ${DIM}was ${metric.previous}${RESET}`);
289
258
  }
290
- }
291
259
 
292
- async function cmdBreakdown(project, property, opts = {}) {
260
+ log('');
261
+ log(` ${BOLD}Trend:${RESET} ${data.trend}`);
262
+ log('');
263
+ });
264
+
265
+ const cmdBreakdown = withApi(async (api, project, property, opts = {}) => {
293
266
  if (!project || !property) error('Usage: npx @agent-analytics/cli breakdown <project-name> --property <key> [--event page_view] [--limit 20]');
294
267
 
295
- const api = requireKey();
268
+ const data = await api.getBreakdown(project, { property, ...opts });
296
269
 
297
- try {
298
- const data = await api.getBreakdown(project, { property, ...opts });
299
-
300
- heading(`Breakdown: ${project} — ${property}${data.event ? ` (${data.event})` : ''}`);
301
- log('');
270
+ heading(`Breakdown: ${project} — ${property}${data.event ? ` (${data.event})` : ''}`);
271
+ log('');
302
272
 
303
- if (!data.values || data.values.length === 0) {
304
- log(' No data found.');
305
- return;
306
- }
273
+ if (ifEmpty(data.values, 'data')) return;
307
274
 
308
- for (const v of data.values) {
309
- log(` ${BOLD}${v.value}${RESET} ${v.count} events ${DIM}(${v.unique_users} users)${RESET}`);
310
- }
311
- log(`\n${DIM}${data.total_with_property} of ${data.total_events} events have this property${RESET}`);
312
- log('');
313
- } catch (err) {
314
- error(`Failed to get breakdown: ${err.message}`);
275
+ for (const v of data.values) {
276
+ log(` ${BOLD}${v.value}${RESET} ${v.count} events ${DIM}(${v.unique_users} users)${RESET}`);
315
277
  }
316
- }
278
+ log(`\n${DIM}${data.total_with_property} of ${data.total_events} events have this property${RESET}`);
279
+ log('');
280
+ });
317
281
 
318
- async function cmdPages(project, type = 'entry', opts = {}) {
282
+ const cmdPages = withApi(async (api, project, type = 'entry', opts = {}) => {
319
283
  if (!project) error('Usage: npx @agent-analytics/cli pages <project-name> [--type entry|exit|both] [--limit 20]');
320
284
 
321
- const api = requireKey();
285
+ const data = await api.getPages(project, { type, ...opts });
322
286
 
323
- try {
324
- const data = await api.getPages(project, { type, ...opts });
287
+ heading(`Pages: ${project} (${type})`);
288
+ log('');
325
289
 
326
- heading(`Pages: ${project} (${type})`);
327
- log('');
290
+ const pages = data.entry_pages || data.exit_pages || [];
291
+ if (ifEmpty(pages, 'page data')) return;
328
292
 
329
- const pages = data.entry_pages || data.exit_pages || [];
330
- if (pages.length === 0) {
331
- log(' No page data found.');
332
- return;
333
- }
334
-
335
- for (const p of pages) {
336
- const bounceStr = `${Math.round(p.bounce_rate * 100)}% bounce`;
337
- const durStr = `${Math.round(p.avg_duration / 1000)}s avg`;
338
- log(` ${BOLD}${p.page}${RESET} ${p.sessions} sessions ${DIM}${bounceStr} ${durStr} ${p.avg_events} events/session${RESET}`);
339
- }
293
+ for (const p of pages) {
294
+ const bounceStr = `${Math.round(p.bounce_rate * 100)}% bounce`;
295
+ const durStr = `${Math.round(p.avg_duration / 1000)}s avg`;
296
+ log(` ${BOLD}${p.page}${RESET} ${p.sessions} sessions ${DIM}${bounceStr} ${durStr} ${p.avg_events} events/session${RESET}`);
297
+ }
340
298
 
341
- if (data.exit_pages && data.entry_pages) {
342
- log('');
343
- heading('Exit pages:');
344
- for (const p of data.exit_pages) {
345
- log(` ${BOLD}${p.page}${RESET} ${p.sessions} sessions`);
346
- }
347
- }
299
+ if (data.exit_pages && data.entry_pages) {
348
300
  log('');
349
- } catch (err) {
350
- error(`Failed to get pages: ${err.message}`);
301
+ heading('Exit pages:');
302
+ for (const p of data.exit_pages) {
303
+ log(` ${BOLD}${p.page}${RESET} ${p.sessions} sessions`);
304
+ }
351
305
  }
352
- }
306
+ log('');
307
+ });
353
308
 
354
- async function cmdSessionsDist(project) {
309
+ const cmdSessionsDist = withApi(async (api, project) => {
355
310
  if (!project) error('Usage: npx @agent-analytics/cli sessions-dist <project-name>');
356
311
 
357
- const api = requireKey();
312
+ const data = await api.getSessionDistribution(project);
358
313
 
359
- try {
360
- const data = await api.getSessionDistribution(project);
361
-
362
- heading(`Session Distribution: ${project}`);
363
- log('');
364
-
365
- if (!data.distribution || data.distribution.length === 0) {
366
- log(' No session data found.');
367
- return;
368
- }
314
+ heading(`Session Distribution: ${project}`);
315
+ log('');
369
316
 
370
- for (const b of data.distribution) {
371
- const bar = '█'.repeat(Math.min(Math.ceil(b.pct / 2), 40));
372
- log(` ${b.bucket.padEnd(7)} ${GREEN}${bar}${RESET} ${b.sessions} (${b.pct}%)`);
373
- }
317
+ if (ifEmpty(data.distribution, 'session data')) return;
374
318
 
375
- log('');
376
- log(` ${BOLD}Median:${RESET} ${data.median_bucket} ${BOLD}Engaged:${RESET} ${data.engaged_pct}% (sessions ≥30s)`);
377
- log('');
378
- } catch (err) {
379
- error(`Failed to get session distribution: ${err.message}`);
319
+ for (const b of data.distribution) {
320
+ const bar = '█'.repeat(Math.min(Math.ceil(b.pct / 2), 40));
321
+ log(` ${b.bucket.padEnd(7)} ${GREEN}${bar}${RESET} ${b.sessions} (${b.pct}%)`);
380
322
  }
381
- }
382
323
 
383
- async function cmdHeatmap(project) {
324
+ log('');
325
+ log(` ${BOLD}Median:${RESET} ${data.median_bucket} ${BOLD}Engaged:${RESET} ${data.engaged_pct}% (sessions ≥30s)`);
326
+ log('');
327
+ });
328
+
329
+ const cmdHeatmap = withApi(async (api, project) => {
384
330
  if (!project) error('Usage: npx @agent-analytics/cli heatmap <project-name>');
385
331
 
386
- const api = requireKey();
332
+ const data = await api.getHeatmap(project);
387
333
 
388
- try {
389
- const data = await api.getHeatmap(project);
334
+ heading(`Heatmap: ${project}`);
335
+ log('');
390
336
 
391
- heading(`Heatmap: ${project}`);
392
- log('');
337
+ if (ifEmpty(data.heatmap, 'heatmap data')) return;
393
338
 
394
- if (!data.heatmap || data.heatmap.length === 0) {
395
- log(' No heatmap data found.');
396
- return;
397
- }
398
-
399
- if (data.peak) {
400
- log(` ${BOLD}Peak:${RESET} ${data.peak.day_name} at ${data.peak.hour}:00 (${data.peak.events} events, ${data.peak.users} users)`);
401
- }
402
- log(` ${BOLD}Busiest day:${RESET} ${data.busiest_day}`);
403
- log(` ${BOLD}Busiest hour:${RESET} ${data.busiest_hour}:00`);
404
- log('');
405
- } catch (err) {
406
- error(`Failed to get heatmap: ${err.message}`);
339
+ if (data.peak) {
340
+ log(` ${BOLD}Peak:${RESET} ${data.peak.day_name} at ${data.peak.hour}:00 (${data.peak.events} events, ${data.peak.users} users)`);
407
341
  }
408
- }
342
+ log(` ${BOLD}Busiest day:${RESET} ${data.busiest_day}`);
343
+ log(` ${BOLD}Busiest hour:${RESET} ${data.busiest_hour}:00`);
344
+ log('');
345
+ });
409
346
 
410
- async function cmdDelete(id) {
347
+ const cmdDelete = withApi(async (api, id) => {
411
348
  if (!id) error('Usage: npx @agent-analytics/cli delete <project-id>');
412
-
413
- const api = requireKey();
414
-
415
- try {
416
- await api.deleteProject(id);
417
- success(`Project ${id} deleted`);
418
- } catch (err) {
419
- error(`Failed to delete project: ${err.message}`);
420
- }
421
- }
349
+ await api.deleteProject(id);
350
+ success(`Project ${id} deleted`);
351
+ });
422
352
 
423
353
  function cmdDeleteAccount() {
424
354
  heading('Delete Account');
@@ -428,42 +358,30 @@ function cmdDeleteAccount() {
428
358
  log('');
429
359
  }
430
360
 
431
- async function cmdRevokeKey() {
432
- const api = requireKey();
433
-
434
- try {
435
- const data = await api.revokeKey();
436
- setApiKey(data.api_key);
437
-
438
- warn('Old API key revoked');
439
- success('New API key generated and saved\n');
440
- heading('New API key:');
441
- log(`${YELLOW}${data.api_key}${RESET}`);
442
- log(`${DIM}Saved to ~/.config/agent-analytics/config.json${RESET}\n`);
443
- warn('Update your agent with this new key!');
444
- } catch (err) {
445
- error(`Failed to revoke key: ${err.message}`);
361
+ const cmdRevokeKey = withApi(async (api) => {
362
+ const data = await api.revokeKey();
363
+ setApiKey(data.api_key);
364
+
365
+ warn('Old API key revoked');
366
+ success('New API key generated and saved\n');
367
+ heading('New API key:');
368
+ log(`${YELLOW}${data.api_key}${RESET}`);
369
+ log(`${DIM}Saved to ~/.config/agent-analytics/config.json${RESET}\n`);
370
+ warn('Update your agent with this new key!');
371
+ });
372
+
373
+ const cmdWhoami = withApi(async (api) => {
374
+ const data = await api.getAccount();
375
+ heading('Account');
376
+ log(` ${BOLD}Email:${RESET} ${data.email}`);
377
+ log(` ${BOLD}GitHub:${RESET} ${data.github_login || 'N/A'}`);
378
+ log(` ${BOLD}Tier:${RESET} ${data.tier}`);
379
+ log(` ${BOLD}Projects:${RESET} ${data.projects_count}/${data.projects_limit}`);
380
+ if (data.tier === 'pro' && data.monthly_spend_cap_dollars != null) {
381
+ log(` ${BOLD}Spend cap:${RESET} $${data.monthly_spend_cap_dollars.toFixed(2)}/month`);
446
382
  }
447
- }
448
-
449
- async function cmdWhoami() {
450
- const api = requireKey();
451
-
452
- try {
453
- const data = await api.getAccount();
454
- heading('Account');
455
- log(` ${BOLD}Email:${RESET} ${data.email}`);
456
- log(` ${BOLD}GitHub:${RESET} ${data.github_login || 'N/A'}`);
457
- log(` ${BOLD}Tier:${RESET} ${data.tier}`);
458
- log(` ${BOLD}Projects:${RESET} ${data.projects_count}/${data.projects_limit}`);
459
- if (data.tier === 'pro' && data.monthly_spend_cap_dollars != null) {
460
- log(` ${BOLD}Spend cap:${RESET} $${data.monthly_spend_cap_dollars.toFixed(2)}/month`);
461
- }
462
- log('');
463
- } catch (err) {
464
- error(`Failed to get account: ${err.message}`);
465
- }
466
- }
383
+ log('');
384
+ });
467
385
 
468
386
  function showHelp() {
469
387
  log(`
package/lib/api.mjs CHANGED
@@ -11,6 +11,13 @@ export class AgentAnalyticsAPI {
11
11
  this.baseUrl = baseUrl;
12
12
  }
13
13
 
14
+ _qs(params) {
15
+ return Object.entries(params)
16
+ .filter(([, v]) => v != null)
17
+ .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
18
+ .join('&');
19
+ }
20
+
14
21
  async request(method, path, body, { returnHeaders = false } = {}) {
15
22
  const opts = {
16
23
  method,
@@ -68,55 +75,39 @@ export class AgentAnalyticsAPI {
68
75
 
69
76
  // Stats
70
77
  async getStats(project, days = 7, { returnHeaders = false } = {}) {
71
- return this.request('GET', `/stats?project=${encodeURIComponent(project)}&days=${days}`, undefined, { returnHeaders });
78
+ return this.request('GET', `/stats?${this._qs({ project, days })}`, undefined, { returnHeaders });
72
79
  }
73
80
 
74
81
  async getEvents(project, { event, days = 7, limit = 100 } = {}) {
75
- let qs = `project=${encodeURIComponent(project)}&days=${days}&limit=${limit}`;
76
- if (event) qs += `&event=${encodeURIComponent(event)}`;
77
- return this.request('GET', `/events?${qs}`);
82
+ return this.request('GET', `/events?${this._qs({ project, days, limit, event })}`);
78
83
  }
79
84
 
80
85
  async getProperties(project, days = 30) {
81
- return this.request('GET', `/properties?project=${encodeURIComponent(project)}&days=${days}`);
86
+ return this.request('GET', `/properties?${this._qs({ project, days })}`);
82
87
  }
83
88
 
84
89
  async getPropertiesReceived(project, { since, sample } = {}) {
85
- let qs = `project=${encodeURIComponent(project)}`;
86
- if (since) qs += `&since=${encodeURIComponent(since)}`;
87
- if (sample) qs += `&sample=${sample}`;
88
- return this.request('GET', `/properties/received?${qs}`);
90
+ return this.request('GET', `/properties/received?${this._qs({ project, since, sample })}`);
89
91
  }
90
92
 
91
93
  // Analytics
92
94
  async getBreakdown(project, { property, event, since, limit = 20 } = {}) {
93
- let qs = `project=${encodeURIComponent(project)}&property=${encodeURIComponent(property)}`;
94
- if (event) qs += `&event=${encodeURIComponent(event)}`;
95
- if (since) qs += `&since=${encodeURIComponent(since)}`;
96
- if (limit) qs += `&limit=${limit}`;
97
- return this.request('GET', `/breakdown?${qs}`);
95
+ return this.request('GET', `/breakdown?${this._qs({ project, property, event, since, limit })}`);
98
96
  }
99
97
 
100
98
  async getInsights(project, { period = '7d' } = {}) {
101
- return this.request('GET', `/insights?project=${encodeURIComponent(project)}&period=${period}`);
99
+ return this.request('GET', `/insights?${this._qs({ project, period })}`);
102
100
  }
103
101
 
104
102
  async getPages(project, { type = 'entry', since, limit = 20 } = {}) {
105
- let qs = `project=${encodeURIComponent(project)}&type=${type}`;
106
- if (since) qs += `&since=${encodeURIComponent(since)}`;
107
- if (limit) qs += `&limit=${limit}`;
108
- return this.request('GET', `/pages?${qs}`);
103
+ return this.request('GET', `/pages?${this._qs({ project, type, since, limit })}`);
109
104
  }
110
105
 
111
106
  async getSessionDistribution(project, { since } = {}) {
112
- let qs = `project=${encodeURIComponent(project)}`;
113
- if (since) qs += `&since=${encodeURIComponent(since)}`;
114
- return this.request('GET', `/sessions/distribution?${qs}`);
107
+ return this.request('GET', `/sessions/distribution?${this._qs({ project, since })}`);
115
108
  }
116
109
 
117
110
  async getHeatmap(project, { since } = {}) {
118
- let qs = `project=${encodeURIComponent(project)}`;
119
- if (since) qs += `&since=${encodeURIComponent(since)}`;
120
- return this.request('GET', `/heatmap?${qs}`);
111
+ return this.request('GET', `/heatmap?${this._qs({ project, since })}`);
121
112
  }
122
113
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-analytics/cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Web analytics your AI agent can read. CLI for managing projects and querying stats.",
5
5
  "bin": {
6
6
  "agent-analytics": "./bin/cli.mjs"