@vibe-cafe/vibe-usage 0.9.8 → 0.9.9
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.
- package/package.json +1 -1
- package/src/parsers/cursor.js +32 -9
package/package.json
CHANGED
package/src/parsers/cursor.js
CHANGED
|
@@ -95,20 +95,34 @@ const FETCH_TIMEOUT_MS = 10_000;
|
|
|
95
95
|
async function fetchUsageCsv(token) {
|
|
96
96
|
const url = `${(process.env.CURSOR_WEB_BASE_URL?.trim() || 'https://cursor.com').replace(/\/+$/, '')}/api/dashboard/export-usage-events-csv?strategy=tokens`;
|
|
97
97
|
const sub = decodeJwtSub(token);
|
|
98
|
-
|
|
98
|
+
// The dashboard API authenticates via the WorkosCursorSessionToken cookie in
|
|
99
|
+
// `{sub}%3A%3A{jwt}` form (what the browser sends). Bearer and bare-token
|
|
100
|
+
// cookies now return 401, so they're kept only as last-resort fallbacks.
|
|
101
|
+
const userId = sub?.includes('|') ? sub.split('|').pop() : null;
|
|
102
|
+
const cookieValues = [
|
|
103
|
+
...(sub ? [`${sub}%3A%3A${token}`] : []),
|
|
104
|
+
...(userId ? [`${userId}%3A%3A${token}`] : []),
|
|
105
|
+
token,
|
|
106
|
+
];
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
// Browser-mimicking headers, matching what the dashboard sends (and what
|
|
109
|
+
// cursor-stats / cursor-price-tracking send) — Node's default UA is a
|
|
110
|
+
// common target for intermittent WAF blocks on cursor.com.
|
|
111
|
+
const baseHeaders = {
|
|
112
|
+
Accept: 'text/csv,*/*;q=0.8',
|
|
113
|
+
Origin: 'https://cursor.com',
|
|
114
|
+
Referer: 'https://cursor.com/dashboard?tab=usage',
|
|
115
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
116
|
+
};
|
|
117
|
+
const attempts = cookieValues.map(cv => ({ Cookie: `${SESSION_COOKIE}=${cv}` }));
|
|
118
|
+
attempts.push({ Authorization: `Bearer ${token}` });
|
|
105
119
|
|
|
106
120
|
const failures = [];
|
|
107
121
|
for (const headers of attempts) {
|
|
108
122
|
let resp;
|
|
109
123
|
try {
|
|
110
124
|
resp = await fetch(url, {
|
|
111
|
-
headers: {
|
|
125
|
+
headers: { ...baseHeaders, ...headers },
|
|
112
126
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
113
127
|
});
|
|
114
128
|
} catch (e) {
|
|
@@ -121,9 +135,18 @@ async function fetchUsageCsv(token) {
|
|
|
121
135
|
}
|
|
122
136
|
if (resp.ok) return await resp.text();
|
|
123
137
|
failures.push(`${resp.status} ${resp.statusText}`);
|
|
138
|
+
// Only auth rejections are worth retrying with different credentials.
|
|
139
|
+
// 429/5xx are transient server-side states — soft-skip like network errors
|
|
140
|
+
// instead of surfacing them as auth failures every daemon cycle.
|
|
141
|
+
if (resp.status !== 401 && resp.status !== 403) {
|
|
142
|
+
const err = new Error(`Cursor usage export skipped (HTTP ${resp.status} ${resp.statusText})`);
|
|
143
|
+
err.skip = true;
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
124
146
|
}
|
|
125
|
-
//
|
|
126
|
-
|
|
147
|
+
// Every auth combo rejected — the stored token no longer works. Surface an
|
|
148
|
+
// actionable message: re-signing in inside Cursor rewrites the token.
|
|
149
|
+
throw new Error(`Cursor session rejected (${failures.join('; ')}). Open Cursor and sign in again (Cursor Settings → Account), then re-run sync.`);
|
|
127
150
|
}
|
|
128
151
|
|
|
129
152
|
function parseCsv(text) {
|