Created
May 20, 2025 20:51
-
-
Save Sdy603/048068374f49fa02483f42f3ccd42435 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// import_cursor_usage.js | |
const fs = require('fs'); | |
const path = require('path'); | |
const dotenv = require('dotenv'); | |
const { Client } = require('pg'); | |
const axios = require('axios'); | |
const { parseISO, format, isAfter } = require('date-fns'); | |
dotenv.config(); | |
const validateEnvVars = () => { | |
const requiredVars = ["DX_DB_CONNECTION", "CURSOR_API_KEY"]; | |
requiredVars.forEach((key) => { | |
if (!process.env[key]) { | |
console.error(`Missing required environment variable: ${key}`); | |
process.exit(1); | |
} | |
}); | |
}; | |
validateEnvVars(); | |
const DX_DB_CONNECTION = process.env.DX_DB_CONNECTION; | |
const CURSOR_API_KEY = process.env.CURSOR_API_KEY; | |
const DRY_RUN = process.env.DRY_RUN === 'true'; | |
const CHUNK_SIZE = parseInt(process.env.CHUNK_SIZE || '100', 10); | |
const client = new Client({ | |
connectionString: DX_DB_CONNECTION, | |
ssl: { rejectUnauthorized: false } | |
}); | |
const REQUIRED_COLUMNS = [ | |
'date', 'source_id', 'email', 'is_active', | |
'chat_suggested_lines_added', 'chat_suggested_lines_deleted', | |
'chat_accepted_lines_added', 'chat_accepted_lines_deleted', | |
'chat_total_applies', 'chat_total_accepts', 'chat_total_rejects', | |
'chat_tabs_shown', 'tabs_accepted', 'edit_requests', 'ask_requests', | |
'agent_requests', 'cmd_k_usages', 'subscription_included_reqs', | |
'api_key_reqs', 'usage_based_reqs', 'bugbot_usages', | |
'most_used_model', 'most_used_apply_extension', | |
'most_used_tab_extension', 'client_version' | |
]; | |
async function fetchCursorUsage() { | |
const url = 'https://api.cursor.com/enterprise/usage'; | |
try { | |
const response = await axios.get(url, { | |
headers: { Authorization: `Bearer ${CURSOR_API_KEY}` }, | |
timeout: 10000 | |
}); | |
return response.data; | |
} catch (err) { | |
console.error('Failed to fetch Cursor usage data:', err.message); | |
process.exit(1); | |
} | |
} | |
async function getLastImportedDate() { | |
try { | |
const res = await client.query('SELECT MAX(date) AS last_imported_date FROM custom.cursor_daily_usages;'); | |
const maxDate = res.rows[0].last_imported_date; | |
return maxDate ? parseISO(maxDate.toISOString()) : null; | |
} catch (err) { | |
console.error('Failed to fetch last imported date:', err.message); | |
process.exit(1); | |
} | |
} | |
async function insertChunk(rows) { | |
if (rows.length === 0) return; | |
const placeholders = rows.map( | |
(_, i) => `(${REQUIRED_COLUMNS.map((_, j) => `$${i * REQUIRED_COLUMNS.length + j + 1}`).join(', ')})` | |
).join(', '); | |
const values = rows.flatMap(row => REQUIRED_COLUMNS.map(col => row[col] ?? null)); | |
const query = ` | |
INSERT INTO custom.cursor_daily_usages (${REQUIRED_COLUMNS.join(', ')}) | |
VALUES ${placeholders} | |
ON CONFLICT DO NOTHING; | |
`; | |
if (DRY_RUN) { | |
console.log(`Dry run: would insert ${rows.length} rows`); | |
} else { | |
await client.query(query, values); | |
console.log(`Inserted ${rows.length} rows`); | |
} | |
} | |
async function main() { | |
await client.connect(); | |
const usageData = await fetchCursorUsage(); | |
const lastImportedDate = await getLastImportedDate(); | |
const filteredRows = usageData | |
.map(entry => ({ | |
...entry, | |
date: format(parseISO(entry.date), 'yyyy-MM-dd') | |
})) | |
.filter(entry => { | |
const entryDate = parseISO(entry.date); | |
return !lastImportedDate || isAfter(entryDate, lastImportedDate); | |
}); | |
for (let i = 0; i < filteredRows.length; i += CHUNK_SIZE) { | |
await insertChunk(filteredRows.slice(i, i + CHUNK_SIZE)); | |
} | |
await client.end(); | |
console.log('Cursor usage data import complete.'); | |
} | |
main().catch(err => { | |
console.error('Fatal error:', err); | |
process.exit(1); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment