Show which Claude account is logged in
"Claude Code / MAX 20X" says nothing about which account the plan belongs to, which matters when switching between a personal and a company login. - Read oauthAccount.emailAddress from ~/.claude.json. Nothing in the shell's own data carries account identity — the usage JSON under ~/.local/state/omarchy/agents/usage/claude.json has limits and token counts but no email — so it comes from Claude Code's own config. The FileView is watched, so switching accounts updates without restarting the shell. - Append it to the hero meta line, after the tier: "MAX 20X · you@example.com". - Put the local part in the bar beside the icon, so the account is visible without opening the panel. A WidgetButton rather than 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 opens the panel and then dismisses it with the same click. Mirrors the icon's three buttons. Only the Claude provider gets either; codex and fireworks store their auth elsewhere and would need their own readers rather than showing a misleading email. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,8 @@ import qs.Ui
|
|||||||
|
|
||||||
Panel {
|
Panel {
|
||||||
id: root
|
id: root
|
||||||
moduleName: "omarchy.agents"
|
moduleName: "pastilhas.agents"
|
||||||
ipcTarget: "omarchy.agents"
|
ipcTarget: "pastilhas.agents"
|
||||||
manageIpc: false
|
manageIpc: false
|
||||||
|
|
||||||
readonly property color foreground: bar ? bar.foreground : Color.foreground
|
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
|
// 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.
|
// 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) {
|
function heroMeta(p) {
|
||||||
if (!p) return ""
|
if (!p) return ""
|
||||||
if (String(p.usageStatusText || "") !== "") return p.usageStatusText
|
|
||||||
|
var base = ""
|
||||||
|
if (String(p.usageStatusText || "") !== "") {
|
||||||
|
base = p.usageStatusText
|
||||||
|
} else {
|
||||||
var tier = String(p.tierLabel || "")
|
var tier = String(p.tierLabel || "")
|
||||||
if (tier === "") return "Subscription"
|
base = tier === "" ? "Subscription" : tier.charAt(0).toUpperCase() + tier.slice(1)
|
||||||
return 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
|
// 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
|
// 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.
|
// stays away entirely on a machine that has never run either CLI.
|
||||||
visible: providers.length > 0
|
visible: providers.length > 0
|
||||||
implicitWidth: button.implicitWidth
|
implicitWidth: barContent.implicitWidth
|
||||||
implicitHeight: button.implicitHeight
|
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
|
onProviderIndexChanged: if (panelFlick) panelFlick.contentY = 0
|
||||||
onOpenedChanged: if (opened) {
|
onOpenedChanged: if (opened) {
|
||||||
@@ -335,9 +384,18 @@ Panel {
|
|||||||
function next(): string { root.selectProvider(root.providerIndex + 1); return "ok" }
|
function next(): string { root.selectProvider(root.providerIndex + 1); return "ok" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
spacing: Style.space(4)
|
||||||
|
|
||||||
BarIconButton {
|
BarIconButton {
|
||||||
id: button
|
id: button
|
||||||
anchors.fill: parent
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: ""
|
text: ""
|
||||||
active: root.alarming
|
active: root.alarming
|
||||||
@@ -348,6 +406,28 @@ Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
KeyboardPanel {
|
KeyboardPanel {
|
||||||
id: panel
|
id: panel
|
||||||
anchorItem: button
|
anchorItem: button
|
||||||
|
|||||||
Reference in New Issue
Block a user