Skip to content

Instantly share code, notes, and snippets.

@Sdy603
Created May 20, 2025 20:51
Show Gist options
  • Save Sdy603/048068374f49fa02483f42f3ccd42435 to your computer and use it in GitHub Desktop.
Save Sdy603/048068374f49fa02483f42f3ccd42435 to your computer and use it in GitHub Desktop.
// 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