Skip to content

Commit 8d1b35d

Browse files
dannyshmueliclaude
andcommitted
Add --breakdown flag to funnel command
Supports --breakdown <property> and --breakdown-limit <N> for segmenting funnels by any property (e.g. country, experiment variant). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8ebadc1 commit 8d1b35d

3 files changed

Lines changed: 35 additions & 3 deletions

File tree

bin/cli.mjs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ const cmdHeatmap = withApi(async (api, project) => {
368368
});
369369

370370
const cmdFunnel = withApi(async (api, project, stepsStr, opts = {}) => {
371-
if (!project || !stepsStr) error('Usage: npx @agent-analytics/cli funnel <project-name> --steps "page_view,signup,purchase" [--window 168] [--since 30d] [--count-by user_id]');
371+
if (!project || !stepsStr) error('Usage: npx @agent-analytics/cli funnel <project-name> --steps "page_view,signup,purchase" [--window 168] [--since 30d] [--count-by user_id] [--breakdown country] [--breakdown-limit 10]');
372372

373373
const steps = stepsStr.split(',').map(s => ({ event: s.trim() }));
374374
if (steps.length < 2) error('At least 2 steps required');
@@ -378,6 +378,8 @@ const cmdFunnel = withApi(async (api, project, stepsStr, opts = {}) => {
378378
conversion_window_hours: opts.window ? parseInt(opts.window, 10) : undefined,
379379
since: opts.since,
380380
count_by: opts.count_by,
381+
breakdown: opts.breakdown || undefined,
382+
breakdown_limit: opts.breakdown_limit ? parseInt(opts.breakdown_limit, 10) : undefined,
381383
});
382384

383385
heading(`Funnel: ${project}`);
@@ -399,6 +401,26 @@ const cmdFunnel = withApi(async (api, project, stepsStr, opts = {}) => {
399401

400402
log('');
401403
log(` ${BOLD}Overall conversion:${RESET} ${Math.round(data.overall_conversion_rate * 100)}%`);
404+
405+
// Breakdown groups
406+
if (data.breakdowns && data.breakdowns.length > 0) {
407+
log('');
408+
heading(`Breakdown by ${opts.breakdown} (${data.breakdowns.length} groups)`);
409+
log('');
410+
for (const bd of data.breakdowns) {
411+
const label = bd.value ?? '(none)';
412+
const bdMax = Math.max(...bd.steps.map(s => s.users));
413+
log(` ${BOLD}${CYAN}${label}${RESET} ${DIM}${Math.round(bd.overall_conversion_rate * 100)}% overall${RESET}`);
414+
for (const step of bd.steps) {
415+
const barLen = bdMax > 0 ? Math.max(1, Math.round((step.users / bdMax) * 25)) : 1;
416+
const bar = '█'.repeat(barLen);
417+
const rate = step.step === 1 ? '' : ` ${DIM}${Math.round(step.conversion_rate * 100)}%${RESET}`;
418+
log(` ${step.step}. ${step.event.padEnd(18)} ${GREEN}${bar}${RESET} ${step.users}${rate}`);
419+
}
420+
log('');
421+
}
422+
}
423+
402424
log('');
403425
});
404426

@@ -1005,6 +1027,8 @@ try {
10051027
window: getArg('--window'),
10061028
since: getArg('--since'),
10071029
count_by: getArg('--count-by'),
1030+
breakdown: getArg('--breakdown'),
1031+
breakdown_limit: getArg('--breakdown-limit'),
10081032
});
10091033
break;
10101034
case 'retention':

lib/api.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ export class AgentAnalyticsAPI {
133133
}
134134

135135
// Funnels
136-
async getFunnel(project, { steps, conversion_window_hours, since, count_by } = {}) {
137-
return this.request('POST', '/funnel', { project, steps, conversion_window_hours, since, count_by });
136+
async getFunnel(project, { steps, conversion_window_hours, since, count_by, breakdown, breakdown_limit } = {}) {
137+
return this.request('POST', '/funnel', { project, steps, conversion_window_hours, since, count_by, breakdown, breakdown_limit });
138138
}
139139

140140
// Retention

skill/SKILL.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ npx @agent-analytics/cli properties my-site # Discover event names &
265265
npx @agent-analytics/cli properties-received my-site # Property keys per event type (sampled)
266266
npx @agent-analytics/cli query my-site --metrics event_count,unique_users --group-by date # Flexible query
267267
npx @agent-analytics/cli funnel my-site --steps "page_view,signup,purchase" # Funnel drop-off analysis
268+
npx @agent-analytics/cli funnel my-site --steps "page_view,signup" --breakdown country # Funnel segmented by country
268269
npx @agent-analytics/cli retention my-site --period week --cohorts 8 # Cohort retention analysis
269270

270271
# A/B experiments (pro)
@@ -291,6 +292,8 @@ npx @agent-analytics/cli revoke-key # Rotate API key
291292
- `--steps <csv>` — comma-separated event names, 2-8 steps max (`funnel`, required)
292293
- `--window <N>` — conversion window in hours (`funnel`, default: 168) or live time window in seconds (`live`, default: 60)
293294
- `--count-by <field>``user_id` or `session_id` (`funnel` only)
295+
- `--breakdown <key>` — segment funnel by a property (e.g. `country`, `variant`) — extracted from step 1 events (`funnel` only)
296+
- `--breakdown-limit <N>` — max breakdown groups, 1-50 (`funnel`, default: 10)
294297
- `--interval <N>` — live refresh in seconds (default: 5)
295298

296299
### The `live` command
@@ -314,6 +317,7 @@ Match the user's question to the right call(s):
314317
| "Give me a summary of all projects" | `live` or loop: `projects` then `insights` per project | Multi-project overview |
315318
| "Which CTA converts better?" | `experiments create` + implement + `experiments get <id>` | Full A/B test lifecycle |
316319
| "Where do users drop off?" | `funnel --steps "page_view,signup,purchase"` | Step-by-step conversion with drop-off rates |
320+
| "Which variant converts better through the funnel?" | `funnel --steps "page_view,signup" --breakdown variant` | Funnel segmented by experiment variant |
317321
| "Are users coming back?" | `retention --period week --cohorts 8` | Cohort retention: % returning per period |
318322

319323
For any "how is X doing" question, **always call `insights` first** — it's the single most useful endpoint. For real-time "who's on the site right now", use `live`.
@@ -429,6 +433,10 @@ API returns `steps: [{ step, event, users, conversion_rate, drop_off_rate, avg_t
429433
- `--window <hours>` — max time from step 1 to last step (default: 168 = 7 days)
430434
- `--since <days>` — lookback period, e.g. `30d` (default: 30d)
431435
- `--count-by <field>``user_id` (default) or `session_id`
436+
- `--breakdown <property>` — segment funnel by a property (e.g. `country`, `variant`). Property is extracted from step 1 events. Returns overall + per-group results.
437+
- `--breakdown-limit <N>` — max groups returned (default: 10, max: 50). Groups ordered by step 1 users descending.
438+
439+
**Breakdown use case — A/B experiments:** `funnel my-site --steps "page_view,signup" --breakdown variant` shows which experiment variant converts better through the funnel.
432440

433441
**API-only: per-step filters** — each step can have a `filters` array with `{ property, op, value }` (ops: `eq`, `neq`, `contains`). Example: filter step 1 to `path=/pricing` to see conversions from the pricing page specifically.
434442

0 commit comments

Comments
 (0)