diff --git a/Panel.qml b/Panel.qml index 6637531..0740899 100644 --- a/Panel.qml +++ b/Panel.qml @@ -7,8 +7,8 @@ import qs.Ui Panel { id: root - moduleName: "omarchy.agents" - ipcTarget: "omarchy.agents" + moduleName: "pastilhas.agents" + ipcTarget: "pastilhas.agents" manageIpc: false readonly property color foreground: bar ? bar.foreground : Color.foreground @@ -178,12 +178,52 @@ Panel { // The plan you pay for, under the name of the tool it pays for. Limits live // in their own section; the hero just says what this is. + // Email of the account Claude Code is currently logged in as, read from + // ~/.claude.json (oauthAccount.emailAddress). Nothing in the shell's own data + // carries it — the usage JSON under + // ~/.local/state/omarchy/agents/usage/claude.json has limits and token counts + // but no account identity — so it comes straight from Claude Code's config. + // + // Watched, so switching accounts updates the panel without a shell restart. + // Only meaningful for the Claude provider; codex and fireworks store their + // auth elsewhere and get nothing appended. + property string claudeAccountEmail: "" + + FileView { + path: Quickshell.env("HOME") + "/.claude.json" + watchChanges: true + printErrors: false + onFileChanged: reload() + onLoadFailed: root.claudeAccountEmail = "" + onLoaded: { + try { + var parsed = JSON.parse(text()) + var account = parsed && parsed.oauthAccount ? parsed.oauthAccount : {} + root.claudeAccountEmail = String(account.emailAddress || "") + } catch (e) { + root.claudeAccountEmail = "" + } + } + } + function heroMeta(p) { if (!p) return "" - if (String(p.usageStatusText || "") !== "") return p.usageStatusText - var tier = String(p.tierLabel || "") - if (tier === "") return "Subscription" - return tier.charAt(0).toUpperCase() + tier.slice(1) + + var base = "" + if (String(p.usageStatusText || "") !== "") { + base = p.usageStatusText + } else { + var tier = String(p.tierLabel || "") + base = tier === "" ? "Subscription" : tier.charAt(0).toUpperCase() + tier.slice(1) + } + + // Which account the plan belongs to matters when you switch between a + // personal and a company login, and "MAX 20X" alone doesn't say. + if (String(p.providerId || "") === "claude" && root.claudeAccountEmail !== "") { + return base + " · " + root.claudeAccountEmail + } + + return base } // Local calendar date, recomputed from nowMs so a panel left open across @@ -298,8 +338,17 @@ Panel { // is invisible, so the icon appears the moment the first scan finds usage and // stays away entirely on a machine that has never run either CLI. visible: providers.length > 0 - implicitWidth: button.implicitWidth - implicitHeight: button.implicitHeight + implicitWidth: barContent.implicitWidth + implicitHeight: barContent.implicitHeight + + // Local part of the logged-in Claude account, for the bar. Switching between a + // personal and a company login is otherwise invisible until the panel is + // opened. Empty for the other providers, which store their auth elsewhere. + readonly property string accountShortName: { + if (!provider || String(provider.providerId || "") !== "claude") return "" + var at = claudeAccountEmail.indexOf("@") + return at > 0 ? claudeAccountEmail.substring(0, at) : claudeAccountEmail + } onProviderIndexChanged: if (panelFlick) panelFlick.contentY = 0 onOpenedChanged: if (opened) { @@ -335,16 +384,47 @@ Panel { function next(): string { root.selectProvider(root.providerIndex + 1); return "ok" } } - BarIconButton { - id: button + // Account label beside the icon, matching the tailnet label on the Offscale + // widget. BarIconButton is icon-only by design (labelVisible: false, + // fixedWidth: slotSize, and its `text` feeds a glyph rather than a label), so + // the name sits next to it in a Row rather than inside it. + Row { + id: barContent anchors.fill: parent - bar: root.bar - text: "󱚣" - active: root.alarming - onPressed: function(buttonCode) { - if (buttonCode === Qt.RightButton) root.launchAgent() - else if (buttonCode === Qt.MiddleButton) root.selectProvider(root.providerIndex + 1) - else root.toggle() + spacing: Style.space(4) + + BarIconButton { + id: button + anchors.verticalCenter: parent.verticalCenter + bar: root.bar + text: "󱚣" + active: root.alarming + onPressed: function(buttonCode) { + if (buttonCode === Qt.RightButton) root.launchAgent() + else if (buttonCode === Qt.MiddleButton) root.selectProvider(root.providerIndex + 1) + else root.toggle() + } + } + + // A WidgetButton, not a Text with a MouseArea: WidgetButton registers itself + // in bar.clickTargets, and while a panel is open KeyboardPanel's full-screen + // overlay forwards bar-strip clicks only to registered targets. An + // unregistered handler would open the panel and have the same click dismiss + // it. Mirrors the icon's three buttons so the widget reads as one target. + WidgetButton { + id: accountLabel + visible: text !== "" + bar: root.bar + text: root.accountShortName + // A step below the icon font: it is a label, not a glyph, and the bar is + // crowded. + fontSize: Style.font.bodySmall + anchors.verticalCenter: parent.verticalCenter + onPressed: function(buttonCode) { + if (buttonCode === Qt.RightButton) root.launchAgent() + else if (buttonCode === Qt.MiddleButton) root.selectProvider(root.providerIndex + 1) + else root.toggle() + } } }