@agent-analytics/cli 0.1.10 → 0.1.11

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 +139 -0
  2. package/lib/api.mjs +21 -0
  3. package/package.json +1 -1
package/bin/cli.mjs CHANGED
@@ -18,6 +18,13 @@
18
18
  * npx @agent-analytics/cli init <name> — Alias for create
19
19
  * npx @agent-analytics/cli delete <id> — Delete a project
20
20
  * npx @agent-analytics/cli revoke-key — Revoke and regenerate API key
21
+ * npx @agent-analytics/cli experiments list <project> — List experiments
22
+ * npx @agent-analytics/cli experiments create <p> ... — Create experiment
23
+ * npx @agent-analytics/cli experiments get <id> — Get experiment with results
24
+ * npx @agent-analytics/cli experiments pause <id> — Pause experiment
25
+ * npx @agent-analytics/cli experiments resume <id> — Resume experiment
26
+ * npx @agent-analytics/cli experiments complete <id> — Complete experiment
27
+ * npx @agent-analytics/cli experiments delete <id> — Delete experiment
21
28
  * npx @agent-analytics/cli delete-account — Delete your account (opens dashboard)
22
29
  * npx @agent-analytics/cli whoami — Show current account
23
30
  */
@@ -383,6 +390,122 @@ const cmdWhoami = withApi(async (api) => {
383
390
  log('');
384
391
  });
385
392
 
