- // ==UserScript==
- // @name Cursor.com Usage Tracker (Enhanced)
- // @author monnef, Sonnet 3.5 (via Perplexity and Cursor), some help from Cursor Tab and Cursor Small, NoahBPeterson, Sonnet 3.7, Gemini
- // @namespace http://monnef.eu
- // @version 0.5.2
- // @description Tracks and displays usage statistics, payment cycles, and detailed model costs for Premium models on Cursor.com.
- // @match https://www.cursor.com/settings
- // @grant GM_getValue
- // @grant GM_setValue
- // @require https://code.jquery.com/jquery-3.6.0.min.js
- // @license AGPL-3.0
- // @icon https://www.cursor.com/favicon-48x48.png
- // ==/UserScript==
-
- (function () {
- 'use strict';
-
- const $ = jQuery.noConflict();
-
- const $c = (cls, parent) => $(`.${cls}`, parent);
- const $i = (id, parent) => $(`#${id}`, parent);
-
- $.fn.nthParent = function (n) {
- return this.parents().eq(n - 1);
- };
-
- const log = (...messages) => {
- console.log(`[UsageTracker]`, ...messages);
- };
-
- const error = (...messages) => {
- console.error(`[UsageTracker]`, ...messages);
- };
-
- const debug = (...messages) => {
- console.debug(`[UsageTracker Debug]`, ...messages);
- };
-
- const genCssId = name => `ut-${name}`;
-
- // --- CSS Class Names ---
- const sigCls = genCssId('sig');
- const buttonCls = genCssId('button');
- const buttonWhiteCls = genCssId('button-white');
- const buttonDarkCls = genCssId('button-dark');
- const mainCaptionCls = genCssId('main-caption');
- const modalCls = genCssId('modal');
- const modalContentCls = genCssId('modal-content');
- const modalCloseCls = genCssId('modal-close');
- const copyButtonCls = genCssId('copy-button');
- const inputCls = genCssId('input');
- const inputWithButtonCls = genCssId('input-with-button');
- const errorMessageCls = genCssId('error-message');
- const settingsModalCls = genCssId('settings-modal');
- const hrCls = genCssId('hr');
- const debugContainerCls = genCssId('debug-container');
- const hSpaceSmCls = genCssId('h-space-sm');
- const hSpaceMdCls = genCssId('h-space-md');
- const hSpaceLgCls = genCssId('h-space-lg');
- const flexCenterCls = genCssId('flex-center');
- const flexRightCls = genCssId('flex-right');
- const flexBetweenCls = genCssId('flex-between');
- const multiBarCls = genCssId('multi-bar');
- const barSegmentCls = genCssId('bar-segment');
- const tooltipCls = genCssId('tooltip');
- const statsContainerCls = genCssId('stats-container');
- const statItemCls = genCssId('stat-item');
-
- const colors = {
- cursor: {
- blue: '#3864f6',
- blueDarker: '#2e53cc',
- lightGray: '#e5e7eb',
- gray: '#a7a9ac',
- grayDark: '#333333',
- green: '#63a11a', // Original green
- },
- segments: [ // A palette for the multi-segment bar
- '#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5',
- '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50',
- '#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107', '#FF9800',
- '#FF5722', '#795548', '#9E9E9E', '#607D8B'
- ]
- };
-
- const styles = `
- .${hSpaceSmCls} { height: 5px; }
- .${hSpaceMdCls} { height: 10px; }
- .${hSpaceLgCls} { height: 20px; }
- .${flexCenterCls} { display: flex; justify-content: center; align-items: center; }
- .${flexRightCls} { display: flex; justify-content: flex-end; align-items: center; }
- .${flexBetweenCls} { display: flex; justify-content: space-between; align-items: center; }
- .${sigCls} { font-size: 0.75rem; color: ${colors.cursor.gray}; margin-left: 0.75rem; opacity: 0.2; transition: opacity 0.1s ease-in-out; }
- .${sigCls}:hover { opacity: 1; }
- .${buttonCls}, .${buttonWhiteCls}, .${buttonDarkCls} { background-color: ${colors.cursor.blue}; color: white; font-size: 14px; border: none; border-radius: 4px; cursor: pointer; padding: 4.25px 8px; font-weight: 400; }
- .${buttonCls}:hover { background-color: ${colors.cursor.blueDarker}; }
- .${buttonWhiteCls} { background-color: white; color: black; border: 1px solid ${colors.cursor.lightGray}; padding: 3px 8px; }
- .${buttonWhiteCls}:hover { background-color: ${colors.cursor.lightGray}; }
- .${buttonDarkCls} { background-color: black; color: white; border: 1px solid black; padding: 3px 8px; }
- .${buttonDarkCls}:hover { background-color: white; color: black; }
- .${modalCls} { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0, 0, 0, 0.4); backdrop-filter: blur(5px) contrast(0.5); }
- .${modalContentCls} { background-color: black; color: white; margin: 15% auto; padding: 15px 20px; width: 600px; border-radius: 4px; position: relative; }
- .${modalCloseCls} { color: white; position: absolute; top: 0px; right: 10px; font-size: 25px; font-weight: bold; cursor: pointer; }
- .${modalCloseCls}:hover { color: ${colors.cursor.lightGray}; }
- .${copyButtonCls} { margin-left: 10px; width: 5em; }
- .${modalContentCls} h2 { margin-bottom: 20px; }
- .${modalContentCls} hr { border: 0; height: 1px; background-color: ${colors.cursor.grayDark}; margin: 10px 0; }
- .${inputCls} { background-color: white; color: black; border: 1px solid ${colors.cursor.lightGray}; padding: 5px; width: 100%; border-radius: 4px; font-size: 14px; }
- .${inputWithButtonCls} { width: calc(100% - 5em - 10px); }
- .${errorMessageCls} { color: #ff4d4f; font-size: 14px; margin-top: 5px; }
- .${hrCls} { border: 0; height: 1px; background-color: #333333; /* Darker HR */ margin: 15px 0; }
- .${debugContainerCls} { background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 4px; padding: 10px; margin-top: 10px; font-family: monospace; font-size: 12px; overflow-wrap: break-word; max-height: 200px; overflow-y: auto; }
- .${statsContainerCls} { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 20px; margin: 15px 0; padding: 15px; background-color: #1a1a1a; border-radius: 8px; }
- .${statItemCls} { font-size: 14px; }
- .${statItemCls} .label { color: ${colors.cursor.gray}; }
- .${statItemCls} .value { color: white; font-weight: bold; }
- .${multiBarCls} { display: flex; width: 100%; height: 15px; background-color: ${colors.cursor.grayDark}; border-radius: 4px; /* REMOVED: overflow: hidden; */ margin: 10px 0; }
- .${barSegmentCls} { height: 100%; position: relative; transition: filter 0.2s ease-in-out; }
- .${barSegmentCls}:hover { filter: brightness(1.2); }
- .${barSegmentCls} .${tooltipCls} {
- visibility: hidden;
- width: max-content;
- background-color: black;
- color: #fff;
- text-align: center;
- border-radius: 6px;
- padding: 5px 10px;
- position: absolute;
- z-index: 50; /* INCREASED Z-INDEX */
- bottom: 150%; /* Adjusted slightly up */
- left: 50%;
- transform: translateX(-50%);
- opacity: 0;
- transition: opacity 0.3s;
- border: 1px solid ${colors.cursor.gray};
- font-size: 12px;
- pointer-events: none; /* Prevent tooltip from interfering with hover */
- }
- .${barSegmentCls}:hover .${tooltipCls} {
- visibility: visible;
- opacity: 1;
- }
- .${barSegmentCls} .${tooltipCls}::after {
- content: "";
- position: absolute;
- top: 100%;
- left: 50%;
- margin-left: -5px;
- border-width: 5px;
- border-style: solid;
- border-color: black transparent transparent transparent;
- }
- `;
-
- const genHr = () => $('<hr>').addClass(hrCls);
-
- const getUsageCard = () => {
- const usageHeading = $('h2:contains("Usage")');
- if (usageHeading.length > 0) {
- const card = usageHeading.closest('.rounded-2xl, .rounded-3xl');
- debug(`Found Usage card via h2: ${card.length > 0}`);
- return card.length > 0 ? card : null;
- }
- debug('Usage card not found.');
- return null;
- };
-
- // --- Data Parsing Functions ---
-
- /**
- * Finds and parses the "Recent Usage Events" table.
- * @returns {{ modelUsage: Record<string, { count: number, cost: number }>, totalPaidRequests: number, totalRequests: number }}
- */
- const parseUsageEventsTable = () => {
- const modelUsage = {};
- let totalPaidRequests = 0;
- let totalRequests = 0;
-
- const table = $('p:contains("Recent Usage Events")').closest('div').find('table tbody tr');
- debug(`Found ${table.length} rows in Recent Usage Events table.`);
-
- if (table.length === 0) {
- error("Could not find 'Recent Usage Events' table.");
- return { modelUsage, totalPaidRequests, totalRequests };
- }
-
- table.each((_, row) => {
- const $row = $(row);
- const model = $row.find('td:eq(1)').text().trim();
- const status = $row.find('td:eq(2)').text().trim();
- const requestsStr = $row.find('td:eq(3)').text().trim();
- const requests = parseFloat(requestsStr) || 0;
-
- if (status !== 'Errored, Not Charged' && model) { // Ensure model name exists
- totalRequests += requests;
- if (!modelUsage[model]) {
- modelUsage[model] = { count: 0, cost: 0 }; // Initialize cost here
- }
- modelUsage[model].count += requests;
-
- if (status === 'Usage-based') {
- totalPaidRequests += requests;
- }
- }
- });
-
- debug('Parsed Usage Events:', { modelUsage, totalPaidRequests, totalRequests });
- return { modelUsage, totalPaidRequests, totalRequests };
- };
-
- /**
- * Finds and parses the "Current Usage" cost summary table.
- * @param {Record<string, { count: number, cost: number }>} modelUsage - The object to update with costs.
- * @returns {{ totalCost: number }}
- */
- const parseCurrentUsageCosts = (modelUsage) => {
- let totalCost = 0;
- const costTable = $('p:contains("Current Usage"):last').closest('div').find('table tbody tr'); // Find the *cost* table
- debug(`Found ${costTable.length} rows in Current Usage (Cost) table.`);
-
- if (costTable.length === 0) {
- error("Could not find 'Current Usage' (Cost) table.");
- return { totalCost };
- }
-
- costTable.each((_, row) => {
- const $row = $(row);
- const description = $row.find('td:eq(0)').text().trim().toLowerCase();
- const costStr = $row.find('td:eq(1)').text().trim().replace('$', '');
- const cost = parseFloat(costStr) || 0;
-
- totalCost += cost; // Sum all costs, including negative ones (payments)
-
- let foundModel = false;
- for (const model in modelUsage) {
- if (description.includes(model.toLowerCase())) {
- modelUsage[model].cost += cost; // Add cost to the model
- foundModel = true;
- }
- }
- if (!foundModel && (description.includes('extra') || description.includes('premium')) && cost > 0 ) {
- if (!modelUsage['Extra/Other Premium']) {
- modelUsage['Extra/Other Premium'] = { count: 0, cost: 0 };
- }
- modelUsage['Extra/Other Premium'].cost += cost;
- foundModel = true;
- }
- if (!foundModel && cost > 0) {
- if (!modelUsage['Other Costs']) {
- modelUsage['Other Costs'] = { count: 0, cost: 0 };
- }
- modelUsage['Other Costs'].cost += cost;
- }
- });
-
- // Ensure all models in modelUsage have a cost, even if 0
- for (const model in modelUsage) {
- modelUsage[model].cost = modelUsage[model].cost || 0;
- }
-
-
- debug('Parsed Costs & Updated Model Usage:', { modelUsage, totalCost });
- return { totalCost }; // Keep returning totalCost in case we need it later, even if not displayed.
- };
-
- /**
- * Extracts the basic "X / Y" usage if available.
- * @returns {{ used: number, total: number }}
- */
- const getBaseUsageData = () => {
- debug('Attempting to find base usage data...');
- const premiumLabel = $('span:contains("Premium models")').first();
- if (premiumLabel.length === 0) {
- debug('Base Premium models label not found.');
- return {};
- }
-
- const usageSpan = premiumLabel.siblings('span').last();
- const usageText = usageSpan.text();
- debug(`Found base usage text: "${usageText}"`);
-
- const regex = /(\d+) \/ (\d+)/;
- const matches = usageText.match(regex);
- if (matches && matches.length === 3) {
- const used = parseInt(matches[1], 10);
- const total = parseInt(matches[2], 10);
- debug(`Parsed base values - Used: ${used}, Total: ${total}`);
- return { used, total };
- } else {
- debug('Regex did not match the base usage text.');
- return {};
- }
- };
-
-
- // --- Display Functions ---
-
- /**
- * Creates a multi-segment progress bar.
- * @param {Record<string, { count: number, cost: number }>} modelUsage
- * @returns {JQuery<HTMLElement>}
- */
- const createMultiSegmentProgressBar = (modelUsage) => {
- const barContainer = $('<div>').addClass(multiBarCls);
- const totalRequests = Object.values(modelUsage).reduce((sum, model) => sum + (model.count || 0), 0);
-
- if (totalRequests === 0) {
- return barContainer.text('No usage data for bar.').css({ height: 'auto', padding: '5px' });
- }
-
- let colorIndex = 0;
- // Sort models by count descending for better visualization
- const sortedModels = Object.entries(modelUsage)
- .filter(([_, data]) => data.count > 0)
- .sort(([, a], [, b]) => b.count - a.count);
-
- for (const [model, data] of sortedModels) {
- const percentage = (data.count / totalRequests) * 100;
- const cost = data.cost.toFixed(2);
- const color = colors.segments[colorIndex % colors.segments.length];
-
- const tooltipText = `${model}: ${data.count.toFixed(1)} reqs ($${cost})`;
- const tooltip = $('<span>').addClass(tooltipCls).text(tooltipText);
-
- const segment = $('<div>')
- .addClass(barSegmentCls)
- .css({
- width: `${percentage}%`,
- backgroundColor: color,
- })
- .append(tooltip);
-
- barContainer.append(segment);
- colorIndex++;
- }
- return barContainer;
- };
-
-
- /**
- * Displays all the new tracker data.
- * @param {number} paymentDay
- */
- const displayEnhancedTrackerData = (paymentDay) => {
- const tracker = $c(mainCaptionCls);
- if (tracker.length === 0) {
- error('Main caption not found for displaying enhanced data.');
- return false; // Indicate failure
- }
- // Clear previous tracker data before parsing again
- tracker.siblings(`.${genCssId('enhanced-tracker')}`).remove();
-
- const { modelUsage, totalPaidRequests, totalRequests } = parseUsageEventsTable();
- parseCurrentUsageCosts(modelUsage); // Call this to populate costs in modelUsage
- const baseUsage = getBaseUsageData();
-
- const container = $('<div>').addClass(genCssId('enhanced-tracker'));
- const statsContainer = $('<div>').addClass(statsContainerCls);
-
- const addStat = (label, value) => {
- statsContainer.append(
- $('<div>').addClass(statItemCls).append(
- $('<span>').addClass('label').text(`${label}: `),
- $('<span>').addClass('value').text(value)
- )
- );
- }
-
- addStat('Total Usage-Based Requests (Weighted)', totalPaidRequests.toFixed(1));
- addStat('Grand Total Requests (Weighted)', (totalRequests + baseUsage.total).toFixed(1));
-
- container.append(
- statsContainer,
- $('<p>').css({ fontSize: '13px', color: colors.cursor.gray, marginTop: '10px' }).text('Model Usage Breakdown (Weighted, Hover for details):'),
- createMultiSegmentProgressBar(modelUsage)
- );
-
- tracker.after(container);
- debug('Enhanced tracker data displayed.');
- return true; // Indicate success
- };
-
-
- // --- Original Script Functions (Mostly Unchanged or Slightly Modified) ---
-
- const decorateUsageCard = () => {
- if ($c(mainCaptionCls).length > 0) {
- return true;
- }
- const usageHeading = $('h2:contains("Usage")');
- if (usageHeading.length > 0) {
- const caption = $('<div>')
- .addClass('font-medium gt-standard-mono text-xl/[1.375rem] font-semibold -tracking-4 md:text-2xl/[1.875rem]')
- .addClass(mainCaptionCls)
- .text('Usage Tracker');
-
- usageHeading.after(genHr(), caption);
- addSettingsButton(caption); // Add settings button here
- debug("Added tracker after Usage heading");
- return true;
- }
- return false;
- };
-
- const addUsageTracker = () => {
- const paymentDay = GM_getValue('paymentDay');
- return displayEnhancedTrackerData(paymentDay);
- };
-
- const calculateDaysPassed = ({ today, paymentDay, disableLog = false }) => {
- if (!paymentDay) return null;
- const currentMonth = today.getMonth();
- const currentYear = today.getFullYear();
- const lastPaymentDate = new Date(currentYear, currentMonth, paymentDay);
- if (today < lastPaymentDate) {
- lastPaymentDate.setMonth(lastPaymentDate.getMonth() - 1);
- }
- const daysPassed = Math.floor((today - lastPaymentDate) / (1000 * 60 * 60 * 24));
- const nextPaymentDate = new Date(lastPaymentDate);
- nextPaymentDate.setMonth(nextPaymentDate.getMonth() + 1);
- const totalDays = Math.floor((nextPaymentDate - lastPaymentDate) / (1000 * 60 * 60 * 24));
- const res = { daysPassed, totalDays, progress: daysPassed / totalDays };
- if (!disableLog) {
- debug(`Calculated days - Passed: ${res.daysPassed}, Total: ${res.totalDays}, Progress: ${res.progress}`);
- }
- return res;
- };
-
- const createModal = ({ className, title, content }) => {
- const modal = $('<div>').addClass(modalCls).addClass(className);
- const modalContent = $('<div>').addClass(modalContentCls);
- const closeButton = $('<span>').addClass(modalCloseCls).text('×');
- const titleElement = $('<h1>')
- .addClass('text-4xl gt-standard-mono font-medium')
- .text(title);
- modalContent.append(
- closeButton,
- titleElement,
- $('<div>').addClass(hSpaceMdCls),
- content
- );
- modal.append(modalContent);
- closeButton.click(() => modal.hide());
- $(window).click(event => {
- if (event.target === modal[0]) {
- modal.hide();
- }
- });
- return modal;
- };
-
- const createSettingsModal = () => {
- const subtitle = $('<p>').text('Enter the day of the month when you are billed (1-31):');
- const input = $('<input>')
- .addClass(inputCls)
- .attr('type', 'number')
- .attr('min', '1')
- .attr('max', '31')
- .val(GM_getValue('paymentDay') || '');
- const tip = $('<p>')
- .addClass('text-sm text-gray-500 mt-1')
- .text('You can find your billing date via the "Manage Subscription" button on the left.');
- const errorMessage = $('<p>').addClass(errorMessageCls).hide();
- const saveAndReload = () => {
- const newPaymentDay = parseInt(input.val(), 10);
- if (newPaymentDay && newPaymentDay >= 1 && newPaymentDay <= 31) {
- GM_setValue('paymentDay', newPaymentDay);
- log(`Payment day has been set to: ${newPaymentDay}`);
- $c(settingsModalCls).hide();
- location.reload();
- } else {
- errorMessage.text('Invalid input. Please enter a number between 1 and 31.').show();
- }
- };
- const saveButton = $('<button>')
- .addClass(buttonCls)
- .text('Save & Reload')
- .click(saveAndReload);
- input.on('keypress', (e) => {
- if (e.which === 13) saveAndReload();
- });
- const madeByText = $('<p>').html( // Updated authors
- 'Made with ❤️ by monnef, Sonnet 3.5 & Gemini'
- );
- const content = $('<div>').append(
- subtitle,
- $('<div>').addClass(hSpaceSmCls),
- input,
- tip,
- errorMessage,
- $('<div>').addClass(hSpaceLgCls),
- $('<div>').addClass(flexBetweenCls).append(madeByText, saveButton)
- );
- return createModal({
- className: settingsModalCls,
- title: 'Usage Tracker Settings',
- content: content
- });
- };
-
- const addSettingsButton = (mainCaption) => {
- const settingsButton = $('<button>')
- .css({
- position: 'absolute',
- top: '-10px',
- right: '0px',
- height: '29.4px',
- width: '29.4px',
- padding: '0px',
- filter: 'invert(1)',
- })
- .addClass(buttonWhiteCls)
- .attr('title', 'Usage Tracker settings')
- .append($(`<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" /><circle cx="12" cy="12" r="3" /></svg>`).css({ display: 'inline-block', verticalAlign: 'text-bottom' }));
-
- settingsButton.click(() => {
- log('Usage Tracker settings button clicked.');
- $c(settingsModalCls).show();
- $c(settingsModalCls).find(`.${inputCls}`).focus();
- });
-
- const buttonWrapper = $('<div>').css({ position: 'relative', height: '0px' });
- buttonWrapper.append(settingsButton);
-
- if (mainCaption.length > 0) {
- mainCaption.prepend(buttonWrapper);
- log('Settings button wrapper added to the page');
- } else {
- log('Main caption not found, settings button not added');
- }
- };
-
-
- // --- Main Execution Logic ---
-
- const state = {
- addingUsageTrackerSucceeded: false,
- addingUsageTrackerAttempts: 0,
- };
-
- const ATTEMPTS_LIMIT = 15;
- const ATTEMPTS_INTERVAL = 500;
- const ATTEMPTS_MAX_DELAY = 4000;
-
- const main = () => {
- state.addingUsageTrackerAttempts++;
- log(`Attempt ${state.addingUsageTrackerAttempts}...`);
-
- const scheduleNextAttempt = () => {
- if (!state.addingUsageTrackerSucceeded && state.addingUsageTrackerAttempts < ATTEMPTS_LIMIT) {
- const delay = Math.min(ATTEMPTS_INTERVAL * (state.addingUsageTrackerAttempts), ATTEMPTS_MAX_DELAY);
- log(`Attempt ${state.addingUsageTrackerAttempts} failed or incomplete. Retrying in ${delay}ms...`);
- setTimeout(main, delay);
- } else if (state.addingUsageTrackerSucceeded) {
- log(`Attempt ${state.addingUsageTrackerAttempts} succeeded.`);
- } else {
- error(`All ${ATTEMPTS_LIMIT} attempts failed. Could not add Usage Tracker. Check selectors or page structure.`);
- }
- };
-
- const decorationOkay = decorateUsageCard();
- if (!decorationOkay) {
- debug('Decoration failed. Will retry.');
- scheduleNextAttempt();
- return;
- }
-
- const usageEventsTable = $('p:contains("Recent Usage Events")').closest('div').find('table tbody tr');
- if (usageEventsTable.length === 0) {
- debug("'Recent Usage Events' table not found yet. Will retry.");
- scheduleNextAttempt();
- return;
- }
- debug(`Found ${usageEventsTable.length} usage events.`);
-
-
- try {
- state.addingUsageTrackerSucceeded = addUsageTracker();
- } catch (e) {
- error("Error during addUsageTracker:", e);
- state.addingUsageTrackerSucceeded = false;
- }
-
- scheduleNextAttempt();
- };
-
- $(document).ready(() => {
- log('Script started');
- unsafeWindow.ut = { // For manual debugging
- jq: $,
- resetSettings: () => {
- GM_setValue('paymentDay', undefined);
- location.reload();
- },
- parseEvents: parseUsageEventsTable,
- parseCosts: parseCurrentUsageCosts,
- getBase: getBaseUsageData,
- };
- $('head').append($('<style>').text(styles));
- $('body').append(createSettingsModal());
- setTimeout(main, ATTEMPTS_INTERVAL); // Initial delay
- });
-
- })();