Hold the Agents widget at the Omarchy 4.0.4 panel
4.5.0rc2 replaced the Agents panel with a compact one: a meter per limit, the percentage only on hover, no tokens by day or by model. noconnor.agents is a clone of omarchy.agents with Panel.qml, Main.qml and README.md from upstream v4.0.4, vendored as plain files in personal-desktop's config/ so the config step installs and captures it. capture.sh's plugin step skips plugins vendored that way instead of warning they aren't git checkouts. shell.json is synced from the live HP: the bar uses noconnor.agents, plus the 4.5 centerBegin/centerEnd layout, elsewhen and the antigravity plugin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -270,6 +270,19 @@ cloning. Two untracked scripts in the same plugin, `bin/herdr-agents` and
|
|||||||
`capture.sh plugins` re-bundles and re-diffs these, so local work keeps flowing
|
`capture.sh plugins` re-bundles and re-diffs these, so local work keeps flowing
|
||||||
back into the repo.
|
back into the repo.
|
||||||
|
|
||||||
|
`noconnor.agents` is the Agents bar widget held at the Omarchy 4.0.4 panel. 4.5
|
||||||
|
replaced it with a compact one (a meter per limit, the percentage only on
|
||||||
|
hover, no tokens by day or by model). It is a clone of `omarchy.agents`
|
||||||
|
(`omarchy plugin clone`) with `Panel.qml`, `Main.qml` and `README.md` taken from
|
||||||
|
upstream tag `v4.0.4`; `Agent.qml` and `assets/` stay at 4.5, and the usage
|
||||||
|
data still comes from the system's `omarchy-agent-usage-*` collectors. It has
|
||||||
|
no git history of its own, so it is carried as plain files in
|
||||||
|
`profiles/personal-desktop/config/omarchy/plugins/noconnor.agents/` and the
|
||||||
|
config step installs it and pulls it back. The 4.5 panel's account controls
|
||||||
|
(Use, Autoswitch, adding an account) are not in it. After installing it on a
|
||||||
|
running shell, `omarchy restart shell`: until then the built-in instance keeps
|
||||||
|
the `omarchy.agents` IPC target.
|
||||||
|
|
||||||
## Not in here, on purpose
|
## Not in here, on purpose
|
||||||
|
|
||||||
- **Secrets.** `~/.config/lan-mouse/` holds a private key and per-machine
|
- **Secrets.** `~/.config/lan-mouse/` holds a private key and per-machine
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ step_plugins() {
|
|||||||
for p in "$dir"/*/; do
|
for p in "$dir"/*/; do
|
||||||
id=$(basename -- "$p")
|
id=$(basename -- "$p")
|
||||||
[[ -n ${listed[$id]:-} ]] && continue
|
[[ -n ${listed[$id]:-} ]] && continue
|
||||||
|
# Vendored as plain files under a layer's config/; step_config owns it.
|
||||||
|
[[ -n $(layers_with "config/omarchy/plugins/$id") ]] && continue
|
||||||
if [[ ! -d $p/.git ]]; then warn "$id is not a git checkout - not tracked"; continue; fi
|
if [[ ! -d $p/.git ]]; then warn "$id is not a git checkout - not tracked"; continue; fi
|
||||||
if ! u=$(git -C "$p" remote get-url origin 2>/dev/null); then
|
if ! u=$(git -C "$p" remote get-url origin 2>/dev/null); then
|
||||||
warn "$id has no origin remote - push it somewhere before it can be pinned"; continue
|
warn "$id has no origin remote - push it somewhere before it can be pinned"; continue
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
// One agent's usage record, read straight off the data file that
|
||||||
|
// omarchy-agent-usage-update maintains. The panel never learns how the
|
||||||
|
// numbers were made — a record that appears in the usage directory is an
|
||||||
|
// agent, whoever wrote it.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
property string agentId: ""
|
||||||
|
property string path: ""
|
||||||
|
property var record: null
|
||||||
|
|
||||||
|
// The text the current record was parsed from, so a reload that finds the
|
||||||
|
// file unchanged doesn't hand every listener an identical new record.
|
||||||
|
property string parsedText: ""
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: agentFile
|
||||||
|
path: root.path
|
||||||
|
watchChanges: true
|
||||||
|
printErrors: false
|
||||||
|
onFileChanged: reload()
|
||||||
|
onLoaded: root.parse(text())
|
||||||
|
onLoadFailed: root.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The usage update rewrites records with an atomic mv, which replaces the
|
||||||
|
// inode. Should inotify fail to rearm on the new one (watch quota, ENOSPC),
|
||||||
|
// the FileView goes quiet, so Main.qml also reloads every record after each
|
||||||
|
// update run (#9974).
|
||||||
|
function reload() { agentFile.reload() }
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
root.parsedText = ""
|
||||||
|
root.record = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(content) {
|
||||||
|
var text = String(content || "")
|
||||||
|
if (root.record && text === root.parsedText) return
|
||||||
|
try {
|
||||||
|
var parsed = JSON.parse(text)
|
||||||
|
root.record = parsed && typeof parsed === "object" ? parsed : null
|
||||||
|
root.parsedText = root.record ? text : ""
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("agents", "Ignoring bad usage record", root.path, e)
|
||||||
|
root.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,748 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
// The display side of agent usage. All extraction lives behind
|
||||||
|
// omarchy-agent-usage-update, which writes one JSON record per agent into
|
||||||
|
// the usage directory; this file only discovers those records, watches them
|
||||||
|
// for changes, and optionally merges snapshots synced from other machines.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
property var settings: ({})
|
||||||
|
|
||||||
|
readonly property string home: Quickshell.env("HOME") || ""
|
||||||
|
readonly property string usageDir: (Quickshell.env("XDG_STATE_HOME") || home + "/.local/state") + "/omarchy/agents/usage"
|
||||||
|
|
||||||
|
// ------------------------------------------------------------- discovery
|
||||||
|
|
||||||
|
property var agentIds: []
|
||||||
|
property var agents: []
|
||||||
|
property int dataRevision: 0
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: listProcess
|
||||||
|
running: false
|
||||||
|
command: ["find", root.usageDir, "-maxdepth", "1", "-name", "*.json", "-printf", "%f\n"]
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
waitForEnd: true
|
||||||
|
onStreamFinished: root.applyAgentListing(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rescanAgents() {
|
||||||
|
if (!listProcess.running) listProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAgentListing(output) {
|
||||||
|
var ids = []
|
||||||
|
var lines = String(output || "").split("\n")
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var name = lines[i].trim()
|
||||||
|
if (name.slice(-5) === ".json") ids.push(name.slice(0, -5))
|
||||||
|
}
|
||||||
|
ids.sort()
|
||||||
|
// Same list, same objects: reassigning the model would tear down every
|
||||||
|
// FileView just to build identical ones.
|
||||||
|
if (JSON.stringify(ids) !== JSON.stringify(agentIds)) agentIds = ids
|
||||||
|
}
|
||||||
|
|
||||||
|
Instantiator {
|
||||||
|
id: agentInstantiator
|
||||||
|
model: root.agentIds
|
||||||
|
|
||||||
|
delegate: Agent {
|
||||||
|
required property var modelData
|
||||||
|
agentId: modelData
|
||||||
|
path: root.usageDir + "/" + modelData + ".json"
|
||||||
|
onRecordChanged: root.recordsChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
onObjectAdded: (index, object) => root.rebuildAgents()
|
||||||
|
onObjectRemoved: (index, object) => root.rebuildAgents()
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildAgents() {
|
||||||
|
var result = []
|
||||||
|
for (var i = 0; i < agentInstantiator.count; i++) {
|
||||||
|
var agent = agentInstantiator.objectAt(i)
|
||||||
|
if (agent) result.push(agent)
|
||||||
|
}
|
||||||
|
agents = result
|
||||||
|
recordsChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordsChanged() {
|
||||||
|
dataRevision++
|
||||||
|
scheduleLimitsRetry()
|
||||||
|
scheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A collector that could not reach its limits endpoint at all — typically
|
||||||
|
// the seconds after login before the network is up — writes retryAdvised
|
||||||
|
// into its record. Honor it with one sooner try instead of waiting out the
|
||||||
|
// full refresh interval; a run that reaches the endpoint clears the flag.
|
||||||
|
// Only the advising agents rerun, so an outage at one provider does not
|
||||||
|
// put every other collector on a 30-second treadmill.
|
||||||
|
property var retryAgentIds: []
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: limitsRetry
|
||||||
|
interval: 30000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.runUpdate("limits", root.retryAgentIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleLimitsRetry() {
|
||||||
|
var advising = []
|
||||||
|
for (var i = 0; i < agents.length; i++) {
|
||||||
|
var record = agents[i] ? agents[i].record : null
|
||||||
|
if (record && record.retryAdvised === true && providerEnabled(String(record.id || "")))
|
||||||
|
advising.push(String(record.id))
|
||||||
|
}
|
||||||
|
retryAgentIds = advising
|
||||||
|
if (advising.length > 0) limitsRetry.restart()
|
||||||
|
else limitsRetry.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
rescanAgents()
|
||||||
|
if (syncConfigured()) scheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------- refresh
|
||||||
|
|
||||||
|
property int refreshIntervalSec: Math.max(30, Number(setting("refreshIntervalSec", 900)))
|
||||||
|
property string pendingUpdateKind: ""
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: root.refreshIntervalSec * 1000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: root.runUpdate("normal")
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: updateProcess
|
||||||
|
running: false
|
||||||
|
onExited: {
|
||||||
|
root.rescanAgents()
|
||||||
|
if (root.pendingUpdateKind !== "") {
|
||||||
|
var kind = root.pendingUpdateKind
|
||||||
|
root.pendingUpdateKind = ""
|
||||||
|
root.runUpdate(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr: StdioCollector {
|
||||||
|
waitForEnd: true
|
||||||
|
onStreamFinished: if (text.trim() !== "") console.warn("agents", text.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCommand(kind, agentIds) {
|
||||||
|
var command = ["omarchy-agent-usage-update"]
|
||||||
|
if (kind === "force") command.push("--force")
|
||||||
|
if (kind === "limits") command.push("--limits-only")
|
||||||
|
var providers = settings && settings.providers ? settings.providers : {}
|
||||||
|
for (var id in providers) {
|
||||||
|
if (providers[id] && providers[id].enabled === false) command.push("--except", id)
|
||||||
|
}
|
||||||
|
if (agentIds) {
|
||||||
|
for (var i = 0; i < agentIds.length; i++) command.push(agentIds[i])
|
||||||
|
}
|
||||||
|
return command
|
||||||
|
}
|
||||||
|
|
||||||
|
function runUpdate(kind, agentIds) {
|
||||||
|
if (updateProcess.running) {
|
||||||
|
// Collapse queued requests to one full rerun; a forced refresh outranks
|
||||||
|
// the cheaper kinds it might have been queued behind.
|
||||||
|
if (kind === "force" || root.pendingUpdateKind === "") root.pendingUpdateKind = kind
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateProcess.command = updateCommand(kind, agentIds)
|
||||||
|
updateProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() { refreshAll(true) }
|
||||||
|
function refreshAll(force) { runUpdate(force === true ? "force" : "normal") }
|
||||||
|
|
||||||
|
// Opening the panel wants the numbers that go stale on the wire, not
|
||||||
|
// another walk over every transcript on disk — the collectors reuse their
|
||||||
|
// recent scans in this mode.
|
||||||
|
function refreshLimits() { runUpdate("limits") }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------- providers
|
||||||
|
|
||||||
|
// An agent earns a place in the bar and the panel by being switched on in
|
||||||
|
// settings and having actually produced numbers — locally or on a synced
|
||||||
|
// device. With nothing to show, the whole module collapses out of the bar
|
||||||
|
// rather than sitting there dimmed.
|
||||||
|
property var enabledProviders: {
|
||||||
|
var rev = dataRevision
|
||||||
|
var syncRev = syncRevision
|
||||||
|
var result = []
|
||||||
|
var localIds = {}
|
||||||
|
for (var i = 0; i < agents.length; i++) {
|
||||||
|
var record = agents[i] ? agents[i].record : null
|
||||||
|
if (!record || !record.id) continue
|
||||||
|
var id = String(record.id)
|
||||||
|
localIds[id] = true
|
||||||
|
if (!providerEnabled(id)) continue
|
||||||
|
var display = displayProvider(record)
|
||||||
|
if (providerHasData(display)) result.push(display)
|
||||||
|
}
|
||||||
|
// An agent that only ever ran on another machine has no local record, but
|
||||||
|
// its synced numbers still deserve a tab. Rate limits stay blank — they
|
||||||
|
// are per-account and never travel.
|
||||||
|
var syncedProviders = syncConfigured() && aggregateData && aggregateData.providers ? aggregateData.providers : {}
|
||||||
|
for (var syncedId in syncedProviders) {
|
||||||
|
if (localIds[syncedId] || !providerEnabled(syncedId)) continue
|
||||||
|
var stats = syncedProviders[syncedId] || {}
|
||||||
|
var syncedDisplay = displayProvider({ id: syncedId, name: stats.providerName || syncedId })
|
||||||
|
if (providerHasData(syncedDisplay)) result.push(syncedDisplay)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function providerEnabled(id) {
|
||||||
|
if (!settings || !settings.providers || !settings.providers[id]) return true
|
||||||
|
return settings.providers[id].enabled !== false
|
||||||
|
}
|
||||||
|
|
||||||
|
// All-time keeps a quiet day from hiding an agent; today's counts admit a
|
||||||
|
// machine whose only source is history.jsonl, which knows nothing older.
|
||||||
|
function providerHasData(p) {
|
||||||
|
return numberValue(p.totalPrompts) > 0 || numberValue(p.totalSessions) > 0
|
||||||
|
|| numberValue(p.activeDays) > 0 || numberValue(p.todayPrompts) > 0
|
||||||
|
|| numberValue(p.todaySessions) > 0 || (p.limits && p.limits.length > 0)
|
||||||
|
|| !!p.balance
|
||||||
|
}
|
||||||
|
|
||||||
|
// A prepaid agent's credit ledger. Like rate limits, the balance is
|
||||||
|
// per-account and never merged across devices.
|
||||||
|
function balanceValue(raw) {
|
||||||
|
if (!raw || typeof raw !== "object") return null
|
||||||
|
var remaining = Number(raw.remaining)
|
||||||
|
var funded = Number(raw.funded)
|
||||||
|
if (!isFinite(remaining) || remaining < 0) return null
|
||||||
|
return {
|
||||||
|
remaining: remaining,
|
||||||
|
funded: isFinite(funded) && funded > 0 ? funded : 0,
|
||||||
|
spent: Math.max(0, Number(raw.spent) || 0),
|
||||||
|
currency: String(raw.currency || "USD"),
|
||||||
|
estimated: raw.estimated === true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayProvider(record) {
|
||||||
|
var stats = syncedStatsFor(String(record.id))
|
||||||
|
var synced = !!stats
|
||||||
|
var deviceCount = synced ? Number(stats.deviceCount || aggregateData.deviceCount || 0) : 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
providerId: String(record.id),
|
||||||
|
providerName: String(record.name || record.id),
|
||||||
|
ready: record.ready === true || synced,
|
||||||
|
usageStatusText: String(record.usageStatusText || ""),
|
||||||
|
authHelpText: String(record.authHelpText || ""),
|
||||||
|
|
||||||
|
// Rate limits and balances stay per-account and are never merged
|
||||||
|
// across devices.
|
||||||
|
limits: Array.isArray(record.limits) ? record.limits : [],
|
||||||
|
tierLabel: String(record.tierLabel || ""),
|
||||||
|
balance: balanceValue(record.balance),
|
||||||
|
|
||||||
|
todayPrompts: synced ? numberValue(stats.todayPrompts) : numberValue(record.todayPrompts),
|
||||||
|
todaySessions: synced ? numberValue(stats.todaySessions) : numberValue(record.todaySessions),
|
||||||
|
todayTotalTokens: synced ? numberValue(stats.todayTotalTokens) : numberValue(record.todayTotalTokens),
|
||||||
|
todayTokensByModel: synced ? (stats.todayTokensByModel || ({})) : (record.todayTokensByModel || ({})),
|
||||||
|
recentDays: synced ? (stats.recentDays || []) : (record.recentDays || []),
|
||||||
|
totalPrompts: synced ? numberValue(stats.totalPrompts) : numberValue(record.totalPrompts),
|
||||||
|
totalSessions: synced ? numberValue(stats.totalSessions) : numberValue(record.totalSessions),
|
||||||
|
activeDays: synced ? numberValue(stats.activeDays) : numberValue(record.activeDays),
|
||||||
|
modelUsage: synced ? (stats.modelUsage || ({})) : (record.modelUsage || ({})),
|
||||||
|
hasLocalStats: synced ? (stats.hasLocalStats !== false) : (record.hasLocalStats !== false),
|
||||||
|
hasPromptStats: synced ? (stats.hasPromptStats !== false) : (record.hasPromptStats !== false),
|
||||||
|
|
||||||
|
syncEnabled: synced,
|
||||||
|
syncDeviceCount: deviceCount,
|
||||||
|
syncUpdatedAt: aggregateData && aggregateData.updatedAt ? aggregateData.updatedAt : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setting(name, fallback) {
|
||||||
|
var value = settings ? settings[name] : undefined
|
||||||
|
return value === undefined || value === null ? fallback : value
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ sync
|
||||||
|
|
||||||
|
property var syncModeSetting: setting("syncMode", setting("syncEnabled", false))
|
||||||
|
property bool syncEnabled: parseSyncEnabled(syncModeSetting)
|
||||||
|
property string syncDir: String(setting("syncDir", ""))
|
||||||
|
property string syncFileName: String(setting("syncFileName", ""))
|
||||||
|
property string syncDeviceId: String(setting("syncDeviceId", ""))
|
||||||
|
property string detectedHostname: ""
|
||||||
|
readonly property string syncEffectiveDir: expandPath(syncDir)
|
||||||
|
readonly property string syncEffectiveFileName: safeSnapshotFileName(syncFileName, syncDeviceId)
|
||||||
|
readonly property string syncEffectiveDeviceId: safeDeviceId(syncDeviceId || syncEffectiveFileName.replace(/\.json$/i, ""))
|
||||||
|
readonly property string syncSnapshotPath: syncConfigured() ? syncEffectiveDir + "/" + syncEffectiveFileName : home + "/.cache/omarchy/agents-disabled.json"
|
||||||
|
property var aggregateData: ({})
|
||||||
|
property int syncRevision: 0
|
||||||
|
property bool syncRunning: false
|
||||||
|
property bool syncRequestedWhileRunning: false
|
||||||
|
property string syncStatusText: ""
|
||||||
|
property double aggregateUpdatedAtMs: aggregateData && aggregateData.updatedAtMs ? Number(aggregateData.updatedAtMs) : 0
|
||||||
|
|
||||||
|
onSyncEnabledChanged: syncSettingsChanged()
|
||||||
|
onSyncDirChanged: syncSettingsChanged()
|
||||||
|
onSyncFileNameChanged: if (syncConfigured()) scheduleSync()
|
||||||
|
onSyncDeviceIdChanged: if (syncConfigured()) scheduleSync()
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: syncDebounce
|
||||||
|
interval: 1000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.runSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: syncMkdirProcess
|
||||||
|
running: false
|
||||||
|
onRunningChanged: root.updateSyncRunning()
|
||||||
|
onExited: function(exitCode) {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
if (root.syncConfigured()) root.syncStatusText = "Usage sync mkdir failed"
|
||||||
|
root.finishSyncRun()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
root.writeSyncSnapshot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: syncScanProcess
|
||||||
|
running: false
|
||||||
|
onRunningChanged: root.updateSyncRunning()
|
||||||
|
onExited: function(exitCode) {
|
||||||
|
if (exitCode !== 0 && root.syncConfigured()) root.syncStatusText = "Usage sync scan failed"
|
||||||
|
root.finishSyncRun()
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
waitForEnd: true
|
||||||
|
onStreamFinished: root.parseSyncScanOutput(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr: StdioCollector {
|
||||||
|
waitForEnd: true
|
||||||
|
onStreamFinished: if (text.trim() !== "") console.warn("agents/sync", text.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: syncSnapshotFile
|
||||||
|
path: root.syncSnapshotPath
|
||||||
|
watchChanges: false
|
||||||
|
atomicWrites: true
|
||||||
|
printErrors: false
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: hostnameFile
|
||||||
|
path: "/etc/hostname"
|
||||||
|
watchChanges: false
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.detectedHostname = String(text() || "").trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSyncEnabled(value) {
|
||||||
|
if (value === true) return true
|
||||||
|
var text = String(value || "").trim().toLowerCase()
|
||||||
|
return text === "on" || text === "enabled" || text === "true" || text === "yes" || text === "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncConfigured() {
|
||||||
|
return root.syncEnabled === true && String(root.syncDir || "").trim() !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncSettingsChanged() {
|
||||||
|
if (syncConfigured()) {
|
||||||
|
scheduleSync()
|
||||||
|
} else {
|
||||||
|
syncDebounce.stop()
|
||||||
|
syncRequestedWhileRunning = false
|
||||||
|
aggregateData = ({})
|
||||||
|
syncStatusText = ""
|
||||||
|
syncRevision++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSyncRunning() {
|
||||||
|
root.syncRunning = syncMkdirProcess.running || syncScanProcess.running
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleSync() {
|
||||||
|
if (!syncConfigured()) return
|
||||||
|
syncDebounce.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function runSync() {
|
||||||
|
if (!syncConfigured()) return
|
||||||
|
if (root.syncRunning) {
|
||||||
|
syncRequestedWhileRunning = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
syncRequestedWhileRunning = false
|
||||||
|
syncStatusText = ""
|
||||||
|
syncMkdirProcess.command = ["mkdir", "-p", root.syncEffectiveDir]
|
||||||
|
syncMkdirProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeSyncSnapshot() {
|
||||||
|
if (!syncConfigured()) {
|
||||||
|
finishSyncRun()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
syncSnapshotFile.setText(JSON.stringify(localSnapshot(), null, 2) + "\n")
|
||||||
|
Qt.callLater(root.startSyncScan)
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSyncScan() {
|
||||||
|
if (!syncConfigured()) {
|
||||||
|
finishSyncRun()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var script = "dir=$0; [[ -d \"$dir\" ]] || exit 0; shopt -s nullglob; for f in \"$dir\"/*.json; do [[ -f \"$f\" ]] || continue; printf '===%s===\\n' \"$f\"; cat \"$f\"; printf '\\n=== EOM ===\\n'; done"
|
||||||
|
syncScanProcess.command = ["bash", "-c", script, root.syncEffectiveDir]
|
||||||
|
syncScanProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishSyncRun() {
|
||||||
|
if (syncRequestedWhileRunning && syncConfigured()) {
|
||||||
|
syncRequestedWhileRunning = false
|
||||||
|
scheduleSync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandPath(path) {
|
||||||
|
var value = String(path || "").trim()
|
||||||
|
if (value === "") return ""
|
||||||
|
if (value === "~") return home
|
||||||
|
if (value.indexOf("~/") === 0) return home + value.substring(1)
|
||||||
|
if (value.indexOf("$HOME/") === 0) return home + value.substring(5)
|
||||||
|
if (value.charAt(0) !== "/") return home + "/" + value
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeDeviceId(raw) {
|
||||||
|
var value = String(raw || "").trim()
|
||||||
|
if (value === "") value = Quickshell.env("HOSTNAME") || root.detectedHostname || Quickshell.env("HOST") || Quickshell.env("USER") || "device"
|
||||||
|
value = value.replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "")
|
||||||
|
if (value === "") value = "device"
|
||||||
|
return value.length > 80 ? value.substring(0, 80) : value
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeSnapshotFileName(rawFileName, rawDeviceId) {
|
||||||
|
var value = String(rawFileName || "").trim()
|
||||||
|
if (value === "") value = safeDeviceId(rawDeviceId) + ".json"
|
||||||
|
value = value.split("/").pop().replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "")
|
||||||
|
if (value === "") value = safeDeviceId(rawDeviceId) + ".json"
|
||||||
|
if (!/\.json$/i.test(value)) value += ".json"
|
||||||
|
return value.length > 100 ? value.substring(0, 95) + ".json" : value
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSyncScanOutput(output) {
|
||||||
|
var lines = String(output || "").split("\n")
|
||||||
|
var snapshots = []
|
||||||
|
var currentPath = ""
|
||||||
|
var currentJson = []
|
||||||
|
|
||||||
|
function flush() {
|
||||||
|
if (currentPath === "") return
|
||||||
|
var raw = currentJson.join("\n").trim()
|
||||||
|
try {
|
||||||
|
var parsed = JSON.parse(raw)
|
||||||
|
if (parsed && parsed.providers) snapshots.push(parsed)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("agents/sync", "Ignoring bad snapshot", currentPath, e)
|
||||||
|
}
|
||||||
|
currentPath = ""
|
||||||
|
currentJson = []
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var line = lines[i]
|
||||||
|
var start = line.match(/^===(.+)===$/)
|
||||||
|
if (start && line !== "=== EOM ===") {
|
||||||
|
flush()
|
||||||
|
currentPath = start[1]
|
||||||
|
currentJson = []
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (line === "=== EOM ===") {
|
||||||
|
flush()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (currentPath !== "") currentJson.push(line)
|
||||||
|
}
|
||||||
|
flush()
|
||||||
|
|
||||||
|
aggregateData = aggregateSnapshots(snapshots)
|
||||||
|
syncStatusText = ""
|
||||||
|
syncRevision++
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneValue(value, fallback) {
|
||||||
|
if (value === undefined || value === null) return fallback
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(value))
|
||||||
|
} catch (e) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberValue(value) {
|
||||||
|
var n = Number(value || 0)
|
||||||
|
return isFinite(n) ? Math.round(n) : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateString(date) {
|
||||||
|
var y = date.getFullYear()
|
||||||
|
var m = String(date.getMonth() + 1).padStart(2, "0")
|
||||||
|
var d = String(date.getDate()).padStart(2, "0")
|
||||||
|
return y + "-" + m + "-" + d
|
||||||
|
}
|
||||||
|
|
||||||
|
function recentDateStrings() {
|
||||||
|
var result = []
|
||||||
|
for (var offset = 6; offset >= 0; offset--) {
|
||||||
|
var date = new Date()
|
||||||
|
date.setDate(date.getDate() - offset)
|
||||||
|
result.push(dateString(date))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyTokenBucket() {
|
||||||
|
return { inputTokens: 0, outputTokens: 0, cacheReadInputTokens: 0, cacheCreationInputTokens: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device-scoped stats add up across machines; account-scoped stats
|
||||||
|
// (Fireworks' billing API) are replicas of the same upstream truth on
|
||||||
|
// every synced device, so the widest value wins — summing them would
|
||||||
|
// double every token per machine.
|
||||||
|
function combineNumber(additive, current, value) {
|
||||||
|
return additive ? numberValue(current) + numberValue(value) : Math.max(numberValue(current), numberValue(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function combineObjectNumbers(additive, target, source) {
|
||||||
|
if (!source) return
|
||||||
|
for (var key in source) target[key] = combineNumber(additive, target[key], source[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
function aggregateSnapshots(snapshots) {
|
||||||
|
var dates = recentDateStrings()
|
||||||
|
var devices = {}
|
||||||
|
var providers = {}
|
||||||
|
|
||||||
|
function providerAcc(id) {
|
||||||
|
if (providers[id]) return providers[id]
|
||||||
|
var recentByDay = {}
|
||||||
|
for (var d = 0; d < dates.length; d++) recentByDay[dates[d]] = 0
|
||||||
|
providers[id] = {
|
||||||
|
providerId: id,
|
||||||
|
providerName: "",
|
||||||
|
ready: false,
|
||||||
|
hasLocalStats: false,
|
||||||
|
hasPromptStats: false,
|
||||||
|
todayPrompts: 0,
|
||||||
|
todaySessions: 0,
|
||||||
|
todayTotalTokens: 0,
|
||||||
|
todayTokensByModel: ({}),
|
||||||
|
recentByDay: recentByDay,
|
||||||
|
totalPrompts: 0,
|
||||||
|
totalSessions: 0,
|
||||||
|
activeDays: 0,
|
||||||
|
activeDates: ({}),
|
||||||
|
modelUsage: ({}),
|
||||||
|
devices: ({})
|
||||||
|
}
|
||||||
|
return providers[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < snapshots.length; i++) {
|
||||||
|
var snapshot = snapshots[i]
|
||||||
|
var device = safeDeviceId(snapshot.deviceId || "device")
|
||||||
|
devices[device] = true
|
||||||
|
var snapshotProviders = snapshot.providers || {}
|
||||||
|
for (var providerId in snapshotProviders) {
|
||||||
|
var stats = snapshotProviders[providerId] || {}
|
||||||
|
var acc = providerAcc(String(providerId))
|
||||||
|
acc.devices[device] = true
|
||||||
|
if (stats.providerName && acc.providerName === "") acc.providerName = String(stats.providerName)
|
||||||
|
acc.ready = acc.ready || stats.ready === true
|
||||||
|
acc.hasLocalStats = acc.hasLocalStats || stats.hasLocalStats !== false
|
||||||
|
// Snapshots from before the field existed only came from agents that
|
||||||
|
// count prompts, so a missing value reads as true.
|
||||||
|
acc.hasPromptStats = acc.hasPromptStats || stats.hasPromptStats !== false
|
||||||
|
var additive = String(stats.scope || "device") !== "account"
|
||||||
|
acc.todayPrompts = combineNumber(additive, acc.todayPrompts, stats.todayPrompts)
|
||||||
|
acc.todaySessions = combineNumber(additive, acc.todaySessions, stats.todaySessions)
|
||||||
|
acc.todayTotalTokens = combineNumber(additive, acc.todayTotalTokens, stats.todayTotalTokens)
|
||||||
|
acc.totalPrompts = combineNumber(additive, acc.totalPrompts, stats.totalPrompts)
|
||||||
|
acc.totalSessions = combineNumber(additive, acc.totalSessions, stats.totalSessions)
|
||||||
|
// Active days overlap between machines, so union the dates rather than
|
||||||
|
// summing counts. Snapshots written before activeDates existed only
|
||||||
|
// carry a count; the widest one stands in for them.
|
||||||
|
var activeDates = Array.isArray(stats.activeDates) ? stats.activeDates : []
|
||||||
|
for (var ad = 0; ad < activeDates.length; ad++) acc.activeDates[String(activeDates[ad])] = true
|
||||||
|
acc.activeDays = Math.max(acc.activeDays, numberValue(stats.activeDays))
|
||||||
|
combineObjectNumbers(additive, acc.todayTokensByModel, stats.todayTokensByModel || {})
|
||||||
|
|
||||||
|
var recent = Array.isArray(stats.recentDays) ? stats.recentDays : []
|
||||||
|
for (var r = 0; r < recent.length; r++) {
|
||||||
|
var day = recent[r] || {}
|
||||||
|
var date = String(day.date || "")
|
||||||
|
if (acc.recentByDay[date] !== undefined)
|
||||||
|
acc.recentByDay[date] = combineNumber(additive, acc.recentByDay[date], day.messageCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
var usage = stats.modelUsage || {}
|
||||||
|
for (var modelId in usage) {
|
||||||
|
var bucket = acc.modelUsage[modelId]
|
||||||
|
if (!bucket) bucket = acc.modelUsage[modelId] = emptyTokenBucket()
|
||||||
|
combineObjectNumbers(additive, bucket, usage[modelId] || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var outProviders = {}
|
||||||
|
for (var id in providers) {
|
||||||
|
var acc = providers[id]
|
||||||
|
var recentDays = []
|
||||||
|
for (var di = 0; di < dates.length; di++) recentDays.push({ date: dates[di], messageCount: acc.recentByDay[dates[di]] || 0 })
|
||||||
|
var providerDevices = Object.keys(acc.devices).sort()
|
||||||
|
outProviders[id] = {
|
||||||
|
providerId: acc.providerId,
|
||||||
|
providerName: acc.providerName,
|
||||||
|
ready: acc.ready || providerDevices.length > 0,
|
||||||
|
hasLocalStats: acc.hasLocalStats,
|
||||||
|
hasPromptStats: acc.hasPromptStats,
|
||||||
|
todayPrompts: acc.todayPrompts,
|
||||||
|
todaySessions: acc.todaySessions,
|
||||||
|
todayTotalTokens: acc.todayTotalTokens,
|
||||||
|
todayTokensByModel: acc.todayTokensByModel,
|
||||||
|
recentDays: recentDays,
|
||||||
|
totalPrompts: acc.totalPrompts,
|
||||||
|
totalSessions: acc.totalSessions,
|
||||||
|
activeDays: Math.max(acc.activeDays, Object.keys(acc.activeDates).length),
|
||||||
|
modelUsage: acc.modelUsage,
|
||||||
|
deviceCount: providerDevices.length,
|
||||||
|
devices: providerDevices
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
schemaVersion: 1,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
updatedAtMs: Date.now(),
|
||||||
|
deviceCount: Object.keys(devices).length,
|
||||||
|
devices: Object.keys(devices).sort(),
|
||||||
|
providers: outProviders
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshots keep the field names older Omarchy versions wrote, so a fleet
|
||||||
|
// of machines on mixed versions still merges cleanly in both directions.
|
||||||
|
function providerSnapshot(record) {
|
||||||
|
return {
|
||||||
|
providerId: String(record.id),
|
||||||
|
providerName: String(record.name || record.id),
|
||||||
|
ready: record.ready === true,
|
||||||
|
hasLocalStats: record.hasLocalStats !== false,
|
||||||
|
hasPromptStats: record.hasPromptStats !== false,
|
||||||
|
scope: String(record.scope || "device"),
|
||||||
|
todayPrompts: numberValue(record.todayPrompts),
|
||||||
|
todaySessions: numberValue(record.todaySessions),
|
||||||
|
todayTotalTokens: numberValue(record.todayTotalTokens),
|
||||||
|
todayTokensByModel: cloneValue(record.todayTokensByModel, ({})),
|
||||||
|
recentDays: cloneValue(record.recentDays, []),
|
||||||
|
totalPrompts: numberValue(record.totalPrompts),
|
||||||
|
totalSessions: numberValue(record.totalSessions),
|
||||||
|
activeDays: numberValue(record.activeDays),
|
||||||
|
activeDates: cloneValue(record.activeDates, []),
|
||||||
|
modelUsage: cloneValue(record.modelUsage, ({}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function localSnapshot() {
|
||||||
|
var providerMap = {}
|
||||||
|
for (var i = 0; i < agents.length; i++) {
|
||||||
|
var record = agents[i] ? agents[i].record : null
|
||||||
|
if (!record || !record.id) continue
|
||||||
|
if (!providerEnabled(String(record.id))) continue
|
||||||
|
providerMap[String(record.id)] = providerSnapshot(record)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
schemaVersion: 1,
|
||||||
|
deviceId: syncEffectiveDeviceId,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
providers: providerMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncedStatsFor(providerId) {
|
||||||
|
var rev = syncRevision
|
||||||
|
if (!syncConfigured() || !aggregateData || !aggregateData.providers) return null
|
||||||
|
return aggregateData.providers[providerId] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- format
|
||||||
|
|
||||||
|
function formatTokenCount(n) {
|
||||||
|
if (n === undefined || n === null) return "0"
|
||||||
|
if (n >= 1e9) return (n / 1e9).toFixed(1) + "B"
|
||||||
|
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M"
|
||||||
|
if (n >= 1e3) return (n / 1e3).toFixed(1) + "K"
|
||||||
|
return String(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
function modelWordCase(word) {
|
||||||
|
if (word === "gpt") return "GPT"
|
||||||
|
if (word === "deepseek") return "DeepSeek"
|
||||||
|
return word.charAt(0).toUpperCase() + word.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model ids arrive hyphenated with the version split across segments
|
||||||
|
// (`claude-opus-4-8`, `gpt-5.6-sol`). Rejoin the numeric run into one
|
||||||
|
// version and title-case the words around it.
|
||||||
|
function friendlyModelName(id) {
|
||||||
|
if (!id) return "Unknown"
|
||||||
|
var name = String(id).replace(/^claude-/, "").replace(/-\d{8}$/, "")
|
||||||
|
var parts = name.split("-")
|
||||||
|
var words = []
|
||||||
|
var version = []
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
var part = parts[i]
|
||||||
|
if (part === "") continue
|
||||||
|
if (/^\d/.test(part)) {
|
||||||
|
version.push(part)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (version.length > 0) {
|
||||||
|
words.push(version.join("."))
|
||||||
|
version = []
|
||||||
|
}
|
||||||
|
words.push(modelWordCase(part))
|
||||||
|
}
|
||||||
|
if (version.length > 0) words.push(version.join("."))
|
||||||
|
return words.length > 0 ? words.join(" ") : "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,942 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Commons
|
||||||
|
import qs.Ui
|
||||||
|
|
||||||
|
Panel {
|
||||||
|
id: root
|
||||||
|
moduleName: "omarchy.agents"
|
||||||
|
ipcTarget: "omarchy.agents"
|
||||||
|
manageIpc: false
|
||||||
|
|
||||||
|
readonly property color foreground: bar ? bar.foreground : Color.foreground
|
||||||
|
readonly property color urgent: bar ? bar.urgent : Color.urgent
|
||||||
|
readonly property color dim: Qt.darker(foreground, 1.55)
|
||||||
|
readonly property color surface: Color.popups.background
|
||||||
|
readonly property color track: Style.selectedFillFor(foreground, Color.accent)
|
||||||
|
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
||||||
|
|
||||||
|
readonly property var providers: usage.enabledProviders
|
||||||
|
// The selection follows the provider, not the slot it happens to sit in: a
|
||||||
|
// provider whose first scan lands while the panel is open would otherwise
|
||||||
|
// shift the list underneath you and swap out what you were reading.
|
||||||
|
property string selectedProviderId: ""
|
||||||
|
readonly property int providerIndex: {
|
||||||
|
for (var i = 0; i < providers.length; i++)
|
||||||
|
if (providers[i].providerId === selectedProviderId) return i
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
readonly property var provider: providers.length > 0 ? providers[providerIndex] : null
|
||||||
|
|
||||||
|
property bool cursorActive: false
|
||||||
|
|
||||||
|
// Countdowns and "updated" read this instead of Date.now() so the
|
||||||
|
// panel keeps telling the truth while it sits open.
|
||||||
|
property double nowMs: Date.now()
|
||||||
|
|
||||||
|
readonly property var limits: limitWindows(provider)
|
||||||
|
readonly property var models: modelRows(provider)
|
||||||
|
readonly property var headline: bindingWindow(provider)
|
||||||
|
readonly property var balance: provider ? (provider.balance || null) : null
|
||||||
|
// A prepaid account runs low the way a subscription window fills up: the
|
||||||
|
// last 10% of the funded credits lights the same alarm.
|
||||||
|
readonly property bool balanceAlarming: !!balance && balance.funded > 0
|
||||||
|
&& balance.remaining / balance.funded <= 0.1
|
||||||
|
readonly property bool alarming: (!!headline && headline.percent >= 0.9) || balanceAlarming
|
||||||
|
|
||||||
|
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)) }
|
||||||
|
function alpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a) }
|
||||||
|
|
||||||
|
function selectProvider(index) {
|
||||||
|
if (providers.length === 0) return
|
||||||
|
var wrapped = ((index % providers.length) + providers.length) % providers.length
|
||||||
|
selectedProviderId = providers[wrapped].providerId
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshNow() {
|
||||||
|
usage.refreshAll(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function launchAgent() {
|
||||||
|
if (root.bar) root.bar.run("omarchy-agent --pick")
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- limits
|
||||||
|
//
|
||||||
|
// Both providers report the same two shapes: a short rolling session window
|
||||||
|
// and a long weekly one. Everything below normalizes them into one record so
|
||||||
|
// the meters and the hero speak a single language.
|
||||||
|
|
||||||
|
// Claude spells its windows out ("Session (5-hour)"), Codex abbreviates
|
||||||
|
// them ("5h window", "30m window"). Both have to land on the same record.
|
||||||
|
function windowIsLong(text) {
|
||||||
|
return text.indexOf("week") >= 0 || text.indexOf("7-day") >= 0 || text.indexOf("seven") >= 0
|
||||||
|
|| text.indexOf("month") >= 0 || text.indexOf("30-day") >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function windowSpanMs(label) {
|
||||||
|
var text = String(label || "").toLowerCase()
|
||||||
|
if (text.indexOf("month") >= 0 || text.indexOf("30-day") >= 0) return 30 * 24 * 3600 * 1000
|
||||||
|
if (windowIsLong(text)) return 7 * 24 * 3600 * 1000
|
||||||
|
var hours = text.match(/(\d+)\s*-?\s*h(?:our)?\b/)
|
||||||
|
if (hours) return Number(hours[1]) * 3600 * 1000
|
||||||
|
var minutes = text.match(/(\d+)\s*-?\s*m(?:in(?:ute)?s?)?\b/)
|
||||||
|
if (minutes) return Number(minutes[1]) * 60 * 1000
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function windowTitle(label) {
|
||||||
|
var text = String(label || "").toLowerCase()
|
||||||
|
if (text.indexOf("month") >= 0) return "Monthly"
|
||||||
|
if (windowIsLong(text)) return "Weekly"
|
||||||
|
if (text.indexOf("session") >= 0 || windowSpanMs(label) > 0) return "Session"
|
||||||
|
var plain = String(label || "").replace(/\s*\(.*\)\s*/, "").trim()
|
||||||
|
return plain === "" ? "Limit" : plain
|
||||||
|
}
|
||||||
|
|
||||||
|
// A collector that already knows which window a limit belongs to says so,
|
||||||
|
// and that beats reading it back out of the label: a model-scoped limit is
|
||||||
|
// titled after its model, and a name like "Opus 5 (1M context)" would parse
|
||||||
|
// as a one-minute window.
|
||||||
|
function limitWindow(label, percent, resetAt, title) {
|
||||||
|
return {
|
||||||
|
title: String(title || "") !== "" ? String(title) : windowTitle(label),
|
||||||
|
percent: Number(percent),
|
||||||
|
resetAt: String(resetAt || "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function limitWindows(p) {
|
||||||
|
if (!p) return []
|
||||||
|
var out = []
|
||||||
|
var list = p.limits || []
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
var entry = list[i] || {}
|
||||||
|
var percent = Number(entry.percent)
|
||||||
|
if (percent >= 0) out.push(limitWindow(entry.label, percent, entry.resetsAt, entry.title))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// The window that decides how much room is left — the fullest one, since
|
||||||
|
// that is what stops the next prompt.
|
||||||
|
function bindingWindow(p) {
|
||||||
|
var windows = limitWindows(p)
|
||||||
|
var best = null
|
||||||
|
for (var i = 0; i < windows.length; i++) {
|
||||||
|
if (!best || windows[i].percent > best.percent) best = windows[i]
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetMsFor(w) {
|
||||||
|
if (!w || w.resetAt === "") return -1
|
||||||
|
var ms = new Date(w.resetAt).getTime()
|
||||||
|
return isFinite(ms) ? ms - root.nowMs : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(ms) {
|
||||||
|
if (!(ms > 0)) return "now"
|
||||||
|
var minutes = Math.floor(ms / 60000)
|
||||||
|
var hours = Math.floor(minutes / 60)
|
||||||
|
var days = Math.floor(hours / 24)
|
||||||
|
if (days > 0) return days + "d " + (hours % 24) + "h"
|
||||||
|
if (hours > 0) return hours + "h " + (minutes % 60) + "m"
|
||||||
|
return Math.max(1, minutes) + "m"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- balance
|
||||||
|
//
|
||||||
|
// Prepaid agents report a credit ledger instead of rate-limit windows: the
|
||||||
|
// record's balance object carries remaining, funded, and spent amounts.
|
||||||
|
|
||||||
|
function currencyPrefix(currency) {
|
||||||
|
var code = String(currency || "USD").toUpperCase()
|
||||||
|
if (code === "USD") return "$"
|
||||||
|
if (code === "EUR") return "€"
|
||||||
|
if (code === "GBP") return "£"
|
||||||
|
return code + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMoney(value, currency) {
|
||||||
|
var amount = Number(value)
|
||||||
|
if (!isFinite(amount)) amount = 0
|
||||||
|
return currencyPrefix(currency) + amount.toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function balanceDetailText(b) {
|
||||||
|
if (!b || !(b.funded > 0)) return ""
|
||||||
|
var text = formatMoney(b.spent, b.currency) + " spent of " + formatMoney(b.funded, b.currency) + " funded"
|
||||||
|
if (b.estimated) text += " · estimated"
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- content
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local calendar date, recomputed from nowMs so a panel left open across
|
||||||
|
// midnight moves the "Today" row with the clock.
|
||||||
|
function todayDate() {
|
||||||
|
var now = new Date(root.nowMs)
|
||||||
|
return now.getFullYear()
|
||||||
|
+ "-" + String(now.getMonth() + 1).padStart(2, "0")
|
||||||
|
+ "-" + String(now.getDate()).padStart(2, "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
function dayName(date) {
|
||||||
|
var parsed = new Date(String(date || "") + "T00:00:00")
|
||||||
|
if (isNaN(parsed.getTime())) return String(date || "")
|
||||||
|
return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][parsed.getDay()]
|
||||||
|
}
|
||||||
|
|
||||||
|
function dayLabel(date, today) {
|
||||||
|
if (today) return "Today"
|
||||||
|
return dayName(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
function dayTooltip(day, today) {
|
||||||
|
if (!day) return ""
|
||||||
|
var parsed = new Date(String(day.date) + "T00:00:00")
|
||||||
|
var label = isNaN(parsed.getTime())
|
||||||
|
? String(day.date)
|
||||||
|
: dayName(day.date) + " " + (parsed.getMonth() + 1) + "/" + parsed.getDate()
|
||||||
|
var text = label + " · " + usage.formatTokenCount(Number(day.messageCount || 0)) + " tokens"
|
||||||
|
// Prompt and session counts only exist for today, so they ride along here
|
||||||
|
// instead of taking a section of their own. Billing-API agents never
|
||||||
|
// count prompts, and "0 prompts" would read as a quiet day, not a gap.
|
||||||
|
if (today && provider && provider.hasPromptStats !== false)
|
||||||
|
text += " · " + Number(provider.todayPrompts || 0) + " prompts · "
|
||||||
|
+ Number(provider.todaySessions || 0) + " sessions"
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
function weekPeak(p) {
|
||||||
|
var days = p ? (p.recentDays || []) : []
|
||||||
|
var peak = 0
|
||||||
|
for (var i = 0; i < days.length; i++) peak = Math.max(peak, Number(days[i].messageCount || 0))
|
||||||
|
return peak
|
||||||
|
}
|
||||||
|
|
||||||
|
function modelRows(p) {
|
||||||
|
var usageByModel = p ? (p.modelUsage || {}) : {}
|
||||||
|
var rows = []
|
||||||
|
for (var id in usageByModel) {
|
||||||
|
var bucket = usageByModel[id] || {}
|
||||||
|
var input = Number(bucket.inputTokens || 0)
|
||||||
|
var output = Number(bucket.outputTokens || 0)
|
||||||
|
var cacheRead = Number(bucket.cacheReadInputTokens || 0)
|
||||||
|
var cacheWrite = Number(bucket.cacheCreationInputTokens || 0)
|
||||||
|
rows.push({
|
||||||
|
name: usage.friendlyModelName(id),
|
||||||
|
total: input + output + cacheRead + cacheWrite,
|
||||||
|
input: input,
|
||||||
|
output: output,
|
||||||
|
cacheRead: cacheRead,
|
||||||
|
cacheWrite: cacheWrite
|
||||||
|
})
|
||||||
|
}
|
||||||
|
rows.sort(function(a, b) { return b.total - a.total })
|
||||||
|
return rows.slice(0, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
function modelTooltip(row) {
|
||||||
|
if (!row) return ""
|
||||||
|
return "In " + usage.formatTokenCount(row.input)
|
||||||
|
+ " · out " + usage.formatTokenCount(row.output)
|
||||||
|
+ " · cache read " + usage.formatTokenCount(row.cacheRead)
|
||||||
|
+ " · cache write " + usage.formatTokenCount(row.cacheWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only speaks up when the numbers cover more than this machine.
|
||||||
|
function footerText() {
|
||||||
|
if (usage.syncStatusText !== "") return usage.syncStatusText
|
||||||
|
if (provider && provider.syncEnabled && provider.syncDeviceCount > 0)
|
||||||
|
return "Merged from " + provider.syncDeviceCount + " device" + (provider.syncDeviceCount === 1 ? "" : "s")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agents that ship a white mark carry an `assets/<id>-light.svg` twin for
|
||||||
|
// light surfaces; marks that work on both (Claude's brand-orange) ship one
|
||||||
|
// file. The luminance check decides which candidate to try first.
|
||||||
|
function colorChannelLuminance(value) {
|
||||||
|
var channel = Number(value)
|
||||||
|
if (!isFinite(channel)) return 0
|
||||||
|
return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorLuminance(color) {
|
||||||
|
return 0.2126 * colorChannelLuminance(color.r)
|
||||||
|
+ 0.7152 * colorChannelLuminance(color.g)
|
||||||
|
+ 0.0722 * colorChannelLuminance(color.b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marks resolve by convention, so a new agent's data file needs nothing
|
||||||
|
// from this panel: assets/<id>.svg if it ships one, the module's bar glyph
|
||||||
|
// if it doesn't.
|
||||||
|
function iconCandidatesForProvider(p, surfaceColor) {
|
||||||
|
if (!p) return []
|
||||||
|
var candidates = []
|
||||||
|
if (colorLuminance(surfaceColor || Color.background) >= 0.5)
|
||||||
|
candidates.push(Qt.resolvedUrl("assets/" + p.providerId + "-light.svg"))
|
||||||
|
candidates.push(Qt.resolvedUrl("assets/" + p.providerId + ".svg"))
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing to report, nothing in the bar: Bar.qml collapses a slot whose item
|
||||||
|
// 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
|
||||||
|
|
||||||
|
onProviderIndexChanged: if (panelFlick) panelFlick.contentY = 0
|
||||||
|
onOpenedChanged: if (opened) {
|
||||||
|
cursorActive = false
|
||||||
|
nowMs = Date.now()
|
||||||
|
if (panelFlick) panelFlick.contentY = 0
|
||||||
|
usage.refreshLimits()
|
||||||
|
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
|
||||||
|
}
|
||||||
|
|
||||||
|
Main {
|
||||||
|
id: usage
|
||||||
|
settings: root.settings
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cheap enough to keep running: it only re-evaluates text bindings, and a
|
||||||
|
// stale "resets in 2h" on a panel that is open is worse than a timer.
|
||||||
|
Timer {
|
||||||
|
interval: 30000
|
||||||
|
running: root.opened
|
||||||
|
repeat: true
|
||||||
|
onTriggered: root.nowMs = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: root.ipcTarget
|
||||||
|
function open(): void { root.open() }
|
||||||
|
function close(): void { root.close() }
|
||||||
|
function show(): void { root.open() }
|
||||||
|
function hide(): void { root.close() }
|
||||||
|
function toggle(): void { root.toggle() }
|
||||||
|
function refresh(): string { root.refreshNow(); return "ok" }
|
||||||
|
function next(): string { root.selectProvider(root.providerIndex + 1); return "ok" }
|
||||||
|
}
|
||||||
|
|
||||||
|
BarIconButton {
|
||||||
|
id: button
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardPanel {
|
||||||
|
id: panel
|
||||||
|
anchorItem: button
|
||||||
|
owner: root
|
||||||
|
bar: root.bar
|
||||||
|
open: root.opened
|
||||||
|
focusTarget: keyCatcher
|
||||||
|
contentWidth: panel.fittedContentWidth(Style.space(380))
|
||||||
|
// Taller than the control panels on purpose: this one is a dashboard, and
|
||||||
|
// the whole point is reading limits and history without scrolling.
|
||||||
|
contentHeight: panel.fittedContentHeight(column.implicitHeight, Style.space(640))
|
||||||
|
|
||||||
|
PanelKeyCatcher {
|
||||||
|
id: keyCatcher
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onMoveRequested: function(dx, dy) {
|
||||||
|
if (dx !== 0) {
|
||||||
|
root.cursorActive = true
|
||||||
|
root.selectProvider(root.providerIndex + dx)
|
||||||
|
}
|
||||||
|
if (dy !== 0)
|
||||||
|
panelFlick.contentY = root.clamp(panelFlick.contentY + dy * Style.space(56), 0,
|
||||||
|
Math.max(0, panelFlick.contentHeight - panelFlick.height))
|
||||||
|
}
|
||||||
|
onActivateRequested: root.refreshNow()
|
||||||
|
onCloseRequested: root.close()
|
||||||
|
onTabRequested: function(direction) { root.switchPanel(direction) }
|
||||||
|
onTextKey: function(t) { if (t === "r" || t === "R") root.refreshNow() }
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: panelFlick
|
||||||
|
anchors.fill: parent
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: column.implicitHeight
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
interactive: contentHeight > height
|
||||||
|
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: column
|
||||||
|
width: panelFlick.width
|
||||||
|
spacing: Style.space(12)
|
||||||
|
|
||||||
|
// ---------- Hero: provider mark · name · plan ----------
|
||||||
|
PanelHero {
|
||||||
|
id: hero
|
||||||
|
visible: !!root.provider
|
||||||
|
width: parent.width
|
||||||
|
title: root.provider ? root.provider.providerName : ""
|
||||||
|
meta: root.heroMeta(root.provider)
|
||||||
|
foreground: root.foreground
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
|
||||||
|
iconComponent: Component {
|
||||||
|
Item {
|
||||||
|
id: heroMark
|
||||||
|
property var candidates: root.iconCandidatesForProvider(root.provider, root.surface)
|
||||||
|
// Provider objects are rebuilt on every refresh, which churns the
|
||||||
|
// array's identity without changing its content. Restart the fallback
|
||||||
|
// walk only when the URLs change: re-pointing source at a URL whose
|
||||||
|
// load already failed emits no statusChanged, so an identity-only
|
||||||
|
// reset would strand the walker on a missing -light twin.
|
||||||
|
property string candidatesKey: candidates.join("\n")
|
||||||
|
property int candidateIndex: 0
|
||||||
|
onCandidatesKeyChanged: candidateIndex = 0
|
||||||
|
|
||||||
|
width: Style.font.display
|
||||||
|
height: Style.font.display
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: heroMarkImage
|
||||||
|
anchors.fill: parent
|
||||||
|
source: heroMark.candidateIndex < heroMark.candidates.length ? heroMark.candidates[heroMark.candidateIndex] : ""
|
||||||
|
sourceSize.width: Style.font.display * 2
|
||||||
|
sourceSize.height: Style.font.display * 2
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
// Advancing source from inside its own status change trips the
|
||||||
|
// binding-loop detector; defer the step one tick.
|
||||||
|
onStatusChanged: if (status === Image.Error && heroMark.candidateIndex < heroMark.candidates.length)
|
||||||
|
Qt.callLater(function() { heroMark.candidateIndex++ })
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: heroMarkImage.status !== Image.Ready
|
||||||
|
text: button.text
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.display
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: root.providers.length === 0
|
||||||
|
width: parent.width
|
||||||
|
topPadding: Style.space(24)
|
||||||
|
text: "No AI coding subscriptions found.\nAgents show up here once you've used them."
|
||||||
|
color: root.dim
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.body
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Provider switch ----------
|
||||||
|
Row {
|
||||||
|
id: providerSwitch
|
||||||
|
visible: root.providers.length > 1
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.spacing.md
|
||||||
|
|
||||||
|
readonly property real cellWidth: root.providers.length > 0
|
||||||
|
? (width - spacing * (root.providers.length - 1)) / root.providers.length
|
||||||
|
: 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.providers
|
||||||
|
|
||||||
|
Button {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
width: providerSwitch.cellWidth
|
||||||
|
text: modelData.providerName
|
||||||
|
selected: index === root.providerIndex
|
||||||
|
hasCursor: root.cursorActive && index === root.providerIndex
|
||||||
|
bordered: true
|
||||||
|
foreground: root.foreground
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
fontSize: Style.font.bodySmall
|
||||||
|
verticalPadding: Style.spacing.controlPaddingY
|
||||||
|
onClicked: {
|
||||||
|
root.cursorActive = true
|
||||||
|
root.selectProvider(index)
|
||||||
|
}
|
||||||
|
onHovered: function(isHovered) { if (isHovered) root.cursorActive = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Status ----------
|
||||||
|
BorderSurface {
|
||||||
|
visible: !!root.provider && String(root.provider.usageStatusText || "") !== ""
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: statusText.implicitHeight + Style.spacing.xl * 2
|
||||||
|
color: root.alpha(root.urgent, 0.10)
|
||||||
|
borderSpec: Border.flat(root.alpha(root.urgent, 0.35), 1)
|
||||||
|
radius: Style.cornerRadius
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: statusText
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: Style.space(12)
|
||||||
|
anchors.rightMargin: Style.space(12)
|
||||||
|
text: root.provider ? String(root.provider.authHelpText || "") : ""
|
||||||
|
color: root.dim
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Balance / limits ----------
|
||||||
|
PanelSeparator {
|
||||||
|
visible: balanceSection.visible || limitsSection.visible
|
||||||
|
foreground: root.foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: balanceSection
|
||||||
|
visible: !!root.balance
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.space(10)
|
||||||
|
|
||||||
|
// The meter shows what is left, not what is used: a prepaid
|
||||||
|
// account drains toward empty rather than filling toward a cap.
|
||||||
|
readonly property real ratio: root.balance && root.balance.funded > 0
|
||||||
|
? root.clamp(root.balance.remaining / root.balance.funded, 0, 1)
|
||||||
|
: -1
|
||||||
|
|
||||||
|
PanelSectionHeader {
|
||||||
|
width: parent.width
|
||||||
|
text: "BALANCE"
|
||||||
|
foreground: root.foreground
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: Math.max(balanceLabel.implicitHeight, balanceValue.implicitHeight)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: balanceLabel
|
||||||
|
text: "Prepaid credits"
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.body
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: balanceValue
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
text: root.balance ? root.formatMoney(root.balance.remaining, root.balance.currency) : ""
|
||||||
|
color: root.balanceAlarming ? root.urgent : root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Meter {
|
||||||
|
visible: balanceSection.ratio >= 0
|
||||||
|
width: parent.width
|
||||||
|
value: balanceSection.ratio
|
||||||
|
alarming: root.balanceAlarming
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
visible: text !== ""
|
||||||
|
width: parent.width
|
||||||
|
text: root.balanceDetailText(root.balance)
|
||||||
|
color: root.dim
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: limitsSection
|
||||||
|
visible: root.limits.length > 0
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.space(10)
|
||||||
|
|
||||||
|
PanelSectionHeader {
|
||||||
|
text: "LIMITS"
|
||||||
|
foreground: root.foreground
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.limits
|
||||||
|
|
||||||
|
LimitRow {
|
||||||
|
required property var modelData
|
||||||
|
width: limitsSection.width
|
||||||
|
window: modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Usage ----------
|
||||||
|
PanelSeparator {
|
||||||
|
visible: usageSection.visible
|
||||||
|
foreground: root.foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: usageSection
|
||||||
|
visible: !!root.provider && root.provider.recentDays && root.provider.recentDays.length > 0
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.spacing.md
|
||||||
|
|
||||||
|
readonly property var days: root.provider ? (root.provider.recentDays || []) : []
|
||||||
|
readonly property real peak: Math.max(1, root.weekPeak(root.provider))
|
||||||
|
|
||||||
|
PanelSectionHeader {
|
||||||
|
width: parent.width
|
||||||
|
text: "TOKENS BY DAY"
|
||||||
|
foreground: root.foreground
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: usageSection.days
|
||||||
|
|
||||||
|
DayRow {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
width: usageSection.width
|
||||||
|
day: modelData
|
||||||
|
ratio: Number(modelData.messageCount || 0) / usageSection.peak
|
||||||
|
// By date, not by position: the Claude stats-cache fallback can
|
||||||
|
// hand us a window that stops short of today.
|
||||||
|
today: String(modelData.date || "") === root.todayDate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Models ----------
|
||||||
|
PanelSeparator {
|
||||||
|
visible: modelSection.visible
|
||||||
|
foreground: root.foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: modelSection
|
||||||
|
visible: root.models.length > 0
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.spacing.md
|
||||||
|
|
||||||
|
PanelSectionHeader {
|
||||||
|
width: parent.width
|
||||||
|
text: "TOKENS BY MODEL"
|
||||||
|
foreground: root.foreground
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.models
|
||||||
|
|
||||||
|
ModelRow {
|
||||||
|
required property var modelData
|
||||||
|
width: modelSection.width
|
||||||
|
row: modelData
|
||||||
|
// Scaled to the heaviest model, so the top row is always full —
|
||||||
|
// the same scale-to-peak the weekly chart uses for its busiest day.
|
||||||
|
share: modelData.total / Math.max(1, root.models[0].total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
visible: text !== ""
|
||||||
|
width: parent.width
|
||||||
|
topPadding: Style.space(2)
|
||||||
|
text: root.footerText()
|
||||||
|
color: root.dim
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A limit window: label and percentage, meter, and reset countdown.
|
||||||
|
component LimitRow: Column {
|
||||||
|
id: limitRow
|
||||||
|
property var window: null
|
||||||
|
|
||||||
|
readonly property bool alarming: window && window.percent >= 0.9
|
||||||
|
|
||||||
|
spacing: Style.space(6)
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: Math.max(limitLabel.implicitHeight, limitValue.implicitHeight)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: limitLabel
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
// A model-scoped window is titled after its model, and those names run
|
||||||
|
// long enough to reach the percentage, so the title gives way first.
|
||||||
|
text: limitRow.window ? limitRow.window.title : ""
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.body
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: limitValue.left
|
||||||
|
anchors.rightMargin: Style.spacing.sm
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: limitValue
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
text: limitRow.window && limitRow.window.percent >= 0
|
||||||
|
? Math.round(limitRow.window.percent * 100) + "%"
|
||||||
|
: "—"
|
||||||
|
color: limitRow.alarming ? root.urgent : root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Meter {
|
||||||
|
width: parent.width
|
||||||
|
value: limitRow.window ? limitRow.window.percent : -1
|
||||||
|
alarming: limitRow.alarming
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: resetText
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
width: parent.width
|
||||||
|
text: {
|
||||||
|
var remainingMs = root.resetMsFor(limitRow.window)
|
||||||
|
return remainingMs > 0 ? "Resets in " + root.formatDuration(remainingMs) : ""
|
||||||
|
}
|
||||||
|
color: root.dim
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rounded track showing the percentage of the allowance used.
|
||||||
|
component Meter: Item {
|
||||||
|
id: meter
|
||||||
|
property real value: -1
|
||||||
|
property bool alarming: false
|
||||||
|
property real thickness: Math.max(Style.space(4), Math.round(Style.spacing.controlHeight * 0.14))
|
||||||
|
|
||||||
|
implicitHeight: thickness
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: meterTrack
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: height / 2
|
||||||
|
color: root.track
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: meterTrack.left
|
||||||
|
anchors.verticalCenter: meterTrack.verticalCenter
|
||||||
|
height: meterTrack.height
|
||||||
|
radius: meterTrack.radius
|
||||||
|
width: meterTrack.width * root.clamp(meter.value, 0, 1)
|
||||||
|
color: meter.alarming ? root.urgent : root.foreground
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// One row per day: label, bar, tokens. Today is picked out in full
|
||||||
|
// foreground so the week reads as a run-up to right now.
|
||||||
|
component DayRow: Item {
|
||||||
|
id: dayRow
|
||||||
|
property var day: null
|
||||||
|
property real ratio: 0
|
||||||
|
property bool today: false
|
||||||
|
|
||||||
|
implicitHeight: Math.max(dayLabel.implicitHeight, dayValue.implicitHeight) + Style.spacing.sm
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: dayLabel
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
text: root.dayLabel(dayRow.day ? dayRow.day.date : "", dayRow.today)
|
||||||
|
color: dayRow.today ? root.foreground : root.dim
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
font.bold: dayRow.today
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Style.space(52)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: dayTrack
|
||||||
|
anchors.left: dayLabel.right
|
||||||
|
anchors.right: dayValue.left
|
||||||
|
anchors.leftMargin: Style.space(8)
|
||||||
|
anchors.rightMargin: Style.space(10)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
height: Math.max(Style.space(4), Math.round(Style.spacing.controlHeight * 0.14))
|
||||||
|
radius: height / 2
|
||||||
|
color: root.track
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
height: parent.height
|
||||||
|
radius: parent.radius
|
||||||
|
width: parent.width * root.clamp(dayRow.ratio, 0, 1)
|
||||||
|
color: dayRow.today ? root.foreground : root.alpha(root.foreground, 0.55)
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: dayValue
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
text: usage.formatTokenCount(dayRow.day ? Number(dayRow.day.messageCount || 0) : 0)
|
||||||
|
color: dayRow.today ? root.foreground : root.dim
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
font.bold: true
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Style.space(52)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dayHover
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelToolTip {
|
||||||
|
visible: dayHover.containsMouse
|
||||||
|
text: root.dayTooltip(dayRow.day, dayRow.today)
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model rows read as a table: the share bar fills the row behind the label
|
||||||
|
// instead of stacking under it, which keeps the whole dashboard on one screen.
|
||||||
|
component ModelRow: Item {
|
||||||
|
id: modelRow
|
||||||
|
property var row: null
|
||||||
|
property real share: 0
|
||||||
|
|
||||||
|
implicitHeight: modelName.implicitHeight + Style.spacing.lg
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Style.cornerRadius
|
||||||
|
color: root.alpha(root.foreground, 0.05)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: parent.width * root.clamp(modelRow.share, 0, 1)
|
||||||
|
radius: Style.cornerRadius
|
||||||
|
color: root.alpha(root.foreground, 0.14)
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: modelName
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
text: modelRow.row ? modelRow.row.name : ""
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
elide: Text.ElideRight
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Style.space(8)
|
||||||
|
anchors.right: modelTokens.left
|
||||||
|
anchors.rightMargin: Style.space(8)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: modelTokens
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
text: modelRow.row ? usage.formatTokenCount(modelRow.row.total) : ""
|
||||||
|
color: root.dim
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
font.bold: true
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Style.space(8)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: modelHover
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelToolTip {
|
||||||
|
visible: modelHover.containsMouse
|
||||||
|
text: root.modelTooltip(modelRow.row)
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# Agents
|
||||||
|
|
||||||
|
One bar icon and one panel for every AI coding subscription on the machine.
|
||||||
|
The panel is strictly a display: it watches the usage records that
|
||||||
|
`omarchy-agent-usage-update` writes to `~/.local/state/omarchy/agents/usage/`
|
||||||
|
and draws whatever appears there. `Panel.qml` owns the bar button and the
|
||||||
|
popup; `Main.qml` discovers and watches the records (and handles the optional
|
||||||
|
cross-device aggregation); `Agent.qml` is the per-record file watcher.
|
||||||
|
|
||||||
|
## Panel
|
||||||
|
|
||||||
|
- **Hero** — the mark, the tool, and the plan it runs on ("Max 20x", "Pro").
|
||||||
|
Auth and endpoint problems replace the plan line and repeat in a card.
|
||||||
|
- **Subscription switch** — one chip per enabled agent (`h`/`l` or click).
|
||||||
|
It appears only when more than one agent is enabled.
|
||||||
|
- **Limits** — the percentage of each allowance used, a matching meter, and
|
||||||
|
the time until the session or weekly window resets.
|
||||||
|
- **Balance** — prepaid agents report a credit ledger instead of limits:
|
||||||
|
remaining credit, a fuel-gauge meter that drains toward empty, and
|
||||||
|
funded-versus-spent detail.
|
||||||
|
- **Tokens by day** — one row per day for the last week: day, bar, tokens, with today
|
||||||
|
bolded at the bottom. Hover today for its prompt and session count.
|
||||||
|
- **Tokens by model** — tokens per model with the bar behind each row scaled
|
||||||
|
to the heaviest model,
|
||||||
|
the same way the weekly chart scales to its busiest day. Hover for the
|
||||||
|
input / output / cache split.
|
||||||
|
|
||||||
|
A subscription appears only when it is enabled in settings and has actually
|
||||||
|
recorded usage — on this machine or on a synced one. With one such agent
|
||||||
|
there is no switch row at all; with none, the module leaves the bar entirely
|
||||||
|
rather than sitting there with nothing to say. A CLI installed mid-session
|
||||||
|
shows up at the next refresh, so nothing polls the disk waiting for it.
|
||||||
|
|
||||||
|
That self-hiding is why the widget ships in the default bar layout: a machine
|
||||||
|
that has never run an AI coding agent draws nothing, and the icon arrives on
|
||||||
|
its own the first time a scan finds usage. Drop it with
|
||||||
|
`omarchy plugin disable omarchy.agents`.
|
||||||
|
|
||||||
|
## Data
|
||||||
|
|
||||||
|
Each agent is one JSON record in `~/.local/state/omarchy/agents/usage/`,
|
||||||
|
written by `omarchy-agent-usage-update`. That command runs one
|
||||||
|
`omarchy-agent-usage-<agent>` collector per agent; the widget invokes it
|
||||||
|
on its refresh timer and whenever you ask for a refresh, and picks up any
|
||||||
|
record that lands in the directory regardless of who wrote it.
|
||||||
|
|
||||||
|
Adding an agent therefore never touches this plugin: ship a collector that
|
||||||
|
prints the record contract (see the `claude` and `codex` collectors in
|
||||||
|
`bin/`), and the panel gains a tab. An `assets/<id>.svg` mark is optional —
|
||||||
|
with an `assets/<id>-light.svg` twin if the mark needs a dark variant for
|
||||||
|
light surfaces — and the bar glyph stands in when there is none.
|
||||||
|
|
||||||
|
| Collector | Limits | Local stats |
|
||||||
|
|---|---|---|
|
||||||
|
| `claude` | Anthropic's OAuth usage endpoint (5-hour session + 7-day weekly) | `~/.claude/projects` transcripts, opencode sessions on an Anthropic provider, plus `stats-cache.json` and `history.jsonl` as fallback |
|
||||||
|
| `codex` | The Codex app-server RPC | native Codex CLI session files (plus pi and opencode sessions) |
|
||||||
|
| `fireworks` | Estimated prepaid balance: configured funding minus rated account costs | Fireworks billing API, grouped by day and model for the last 30 days |
|
||||||
|
|
||||||
|
Claude limits need a signed-in CLI; without credentials the panel says so and
|
||||||
|
falls back to local stats only. A non-default Claude directory is honored via
|
||||||
|
`CLAUDE_CONFIG_DIR`, Codex via `CODEX_HOME`. Fireworks reads
|
||||||
|
`FIREWORKS_API_KEY` and `FIREWORKS_ACCOUNT_ID` first, then
|
||||||
|
`~/.fireworks/auth.ini` (which `firectl set-api-key` creates), then the key
|
||||||
|
opencode stores in `~/.local/share/opencode/auth.json` when Fireworks is
|
||||||
|
signed in there.
|
||||||
|
|
||||||
|
### Fireworks balance
|
||||||
|
|
||||||
|
The collector first asks the account's `:getBalance` endpoint for the real
|
||||||
|
prepaid ledger. That endpoint exists but is permission-gated, and as of
|
||||||
|
August 2026 no console-issued API key passes it — Fireworks appears to
|
||||||
|
reserve it for the dashboard session. The probe stays because it is cheap
|
||||||
|
and the live figure lights up automatically if Fireworks ever opens it to
|
||||||
|
keys. Until then the collector falls back to estimating the balance from
|
||||||
|
configuration in `~/.config/omarchy/agents/fireworks.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"accountId": "",
|
||||||
|
"fundedAmount": 20,
|
||||||
|
"fundedAt": "2026-07-01"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `fundedAmount` to the credits purchased and optionally `fundedAt` to the
|
||||||
|
purchase date; with no date, the collector uses the account creation time. It
|
||||||
|
subtracts rated account costs and the panel labels the result as estimated.
|
||||||
|
For a later top-up, increase `fundedAmount` by the new credit while keeping
|
||||||
|
the original `fundedAt`, so both the funding and spend still cover the same
|
||||||
|
period. `accountId` only matters when one API key can access several
|
||||||
|
accounts. Without a configured `fundedAmount` the tab still shows token
|
||||||
|
usage, just no balance. With a live ledger, `fundedAmount` is optional and
|
||||||
|
only adds the meter and the spent-of-funded line under the real figure.
|
||||||
|
|
||||||
|
## Interactions
|
||||||
|
|
||||||
|
- Bar icon: left = panel, right = launch agent, middle = next subscription.
|
||||||
|
- Panel: `h`/`l` switch subscription, `j`/`k` scroll, `r` or Enter refresh,
|
||||||
|
Tab moves to the neighboring bar panel, Esc closes.
|
||||||
|
- IPC: `omarchy-shell omarchy.agents <open|close|toggle|refresh|next>`.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
Settings live in the widget's entry in `~/.config/omarchy/shell.json`. The
|
||||||
|
top-level keys can be set with
|
||||||
|
`omarchy bar set omarchy.agents <key> <value>`:
|
||||||
|
|
||||||
|
| Key | Default | What it does |
|
||||||
|
|---|---|---|
|
||||||
|
| `refreshIntervalSec` | `900` | How often the usage records regenerate |
|
||||||
|
| `syncMode` | `"Off"` | `"On"` writes this machine's snapshot and merges the others |
|
||||||
|
| `syncDir` | `""` | A folder synced by Syncthing, Dropbox, rsync, … |
|
||||||
|
| `syncFileName` | `<hostname>.json` | This machine's snapshot file |
|
||||||
|
| `syncDeviceId` | hostname | Stable device name inside the snapshot |
|
||||||
|
|
||||||
|
Numbers need `--json`, or they land in `shell.json` as strings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy bar set omarchy.agents refreshIntervalSec 300 --json
|
||||||
|
omarchy bar set omarchy.agents syncDir '~/Sync/agent-usage'
|
||||||
|
```
|
||||||
|
|
||||||
|
Per-agent enablement is nested, and `set` writes its key literally rather
|
||||||
|
than walking a dotted path — so pass the whole `providers` object as JSON (or
|
||||||
|
edit `shell.json` directly):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy bar set omarchy.agents providers '{
|
||||||
|
"claude": { "enabled": true },
|
||||||
|
"codex": { "enabled": false },
|
||||||
|
"fireworks": { "enabled": true }
|
||||||
|
}' --json
|
||||||
|
```
|
||||||
|
|
||||||
|
`enabled` defaults to `true` for every discovered agent; set it to `false` to
|
||||||
|
hide a subscription that is installed. Disabled agents are also skipped when
|
||||||
|
the records regenerate.
|
||||||
|
|
||||||
|
With `syncMode` on, every `*.json` snapshot in `syncDir` is merged, so today,
|
||||||
|
the last 7 days, and the all-time totals cover every machine you code on —
|
||||||
|
active days are unioned by date rather than summed. Rate limits stay
|
||||||
|
per-account and are never merged. A record may declare `"scope": "account"`
|
||||||
|
when its stats are account-global rather than machine-local (Fireworks'
|
||||||
|
billing API); those merge by taking the widest value instead of summing, so
|
||||||
|
the same account synced from two machines is not counted twice.
|
||||||
|
|
||||||
|
One caveat on "all-time": the Codex collector only reads native session files
|
||||||
|
touched in the last 30 days, and Fireworks requests the last 30 days from its
|
||||||
|
billing API, so their totals and day counts cover that window. Claude's cover
|
||||||
|
every transcript still on disk.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 91.583 78.750" fill="#111"><title>Antigravity</title><path d="M79.917,75.250 C84.583,78.750 91.583,76.417 85.167,70.000 C65.917,51.333 70.000,0.000 46.083,0.000 C22.167,0.000 26.250,51.333 7.000,70.000 C0.000,77.000 7.583,78.750 12.250,75.250 C30.333,63.000 29.167,41.417 46.083,41.417 C63.000,41.417 61.833,63.000 79.917,75.250 Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 393 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 91.583 78.750" fill="#fff"><title>Antigravity</title><path d="M79.917,75.250 C84.583,78.750 91.583,76.417 85.167,70.000 C65.917,51.333 70.000,0.000 46.083,0.000 C22.167,0.000 26.250,51.333 7.000,70.000 C0.000,77.000 7.583,78.750 12.250,75.250 C30.333,63.000 29.167,41.417 46.083,41.417 C63.000,41.417 61.833,63.000 79.917,75.250 Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 393 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="257" preserveAspectRatio="xMidYMid" viewBox="0 0 256 257"><path fill="#D97757" d="m50.228 170.321 50.357-28.257.843-2.463-.843-1.361h-2.462l-8.426-.518-28.775-.778-24.952-1.037-24.175-1.296-6.092-1.297L0 125.796l.583-3.759 5.12-3.434 7.324.648 16.202 1.101 24.304 1.685 17.629 1.037 26.118 2.722h4.148l.583-1.685-1.426-1.037-1.101-1.037-25.147-17.045-27.22-18.017-14.258-10.37-7.713-5.25-3.888-4.925-1.685-10.758 7-7.713 9.397.649 2.398.648 9.527 7.323 20.35 15.75L94.817 91.9l3.889 3.24 1.555-1.102.195-.777-1.75-2.917-14.453-26.118-15.425-26.572-6.87-11.018-1.814-6.61c-.648-2.723-1.102-4.991-1.102-7.778l7.972-10.823L71.42 0 82.05 1.426l4.472 3.888 6.61 15.101 10.694 23.786 16.591 32.34 4.861 9.592 2.592 8.879.973 2.722h1.685v-1.556l1.36-18.211 2.528-22.36 2.463-28.776.843-8.1 4.018-9.722 7.971-5.25 6.222 2.981 5.12 7.324-.713 4.73-3.046 19.768-5.962 30.98-3.889 20.739h2.268l2.593-2.593 10.499-13.934 17.628-22.036 7.778-8.749 9.073-9.657 5.833-4.601h11.018l8.1 12.055-3.628 12.443-11.342 14.388-9.398 12.184-13.48 18.147-8.426 14.518.778 1.166 2.01-.194 30.46-6.481 16.462-2.982 19.637-3.37 8.88 4.148.971 4.213-3.5 8.62-20.998 5.184-24.628 4.926-36.682 8.685-.454.324.519.648 16.526 1.555 7.065.389h17.304l32.21 2.398 8.426 5.574 5.055 6.805-.843 5.184-12.962 6.611-17.498-4.148-40.83-9.721-14-3.5h-1.944v1.167l11.666 11.406 21.387 19.314 26.767 24.887 1.36 6.157-3.434 4.86-3.63-.518-23.526-17.693-9.073-7.972-20.545-17.304h-1.36v1.814l4.73 6.935 25.017 37.59 1.296 11.536-1.814 3.76-6.481 2.268-7.13-1.297-14.647-20.544-15.1-23.138-12.185-20.739-1.49.843-7.194 77.448-3.37 3.953-7.778 2.981-6.48-4.925-3.436-7.972 3.435-15.749 4.148-20.544 3.37-16.333 3.046-20.285 1.815-6.74-.13-.454-1.49.194-15.295 20.999-23.267 31.433-18.406 19.702-4.407 1.75-7.648-3.954.713-7.064 4.277-6.286 25.47-32.405 15.36-20.092 9.917-11.6-.065-1.686h-.583L44.07 198.125l-12.055 1.555-5.185-4.86.648-7.972 2.463-2.593 20.35-13.999-.064.065Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#111" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Codex</title><path clip-rule="evenodd" d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#fff" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Codex</title><path clip-rule="evenodd" d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#111" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>GitHub Copilot</title><path fill-rule="evenodd" d="M3.5 7C4.1 6.4 4.9 6 5.7 6l9.7.1c.8 0 1.6.3 2.2.9l2.6 2.6c.6.6.9 1.4.9 2.2V16c0 .6-.4 1-1 1h-1c0-.1-.1-.2-.1-.3a2.7 2.7 0 1 0-5.3.3h-5.2a2.7 2.7 0 1 0-4.5 0c0-.1-.1-.1-.2-.1a1.8 1.8 0 0 1-1.8-1.8V11c0-.8.3-1.6.9-2.2.5-.5 1.1-.8 1.7-1zM5.7 8c-.2 0-.4.1-.5.2-.3.3-.4.7-.4 1v4.3c.9-.8 2.1-1.2 3.3-1.2s2.4.5 3.3 1.2V9.9l-5.7-.2h-.6zm9.3 1.1v2.3c.9-.8 2.1-1.3 3.3-1.3s2.4.5 3.3 1.2V10.6c0-.4-.1-.7-.4-1-.3-.3-.6-.4-.9-.5l-5.3-.1zm-8.5 6c-.8 0-1.5.4-1.9 1.2a2.4 2.4 0 1 0 3.8 0 2 2 0 0 0-.6-.7 2 2 0 0 0-1.3-.5zm7.8 0c-.8 0-1.5.4-1.9 1.2a2.4 2.4 0 1 0 3.8 0 2 2 0 0 0-.6-.7 2 2 0 0 0-1.3-.5z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 821 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#fff" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>GitHub Copilot</title><path fill-rule="evenodd" d="M3.5 7C4.1 6.4 4.9 6 5.7 6l9.7.1c.8 0 1.6.3 2.2.9l2.6 2.6c.6.6.9 1.4.9 2.2V16c0 .6-.4 1-1 1h-1c0-.1-.1-.2-.1-.3a2.7 2.7 0 1 0-5.3.3h-5.2a2.7 2.7 0 1 0-4.5 0c0 .1-.1.1-.2.1a1.8 1.8 0 0 1-1.8-1.8V11c0-.8.3-1.6.9-2.2.5-.5 1.1-.8 1.7-1zM5.7 8c-.2 0-.4.1-.5.2-.3.3-.4.7-.4 1v4.3c.9-.8 2.1-1.2 3.3-1.2s2.4.5 3.3 1.2V9.9l-5.7-.2h-.6zm9.3 1.1v2.3c.9-.8 2.1-1.3 3.3-1.3s2.4.5 3.3 1.2V10.6c0-.4-.1-.7-.4-1-.3-.3-.6-.4-.9-.5l-5.3-.1zm-8.5 6c-.8 0-1.5.4-1.9 1.2a2.4 2.4 0 1 0 3.8 0 2 2 0 0 0-.6-.7 2 2 0 0 0-1.3-.5zm7.8 0c-.8 0-1.5.4-1.9 1.2a2.4 2.4 0 1 0 3.8 0 2 2 0 0 0-.6-.7 2 2 0 0 0-1.3-.5z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 819 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<g fill="none" stroke="#111" stroke-linejoin="round" stroke-width="4">
|
||||||
|
<path d="M32 6 54 19v26L32 58 10 45V19z"/>
|
||||||
|
<path d="M32 32 10 19M32 32l22-13M32 32v26"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 245 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<g fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="4">
|
||||||
|
<path d="M32 6 54 19v26L32 58 10 45V19z"/>
|
||||||
|
<path d="M32 32 10 19M32 32l22-13M32 32v26"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 245 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#4D6BFE" fill-rule="evenodd">
|
||||||
|
<title>DeepSeek</title>
|
||||||
|
<path d="M6.6 8.8C5 7.2 3.4 6.4 2.2 6.4C3 8 4.6 10 6.4 11.2C4.8 13 3.2 15.6 2.6 16.6C3.8 18 5.8 16 7.6 13.6C9.4 14.6 11.8 15 13.6 14.6L14.6 16.8L13.2 17.2L12.6 14.8C15.8 15.4 18.4 15.2 19.8 14.2C22 13.2 22.6 11.4 21.8 9.8C21 8 18 6.6 14.8 7.2C12 6.6 8.6 7.6 6.6 8.8ZM17.6 9.9A1.05 1.05 0 1 0 17.6 12A1.05 1.05 0 1 0 17.6 9.9Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 460 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29.9185 34" fill="none">
|
||||||
|
<path d="M14.1174 13.9205C13.0208 13.2872 11.6461 13.2872 10.5495 13.9205L7.70744 15.5732C7.61476 15.6195 7.52209 15.6659 7.41397 15.6968C7.39852 15.6968 7.36763 15.6968 7.35218 15.7122C7.25951 15.7277 7.15139 15.7431 7.05871 15.7585H7.04327C7.04327 15.7585 7.02782 15.7585 7.01238 15.7585C6.9197 15.7585 6.82703 15.7431 6.73435 15.7277C6.71891 15.7277 6.70346 15.7277 6.68802 15.7277C6.59534 15.6968 6.50267 15.6659 6.40999 15.6195C6.40999 15.6195 6.39454 15.6195 6.3791 15.6195L0.679612 12.3296C0.278022 12.0979 0.0154432 11.6654 0.0154432 11.1866V4.6067C0.0154432 4.12788 0.262576 3.6954 0.679612 3.46371L6.3791 0.173765C6.78069 -0.0579216 7.2904 -0.0579216 7.69199 0.173765L13.3915 3.46371C13.3915 3.46371 13.3915 3.46371 13.4069 3.46371C13.4996 3.51005 13.5768 3.57183 13.6541 3.64906C13.6541 3.64906 13.6695 3.66451 13.6849 3.67995C13.7467 3.74174 13.8085 3.81897 13.8549 3.8962C13.8549 3.8962 13.8703 3.91164 13.8703 3.94253C13.9166 4.03521 13.963 4.12788 13.9784 4.22056C13.9784 4.236 13.9784 4.26689 13.9939 4.28234C14.0093 4.37501 14.0402 4.48313 14.0402 4.59125V7.8812C14.0402 9.14776 14.7198 10.3371 15.8165 10.9704C16.9131 11.6036 18.2878 11.6036 19.3844 10.9704L22.2265 9.31766C22.3191 9.27132 22.4118 9.22499 22.5199 9.19409C22.5354 9.19409 22.5663 9.19409 22.5817 9.17865C22.6744 9.1632 22.7825 9.14776 22.8752 9.13231H22.9215C23.0142 9.13231 23.1069 9.14776 23.1995 9.1632C23.215 9.1632 23.2304 9.1632 23.2459 9.1632C23.3386 9.19409 23.4312 9.22499 23.5239 9.27132C23.5239 9.27132 23.5393 9.27132 23.5548 9.27132L29.2543 12.5613C29.6559 12.793 29.9185 13.2254 29.9185 13.7043V20.2842C29.9185 20.763 29.6713 21.1955 29.2543 21.4271L23.5548 24.7171C23.5548 24.7171 23.5393 24.7171 23.5239 24.7171C23.4312 24.7634 23.3386 24.8098 23.2459 24.8252C23.2304 24.8252 23.215 24.8252 23.1995 24.8252C23.1069 24.8407 23.0142 24.8561 22.9215 24.8561C22.9215 24.8561 22.9061 24.8561 22.8906 24.8561C22.798 24.8561 22.6898 24.8407 22.5972 24.8098C22.5817 24.8098 22.5508 24.8098 22.5354 24.7943C22.4427 24.7634 22.3346 24.7325 22.2419 24.6708L19.3999 23.0181C18.3032 22.3848 16.9286 22.3848 15.8319 23.0181C14.7353 23.6513 14.0556 24.8407 14.0556 26.1072V29.3972C14.0556 29.5053 14.0402 29.6134 14.0093 29.7061C14.0093 29.7215 14.0093 29.7524 13.9939 29.7679C13.963 29.8605 13.9321 29.9532 13.8857 30.0459C13.8857 30.0459 13.8703 30.0768 13.8549 30.0922C13.8085 30.1694 13.7467 30.2467 13.6849 30.3085C13.6849 30.3085 13.6695 30.3239 13.6541 30.3394C13.5768 30.4166 13.4996 30.4784 13.4069 30.5247C13.4069 30.5247 13.4069 30.5247 13.3915 30.5247L7.69199 33.8146C7.4912 33.9382 7.25951 34 7.02782 34C6.79614 34 6.56445 33.9382 6.36365 33.8146L0.664168 30.5247C0.262579 30.293 0 29.8605 0 29.3817V22.8018C0 22.323 0.247133 21.8905 0.664168 21.6588L6.36365 18.3689C6.36365 18.3689 6.3791 18.3689 6.39455 18.3689C6.48722 18.3225 6.5799 18.2762 6.67257 18.2608C6.67257 18.2608 6.70346 18.2608 6.71891 18.2608C6.81158 18.2299 6.90425 18.2299 6.99693 18.2299H7.04327C7.13594 18.2299 7.24406 18.2453 7.33674 18.2762C7.35218 18.2762 7.38307 18.2762 7.39852 18.2762C7.49119 18.3071 7.59932 18.338 7.69199 18.3998L10.534 20.0525C11.6307 20.6857 13.0053 20.6857 14.102 20.0525C15.1986 19.4192 15.8782 18.2299 15.8782 16.9633C15.8782 15.6968 15.1986 14.5074 14.102 13.8742V13.8587L14.1174 13.9205Z" fill="#111"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29.9185 34" fill="none">
|
||||||
|
<path d="M14.1174 13.9205C13.0208 13.2872 11.6461 13.2872 10.5495 13.9205L7.70744 15.5732C7.61476 15.6195 7.52209 15.6659 7.41397 15.6968C7.39852 15.6968 7.36763 15.6968 7.35218 15.7122C7.25951 15.7277 7.15139 15.7431 7.05871 15.7585H7.04327C7.04327 15.7585 7.02782 15.7585 7.01238 15.7585C6.9197 15.7585 6.82703 15.7431 6.73435 15.7277C6.71891 15.7277 6.70346 15.7277 6.68802 15.7277C6.59534 15.6968 6.50267 15.6659 6.40999 15.6195C6.40999 15.6195 6.39454 15.6195 6.3791 15.6195L0.679612 12.3296C0.278022 12.0979 0.0154432 11.6654 0.0154432 11.1866V4.6067C0.0154432 4.12788 0.262576 3.6954 0.679612 3.46371L6.3791 0.173765C6.78069 -0.0579216 7.2904 -0.0579216 7.69199 0.173765L13.3915 3.46371C13.3915 3.46371 13.3915 3.46371 13.4069 3.46371C13.4996 3.51005 13.5768 3.57183 13.6541 3.64906C13.6541 3.64906 13.6695 3.66451 13.6849 3.67995C13.7467 3.74174 13.8085 3.81897 13.8549 3.8962C13.8549 3.8962 13.8703 3.91164 13.8703 3.94253C13.9166 4.03521 13.963 4.12788 13.9784 4.22056C13.9784 4.236 13.9784 4.26689 13.9939 4.28234C14.0093 4.37501 14.0402 4.48313 14.0402 4.59125V7.8812C14.0402 9.14776 14.7198 10.3371 15.8165 10.9704C16.9131 11.6036 18.2878 11.6036 19.3844 10.9704L22.2265 9.31766C22.3191 9.27132 22.4118 9.22499 22.5199 9.19409C22.5354 9.19409 22.5663 9.19409 22.5817 9.17865C22.6744 9.1632 22.7825 9.14776 22.8752 9.13231H22.9215C23.0142 9.13231 23.1069 9.14776 23.1995 9.1632C23.215 9.1632 23.2304 9.1632 23.2459 9.1632C23.3386 9.19409 23.4312 9.22499 23.5239 9.27132C23.5239 9.27132 23.5393 9.27132 23.5548 9.27132L29.2543 12.5613C29.6559 12.793 29.9185 13.2254 29.9185 13.7043V20.2842C29.9185 20.763 29.6713 21.1955 29.2543 21.4271L23.5548 24.7171C23.5548 24.7171 23.5393 24.7171 23.5239 24.7171C23.4312 24.7634 23.3386 24.8098 23.2459 24.8252C23.2304 24.8252 23.215 24.8252 23.1995 24.8252C23.1069 24.8407 23.0142 24.8561 22.9215 24.8561C22.9215 24.8561 22.9061 24.8561 22.8906 24.8561C22.798 24.8561 22.6898 24.8407 22.5972 24.8098C22.5817 24.8098 22.5508 24.8098 22.5354 24.7943C22.4427 24.7634 22.3346 24.7325 22.2419 24.6708L19.3999 23.0181C18.3032 22.3848 16.9286 22.3848 15.8319 23.0181C14.7353 23.6513 14.0556 24.8407 14.0556 26.1072V29.3972C14.0556 29.5053 14.0402 29.6134 14.0093 29.7061C14.0093 29.7215 14.0093 29.7524 13.9939 29.7679C13.963 29.8605 13.9321 29.9532 13.8857 30.0459C13.8857 30.0459 13.8703 30.0768 13.8549 30.0922C13.8085 30.1694 13.7467 30.2467 13.6849 30.3085C13.6849 30.3085 13.6695 30.3239 13.6541 30.3394C13.5768 30.4166 13.4996 30.4784 13.4069 30.5247C13.4069 30.5247 13.4069 30.5247 13.3915 30.5247L7.69199 33.8146C7.4912 33.9382 7.25951 34 7.02782 34C6.79614 34 6.56445 33.9382 6.36365 33.8146L0.664168 30.5247C0.262579 30.293 0 29.8605 0 29.3817V22.8018C0 22.323 0.247133 21.8905 0.664168 21.6588L6.36365 18.3689C6.36365 18.3689 6.3791 18.3689 6.39455 18.3689C6.48722 18.3225 6.5799 18.2762 6.67257 18.2608C6.67257 18.2608 6.70346 18.2608 6.71891 18.2608C6.81158 18.2299 6.90425 18.2299 6.99693 18.2299H7.04327C7.13594 18.2299 7.24406 18.2453 7.33674 18.2762C7.35218 18.2762 7.38307 18.2762 7.39852 18.2762C7.49119 18.3071 7.59932 18.338 7.69199 18.3998L10.534 20.0525C11.6307 20.6857 13.0053 20.6857 14.102 20.0525C15.1986 19.4192 15.8782 18.2299 15.8782 16.9633C15.8782 15.6968 15.1986 14.5074 14.102 13.8742V13.8587L14.1174 13.9205Z" fill="#fff"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 8 32 15.81" fill="#6720FF"><title>Fireworks AI</title><path d="M15.9851 19.1274C15.0969 19.1274 14.2999 18.6001 13.96 17.7838L9.86258 8H12.2608L15.9991 16.9499L19.7339 8H22.1321L18.0102 17.7873C17.6686 18.6001 16.8733 19.1274 15.9851 19.1274Z"/><path d="M21.3316 23.8029C20.4469 23.8029 19.6533 23.2792 19.31 22.4698C18.9649 21.6535 19.1436 20.7215 19.7672 20.0891L27.2299 12.5302L28.1618 14.7287L21.3298 21.636L31.068 21.5817L32 23.7802L21.3333 23.8065L21.3298 23.8029H21.3316Z"/><path d="M0 23.7766L0.931955 21.5781L10.6702 21.6324L3.83993 14.7234L4.77189 12.5249L12.2345 20.0838C12.8582 20.7145 13.0386 21.65 12.6918 22.4645C12.3484 23.2756 11.5513 23.7977 10.6702 23.7977L0.00350359 23.7731L0 23.7766Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 766 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#111" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>
|
||||||
|
After Width: | Height: | Size: 748 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#fff" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>
|
||||||
|
After Width: | Height: | Size: 748 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#111" role="img" viewBox="-15 -15 259 259" xmlns="http://www.w3.org/2000/svg"><title>Grok Bot</title><path fill-rule="evenodd" d="M228.541 114.228C228.541 130.133 225.184 145.994 218.738 160.534C212.674 174.217 203.904 186.669 193.065 196.988C155.933 232.34 99.497 238.596 55.5255 212.24C45.097 205.99 35.6851 198.072 27.7451 188.866C19.1926 178.953 12.3686 167.569 7.65781 155.351C2.60712 142.264 0 128.257 0 114.228C0 98.3219 3.35751 82.4611 9.80315 67.9215C15.8672 54.2382 24.6377 41.7862 35.4767 31.4668C72.6081 -3.88483 129.044 -10.1413 173.016 16.2153C183.444 22.4653 192.856 30.3829 200.796 39.5896C209.349 49.5018 216.173 60.8859 220.883 73.1037C225.934 86.1906 228.541 100.198 228.541 114.228Z M130.36 45.98L132.71 46.19L134.98 46.81L137.11 47.83L138.97 49.28L140.47 51.09L141.68 53.12L142.73 55.23L143.76 57.36L144.78 59.49L145.79 61.62L146.79 63.76L147.76 65.91L148.71 68.07L149.63 70.25L150.52 72.43L151.37 74.63L151.99 76.91L152.10 79.26L151.64 81.57L150.59 83.68L149.04 85.45L147.10 86.78L144.90 87.62L142.56 87.93L140.22 87.71L137.98 86.99L135.93 85.82L134.17 84.24L132.78 82.34L131.69 80.25L130.77 78.08L129.87 75.89L128.94 73.72L128.00 71.56L127.03 69.40L126.05 67.26L125.05 65.12L124.03 62.99L122.93 60.90L121.87 58.79L121.03 56.59L120.72 54.26L121.10 51.93L122.15 49.83L123.75 48.10L125.76 46.89L128.01 46.19Z M176.61 37.08L178.72 37.59L180.70 38.48L182.52 39.65L184.20 41.03L185.71 42.59L187.03 44.31L188.20 46.14L189.26 48.03L190.27 49.96L191.26 51.89L192.23 53.84L193.16 55.80L194.05 57.78L194.92 59.77L195.74 61.78L196.53 63.80L197.27 65.84L197.97 67.90L198.47 70.01L198.63 72.18L198.40 74.33L197.58 76.33L195.95 77.72L193.83 78.08L191.71 77.65L189.76 76.69L188.03 75.38L186.53 73.82L185.28 72.05L184.25 70.13L183.40 68.14L182.63 66.11L181.87 64.07L181.07 62.05L180.25 60.04L179.39 58.05L178.49 56.07L177.57 54.10L176.61 52.15L175.62 50.22L174.59 48.31L173.53 46.41L172.54 44.48L171.86 42.42L171.76 40.26L172.62 38.30L174.45 37.19Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#fff" role="img" viewBox="-15 -15 259 259" xmlns="http://www.w3.org/2000/svg"><title>Grok Bot</title><path fill-rule="evenodd" d="M228.541 114.228C228.541 130.133 225.184 145.994 218.738 160.534C212.674 174.217 203.904 186.669 193.065 196.988C155.933 232.34 99.497 238.596 55.5255 212.24C45.097 205.99 35.6851 198.072 27.7451 188.866C19.1926 178.953 12.3686 167.569 7.65781 155.351C2.60712 142.264 0 128.257 0 114.228C0 98.3219 3.35751 82.4611 9.80315 67.9215C15.8672 54.2382 24.6377 41.7862 35.4767 31.4668C72.6081 -3.88483 129.044 -10.1413 173.016 16.2153C183.444 22.4653 192.856 30.3829 200.796 39.5896C209.349 49.5018 216.173 60.8859 220.883 73.1037C225.934 86.1906 228.541 100.198 228.541 114.228Z M130.36 45.98L132.71 46.19L134.98 46.81L137.11 47.83L138.97 49.28L140.47 51.09L141.68 53.12L142.73 55.23L143.76 57.36L144.78 59.49L145.79 61.62L146.79 63.76L147.76 65.91L148.71 68.07L149.63 70.25L150.52 72.43L151.37 74.63L151.99 76.91L152.10 79.26L151.64 81.57L150.59 83.68L149.04 85.45L147.10 86.78L144.90 87.62L142.56 87.93L140.22 87.71L137.98 86.99L135.93 85.82L134.17 84.24L132.78 82.34L131.69 80.25L130.77 78.08L129.87 75.89L128.94 73.72L128.00 71.56L127.03 69.40L126.05 67.26L125.05 65.12L124.03 62.99L122.93 60.90L121.87 58.79L121.03 56.59L120.72 54.26L121.10 51.93L122.15 49.83L123.75 48.10L125.76 46.89L128.01 46.19Z M176.61 37.08L178.72 37.59L180.70 38.48L182.52 39.65L184.20 41.03L185.71 42.59L187.03 44.31L188.20 46.14L189.26 48.03L190.27 49.96L191.26 51.89L192.23 53.84L193.16 55.80L194.05 57.78L194.92 59.77L195.74 61.78L196.53 63.80L197.27 65.84L197.97 67.90L198.47 70.01L198.63 72.18L198.40 74.33L197.58 76.33L195.95 77.72L193.83 78.08L191.71 77.65L189.76 76.69L188.03 75.38L186.53 73.82L185.28 72.05L184.25 70.13L183.40 68.14L182.63 66.11L181.87 64.07L181.07 62.05L180.25 60.04L179.39 58.05L178.49 56.07L177.57 54.10L176.61 52.15L175.62 50.22L174.59 48.31L173.53 46.41L172.54 44.48L171.86 42.42L171.76 40.26L172.62 38.30L174.45 37.19Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="104" height="104" viewBox="0 0 104 104" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="52" cy="52" r="52" fill="#FF521D"/>
|
||||||
|
<path d="M68.4459 24.9438C69.8639 24.1762 71.6471 23.7178 73.4821 24.2163C73.8278 24.2966 74.1339 24.3916 74.4362 24.5024L75.9186 25.0464L75.5104 26.5727C75.3469 27.1827 75.202 27.723 75.0387 28.3325L74.6442 29.8042L73.1354 29.5972C73.0242 29.5819 72.9246 29.5753 72.8082 29.5747C72.2474 29.5865 71.7542 29.8645 71.2662 30.6772C70.8883 31.307 70.5939 32.1486 70.3815 33.1079C74.871 36.0331 77.8444 41.0994 77.8444 46.8638C77.8443 49.5292 77.2072 52.0505 76.0758 54.2798C71.5108 64.3996 54.1783 84.8815 23.4655 79.0083C21.8797 78.7048 20.9918 77.4165 20.902 76.1245C20.8141 74.8561 21.479 73.487 22.8981 72.8813C31.0698 69.394 35.5884 65.7091 38.5983 61.2329C41.6553 56.6866 43.2606 51.1907 45.3287 43.7251C46.7942 36.1645 53.4462 30.4547 61.4362 30.4546L61.8834 30.4604C62.4398 30.4755 62.9894 30.519 63.5309 30.5884C63.9515 29.7288 64.4424 28.8863 65.0211 28.0659L65.1745 27.8423C65.9623 26.731 67.1108 25.6667 68.4459 24.9438Z" fill="#18181A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><title>Kimi</title><path fill="#111" d="M272.597333 244.522667c3.925333 0 7.125333 3.925333 7.125334 8.832v284.074666c0 7.338667 6.954667 11.477333 11.605333 6.826667l300.245333-297.813333a6.272 6.272 0 0 1 4.48-1.92h149.973334c6.741333 0 9.685333 10.496 4.437333 15.701333l-278.656 276.48c-3.968 3.925333-3.413333 11.648 1.066667 14.634667l215.552 156.032c24.021333 16.213333 50.602667 26.752 77.824 31.018666 3.626667 0.512 6.357333 4.224 6.357333 8.704v117.546667c0 5.12-3.584 9.216-7.722667 8.789333a294.741333 294.741333 0 0 1-132.565333-49.28l-249.002667-183.210666a6.058667 6.058667 0 0 0-7.850666 0.853333l-93.098667 92.373333a9.813333 9.813333 0 0 0-2.645333 6.869334v124.8c0 4.864-3.2 8.832-7.125334 8.832H169.258667c-3.925333 0-7.125333-3.968-7.125334-8.832V253.354667c0-4.906667 3.2-8.789333 7.125334-8.832h103.338666z"/><path fill="#1783FF" d="M885.76 106.666667a73.856 73.856 0 0 1 0 147.712h-65.152a8.704 8.704 0 0 1-8.704-8.704V180.48c0-40.746667 33.066667-73.856 73.813333-73.856z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><title>Kimi</title><path fill="#fff" d="M272.597333 244.522667c3.925333 0 7.125333 3.925333 7.125334 8.832v284.074666c0 7.338667 6.954667 11.477333 11.605333 6.826667l300.245333-297.813333a6.272 6.272 0 0 1 4.48-1.92h149.973334c6.741333 0 9.685333 10.496 4.437333 15.701333l-278.656 276.48c-3.968 3.925333-3.413333 11.648 1.066667 14.634667l215.552 156.032c24.021333 16.213333 50.602667 26.752 77.824 31.018666 3.626667 0.512 6.357333 4.224 6.357333 8.704v117.546667c0 5.12-3.584 9.216-7.722667 8.789333a294.741333 294.741333 0 0 1-132.565333-49.28l-249.002667-183.210666a6.058667 6.058667 0 0 0-7.850666 0.853333l-93.098667 92.373333a9.813333 9.813333 0 0 0-2.645333 6.869334v124.8c0 4.864-3.2 8.832-7.125334 8.832H169.258667c-3.925333 0-7.125333-3.968-7.125334-8.832V253.354667c0-4.906667 3.2-8.789333 7.125334-8.832h103.338666z"/><path fill="#1783FF" d="M885.76 106.666667a73.856 73.856 0 0 1 0 147.712h-65.152a8.704 8.704 0 0 1-8.704-8.704V180.48c0-40.746667 33.066667-73.856 73.813333-73.856z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#000000" d="M16.278,2C17.434,2 18.371,2.927 18.371,4.07L18.371,16.571C18.388,16.967 18.719,17.282 19.115,17.28C19.511,17.282 19.841,16.966 19.858,16.571L19.858,9.099C19.864,7.973 20.792,7.05 21.918,7.05C21.922,7.05 21.936,7.05 21.94,7.05C23.066,7.05 23.995,7.974 24,9.1L24,15.661C23.998,16.017 23.704,16.308 23.348,16.306C23.347,16.306 23.345,16.306 23.344,16.306C22.989,16.306 22.697,16.016 22.695,15.661L22.695,9.1C22.693,8.683 22.35,8.342 21.933,8.342L21.925,8.342C21.508,8.342 21.165,8.683 21.163,9.1L21.163,16.572C21.157,17.685 20.239,18.598 19.126,18.598L19.104,18.598C17.991,18.598 17.073,17.685 17.067,16.572L17.067,4.072C17.05,3.652 16.699,3.317 16.279,3.319C15.859,3.316 15.508,3.651 15.49,4.071L15.489,19.975C15.482,21.092 14.558,22.006 13.441,22C13.437,22 13.434,22 13.43,22C12.317,22 11.399,21.087 11.393,19.974L11.393,18.04C11.393,17.684 11.685,17.395 12.045,17.395C12.405,17.395 12.697,17.684 12.697,18.04L12.697,19.974C12.697,20.237 12.839,20.48 13.069,20.612C13.299,20.743 13.583,20.743 13.813,20.612C14.042,20.482 14.185,20.238 14.185,19.974L14.185,4.07C14.185,2.927 15.122,2 16.278,2ZM10.604,2C11.76,2 12.697,2.927 12.697,4.07L12.697,15.593C12.695,15.947 12.403,16.238 12.049,16.238L12.041,16.238C11.687,16.238 11.395,15.947 11.393,15.593L11.393,4.07C11.39,3.64 11.034,3.288 10.604,3.29C10.174,3.288 9.818,3.64 9.815,4.07L9.815,18.083C9.808,19.212 8.874,20.137 7.745,20.131C6.615,20.137 5.681,19.213 5.674,18.083L5.674,9.1C5.672,8.683 5.329,8.342 4.912,8.342L4.904,8.342C4.487,8.342 4.144,8.683 4.142,9.1L4.142,12.9C4.136,14.026 3.208,14.949 2.082,14.949L2.06,14.949C0.934,14.949 0.006,14.026 0,12.9L0,11.522C0,11.165 0.292,10.876 0.652,10.876C1.012,10.876 1.305,11.166 1.305,11.522L1.305,12.9C1.305,13.318 1.648,13.657 2.071,13.657C2.494,13.657 2.837,13.318 2.837,12.9L2.837,9.099C2.844,7.97 3.778,7.045 4.907,7.051C6.037,7.045 6.971,7.969 6.978,9.099L6.978,18.083C6.978,18.502 7.321,18.841 7.745,18.841C8.168,18.841 8.511,18.502 8.511,18.083L8.511,4.07C8.511,2.927 9.448,2 10.604,2Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ffffff" d="M16.278,2C17.434,2 18.371,2.927 18.371,4.07L18.371,16.571C18.388,16.967 18.719,17.282 19.115,17.28C19.511,17.282 19.841,16.966 19.858,16.571L19.858,9.099C19.864,7.973 20.792,7.05 21.918,7.05C21.922,7.05 21.936,7.05 21.94,7.05C23.066,7.05 23.995,7.974 24,9.1L24,15.661C23.998,16.017 23.704,16.308 23.348,16.306C23.347,16.306 23.345,16.306 23.344,16.306C22.989,16.306 22.697,16.016 22.695,15.661L22.695,9.1C22.693,8.683 22.35,8.342 21.933,8.342L21.925,8.342C21.508,8.342 21.165,8.683 21.163,9.1L21.163,16.572C21.157,17.685 20.239,18.598 19.126,18.598L19.104,18.598C17.991,18.598 17.073,17.685 17.067,16.572L17.067,4.072C17.05,3.652 16.699,3.317 16.279,3.319C15.859,3.316 15.508,3.651 15.49,4.071L15.489,19.975C15.482,21.092 14.558,22.006 13.441,22C13.437,22 13.434,22 13.43,22C12.317,22 11.399,21.087 11.393,19.974L11.393,18.04C11.393,17.684 11.685,17.395 12.045,17.395C12.405,17.395 12.697,17.684 12.697,18.04L12.697,19.974C12.697,20.237 12.839,20.48 13.069,20.612C13.299,20.743 13.583,20.743 13.813,20.612C14.042,20.482 14.185,20.238 14.185,19.974L14.185,4.07C14.185,2.927 15.122,2 16.278,2ZM10.604,2C11.76,2 12.697,2.927 12.697,4.07L12.697,15.593C12.695,15.947 12.403,16.238 12.049,16.238L12.041,16.238C11.687,16.238 11.395,15.947 11.393,15.593L11.393,4.07C11.39,3.64 11.034,3.288 10.604,3.29C10.174,3.288 9.818,3.64 9.815,4.07L9.815,18.083C9.808,19.212 8.874,20.137 7.745,20.131C6.615,20.137 5.681,19.213 5.674,18.083L5.674,9.1C5.672,8.683 5.329,8.342 4.912,8.342L4.904,8.342C4.487,8.342 4.144,8.683 4.142,9.1L4.142,12.9C4.136,14.026 3.208,14.949 2.082,14.949L2.06,14.949C0.934,14.949 0.006,14.026 0,12.9L0,11.522C0,11.165 0.292,10.876 0.652,10.876C1.012,10.876 1.305,11.166 1.305,11.522L1.305,12.9C1.305,13.318 1.648,13.657 2.071,13.657C2.494,13.657 2.837,13.318 2.837,12.9L2.837,9.099C2.844,7.97 3.778,7.045 4.907,7.051C6.037,7.045 6.971,7.969 6.978,9.099L6.978,18.083C6.978,18.502 7.321,18.841 7.745,18.841C8.168,18.841 8.511,18.502 8.511,18.083L8.511,4.07C8.511,2.927 9.448,2 10.604,2Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#000" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Muse</title><path d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#fff" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Muse</title><path d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<title>Ollama</title>
|
||||||
|
<path fill="#111" fill-rule="evenodd" d="M22 12c-2-7 2-10 5-10c2 0 3 2 5 2s3-2 5-2c3 0 7 3 5 10c6 3 10 9 10 15c0 12-9 20-20 20c-11 0-20-8-20-20c0-6 4-12 10-15zM26 32c0-3 2-5 6-5s6 2 6 5-2 6-6 6-6-3-6-6zM26 21a2 2 0 1 0 .01 0zM38 21a2 2 0 1 0 .01 0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 346 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<title>Ollama</title>
|
||||||
|
<path fill="#fff" fill-rule="evenodd" d="M22 12c-2-7 2-10 5-10c2 0 3 2 5 2s3-2 5-2c3 0 7 3 5 10c6 3 10 9 10 15c0 12-9 20-20 20c-11 0-20-8-20-20c0-6 4-12 10-15zM26 32c0-3 2-5 6-5s6 2 6 5-2 6-6 6-6-3-6-6zM26 21a2 2 0 1 0 .01 0zM38 21a2 2 0 1 0 .01 0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 346 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg width='240' height='300' viewBox='0 0 240 300' fill='none' xmlns='http://www.w3.org/2000/svg'><g clip-path='url(#clip0_1401_86274)'><mask id='mask0_1401_86274' style='mask-type:luminance' maskUnits='userSpaceOnUse' x='0' y='0' width='240' height='300'><path d='M240 0H0V300H240V0Z' fill='white'/></mask><g mask='url(#mask0_1401_86274)'><path d='M180 240H60V120H180V240Z' fill='#CFCECD'/><path d='M180 60H60V240H180V60ZM240 300H0V0H240V300Z' fill='#211E1E'/></g></g><defs><clipPath id='clip0_1401_86274'><rect width='240' height='300' fill='white'/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 577 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg width='240' height='300' viewBox='0 0 240 300' fill='none' xmlns='http://www.w3.org/2000/svg'><g clip-path='url(#clip0_1401_86283)'><mask id='mask0_1401_86283' style='mask-type:luminance' maskUnits='userSpaceOnUse' x='0' y='0' width='240' height='300'><path d='M240 0H0V300H240V0Z' fill='white'/></mask><g mask='url(#mask0_1401_86283)'><path d='M180 240H60V120H180V240Z' fill='#4B4646'/><path d='M180 60H60V240H180V60ZM240 300H0V0H240V300Z' fill='#F1ECEC'/></g></g><defs><clipPath id='clip0_1401_86283'><rect width='240' height='300' fill='white'/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 577 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 401.4 293.7" fill="#7624F4"><title>OpenRouter</title><path d="M303.9475,17.19926c42.79734,0,77.48933,34.69327,77.48933,77.48933s-34.69199,77.48933-77.48933,77.48933l76.86166,76.86244c9.76367,9.76313,2.84903,26.45667-10.95697,26.45667h-220.88335c-71.32686,0-129.14889-57.82202-129.14889-129.14889S77.64197,17.19926,148.96884,17.19926h154.97866ZM148.96884,68.85881c-42.79607,0-77.48933,34.69327-77.48933,77.48933s34.69327,77.48933,77.48933,77.48933,77.48933-34.69327,77.48933-77.48933-34.69327-77.48933-77.48933-77.48933Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 582 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 401.4 293.7" fill="#C8FF00"><title>OpenRouter</title><path d="M303.9475,17.19926c42.79734,0,77.48933,34.69327,77.48933,77.48933s-34.69199,77.48933-77.48933,77.48933l76.86166,76.86244c9.76367,9.76313,2.84903,26.45667-10.95697,26.45667h-220.88335c-71.32686,0-129.14889-57.82202-129.14889-129.14889S77.64197,17.19926,148.96884,17.19926h154.97866ZM148.96884,68.85881c-42.79607,0-77.48933,34.69327-77.48933,77.48933s34.69327,77.48933,77.48933,77.48933,77.48933-34.69327,77.48933-77.48933-34.69327-77.48933-77.48933-77.48933Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 582 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 141.38 140"><title>Qwen Code</title><path fill="#6D44E8" d="m140.93 85-16.35-28.33-1.93-3.34 8.66-15a3.323 3.323 0 0 0 0-3.34l-9.62-16.67c-.3-.51-.72-.93-1.22-1.22s-1.07-.45-1.67-.45H82.23l-8.66-15a3.33 3.33 0 0 0-2.89-1.67H51.43c-.59 0-1.17.16-1.66.45-.5.29-.92.71-1.22 1.22L32.19 29.98l-1.92 3.33H12.96c-.59 0-1.17.16-1.66.45-.5.29-.93.71-1.22 1.22L.45 51.66a3.323 3.323 0 0 0 0 3.34l18.28 31.67-8.66 15a3.32 3.32 0 0 0 0 3.34l9.62 16.67c.3.51.72.93 1.22 1.22s1.07.45 1.67.45h36.56l8.66 15a3.35 3.35 0 0 0 2.89 1.67h19.25a3.34 3.34 0 0 0 2.89-1.67l18.28-31.67h17.32c.6 0 1.17-.16 1.67-.45s.92-.71 1.22-1.22l9.62-16.67a3.323 3.323 0 0 0 0-3.34ZM51.44 3.33 61.07 20l-9.63 16.66h76.98l-9.62 16.66H45.67l-11.54-20zM57.21 120H22.58l9.63-16.67h19.25l-38.5-66.67h19.25l9.62 16.67L68.78 100l-11.55 20Zm61.59-33.34-9.62-16.67-38.49 66.67-9.63-16.67 9.63-16.66 26.94-46.67h23.1l17.32 30z"/></svg>
|
||||||
|
After Width: | Height: | Size: 942 B |
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="none" width="54" height="54">
|
||||||
|
<path d="M27.8517 26.3314C27.8439 26.1451 27.8371 25.9303 27.8337 25.692C27.8203 24.7571 27.8545 23.4581 28.0457 22.0052C28.4322 19.0688 29.4394 15.6521 31.8222 13.2693C34.205 10.8867 37.6212 9.87992 40.5575 9.49344C42.0107 9.30219 43.3101 9.26734 44.245 9.28076C44.4829 9.28417 44.6968 9.29231 44.883 9.30009C44.8908 9.48595 44.8975 9.69933 44.901 9.93676C44.9144 10.8716 44.8795 12.171 44.6883 13.6242C44.3018 16.5605 43.2958 19.9773 40.9131 22.3602C38.5303 24.743 35.1129 25.7495 32.1765 26.136C30.7234 26.3272 29.4246 26.3614 28.4897 26.348C28.2518 26.3446 28.0378 26.3392 27.8517 26.3314Z" stroke="#000000" stroke-width="2" />
|
||||||
|
<path d="M27.8916 27.1592C28.0179 27.0219 28.165 26.8653 28.3311 26.6943C28.9826 26.0238 29.9253 25.1295 31.0879 24.2373C33.4375 22.4342 36.5658 20.7305 39.9355 20.7305C43.3051 20.7306 46.4327 22.4343 48.7822 24.2373C49.945 25.1296 50.8885 26.0238 51.54 26.6943C51.7059 26.865 51.8514 27.022 51.9775 27.1592C51.8516 27.2961 51.7055 27.4518 51.54 27.6221C50.8885 28.2926 49.945 29.1868 48.7822 30.0791C46.4327 31.8821 43.3052 33.5868 39.9355 33.5869C36.5657 33.5869 33.4375 31.8822 31.0879 30.0791C29.9252 29.1868 28.9826 28.2926 28.3311 27.6221C28.1652 27.4514 28.0177 27.2963 27.8916 27.1592Z" stroke="#000000" stroke-width="2" />
|
||||||
|
<path d="M27.4459 27.6915C27.6323 27.6837 27.8471 27.677 28.0854 27.6736C29.0202 27.6602 30.3192 27.6943 31.7721 27.8856C34.7085 28.272 38.1253 29.2793 40.5081 31.6621C42.8906 34.0448 43.8974 37.4611 44.2839 40.3973C44.4752 41.8505 44.51 43.1499 44.4966 44.0848C44.4932 44.3228 44.485 44.5367 44.4773 44.7229C44.2914 44.7306 44.078 44.7374 43.8406 44.7408C42.9057 44.7542 41.6063 44.7194 40.1531 44.5281C37.2168 44.1417 33.8 43.1356 31.4172 40.753C29.0343 38.3701 28.0278 34.9528 27.6413 32.0163C27.4501 30.5633 27.4159 29.2644 27.4294 28.3296C27.4328 28.0916 27.4381 27.8777 27.4459 27.6915Z" stroke="#000000" stroke-width="2" />
|
||||||
|
<path d="M26.916 28.1289C27.0533 28.2552 27.2099 28.4023 27.3809 28.5684C28.0514 29.2199 28.9457 30.1626 29.8379 31.3252C31.641 33.6748 33.3447 36.8031 33.3447 40.1728C33.3446 43.5424 31.6409 46.67 29.8379 49.0195C28.9456 50.1823 28.0514 51.1258 27.3809 51.7773C27.2102 51.9432 27.0532 52.0887 26.916 52.2148C26.7791 52.0889 26.6234 51.9428 26.4531 51.7773C25.7826 51.1258 24.8884 50.1823 23.9961 49.0195C22.1931 46.67 20.4884 43.5426 20.4883 40.1728C20.4883 36.803 22.193 33.6748 23.9961 31.3252C24.8884 30.1625 25.7826 29.2199 26.4531 28.5684C26.6238 28.4025 26.7789 28.255 26.916 28.1289Z" stroke="#000000" stroke-width="2" />
|
||||||
|
<path d="M26.2147 27.691C26.2225 27.8774 26.2293 28.0922 26.2327 28.3305C26.2461 29.2653 26.2119 30.5643 26.0207 32.0172C25.6342 34.9536 24.627 38.3704 22.2442 40.7532C19.8614 43.1357 16.4452 44.1425 13.5089 44.529C12.0557 44.7203 10.7563 44.7551 9.82145 44.7417C9.58349 44.7383 9.36957 44.7301 9.1834 44.7224C9.17564 44.5365 9.16886 44.3231 9.16545 44.0857C9.15203 43.1508 9.18689 41.8514 9.37813 40.3982C9.76458 37.4619 10.7706 34.0451 13.1533 31.6623C15.5361 29.2795 18.9535 28.2729 21.8899 27.8865C23.343 27.6952 24.6419 27.6611 25.5767 27.6745C25.8146 27.6779 26.0286 27.6833 26.2147 27.691Z" stroke="#000000" stroke-width="2" />
|
||||||
|
<path d="M25.7021 27.1602C25.5759 27.2974 25.4288 27.4541 25.2627 27.625C24.6111 28.2955 23.6684 29.1899 22.5059 30.082C20.1562 31.8851 17.028 33.5889 13.6582 33.5889C10.2886 33.5887 7.16105 31.885 4.81153 30.082C3.64873 29.1897 2.70529 28.2955 2.05372 27.625C1.88786 27.4543 1.74236 27.2973 1.61622 27.1602C1.74215 27.0232 1.88824 26.8676 2.05372 26.6973C2.70527 26.0267 3.64874 25.1326 4.81153 24.2402C7.16108 22.4372 10.2885 20.7326 13.6582 20.7324C17.0281 20.7324 20.1562 22.4371 22.5059 24.2402C23.6686 25.1325 24.6112 26.0268 25.2627 26.6973C25.4285 26.8679 25.576 27.023 25.7021 27.1602Z" stroke="#000000" stroke-width="2" />
|
||||||
|
<path d="M25.7338 25.9813C25.5474 25.9891 25.3326 25.9959 25.0943 25.9993C24.1595 26.0127 22.8605 25.9785 21.4076 25.7873C18.4712 25.4008 15.0544 24.3936 12.6716 22.0108C10.2891 19.628 9.28226 16.2118 8.89579 13.2755C8.70453 11.8223 8.66969 10.5229 8.6831 9.58806C8.68652 9.35009 8.69466 9.13618 8.70244 8.95C8.8883 8.94224 9.10168 8.93546 9.33911 8.93205C10.274 8.91863 11.5734 8.95349 13.0266 9.14473C15.9629 9.53118 19.3797 10.5372 21.7625 12.9199C24.1454 15.3027 25.1519 18.7201 25.5383 21.6565C25.7296 23.1096 25.7638 24.4085 25.7503 25.3433C25.7469 25.5812 25.7415 25.7952 25.7338 25.9813Z" stroke="#000000" stroke-width="2" />
|
||||||
|
<path d="M26.9121 25.7031C26.7748 25.5769 26.6182 25.4298 26.4473 25.2637C25.7768 24.6121 24.8824 23.6694 23.9902 22.5068C22.1872 20.1572 20.4834 17.029 20.4834 13.6592C20.4835 10.2896 22.1873 7.16203 23.9902 4.8125C24.8826 3.6497 25.7767 2.70627 26.4473 2.05469C26.618 1.88883 26.775 1.74333 26.9121 1.61719C27.049 1.74312 27.2047 1.88921 27.375 2.05469C28.0455 2.70624 28.9397 3.64971 29.832 4.8125C31.6351 7.16206 33.3397 10.2895 33.3398 13.6592C33.3398 17.029 31.6351 20.1572 29.832 22.5068C28.9398 23.6695 28.0455 24.6121 27.375 25.2637C27.2043 25.4295 27.0492 25.577 26.9121 25.7031Z" stroke="#000000" stroke-width="2" />
|
||||||
|
<path d="M25.5607 19.3789L27.4618 19.3457C27.5492 19.3442 27.6286 19.3884 27.6747 19.4583L27.7093 19.5368L28.045 20.9198C28.1559 21.3761 28.4798 21.7513 28.9152 21.9272C29.3419 22.0994 29.8251 22.0595 30.2183 21.8205L31.3036 21.1608C31.3771 21.116 31.4662 21.1134 31.5407 21.1488L31.6099 21.1965L32.9965 22.5729C33.0577 22.6336 33.0825 22.7203 33.0659 22.8012L33.035 22.8799L32.4788 23.8048C32.2386 24.2041 32.1867 24.6853 32.3299 25.122L32.4034 25.3063C32.5966 25.715 32.9524 26.0199 33.3793 26.1498L33.5666 26.1943L34.4238 26.3454C34.5414 26.3663 34.628 26.4675 34.6302 26.587L34.6642 28.532C34.6662 28.6469 34.5891 28.7489 34.478 28.7784L33.3785 29.0711C32.9508 29.1849 32.5956 29.4757 32.399 29.8657L32.3249 30.0389C32.1648 30.4848 32.2187 30.9794 32.4714 31.3803L33.0028 32.2237C33.0497 32.2981 33.0532 32.3898 33.0168 32.4656L32.9672 32.5349L31.5394 33.9438C31.4825 33.9998 31.4038 34.0241 31.3277 34.0129L31.2531 33.9898L30.2136 33.4737C29.8594 33.298 29.4528 33.2687 29.0801 33.387L28.9219 33.4464C28.5639 33.6057 28.2838 33.8968 28.1361 34.2552L28.0812 34.4124L27.7617 35.5207C27.7312 35.6261 27.6352 35.6996 27.5255 35.7016L25.5874 35.7354C25.4685 35.7375 25.3646 35.6555 25.3388 35.5396L25.0832 34.3808L25.0342 34.2068C24.9146 33.8684 24.6748 33.5849 24.361 33.4108L24.1976 33.3335L24.0433 33.2806C23.7308 33.1931 23.3978 33.2118 23.0968 33.3332L22.9495 33.4022L21.7729 34.0361C21.6979 34.0764 21.6097 34.0746 21.5375 34.0363L21.4722 33.9867L20.1375 32.5586C20.0776 32.4944 20.0574 32.4054 20.0787 32.3242L20.1135 32.2474L20.7373 31.3321C20.9834 30.9707 21.049 30.5199 20.923 30.108L20.8575 29.9343L20.7817 29.7901C20.6149 29.5119 20.3581 29.2988 20.0542 29.1855L19.8981 29.1365L18.4973 28.7898C18.3875 28.7625 18.3097 28.6649 18.3075 28.5518L18.275 26.6888C18.2733 26.5838 18.3372 26.4906 18.4321 26.4527L18.4738 26.4402L19.6961 26.1854C20.1451 26.0918 20.5232 25.7997 20.7294 25.3977L20.8074 25.2186C20.9466 24.8306 20.9208 24.4049 20.7399 24.0399L20.6523 23.8881L20.0481 22.9571C19.9825 22.8563 19.9975 22.723 20.0845 22.64L21.5852 21.2083C21.6715 21.126 21.8046 21.1157 21.9022 21.1843L22.718 21.758C23.1295 22.0472 23.659 22.111 24.1274 21.9278C24.5375 21.7673 24.8531 21.4365 24.9966 21.0267L25.0481 20.8461L25.3209 19.5765C25.3453 19.4629 25.4445 19.3809 25.5607 19.3789Z" fill="white" stroke="#000000" stroke-width="1.5" />
|
||||||
|
<circle cx="26.4669" cy="27.5406" r="2.53895" transform="rotate(-1 26.4669 27.5406)" fill="white" stroke="#000000" stroke-width="1.5" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="none" width="54" height="54">
|
||||||
|
<path d="M27.8517 26.3314C27.8439 26.1451 27.8371 25.9303 27.8337 25.692C27.8203 24.7571 27.8545 23.4581 28.0457 22.0052C28.4322 19.0688 29.4394 15.6521 31.8222 13.2693C34.205 10.8867 37.6212 9.87992 40.5575 9.49344C42.0107 9.30219 43.3101 9.26734 44.245 9.28076C44.4829 9.28417 44.6968 9.29231 44.883 9.30009C44.8908 9.48595 44.8975 9.69933 44.901 9.93676C44.9144 10.8716 44.8795 12.171 44.6883 13.6242C44.3018 16.5605 43.2958 19.9773 40.9131 22.3602C38.5303 24.743 35.1129 25.7495 32.1765 26.136C30.7234 26.3272 29.4246 26.3614 28.4897 26.348C28.2518 26.3446 28.0378 26.3392 27.8517 26.3314Z" stroke="#f5f5f5" stroke-width="2" />
|
||||||
|
<path d="M27.8916 27.1592C28.0179 27.0219 28.165 26.8653 28.3311 26.6943C28.9826 26.0238 29.9253 25.1295 31.0879 24.2373C33.4375 22.4342 36.5658 20.7305 39.9355 20.7305C43.3051 20.7306 46.4327 22.4343 48.7822 24.2373C49.945 25.1296 50.8885 26.0238 51.54 26.6943C51.7059 26.865 51.8514 27.022 51.9775 27.1592C51.8516 27.2961 51.7055 27.4518 51.54 27.6221C50.8885 28.2926 49.945 29.1868 48.7822 30.0791C46.4327 31.8821 43.3052 33.5868 39.9355 33.5869C36.5657 33.5869 33.4375 31.8822 31.0879 30.0791C29.9252 29.1868 28.9826 28.2926 28.3311 27.6221C28.1652 27.4514 28.0177 27.2963 27.8916 27.1592Z" stroke="#f5f5f5" stroke-width="2" />
|
||||||
|
<path d="M27.4459 27.6915C27.6323 27.6837 27.8471 27.677 28.0854 27.6736C29.0202 27.6602 30.3192 27.6943 31.7721 27.8856C34.7085 28.272 38.1253 29.2793 40.5081 31.6621C42.8906 34.0448 43.8974 37.4611 44.2839 40.3973C44.4752 41.8505 44.51 43.1499 44.4966 44.0848C44.4932 44.3228 44.485 44.5367 44.4773 44.7229C44.2914 44.7306 44.078 44.7374 43.8406 44.7408C42.9057 44.7542 41.6063 44.7194 40.1531 44.5281C37.2168 44.1417 33.8 43.1356 31.4172 40.753C29.0343 38.3701 28.0278 34.9528 27.6413 32.0163C27.4501 30.5633 27.4159 29.2644 27.4294 28.3296C27.4328 28.0916 27.4381 27.8777 27.4459 27.6915Z" stroke="#f5f5f5" stroke-width="2" />
|
||||||
|
<path d="M26.916 28.1289C27.0533 28.2552 27.2099 28.4023 27.3809 28.5684C28.0514 29.2199 28.9457 30.1626 29.8379 31.3252C31.641 33.6748 33.3447 36.8031 33.3447 40.1728C33.3446 43.5424 31.6409 46.67 29.8379 49.0195C28.9456 50.1823 28.0514 51.1258 27.3809 51.7773C27.2102 51.9432 27.0532 52.0887 26.916 52.2148C26.7791 52.0889 26.6234 51.9428 26.4531 51.7773C25.7826 51.1258 24.8884 50.1823 23.9961 49.0195C22.1931 46.67 20.4884 43.5426 20.4883 40.1728C20.4883 36.803 22.193 33.6748 23.9961 31.3252C24.8884 30.1625 25.7826 29.2199 26.4531 28.5684C26.6238 28.4025 26.7789 28.255 26.916 28.1289Z" stroke="#f5f5f5" stroke-width="2" />
|
||||||
|
<path d="M26.2147 27.691C26.2225 27.8774 26.2293 28.0922 26.2327 28.3305C26.2461 29.2653 26.2119 30.5643 26.0207 32.0172C25.6342 34.9536 24.627 38.3704 22.2442 40.7532C19.8614 43.1357 16.4452 44.1425 13.5089 44.529C12.0557 44.7203 10.7563 44.7551 9.82145 44.7417C9.58349 44.7383 9.36957 44.7301 9.1834 44.7224C9.17564 44.5365 9.16886 44.3231 9.16545 44.0857C9.15203 43.1508 9.18689 41.8514 9.37813 40.3982C9.76458 37.4619 10.7706 34.0451 13.1533 31.6623C15.5361 29.2795 18.9535 28.2729 21.8899 27.8865C23.343 27.6952 24.6419 27.6611 25.5767 27.6745C25.8146 27.6779 26.0286 27.6833 26.2147 27.691Z" stroke="#f5f5f5" stroke-width="2" />
|
||||||
|
<path d="M25.7021 27.1602C25.5759 27.2974 25.4288 27.4541 25.2627 27.625C24.6111 28.2955 23.6684 29.1899 22.5059 30.082C20.1562 31.8851 17.028 33.5889 13.6582 33.5889C10.2886 33.5887 7.16105 31.885 4.81153 30.082C3.64873 29.1897 2.70529 28.2955 2.05372 27.625C1.88786 27.4543 1.74236 27.2973 1.61622 27.1602C1.74215 27.0232 1.88824 26.8676 2.05372 26.6973C2.70527 26.0267 3.64874 25.1326 4.81153 24.2402C7.16108 22.4372 10.2885 20.7326 13.6582 20.7324C17.0281 20.7324 20.1562 22.4371 22.5059 24.2402C23.6686 25.1325 24.6112 26.0268 25.2627 26.6973C25.4285 26.8679 25.576 27.023 25.7021 27.1602Z" stroke="#f5f5f5" stroke-width="2" />
|
||||||
|
<path d="M25.7338 25.9813C25.5474 25.9891 25.3326 25.9959 25.0943 25.9993C24.1595 26.0127 22.8605 25.9785 21.4076 25.7873C18.4712 25.4008 15.0544 24.3936 12.6716 22.0108C10.2891 19.628 9.28226 16.2118 8.89579 13.2755C8.70453 11.8223 8.66969 10.5229 8.6831 9.58806C8.68652 9.35009 8.69466 9.13618 8.70244 8.95C8.8883 8.94224 9.10168 8.93546 9.33911 8.93205C10.274 8.91863 11.5734 8.95349 13.0266 9.14473C15.9629 9.53118 19.3797 10.5372 21.7625 12.9199C24.1454 15.3027 25.1519 18.7201 25.5383 21.6565C25.7296 23.1096 25.7638 24.4085 25.7503 25.3433C25.7469 25.5812 25.7415 25.7952 25.7338 25.9813Z" stroke="#f5f5f5" stroke-width="2" />
|
||||||
|
<path d="M26.9121 25.7031C26.7748 25.5769 26.6182 25.4298 26.4473 25.2637C25.7768 24.6121 24.8824 23.6694 23.9902 22.5068C22.1872 20.1572 20.4834 17.029 20.4834 13.6592C20.4835 10.2896 22.1873 7.16203 23.9902 4.8125C24.8826 3.6497 25.7767 2.70627 26.4473 2.05469C26.618 1.88883 26.775 1.74333 26.9121 1.61719C27.049 1.74312 27.2047 1.88921 27.375 2.05469C28.0455 2.70624 28.9397 3.64971 29.832 4.8125C31.6351 7.16206 33.3397 10.2895 33.3398 13.6592C33.3398 17.029 31.6351 20.1572 29.832 22.5068C28.9398 23.6695 28.0455 24.6121 27.375 25.2637C27.2043 25.4295 27.0492 25.577 26.9121 25.7031Z" stroke="#f5f5f5" stroke-width="2" />
|
||||||
|
<path d="M25.5607 19.3789L27.4618 19.3457C27.5492 19.3442 27.6286 19.3884 27.6747 19.4583L27.7093 19.5368L28.045 20.9198C28.1559 21.3761 28.4798 21.7513 28.9152 21.9272C29.3419 22.0994 29.8251 22.0595 30.2183 21.8205L31.3036 21.1608C31.3771 21.116 31.4662 21.1134 31.5407 21.1488L31.6099 21.1965L32.9965 22.5729C33.0577 22.6336 33.0825 22.7203 33.0659 22.8012L33.035 22.8799L32.4788 23.8048C32.2386 24.2041 32.1867 24.6853 32.3299 25.122L32.4034 25.3063C32.5966 25.715 32.9524 26.0199 33.3793 26.1498L33.5666 26.1943L34.4238 26.3454C34.5414 26.3663 34.628 26.4675 34.6302 26.587L34.6642 28.532C34.6662 28.6469 34.5891 28.7489 34.478 28.7784L33.3785 29.0711C32.9508 29.1849 32.5956 29.4757 32.399 29.8657L32.3249 30.0389C32.1648 30.4848 32.2187 30.9794 32.4714 31.3803L33.0028 32.2237C33.0497 32.2981 33.0532 32.3898 33.0168 32.4656L32.9672 32.5349L31.5394 33.9438C31.4825 33.9998 31.4038 34.0241 31.3277 34.0129L31.2531 33.9898L30.2136 33.4737C29.8594 33.298 29.4528 33.2687 29.0801 33.387L28.9219 33.4464C28.5639 33.6057 28.2838 33.8968 28.1361 34.2552L28.0812 34.4124L27.7617 35.5207C27.7312 35.6261 27.6352 35.6996 27.5255 35.7016L25.5874 35.7354C25.4685 35.7375 25.3646 35.6555 25.3388 35.5396L25.0832 34.3808L25.0342 34.2068C24.9146 33.8684 24.6748 33.5849 24.361 33.4108L24.1976 33.3335L24.0433 33.2806C23.7308 33.1931 23.3978 33.2118 23.0968 33.3332L22.9495 33.4022L21.7729 34.0361C21.6979 34.0764 21.6097 34.0746 21.5375 34.0363L21.4722 33.9867L20.1375 32.5586C20.0776 32.4944 20.0574 32.4054 20.0787 32.3242L20.1135 32.2474L20.7373 31.3321C20.9834 30.9707 21.049 30.5199 20.923 30.108L20.8575 29.9343L20.7817 29.7901C20.6149 29.5119 20.3581 29.2988 20.0542 29.1855L19.8981 29.1365L18.4973 28.7898C18.3875 28.7625 18.3097 28.6649 18.3075 28.5518L18.275 26.6888C18.2733 26.5838 18.3372 26.4906 18.4321 26.4527L18.4738 26.4402L19.6961 26.1854C20.1451 26.0918 20.5232 25.7997 20.7294 25.3977L20.8074 25.2186C20.9466 24.8306 20.9208 24.4049 20.7399 24.0399L20.6523 23.8881L20.0481 22.9571C19.9825 22.8563 19.9975 22.723 20.0845 22.64L21.5852 21.2083C21.6715 21.126 21.8046 21.1157 21.9022 21.1843L22.718 21.758C23.1295 22.0472 23.659 22.111 24.1274 21.9278C24.5375 21.7673 24.8531 21.4365 24.9966 21.0267L25.0481 20.8461L25.3209 19.5765C25.3453 19.4629 25.4445 19.3809 25.5607 19.3789Z" fill="white" stroke="#f5f5f5" stroke-width="1.5" />
|
||||||
|
<circle cx="26.4669" cy="27.5406" r="2.53895" transform="rotate(-1 26.4669 27.5406)" fill="white" stroke="#f5f5f5" stroke-width="1.5" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 183 183"><title>Mistral Vibe</title><rect x="33" y="33" width="24" height="23" fill="#FFAF01"/><rect x="33" y="56" width="47" height="24" fill="#FF8204"/><rect x="33" y="80" width="118" height="23" fill="#FA500F"/><rect x="127" y="103" width="24" height="24" fill="#E10500"/><rect x="80" y="103" width="24" height="24" fill="#E10500"/><rect x="33" y="103" width="24" height="24" fill="#E10500"/><rect x="104" y="127" width="70" height="23" fill="#C4001D"/><rect x="10" y="127" width="70" height="23" fill="#C4001D"/><rect x="104" y="56" width="47" height="24" fill="#FF8204"/><rect x="127" y="33" width="24" height="23" fill="#FFAF01"/></svg>
|
||||||
|
After Width: | Height: | Size: 695 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#111" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Z.ai</title><path d="M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 312 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#fff" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Z.ai</title><path d="M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 312 B |
@@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "noconnor.agents",
|
||||||
|
"name": "My Agents",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Omarchy",
|
||||||
|
"license": "MIT",
|
||||||
|
"description": "Omarchy 4.0.4 Agents panel (per-agent limits with percentages, tokens by day, tokens by model), restored over the 4.5 redesign.",
|
||||||
|
"kinds": [
|
||||||
|
"bar-widget"
|
||||||
|
],
|
||||||
|
"activation": "on-demand",
|
||||||
|
"entryPoints": {
|
||||||
|
"barWidget": "Panel.qml"
|
||||||
|
},
|
||||||
|
"barWidget": {
|
||||||
|
"displayName": "My Agents",
|
||||||
|
"description": "One bar icon and one panel: rate-limit meters with pace, today, the last week, and the all-time model breakdown for every subscription.",
|
||||||
|
"category": "AI",
|
||||||
|
"aliases": [
|
||||||
|
"agents",
|
||||||
|
"model-usage"
|
||||||
|
],
|
||||||
|
"allowMultiple": false,
|
||||||
|
"defaults": {
|
||||||
|
"providers": {
|
||||||
|
"claude": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"codex": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"grok": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"kimi": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"cursor": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"grokbot": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"copilot": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"muse": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"agy": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"kimchi": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"hermes": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"devin": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"vibe": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"qwen": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"opencode": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"zai": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"ollama": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"minimax": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"openrouter": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"deepseek": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"fireworks": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"synthetic": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"local": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refreshIntervalSec": 900,
|
||||||
|
"syncMode": "Off",
|
||||||
|
"syncDir": "",
|
||||||
|
"syncFileName": "",
|
||||||
|
"syncDeviceId": ""
|
||||||
|
},
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"key": "refreshIntervalSec",
|
||||||
|
"type": "integer",
|
||||||
|
"label": "Refresh interval (seconds)",
|
||||||
|
"min": 30,
|
||||||
|
"max": 3600,
|
||||||
|
"step": 30,
|
||||||
|
"defaultValue": 900
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "syncMode",
|
||||||
|
"type": "enum",
|
||||||
|
"label": "Synced aggregation",
|
||||||
|
"options": [
|
||||||
|
"Off",
|
||||||
|
"On"
|
||||||
|
],
|
||||||
|
"defaultValue": "Off",
|
||||||
|
"description": "When On, write this machine's local usage snapshot and merge snapshots from other machines."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "syncDir",
|
||||||
|
"type": "path",
|
||||||
|
"label": "Sync folder",
|
||||||
|
"defaultValue": "",
|
||||||
|
"description": "A folder synced by Syncthing, Dropbox, rsync, etc."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "syncFileName",
|
||||||
|
"type": "string",
|
||||||
|
"label": "Snapshot file name",
|
||||||
|
"defaultValue": "",
|
||||||
|
"description": "Optional. Defaults to <hostname>.json. Use a different file name on each machine, such as laptop.json or desktop.json."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "syncDeviceId",
|
||||||
|
"type": "string",
|
||||||
|
"label": "Device id",
|
||||||
|
"defaultValue": "",
|
||||||
|
"description": "Optional stable device name used inside synced aggregate snapshots."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"omarchy": {
|
||||||
|
"clonedFrom": "omarchy.agents"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,55 +7,36 @@
|
|||||||
"bar": {
|
"bar": {
|
||||||
"position": "top",
|
"position": "top",
|
||||||
"transparent": false,
|
"transparent": false,
|
||||||
"centerAnchor": "omarchy.clock",
|
|
||||||
"layout": {
|
"layout": {
|
||||||
"left": [
|
"left": [
|
||||||
{
|
{
|
||||||
"id": "omarchy.menu"
|
"id": "omarchy.menu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "omarchy.workspaces"
|
"id": "niccoconnor.mantel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "niccoconnor.mantel"
|
"id": "omarchy.workspaces"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "omarchy.active-window"
|
"id": "omarchy.active-window"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"center": [
|
"center": [
|
||||||
{
|
|
||||||
"id": "omarchy.indicators"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "omarchy.keyboard-layout"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "omarchy.clock",
|
"id": "omarchy.clock",
|
||||||
"format": "dddd HH:mm",
|
"format": "dddd HH:mm",
|
||||||
"formatAlt": "d MMMM 'W'ww yyyy",
|
"formatAlt": "d MMMM 'W'ww yyyy",
|
||||||
"verticalFormat": "HH\n—\nmm"
|
"verticalFormat": "HH\n—\nmm"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "omarchy.weather"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "kb2uka.port-doctor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "akshar.radio-atlas"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "omaplug"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "omarchy.system-update"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"right": [
|
"right": [
|
||||||
{
|
{
|
||||||
"id": "omarchy.tray"
|
"id": "omarchy.tray"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "omarchy.keyboard-layout"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "izeesoft.omarchy-phone"
|
"id": "izeesoft.omarchy-phone"
|
||||||
},
|
},
|
||||||
@@ -79,7 +60,7 @@
|
|||||||
"id": "vstoms.netbird"
|
"id": "vstoms.netbird"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "omarchy.agents"
|
"id": "noconnor.agents"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "omarchy.bluetooth"
|
"id": "omarchy.bluetooth"
|
||||||
@@ -96,7 +77,55 @@
|
|||||||
{
|
{
|
||||||
"id": "omarchy.power"
|
"id": "omarchy.power"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"centerBegin": [
|
||||||
|
{
|
||||||
|
"id": "omarchy.indicators"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"centerEnd": [
|
||||||
|
{
|
||||||
|
"id": "omarchy.elsewhen",
|
||||||
|
"cities": [
|
||||||
|
{
|
||||||
|
"name": "New York",
|
||||||
|
"zone": "America/New_York"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "London",
|
||||||
|
"zone": "Europe/London"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dubai",
|
||||||
|
"zone": "Asia/Dubai"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tokyo",
|
||||||
|
"zone": "Asia/Tokyo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Los Angeles",
|
||||||
|
"zone": "America/Los_Angeles"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "omarchy.weather"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kb2uka.port-doctor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "akshar.radio-atlas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "omaplug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "omarchy.system-update"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tray": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
@@ -105,6 +134,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "niccoconnor.keyhints"
|
"id": "niccoconnor.keyhints"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "devmercenario.antigravity"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||