Skip to content
Open
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
10 changes: 8 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,19 @@ export async function showDailyReport(
const grouped = groupByDay(events);
const stats = calculateDailyStats(grouped);

const startStr = startDate.toISOString().split('T')[0];
const endStr = endDate.toISOString().split('T')[0];
const periodLabel = options.startDate || options.endDate
? `${startStr} to ${endStr}`
: `Last ${days} days`;

if (outputJson) {
// Output JSON only - no table display
const jsonOutput = statsToJSON('daily', stats, events, { breakdown: options.breakdown, period: `last ${days} days` });
const jsonOutput = statsToJSON('daily', stats, events, { breakdown: options.breakdown, period: periodLabel });
console.log(jsonOutput);
} else {
// Display title box
logger.log(createTitleBox(`Cursor Usage Report - Daily (Last ${days} days)`));
logger.log(createTitleBox(`Cursor Usage Report - Daily (${periodLabel})`));

const tableData = stats.map((day) => {
const modelList = Array.from(day.models.entries())
Expand Down
108 changes: 60 additions & 48 deletions src/event-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,68 +34,80 @@ export interface EventsResponse {
}

/**
* Fetch usage events for a date range
* Fetch usage events for a date range (handles pagination)
*/
export async function fetchUsageEvents(
credentials: CursorCredentials,
startDate: Date,
endDate: Date,
pageSize: number = 100
): Promise<UsageEvent[]> {
const sessionToken = `${credentials.userId}::${credentials.accessToken}`;

const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': USER_AGENT,
Origin: 'https://cursor.com',
};

const cookieHeader = `WorkosCursorSessionToken=${encodeURIComponent(
sessionToken
)}`;

const startTimestamp = startDate.getTime().toString();
const endTimestamp = endDate.getTime().toString();

const allEvents: UsageEvent[] = [];
let page = 1;

try {
const sessionToken = `${credentials.userId}::${credentials.accessToken}`;

const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': USER_AGENT,
Origin: 'https://cursor.com',
};

const cookieHeader = `WorkosCursorSessionToken=${encodeURIComponent(
sessionToken
)}`;

// Convert dates to millisecond timestamps
const startTimestamp = startDate.getTime().toString();
const endTimestamp = endDate.getTime().toString();

const body = {
teamId: 0,
startDate: startTimestamp,
endDate: endTimestamp,
page: 1,
pageSize,
};

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);

const response = await fetch(CURSOR_API_URL_EVENTS, {
method: 'POST',
headers: {
...headers,
Cookie: cookieHeader,
},
body: JSON.stringify(body),
signal: controller.signal,
});
while (true) {
const body = {
teamId: 0,
startDate: startTimestamp,
endDate: endTimestamp,
page,
pageSize,
};

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);

const response = await fetch(CURSOR_API_URL_EVENTS, {
method: 'POST',
headers: {
...headers,
Cookie: cookieHeader,
},
body: JSON.stringify(body),
signal: controller.signal,
});

clearTimeout(timeoutId);
clearTimeout(timeoutId);

if (!response.ok) {
logger.error(
`Events API request failed with status ${response.status}: ${response.statusText}`
);
return [];
}
if (!response.ok) {
logger.error(
`Events API request failed with status ${response.status}: ${response.statusText}`
);
break;
}

const data = (await response.json()) as any;
const pageEvents = parseEvents(data);
allEvents.push(...pageEvents);

const data = (await response.json()) as any;
return parseEvents(data);
if (pageEvents.length < pageSize) {
break;
}

page++;
}
} catch (error) {
logger.error(`Error fetching usage events: ${String(error)}`);
return [];
}

return allEvents;
}

/**
Expand Down