Skip to content

Commit f6ae671

Browse files
dannyshmueliclaude
andcommitted
fix: CLI --days flag was silently ignored, daily chart never rendered
The CLI sent ?days=N to the API but the core only reads ?since=. Users always got 7 days of data regardless of --days value. Also, the stats daily chart checked data.daily but the API returns data.timeSeries, so the chart section was permanently dead code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 57b07db commit f6ae671

4 files changed

Lines changed: 90 additions & 9 deletions

File tree

bin/cli.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,10 @@ const cmdStats = withApi(async (api, project, days = 7) => {
188188
}
189189
}
190190

191-
if (data.daily && data.daily.length > 0) {
191+
if (data.timeSeries && data.timeSeries.length > 0) {
192192
log('');
193193
heading('Daily:');
194-
for (const d of data.daily) {
194+
for (const d of data.timeSeries) {
195195
const bar = '█'.repeat(Math.min(Math.ceil(d.total_events / 5), 40));
196196
log(` ${d.date} ${GREEN}${bar}${RESET} ${d.total_events} events`);
197197
}

lib/api.mjs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,18 @@ export class AgentAnalyticsAPI {
8383

8484
// Stats
8585
async getStats(project, days = 7, { returnHeaders = false } = {}) {
86-
return this.request('GET', `/stats?${this._qs({ project, days })}`, undefined, { returnHeaders });
86+
const since = `${days}d`;
87+
return this.request('GET', `/stats?${this._qs({ project, since })}`, undefined, { returnHeaders });
8788
}
8889

8990
async getEvents(project, { event, days = 7, limit = 100 } = {}) {
90-
return this.request('GET', `/events?${this._qs({ project, days, limit, event })}`);
91+
const since = `${days}d`;
92+
return this.request('GET', `/events?${this._qs({ project, since, limit, event })}`);
9193
}
9294

9395
async getProperties(project, days = 30) {
94-
return this.request('GET', `/properties?${this._qs({ project, days })}`);
96+
const since = `${days}d`;
97+
return this.request('GET', `/properties?${this._qs({ project, since })}`);
9598
}
9699

97100
async getPropertiesReceived(project, { since, sample } = {}) {

test/api.test.mjs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe('AgentAnalyticsAPI', () => {
139139

140140
it('getStats → GET /stats with query params', async () => {
141141
await api.getStats('my-site', 30);
142-
assert.equal(lastUrl, 'https://api.test/stats?project=my-site&days=30');
142+
assert.equal(lastUrl, 'https://api.test/stats?project=my-site&since=30d');
143143
});
144144

145145
it('getStats encodes project name', async () => {
@@ -149,17 +149,17 @@ describe('AgentAnalyticsAPI', () => {
149149

150150
it('getEvents → GET /events with defaults', async () => {
151151
await api.getEvents('my-site');
152-
assert.equal(lastUrl, 'https://api.test/events?project=my-site&days=7&limit=100');
152+
assert.equal(lastUrl, 'https://api.test/events?project=my-site&since=7d&limit=100');
153153
});
154154

155155
it('getEvents with event filter', async () => {
156156
await api.getEvents('my-site', { event: 'page_view', days: 14, limit: 50 });
157-
assert.equal(lastUrl, 'https://api.test/events?project=my-site&days=14&limit=50&event=page_view');
157+
assert.equal(lastUrl, 'https://api.test/events?project=my-site&since=14d&limit=50&event=page_view');
158158
});
159159

160160
it('getProperties → GET /properties', async () => {
161161
await api.getProperties('my-site', 60);
162-
assert.equal(lastUrl, 'https://api.test/properties?project=my-site&days=60');
162+
assert.equal(lastUrl, 'https://api.test/properties?project=my-site&since=60d');
163163
});
164164
});
165165

test/stats-render.test.mjs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { describe, it } from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import { execFile } from 'node:child_process';
4+
import { createServer } from 'node:http';
5+
import { fileURLToPath } from 'node:url';
6+
import { dirname, join } from 'node:path';
7+
8+
const __dirname = dirname(fileURLToPath(import.meta.url));
9+
const CLI = join(__dirname, '..', 'bin', 'cli.mjs');
10+
11+
function startMockServer(responseData) {
12+
return new Promise((resolve) => {
13+
const server = createServer((req, res) => {
14+
res.writeHead(200, { 'Content-Type': 'application/json' });
15+
res.end(JSON.stringify(responseData));
16+
});
17+
server.listen(0, '127.0.0.1', () => {
18+
const port = server.address().port;
19+
resolve({ server, url: `http://127.0.0.1:${port}` });
20+
});
21+
});
22+
}
23+
24+
function runCli(args, env) {
25+
return new Promise((resolve) => {
26+
execFile('node', [CLI, ...args], { timeout: 5000, env: { ...process.env, ...env } }, (err, stdout, stderr) => {
27+
resolve({ code: err ? err.code : 0, stdout, stderr });
28+
});
29+
});
30+
}
31+
32+
describe('stats command rendering', () => {
33+
it('renders timeSeries as daily chart', async () => {
34+
const { server, url } = await startMockServer({
35+
totals: { total_events: 100, unique_users: 10 },
36+
events: [{ event: 'page_view', count: 100, unique_users: 10 }],
37+
timeSeries: [
38+
{ date: '2026-02-25', total_events: 40, unique_users: 5 },
39+
{ date: '2026-02-26', total_events: 60, unique_users: 8 },
40+
],
41+
});
42+
43+
try {
44+
const { stdout } = await runCli(['stats', 'test-project'], {
45+
AGENT_ANALYTICS_API_KEY: 'aak_test',
46+
AGENT_ANALYTICS_URL: url,
47+
});
48+
49+
// Should render the daily chart section
50+
assert.ok(stdout.includes('Daily:'), 'should show Daily heading');
51+
assert.ok(stdout.includes('2026-02-25'), 'should show first date');
52+
assert.ok(stdout.includes('2026-02-26'), 'should show second date');
53+
assert.ok(stdout.includes('40 events'), 'should show first day count');
54+
assert.ok(stdout.includes('60 events'), 'should show second day count');
55+
} finally {
56+
server.close();
57+
}
58+
});
59+
60+
it('does not crash when timeSeries is absent', async () => {
61+
const { server, url } = await startMockServer({
62+
totals: { total_events: 5, unique_users: 1 },
63+
events: [],
64+
});
65+
66+
try {
67+
const { code, stdout } = await runCli(['stats', 'test-project'], {
68+
AGENT_ANALYTICS_API_KEY: 'aak_test',
69+
AGENT_ANALYTICS_URL: url,
70+
});
71+
72+
assert.equal(code, 0);
73+
assert.ok(!stdout.includes('Daily:'), 'should not show Daily when no timeSeries');
74+
} finally {
75+
server.close();
76+
}
77+
});
78+
});

0 commit comments

Comments
 (0)