Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ delete <project> Delete a project by exact name or ID
# Analytics
all-sites Historical summary across all projects
bot-traffic <name> Filtered automated traffic by project or --all
stats <name> Overview: events, users, daily trends
stats <name> Overview: events, users, daily trends, countries
live [name] Real-time terminal dashboard across all projects
insights <name> Period-over-period comparison with trends
breakdown <name> --property path Top pages, referrers, UTM sources, countries
Expand All @@ -67,7 +67,7 @@ funnel <name> --steps-json <arg> Structured funnel analysis with raw-vs-strict d
funnel <name> --steps a,b,c Legacy comma-separated funnel steps
retention <name> Cohort retention: % of users who return
sessions-dist <name> Session duration distribution
events <name> Raw event log
events <name> Raw event log, including each event's country
sessions <name> Individual session records
query <name> Flexible analytics query (metrics, group_by, filters)
properties <name> Discover event names & property keys
Expand Down
13 changes: 11 additions & 2 deletions bin/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,15 @@ const cmdStats = withApi(async (api, project, days = 7) => {
}
}

if (Array.isArray(data.countries) && data.countries.length > 0) {
log('');
heading('Countries:');
for (const row of data.countries) {
log(` ${BOLD}${row.country || 'Unknown'}${RESET} ${row.count} events ${DIM}(${row.unique_users} users)${RESET}`);
}
log(` ${DIM}Users can appear in more than one country.${RESET}`);
}

// Monthly usage summary from response headers
const monthlyUsage = headers['x-monthly-usage'];
if (monthlyUsage) {
Expand Down Expand Up @@ -987,7 +996,7 @@ const cmdEvents = withApi(async (api, project, opts = {}) => {

for (const e of data.events) {
const time = new Date(e.timestamp).toLocaleString();
log(` ${DIM}${time}${RESET} ${BOLD}${e.event}${RESET} ${DIM}${e.user_id || ''}${RESET}`);
log(` ${DIM}${time}${RESET} ${BOLD}${e.event}${RESET} ${DIM}${e.user_id || ''}${RESET} ${DIM}country: ${e.country || 'Unknown'}${RESET}`);
if (e.properties) {
log(` ${DIM}${JSON.stringify(e.properties)}${RESET}`);
}
Expand Down Expand Up @@ -2344,7 +2353,7 @@ ${BOLD}SETUP${RESET}
${BOLD}ANALYTICS${RESET}
${CYAN}all-sites${RESET} Historical summary across all projects
${CYAN}bot-traffic${RESET} <name> Filtered automated traffic by project or --all
${CYAN}stats${RESET} <name> Overview: events, users, daily trends
${CYAN}stats${RESET} <name> Overview: events, users, daily trends, countries
${CYAN}live${RESET} [name] Real-time terminal dashboard across all projects
${CYAN}insights${RESET} <name> Period-over-period comparison with trends
${CYAN}breakdown${RESET} <name> Top pages, referrers, UTM sources, countries
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agent-analytics/cli",
"version": "0.5.34",
"version": "0.5.35",
"description": "Stop juggling dashboards. Let your agent do it. Analytics your AI agent can actually use — track, analyze, experiment, optimize.",
"bin": {
"agent-analytics": "bin/cli.mjs"
Expand Down
30 changes: 30 additions & 0 deletions test/stats-render.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,36 @@ function runCli(args, env) {
}

describe('stats command rendering', () => {
it('renders countries, raw event counts, unknown geography and users', async () => {
const { server, url } = await startMockServer({
totals: { total_events: 7, unique_users: 2 },
countries: [{ country: 'NL', count: 5, unique_users: 1 }, { country: null, count: 2, unique_users: 1 }],
});
try {
const result = await runCli(['stats', 'test-project'], { ...agentSessionEnv(), AGENT_ANALYTICS_URL: url });
const plain = stripAnsi(result.stdout);
assert.equal(result.code, 0);
assert.ok(plain.includes('Countries:'));
assert.ok(plain.includes('NL 5 events'));
assert.ok(plain.includes('Unknown 2 events'));
assert.ok(plain.includes('Users can appear in more than one country.'));
} finally { server.close(); }
});

it('shows event country outside the properties JSON', async () => {
const { server, url } = await startMockServer({ events: [
{ event: 'play', country: 'NL', timestamp: 1, properties: { stage: 2 } },
{ event: 'play', country: null, timestamp: 2, properties: null },
] });
try {
const result = await runCli(['events', 'test-project'], { ...agentSessionEnv(), AGENT_ANALYTICS_URL: url });
const plain = stripAnsi(result.stdout);
assert.equal(result.code, 0);
assert.ok(plain.includes('country: NL'));
assert.ok(plain.includes('country: Unknown'));
assert.ok(plain.includes('"stage":2'));
} finally { server.close(); }
});
it('renders timeSeries as daily chart', async () => {
const { server, url } = await startMockServer({
totals: { total_events: 100, unique_users: 10 },
Expand Down
Loading