GigaProjects

← Back to claude-usage-gnome

extension.js

import St from 'gi://St';
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import Clutter from 'gi://Clutter';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';

const INTERVAL_SECONDS = 60; // refresh every 1 minute

export default class ClaudeUsageExtension extends Extension {
    enable() {
        this._refreshCancellable = null;
        this._button = new PanelMenu.Button(0.0, 'Claude Usage', true);
        this._label = new St.Label({
            text: 'Claude…',
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
            style_class: 'claude-usage-label',
        });
        this._label.get_clutter_text().ellipsize = 0;
        this._button.add_child(this._label);
        Main.panel.addToStatusArea('claude-usage', this._button, 1, 'right');
        this._refresh();
        this._timer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, INTERVAL_SECONDS, () => {
            this._refresh();
            return GLib.SOURCE_CONTINUE;
        });
    }

    disable() {
        this._refreshCancellable?.cancel();
        this._refreshCancellable = null;

        if (this._timer) {
            GLib.Source.remove(this._timer);
            this._timer = null;
        }

        this._button?.destroy();
        this._button = null;
        this._label = null;
    }

    _refresh() {
        if (!this._label) {
            return;
        }

        this._refreshCancellable?.cancel();
        const refreshCancellable = new Gio.Cancellable();
        this._refreshCancellable = refreshCancellable;

        const scriptPath = `${this.path}/fetch-usage.py`;
        try {
            const process = Gio.Subprocess.new(
                ['python3', scriptPath],
                Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
            );
            process.communicate_utf8_async(null, refreshCancellable, (process, result) => {
                try {
                    const [, stdout, stderr] = process.communicate_utf8_finish(result);
                    if (refreshCancellable.is_cancelled() || !this._label) {
                        return;
                    }

                    const text = stdout?.trim();
                    if (!process.get_successful() || !text) {
                        const errorText = stderr?.trim();
                        if (errorText) {
                            console.error(`Claude Usage: ${errorText}`);
                        }

                        this._label.set_text('Claude ?');
                        return;
                    }

                    this._label.set_text(text);
                } catch (error) {
                    if (refreshCancellable.is_cancelled() || !this._label) {
                        return;
                    }

                    console.error('Claude Usage refresh failed:', error);
                    this._label.set_text('Claude ?');
                } finally {
                    if (this._refreshCancellable === refreshCancellable) {
                        this._refreshCancellable = null;
                    }
                }
            });
        } catch (error) {
            console.error('Claude Usage could not start the usage helper:', error);

            if (this._label) {
                this._label.set_text('Claude ?');
            }

            this._refreshCancellable = null;
        }
    }
}