393
+ // ==================== EXPERIMENTS ====================
394
+
395
+ const cmdExperiments = withApi(async (api, sub, ...rest) => {
396
+ if (!sub) error('Usage: npx @agent-analytics/cli experiments <list|create|get|pause|resume|complete|delete> ...');
397
+
398
+ switch (sub) {
399
+ case 'list': {
400
+ const project = rest[0];
401
+ if (!project) error('Usage: npx @agent-analytics/cli experiments list <project>');
402
+ const data = await api.listExperiments(project);
403
+ heading(`Experiments: ${project}`);
404
+ log('');
405
+ if (ifEmpty(data.experiments, 'experiments')) return;
406
+ for (const e of data.experiments) {
407
+ const status = e.status === 'active' ? `${GREEN}active${RESET}` : e.status === 'paused' ? `${YELLOW}paused${RESET}` : `${DIM}completed${RESET}`;
408
+ log(` ${BOLD}${e.name}${RESET} ${DIM}${e.id}${RESET} ${status} ${DIM}goal: ${e.goal_event}${RESET}`);
409
+ const variants = e.variants.map(v => `${v.key}(${v.weight}%)`).join(', ');
410
+ log(` variants: ${variants}`);
411
+ if (e.winner) log(` ${GREEN}winner: ${e.winner}${RESET}`);
412
+ }
413
+ log('');
414
+ break;
415
+ }
416
+ case 'create': {
417
+ const project = rest[0];
418
+ if (!project) error('Usage: npx @agent-analytics/cli experiments create <project> --name <name> --variants control,new_cta --goal <event> [--weights 60,40]');
419
+ const name = getArg('--name');
420
+ const variantsStr = getArg('--variants');
421
+ const goal = getArg('--goal');
422
+ if (!name || !variantsStr || !goal) error('Required: --name, --variants, --goal');
423
+ const variants = variantsStr.split(',').map(v => v.trim());
424
+ const weightsStr = getArg('--weights');
425
+ const weights = weightsStr ? weightsStr.split(',').map(w => parseInt(w.trim(), 10)) : undefined;
426
+ const data = await api.createExperiment(project, { name, variants, goal_event: goal, weights });
427
+ success(`Experiment created: ${BOLD}${data.name}${RESET} (${data.id})`);
428
+ log(` ${DIM}variants:${RESET} ${data.variants.map(v => `${v.key}(${v.weight}%)`).join(', ')}`);
429
+ log(` ${DIM}goal:${RESET} ${data.goal_event}`);
430
+ log('');
431
+ break;
432
+ }
433
+ case 'get': {
434
+ const id = rest[0];
435
+ if (!id) error('Usage: npx @agent-analytics/cli experiments get <id>');
436
+ const data = await api.getExperiment(id);
437
+ const status = data.status === 'active' ? `${GREEN}active${RESET}` : data.status === 'paused' ? `${YELLOW}paused${RESET}` : `${DIM}completed${RESET}`;
438
+ heading(`Experiment: ${data.name}`);
439
+ log(` ${DIM}id:${RESET} ${data.id} status: ${status} ${DIM}goal: ${data.goal_event}${RESET}`);
440
+ if (data.winner) log(` ${GREEN}winner: ${data.winner}${RESET}`);
441
+ log('');
442
+ if (data.results) {
443
+ heading('Results:');
444
+ for (const v of data.results.variants) {
445
+ const rate = (v.conversion_rate * 100).toFixed(1);
446
+ log(` ${BOLD}${v.key}${RESET} ${v.unique_users} users ${v.conversions} conversions ${CYAN}${rate}%${RESET}`);
447
+ }
448
+ log('');
449
+ if (data.results.probability_best) {
450
+ heading('Probability best:');
451
+ for (const [k, v] of Object.entries(data.results.probability_best)) {
452
+ const pct = (v * 100).toFixed(1);
453
+ log(` ${BOLD}${k}:${RESET} ${pct}%`);
454
+ }
455
+ }
456
+ if (data.results.lift) {
457
+ heading('Lift:');
458
+ for (const [k, v] of Object.entries(data.results.lift)) {
459
+ const pct = (v * 100).toFixed(1);
460
+ const arrow = v > 0 ? `${GREEN}+${pct}%` : v < 0 ? `${RED}${pct}%` : `${DIM}0%`;
461
+ log(` ${BOLD}${k}:${RESET} ${arrow}${RESET}`);
462
+ }
463
+ }
464
+ log('');
465
+ log(` ${BOLD}Sufficient data:${RESET} ${data.results.sufficient_data ? `${GREEN}yes` : `${YELLOW}no`}${RESET}`);
466
+ if (data.results.recommendation) {
467
+ log(` ${BOLD}Recommendation:${RESET} ${data.results.recommendation}`);
468
+ }
469
+ } else {
470
+ log(` ${DIM}No results available yet (need exposure + conversion events)${RESET}`);
471
+ }
472
+ log('');
473
+ break;
474
+ }
475
+ case 'pause': {
476
+ const id = rest[0];
477
+ if (!id) error('Usage: npx @agent-analytics/cli experiments pause <id>');
478
+ await api.updateExperiment(id, { status: 'paused' });
479
+ success(`Experiment ${id} paused`);
480
+ break;
481
+ }
482
+ case 'resume': {
483
+ const id = rest[0];
484
+ if (!id) error('Usage: npx @agent-analytics/cli experiments resume <id>');
485
+ await api.updateExperiment(id, { status: 'active' });
486
+ success(`Experiment ${id} resumed`);
487
+ break;
488
+ }
489
+ case 'complete': {
490
+ const id = rest[0];
491
+ if (!id) error('Usage: npx @agent-analytics/cli experiments complete <id> [--winner <variant>]');
492
+ const winner = getArg('--winner');
493
+ await api.updateExperiment(id, { status: 'completed', winner });
494
+ success(`Experiment ${id} completed${winner ? ` — winner: ${winner}` : ''}`);
495
+ break;
496
+ }
497
+ case 'delete': {
498
+ const id = rest[0];
499
+ if (!id) error('Usage: npx @agent-analytics/cli experiments delete <id>');
500
+ await api.deleteExperiment(id);
501
+ success(`Experiment ${id} deleted`);
502
+ break;
503
+ }
504
+ default:
505
+ error(`Unknown experiments subcommand: ${sub}. Use: list, create, get, pause, resume, complete, delete`);
506
+ }
507
+ });
508
+
386
509
  function showHelp() {
387
510
  log(`
388
511
  ${BOLD}agent-analytics${RESET} — Web analytics your AI agent can read
@@ -404,6 +527,14 @@ ${BOLD}COMMANDS${RESET}
404
527
  ${CYAN}pages${RESET} <name> Entry/exit page performance
405
528
  ${CYAN}sessions-dist${RESET} <name> Session duration distribution
406
529
  ${CYAN}heatmap${RESET} <name> Peak hours & busiest days
530
+ ${CYAN}experiments${RESET} <sub> A/B testing (pro only)
531
+ ${DIM}list <project>${RESET} List experiments
532
+ ${DIM}create <project>${RESET} Create experiment
533
+ ${DIM}get <id>${RESET} Get experiment with results
534
+ ${DIM}pause <id>${RESET} Pause experiment
535
+ ${DIM}resume <id>${RESET} Resume experiment
536
+ ${DIM}complete <id>${RESET} Complete experiment
537
+ ${DIM}delete <id>${RESET} Delete experiment
407
538
  ${CYAN}whoami${RESET} Show current account
408
539
  ${CYAN}revoke-key${RESET} Revoke and regenerate API key
409
540
  ${CYAN}delete-account${RESET} Delete your account (opens dashboard)
@@ -418,6 +549,11 @@ ${BOLD}OPTIONS${RESET}
418
549
  --property <key> Property key for breakdown (required)
419
550
  --event <name> Filter by event name (breakdown only)
420
551
  --type <T> Page type: entry, exit, both (default: entry)
552
+ --name <name> Experiment name (experiments create)
553
+ --variants <a,b> Comma-separated variant keys (experiments create)
554
+ --goal <event> Goal event name (experiments create)
555
+ --weights <50,50> Comma-separated weights (experiments create, optional)
556
+ --winner <variant> Winning variant (experiments complete, optional)
421
557
 
422
558
  ${BOLD}ENVIRONMENT${RESET}
423
559
  AGENT_ANALYTICS_API_KEY API key (overrides config file)
@@ -499,6 +635,9 @@ try {
499
635
  case 'heatmap':
500
636
  await cmdHeatmap(args[1]);
501
637
  break;
638
+ case 'experiments':
639
+ await cmdExperiments(args[1], args[2]);
640
+ break;
502
641
  case 'delete':
503
642
  await cmdDelete(args[1]);
504
643
  break;
package/lib/api.mjs CHANGED
@@ -110,4 +110,25 @@ export class AgentAnalyticsAPI {
110
110
  async getHeatmap(project, { since } = {}) {
111
111
  return this.request('GET', `/heatmap?${this._qs({ project, since })}`);
112
112
  }
113
+
114
+ // Experiments
115
+ async createExperiment(project, { name, variants, goal_event, weights }) {
116
+ return this.request('POST', '/experiments', { project, name, variants, goal_event, weights });
117
+ }
118
+
119
+ async listExperiments(project) {
120
+ return this.request('GET', `/experiments?${this._qs({ project })}`);
121
+ }
122
+
123
+ async getExperiment(id) {
124
+ return this.request('GET', `/experiments/${id}`);
125
+ }
126
+
127
+ async updateExperiment(id, { status, winner }) {
128
+ return this.request('PATCH', `/experiments/${id}`, { status, winner });
129
+ }
130
+
131
+ async deleteExperiment(id) {
132
+ return this.request('DELETE', `/experiments/${id}`);
133
+ }
113
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-analytics/cli",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
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"