Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
175595e585 | ||
|
|
0b520d97ac | ||
|
|
a52ef3c46c |
@@ -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 tier = String(p.tierLabel || "")
|
var base = ""
|
||||||
if (tier === "") return "Subscription"
|
if (String(p.usageStatusText || "") !== "") {
|
||||||
return tier.charAt(0).toUpperCase() + tier.slice(1)
|
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
|
// 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,16 +384,83 @@ Panel {
|
|||||||
function next(): string { root.selectProvider(root.providerIndex + 1); return "ok" }
|
function next(): string { root.selectProvider(root.providerIndex + 1); return "ok" }
|
||||||
}
|
}
|
||||||
|
|
||||||
BarIconButton {
|
// Account label beside the icon, matching the tailnet label on the Offscale
|
||||||
id: button
|
// 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
|
anchors.fill: parent
|
||||||
bar: root.bar
|
spacing: Style.space(1)
|
||||||
text: ""
|
|
||||||
active: root.alarming
|
// Bar sections lay their widgets out with spacing: 0, so separation between
|
||||||
onPressed: function(buttonCode) {
|
// neighbouring widgets comes from whatever slack each one reserves. The
|
||||||
if (buttonCode === Qt.RightButton) root.launchAgent()
|
// slot is trimmed tight to the glyph below, which leaves nothing between
|
||||||
else if (buttonCode === Qt.MiddleButton) root.selectProvider(root.providerIndex + 1)
|
// this icon and the Offscale mark to its left — hence an explicit spacer.
|
||||||
else root.toggle()
|
Item {
|
||||||
|
width: Style.space(7)
|
||||||
|
height: 1
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
BarIconButton {
|
||||||
|
id: button
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
bar: root.bar
|
||||||
|
text: ""
|
||||||
|
active: root.alarming
|
||||||
|
// Stock draws this glyph at Style.bar.iconFont (26px at base-size 24)
|
||||||
|
// inside a Style.bar.iconCanvas box (32px), in a Style.bar.iconSlot of
|
||||||
|
// reserved width (54px). That left the mark small next to the Offscale
|
||||||
|
// logo, and ~11px of dead slot either side of it — which read as the
|
||||||
|
// label floating away from the icon.
|
||||||
|
//
|
||||||
|
// Sized to match the Offscale mark (32px), with the canvas just big
|
||||||
|
// enough to hold it and the slot trimmed to the canvas so the label sits
|
||||||
|
// right beside the glyph rather than beyond its padding.
|
||||||
|
fontSize: Style.space(20)
|
||||||
|
opticalSize: Style.space(21)
|
||||||
|
slotSize: Style.space(21)
|
||||||
|
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
|
||||||
|
// WidgetButton defaults horizontalMargin to 8.5, which at this scale is
|
||||||
|
// ~17px of padding on each side — that, not the Row spacing, was what
|
||||||
|
// held the label away from the icon.
|
||||||
|
horizontalMargin: 1
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trailing counterpart to the leading spacer, so Offscale + agents read as
|
||||||
|
// their own group rather than running into the bluetooth/network/audio run.
|
||||||
|
// Bluetooth already contributes ~11px of leading slot slack; this adds to
|
||||||
|
// it rather than replacing it.
|
||||||
|
Item {
|
||||||
|
width: Style.space(9)
|
||||||
|
height: 1
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user