@agent-analytics/cli 0.1.8 → 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.
- package/README.md +5 -0
- package/bin/cli.mjs +276 -179
- package/lib/api.mjs +32 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,11 @@ npx @agent-analytics/cli stats <name> # Stats (last 7 days)
|
|
|
34
34
|
npx @agent-analytics/cli stats <name> --days 30 # Stats (last 30 days)
|
|
35
35
|
npx @agent-analytics/cli events <name> # Recent events
|
|
36
36
|
npx @agent-analytics/cli properties-received <name> # Property keys per event
|
|
37
|
+
npx @agent-analytics/cli insights <name> # Period-over-period comparison (--period 7d)
|
|
38
|
+
npx @agent-analytics/cli breakdown <name> --property path # Top values for a property
|
|
39
|
+
npx @agent-analytics/cli pages <name> # Landing page performance (--type entry|exit|both)
|
|
40
|
+
npx @agent-analytics/cli sessions-dist <name> # Session duration histogram
|
|
41
|
+
npx @agent-analytics/cli heatmap <name> # Peak hours & busiest days
|
|
37
42
|
|
|
38
43
|
# Security
|
|
39
44
|
npx @agent-analytics/cli revoke-key # Revoke and regenerate API key
|
package/bin/cli.mjs
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* npx @agent-analytics/cli stats <name> — Get stats for a project
|
|
11
11
|
* npx @agent-analytics/cli events <name> — Get recent events
|
|
12
12
|
* npx @agent-analytics/cli properties-received <name> — Show property keys per event
|
|
13
|
+
* npx @agent-analytics/cli insights <name> — Period-over-period comparison
|
|
14
|
+
* npx @agent-analytics/cli breakdown <name> — Property value distribution
|
|
15
|
+
* npx @agent-analytics/cli pages <name> — Entry/exit page stats
|
|
16
|
+
* npx @agent-analytics/cli sessions-dist <name> — Session duration distribution
|
|
17
|
+
* npx @agent-analytics/cli heatmap <name> — Peak hours & busiest days
|
|
13
18
|
* npx @agent-analytics/cli init <name> — Alias for create
|
|
14
19
|
* npx @agent-analytics/cli delete <id> — Delete a project
|
|
15
20
|
* npx @agent-analytics/cli revoke-key — Revoke and regenerate API key
|
|
@@ -42,6 +47,22 @@ function requireKey() {
|
|
|
42
47
|
return new AgentAnalyticsAPI(key, getBaseUrl());
|
|
43
48
|
}
|
|
44
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
|
+
|
|
45
66
|
// ==================== COMMANDS ====================
|
|
46
67
|
|
|
47
68
|
async function cmdLogin(token) {
|
|
@@ -77,198 +98,257 @@ async function cmdLogin(token) {
|
|
|
77
98
|
}
|
|
78
99
|
}
|
|
79
100
|
|
|
80
|
-
async
|
|
101
|
+
const cmdCreate = withApi(async (api, name, domain) => {
|
|
81
102
|
if (!name) error('Usage: npx @agent-analytics/cli create <project-name> --domain https://mysite.com');
|
|
82
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.');
|
|
83
104
|
|
|
84
|
-
const api = requireKey();
|
|
85
|
-
|
|
86
105
|
heading(`Creating project: ${name}`);
|
|
87
106
|
|
|
88
|
-
|
|
89
|
-
const data = await api.createProject(name, domain);
|
|
107
|
+
const data = await api.createProject(name, domain);
|
|
90
108
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
109
|
+
success(data.existing
|
|
110
|
+
? `Found existing project for ${BOLD}${domain}${RESET}!\n`
|
|
111
|
+
: `Project created for ${BOLD}${domain}${RESET}!\n`);
|
|
94
112
|
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
heading('1. Add this snippet to your site:');
|
|
114
|
+
log(`${CYAN}${data.snippet}${RESET}\n`);
|
|
97
115
|
|
|
98
|
-
|
|
99
|
-
|
|
116
|
+
heading('2. Your agent queries stats with:');
|
|
117
|
+
log(`${CYAN}${data.api_example}${RESET}\n`);
|
|
100
118
|
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
heading('Project token (for the snippet):');
|
|
120
|
+
log(`${YELLOW}${data.project_token}${RESET}\n`);
|
|
121
|
+
});
|
|
103
122
|
|
|
104
|
-
|
|
105
|
-
|
|
123
|
+
const cmdProjects = withApi(async (api) => {
|
|
124
|
+
const data = await api.listProjects();
|
|
125
|
+
const projects = data.projects;
|
|
126
|
+
|
|
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;
|
|
106
131
|
}
|
|
107
|
-
}
|
|
108
132
|
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
heading(`Your Projects (${projects.length})`);
|
|
134
|
+
log('');
|
|
111
135
|
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
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 || '*'}`);
|
|
141
|
+
log('');
|
|
142
|
+
}
|
|
143
|
+
});
|
|
115
144
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
145
|
+
const cmdStats = withApi(async (api, project, days = 7) => {
|
|
146
|
+
if (!project) error('Usage: npx @agent-analytics/cli stats <project-name> [--days N]');
|
|
147
|
+
|
|
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
|
+
}
|
|
159
|
+
|
|
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}`);
|
|
120
165
|
}
|
|
166
|
+
}
|
|
121
167
|
|
|
122
|
-
|
|
168
|
+
if (data.daily && data.daily.length > 0) {
|
|
123
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
|
+
}
|
|
175
|
+
}
|
|
124
176
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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)})`);
|
|
131
190
|
}
|
|
132
|
-
} catch (err) {
|
|
133
|
-
error(`Failed to list projects: ${err.message}`);
|
|
134
191
|
}
|
|
135
|
-
}
|
|
136
192
|
|
|
137
|
-
|
|
138
|
-
|
|
193
|
+
log('');
|
|
194
|
+
});
|
|
139
195
|
|
|
140
|
-
|
|
196
|
+
const cmdEvents = withApi(async (api, project, opts = {}) => {
|
|
197
|
+
if (!project) error('Usage: npx @agent-analytics/cli events <project-name> [--days N] [--limit N]');
|
|
141
198
|
|
|
142
|
-
|
|
143
|
-
const result = await api.getStats(project, days, { returnHeaders: true });
|
|
144
|
-
const data = result.data;
|
|
145
|
-
const headers = result.headers;
|
|
199
|
+
const data = await api.getEvents(project, opts);
|
|
146
200
|
|
|
147
|
-
|
|
148
|
-
|
|
201
|
+
heading(`Events: ${project}`);
|
|
202
|
+
log('');
|
|
149
203
|
|
|
150
|
-
|
|
151
|
-
log(` ${BOLD}Total events:${RESET} ${data.totals.total_events || 0}`);
|
|
152
|
-
log(` ${BOLD}Unique users:${RESET} ${data.totals.unique_users || 0}`);
|
|
153
|
-
}
|
|
204
|
+
if (ifEmpty(data.events, 'events')) return;
|
|
154
205
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
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}`);
|
|
161
211
|
}
|
|
212
|
+
}
|
|
213
|
+
log('');
|
|
214
|
+
});
|
|
162
215
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
216
|
+
const cmdPropertiesReceived = withApi(async (api, project, opts = {}) => {
|
|
217
|
+
if (!project) error('Usage: npx @agent-analytics/cli properties-received <project-name> [--since DATE] [--sample N]');
|
|
218
|
+
|
|
219
|
+
const data = await api.getPropertiesReceived(project, opts);
|
|
220
|
+
|
|
221
|
+
heading(`Received Properties: ${project}`);
|
|
222
|
+
log('');
|
|
223
|
+
|
|
224
|
+
if (ifEmpty(data.properties, 'properties')) return;
|
|
171
225
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
} else {
|
|
184
|
-
log(` ${DIM}Monthly usage:${RESET} ${events.toLocaleString()} events ($${bill.toFixed(2)})`);
|
|
185
|
-
}
|
|
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
|
+
}
|
|
232
|
+
|
|
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}`);
|
|
186
237
|
}
|
|
238
|
+
}
|
|
187
239
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
240
|
+
log(`\n${DIM}Sampled from last ${data.sample_size} events${RESET}`);
|
|
241
|
+
log('');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const cmdInsights = withApi(async (api, project, period = '7d') => {
|
|
245
|
+
if (!project) error('Usage: npx @agent-analytics/cli insights <project-name> [--period 7d]');
|
|
246
|
+
|
|
247
|
+
const data = await api.getInsights(project, { period });
|
|
248
|
+
|
|
249
|
+
heading(`Insights: ${project} (${period} vs previous)`);
|
|
250
|
+
log('');
|
|
251
|
+
|
|
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}`);
|
|
191
258
|
}
|
|
192
|
-
}
|
|
193
259
|
|
|
194
|
-
|
|
195
|
-
|
|
260
|
+
log('');
|
|
261
|
+
log(` ${BOLD}Trend:${RESET} ${data.trend}`);
|
|
262
|
+
log('');
|
|
263
|
+
});
|
|
196
264
|
|
|
197
|
-
|
|
265
|
+
const cmdBreakdown = withApi(async (api, project, property, opts = {}) => {
|
|
266
|
+
if (!project || !property) error('Usage: npx @agent-analytics/cli breakdown <project-name> --property <key> [--event page_view] [--limit 20]');
|
|
198
267
|
|
|
199
|
-
|
|
200
|
-
const data = await api.getEvents(project, opts);
|
|
268
|
+
const data = await api.getBreakdown(project, { property, ...opts });
|
|
201
269
|
|
|
202
|
-
|
|
203
|
-
|
|
270
|
+
heading(`Breakdown: ${project} — ${property}${data.event ? ` (${data.event})` : ''}`);
|
|
271
|
+
log('');
|
|
204
272
|
|
|
205
|
-
|
|
206
|
-
log(' No events yet.');
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
273
|
+
if (ifEmpty(data.values, 'data')) return;
|
|
209
274
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
log(` ${DIM}${time}${RESET} ${BOLD}${e.event}${RESET} ${DIM}${e.user_id || ''}${RESET}`);
|
|
213
|
-
if (e.properties) {
|
|
214
|
-
log(` ${DIM}${JSON.stringify(e.properties)}${RESET}`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
log('');
|
|
218
|
-
} catch (err) {
|
|
219
|
-
error(`Failed to get events: ${err.message}`);
|
|
275
|
+
for (const v of data.values) {
|
|
276
|
+
log(` ${BOLD}${v.value}${RESET} ${v.count} events ${DIM}(${v.unique_users} users)${RESET}`);
|
|
220
277
|
}
|
|
221
|
-
}
|
|
278
|
+
log(`\n${DIM}${data.total_with_property} of ${data.total_events} events have this property${RESET}`);
|
|
279
|
+
log('');
|
|
280
|
+
});
|
|
222
281
|
|
|
223
|
-
async
|
|
224
|
-
if (!project) error('Usage: npx @agent-analytics/cli
|
|
282
|
+
const cmdPages = withApi(async (api, project, type = 'entry', opts = {}) => {
|
|
283
|
+
if (!project) error('Usage: npx @agent-analytics/cli pages <project-name> [--type entry|exit|both] [--limit 20]');
|
|
225
284
|
|
|
226
|
-
const
|
|
285
|
+
const data = await api.getPages(project, { type, ...opts });
|
|
227
286
|
|
|
228
|
-
|
|
229
|
-
|
|
287
|
+
heading(`Pages: ${project} (${type})`);
|
|
288
|
+
log('');
|
|
230
289
|
|
|
231
|
-
|
|
232
|
-
|
|
290
|
+
const pages = data.entry_pages || data.exit_pages || [];
|
|
291
|
+
if (ifEmpty(pages, 'page data')) return;
|
|
233
292
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
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
|
+
}
|
|
238
298
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
299
|
+
if (data.exit_pages && data.entry_pages) {
|
|
300
|
+
log('');
|
|
301
|
+
heading('Exit pages:');
|
|
302
|
+
for (const p of data.exit_pages) {
|
|
303
|
+
log(` ${BOLD}${p.page}${RESET} ${p.sessions} sessions`);
|
|
244
304
|
}
|
|
305
|
+
}
|
|
306
|
+
log('');
|
|
307
|
+
});
|
|
245
308
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
for (const key of keys) {
|
|
249
|
-
log(` ${CYAN}${key}${RESET}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
309
|
+
const cmdSessionsDist = withApi(async (api, project) => {
|
|
310
|
+
if (!project) error('Usage: npx @agent-analytics/cli sessions-dist <project-name>');
|
|
252
311
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
312
|
+
const data = await api.getSessionDistribution(project);
|
|
313
|
+
|
|
314
|
+
heading(`Session Distribution: ${project}`);
|
|
315
|
+
log('');
|
|
316
|
+
|
|
317
|
+
if (ifEmpty(data.distribution, 'session data')) return;
|
|
318
|
+
|
|
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}%)`);
|
|
257
322
|
}
|
|
258
|
-
}
|
|
259
323
|
|
|
260
|
-
|
|
261
|
-
|
|
324
|
+
log('');
|
|
325
|
+
log(` ${BOLD}Median:${RESET} ${data.median_bucket} ${BOLD}Engaged:${RESET} ${data.engaged_pct}% (sessions ≥30s)`);
|
|
326
|
+
log('');
|
|
327
|
+
});
|
|
262
328
|
|
|
263
|
-
|
|
329
|
+
const cmdHeatmap = withApi(async (api, project) => {
|
|
330
|
+
if (!project) error('Usage: npx @agent-analytics/cli heatmap <project-name>');
|
|
264
331
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
332
|
+
const data = await api.getHeatmap(project);
|
|
333
|
+
|
|
334
|
+
heading(`Heatmap: ${project}`);
|
|
335
|
+
log('');
|
|
336
|
+
|
|
337
|
+
if (ifEmpty(data.heatmap, 'heatmap data')) return;
|
|
338
|
+
|
|
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)`);
|
|
270
341
|
}
|
|
271
|
-
}
|
|
342
|
+
log(` ${BOLD}Busiest day:${RESET} ${data.busiest_day}`);
|
|
343
|
+
log(` ${BOLD}Busiest hour:${RESET} ${data.busiest_hour}:00`);
|
|
344
|
+
log('');
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const cmdDelete = withApi(async (api, id) => {
|
|
348
|
+
if (!id) error('Usage: npx @agent-analytics/cli delete <project-id>');
|
|
349
|
+
await api.deleteProject(id);
|
|
350
|
+
success(`Project ${id} deleted`);
|
|
351
|
+
});
|
|
272
352
|
|
|
273
353
|
function cmdDeleteAccount() {
|
|
274
354
|
heading('Delete Account');
|
|
@@ -278,42 +358,30 @@ function cmdDeleteAccount() {
|
|
|
278
358
|
log('');
|
|
279
359
|
}
|
|
280
360
|
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
const data = await api.getAccount();
|
|
304
|
-
heading('Account');
|
|
305
|
-
log(` ${BOLD}Email:${RESET} ${data.email}`);
|
|
306
|
-
log(` ${BOLD}GitHub:${RESET} ${data.github_login || 'N/A'}`);
|
|
307
|
-
log(` ${BOLD}Tier:${RESET} ${data.tier}`);
|
|
308
|
-
log(` ${BOLD}Projects:${RESET} ${data.projects_count}/${data.projects_limit}`);
|
|
309
|
-
if (data.tier === 'pro' && data.monthly_spend_cap_dollars != null) {
|
|
310
|
-
log(` ${BOLD}Spend cap:${RESET} $${data.monthly_spend_cap_dollars.toFixed(2)}/month`);
|
|
311
|
-
}
|
|
312
|
-
log('');
|
|
313
|
-
} catch (err) {
|
|
314
|
-
error(`Failed to get account: ${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`);
|
|
315
382
|
}
|
|
316
|
-
|
|
383
|
+
log('');
|
|
384
|
+
});
|
|
317
385
|
|
|
318
386
|
function showHelp() {
|
|
319
387
|
log(`
|
|
@@ -331,16 +399,25 @@ ${BOLD}COMMANDS${RESET}
|
|
|
331
399
|
${CYAN}stats${RESET} <name> Get stats for a project
|
|
332
400
|
${CYAN}events${RESET} <name> Get recent events
|
|
333
401
|
${CYAN}properties-received${RESET} <name> Show property keys per event
|
|
402
|
+
${CYAN}insights${RESET} <name> Period-over-period comparison
|
|
403
|
+
${CYAN}breakdown${RESET} <name> Property value distribution
|
|
404
|
+
${CYAN}pages${RESET} <name> Entry/exit page performance
|
|
405
|
+
${CYAN}sessions-dist${RESET} <name> Session duration distribution
|
|
406
|
+
${CYAN}heatmap${RESET} <name> Peak hours & busiest days
|
|
334
407
|
${CYAN}whoami${RESET} Show current account
|
|
335
408
|
${CYAN}revoke-key${RESET} Revoke and regenerate API key
|
|
336
409
|
${CYAN}delete-account${RESET} Delete your account (opens dashboard)
|
|
337
410
|
|
|
338
411
|
${BOLD}OPTIONS${RESET}
|
|
339
412
|
--days <N> Days of data (default: 7)
|
|
340
|
-
--limit <N> Max events to return (default: 100)
|
|
413
|
+
--limit <N> Max events/rows to return (default: 100)
|
|
341
414
|
--domain <url> Your site domain (required for create)
|
|
342
415
|
--since <date> ISO date for properties-received (default: 7 days)
|
|
343
416
|
--sample <N> Max events to sample (default: 5000)
|
|
417
|
+
--period <P> Period for insights: 1d, 7d, 14d, 30d, 90d (default: 7d)
|
|
418
|
+
--property <key> Property key for breakdown (required)
|
|
419
|
+
--event <name> Filter by event name (breakdown only)
|
|
420
|
+
--type <T> Page type: entry, exit, both (default: entry)
|
|
344
421
|
|
|
345
422
|
${BOLD}ENVIRONMENT${RESET}
|
|
346
423
|
AGENT_ANALYTICS_API_KEY API key (overrides config file)
|
|
@@ -388,20 +465,40 @@ try {
|
|
|
388
465
|
await cmdProjects();
|
|
389
466
|
break;
|
|
390
467
|
case 'stats':
|
|
391
|
-
await cmdStats(args[1], parseInt(getArg('--days') || '7'));
|
|
468
|
+
await cmdStats(args[1], parseInt(getArg('--days') || '7', 10));
|
|
392
469
|
break;
|
|
393
470
|
case 'events':
|
|
394
471
|
await cmdEvents(args[1], {
|
|
395
|
-
days: parseInt(getArg('--days') || '7'),
|
|
396
|
-
limit: parseInt(getArg('--limit') || '100'),
|
|
472
|
+
days: parseInt(getArg('--days') || '7', 10),
|
|
473
|
+
limit: parseInt(getArg('--limit') || '100', 10),
|
|
397
474
|
});
|
|
398
475
|
break;
|
|
399
476
|
case 'properties-received':
|
|
400
477
|
await cmdPropertiesReceived(args[1], {
|
|
401
478
|
since: getArg('--since'),
|
|
402
|
-
sample: getArg('--sample') ? parseInt(getArg('--sample')) : undefined,
|
|
479
|
+
sample: getArg('--sample') ? parseInt(getArg('--sample'), 10) : undefined,
|
|
403
480
|
});
|
|
404
481
|
break;
|
|
482
|
+
case 'insights':
|
|
483
|
+
await cmdInsights(args[1], getArg('--period') || '7d');
|
|
484
|
+
break;
|
|
485
|
+
case 'breakdown':
|
|
486
|
+
await cmdBreakdown(args[1], getArg('--property'), {
|
|
487
|
+
event: getArg('--event'),
|
|
488
|
+
limit: getArg('--limit') ? parseInt(getArg('--limit'), 10) : undefined,
|
|
489
|
+
});
|
|
490
|
+
break;
|
|
491
|
+
case 'pages':
|
|
492
|
+
await cmdPages(args[1], getArg('--type') || 'entry', {
|
|
493
|
+
limit: getArg('--limit') ? parseInt(getArg('--limit'), 10) : undefined,
|
|
494
|
+
});
|
|
495
|
+
break;
|
|
496
|
+
case 'sessions-dist':
|
|
497
|
+
await cmdSessionsDist(args[1]);
|
|
498
|
+
break;
|
|
499
|
+
case 'heatmap':
|
|
500
|
+
await cmdHeatmap(args[1]);
|
|
501
|
+
break;
|
|
405
502
|
case 'delete':
|
|
406
503
|
await cmdDelete(args[1]);
|
|
407
504
|
break;
|
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,23 +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
|
|
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
|
-
|
|
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
|
|
86
|
+
return this.request('GET', `/properties?${this._qs({ project, days })}`);
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
async getPropertiesReceived(project, { since, sample } = {}) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
return this.request('GET', `/properties/received?${this._qs({ project, since, sample })}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Analytics
|
|
94
|
+
async getBreakdown(project, { property, event, since, limit = 20 } = {}) {
|
|
95
|
+
return this.request('GET', `/breakdown?${this._qs({ project, property, event, since, limit })}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getInsights(project, { period = '7d' } = {}) {
|
|
99
|
+
return this.request('GET', `/insights?${this._qs({ project, period })}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getPages(project, { type = 'entry', since, limit = 20 } = {}) {
|
|
103
|
+
return this.request('GET', `/pages?${this._qs({ project, type, since, limit })}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async getSessionDistribution(project, { since } = {}) {
|
|
107
|
+
return this.request('GET', `/sessions/distribution?${this._qs({ project, since })}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async getHeatmap(project, { since } = {}) {
|
|
111
|
+
return this.request('GET', `/heatmap?${this._qs({ project, since })}`);
|
|
89
112
|
}
|
|
90
113
|
}
|