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;
}
}
}