@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.
- package/bin/cli.mjs +208 -290
- package/lib/api.mjs +16 -25
- 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
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
109
|
+
success(data.existing
|
|
110
|
+
? `Found existing project for ${BOLD}${domain}${RESET}!\n`
|
|
111
|
+
: `Project created for ${BOLD}${domain}${RESET}!\n`);
|
|
102
112
|
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
heading('1. Add this snippet to your site:');
|
|
114
|
+
log(`${CYAN}${data.snippet}${RESET}\n`);
|
|
105
115
|
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
heading('2. Your agent queries stats with:');
|
|
117
|
+
log(`${CYAN}${data.api_example}${RESET}\n`);
|
|
108
118
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
119
|
+
heading('Project token (for the snippet):');
|
|
120
|
+
log(`${YELLOW}${data.project_token}${RESET}\n`);
|
|
121
|
+
});
|
|
113
122
|
|
|
114
|
-
|
|
115
|
-
const
|
|
123
|
+
const cmdProjects = withApi(async (api) => {
|
|
124
|
+
const data = await api.listProjects();
|
|
125
|
+
const projects = data.projects;
|
|
116
126
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
148
|
+
const result = await api.getStats(project, days, { returnHeaders: true });
|
|
149
|
+
const data = result.data;
|
|
150
|
+
const headers = result.headers;
|
|
146
151
|
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
200
|
-
|
|
193
|
+
log('');
|
|
194
|
+
});
|
|
201
195
|
|
|
202
|
-
|
|
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
|
-
|
|
205
|
-
const data = await api.getEvents(project, opts);
|
|
199
|
+
const data = await api.getEvents(project, opts);
|
|
206
200
|
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
heading(`Events: ${project}`);
|
|
202
|
+
log('');
|
|
209
203
|
|
|
210
|
-
|
|
211
|
-
log(' No events yet.');
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
204
|
+
if (ifEmpty(data.events, 'events')) return;
|
|
214
205
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
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
|
|
219
|
+
const data = await api.getPropertiesReceived(project, opts);
|
|
232
220
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
heading(`Received Properties: ${project}`);
|
|
237
|
-
log('');
|
|
221
|
+
heading(`Received Properties: ${project}`);
|
|
222
|
+
log('');
|
|
238
223
|
|
|
239
|
-
|
|
240
|
-
log(' No properties found.');
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
224
|
+
if (ifEmpty(data.properties, 'properties')) return;
|
|
243
225
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
240
|
+
log(`\n${DIM}Sampled from last ${data.sample_size} events${RESET}`);
|
|
241
|
+
log('');
|
|
242
|
+
});
|
|
269
243
|
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
274
|
-
log('');
|
|
247
|
+
const data = await api.getInsights(project, { period });
|
|
275
248
|
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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
|
|
268
|
+
const data = await api.getBreakdown(project, { property, ...opts });
|
|
296
269
|
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
304
|
-
log(' No data found.');
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
273
|
+
if (ifEmpty(data.values, 'data')) return;
|
|
307
274
|
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
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
|
|
285
|
+
const data = await api.getPages(project, { type, ...opts });
|
|
322
286
|
|
|
323
|
-
|
|
324
|
-
|
|
287
|
+
heading(`Pages: ${project} (${type})`);
|
|
288
|
+
log('');
|
|
325
289
|
|
|
326
|
-
|
|
327
|
-
|
|
290
|
+
const pages = data.entry_pages || data.exit_pages || [];
|
|
291
|
+
if (ifEmpty(pages, 'page data')) return;
|
|
328
292
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
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
|
|
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
|
|
312
|
+
const data = await api.getSessionDistribution(project);
|
|
358
313
|
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
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
|
-
|
|
376
|
-
|
|
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
|
-
|
|
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
|
|
332
|
+
const data = await api.getHeatmap(project);
|
|
387
333
|
|
|
388
|
-
|
|
389
|
-
|
|
334
|
+
heading(`Heatmap: ${project}`);
|
|
335
|
+
log('');
|
|
390
336
|
|
|
391
|
-
|
|
392
|
-
log('');
|
|
337
|
+
if (ifEmpty(data.heatmap, 'heatmap data')) return;
|
|
393
338
|
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
347
|
+
const cmdDelete = withApi(async (api, id) => {
|
|
411
348
|
if (!id) error('Usage: npx @agent-analytics/cli delete <project-id>');
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
|
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
|
-
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|