Cursor.com Usage Tracker (Enhanced)

Tracks and displays usage statistics, payment cycles, and detailed model costs for Premium models on Cursor.com.

当前为 2025-05-26 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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
    });

})();