From 335ead5a6f04926dd20e151d4605873a573e1342 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 20 Aug 2026 09:05:02 -0300 Subject: [PATCH 01/35] ai-usagebar: widen the credential scrubber safeText() gated its redaction on a literal `=` or `Bearer `, so a secret written any other way reached the screen. A CLI that fails an HTTP request tends to quote the request, and 5 of 11 realistic shapes survived: an `X-Api-Key:` header, a `{"api_key": "..."}` field, credentials in a URL's userinfo half, and a bare provider key. The gate is now the keyword. A separator is a bad one: `=` and `:` both appear in ordinary readings, so the old check ran three backtracking patterns over almost every string it saw. Measured over a realistic corpus of 165 strings the two cost the same, and the new one runs nothing at all for a plan name. tests/scrub_test.lua reads the function out of service.luau instead of copying it, so it cannot pass against a version that no longer exists. It covers the eleven secrets, twelve readings that must survive untouched, and the length cap. --- ai-usagebar/service.luau | 56 +++++++++++++--- ai-usagebar/tests/scrub_test.lua | 108 +++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 ai-usagebar/tests/scrub_test.lua diff --git a/ai-usagebar/service.luau b/ai-usagebar/service.luau index 7e92a7e4..3b55351e 100644 --- a/ai-usagebar/service.luau +++ b/ai-usagebar/service.luau @@ -17,21 +17,57 @@ end -- here, where it enters the plugin: -- an error can quote the request that failed, and a request can carry a key in -- its query string. A runaway line would also push a bar capsule off screen. +-- A secret's value runs until whitespace or the quote or brace that closes it, +-- so a JSON field loses its value and keeps its punctuation. +local SECRET_VALUE = "[^%s\"',}]+" +local SECRET_WORDS = { + "[Kk][Ee][Yy]", + "[Tt][Oo][Kk][Ee][Nn]", + "[Ss][Ee][Cc][Rr][Ee][Tt]", + "[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd]", +} +-- Nine characters before the rest of a provider key, so a bare "sk-" in prose +-- is not mistaken for one. +local KEY_TAIL = string.rep("[%w_%-]", 9) + local function safeText(value) local text = noctalia.string.trim(tostring(value or "")) text = text:gsub("%s+", " ") - -- `scrub` runs this over ~165 strings per two-vendor read, and four - -- backtracking patterns on each one exhaust the callback's CPU budget, - -- which costs the whole report. All four need a literal `=` or `earer` to - -- match, so a plan name or a percentage skips them. - if text:find("=", 1, true) then - text = text:gsub("([%w_%-]*[Kk][Ee][Yy][%w_%-]*=)[^%s]+", "%1") - text = text:gsub("([Tt][Oo][Kk][Ee][Nn][%w_%-]*=)[^%s]+", "%1") - text = text:gsub("([Ss][Ee][Cc][Rr][Ee][Tt][%w_%-]*=)[^%s]+", "%1") + + -- `scrub` runs this over ~165 strings per two-vendor read, and backtracking + -- patterns on every one of them exhaust the callback's CPU budget, which + -- costs the whole report. So nothing expensive runs until a literal search + -- says it could match. The keyword is what opens the gate. A separator will + -- not do: `=` and `:` both turn up in ordinary readings, in a ratio, a clock + -- time, a URL, so gating on those ran the patterns over almost every string. + local lower = text:lower() + + if lower:find("key", 1, true) or lower:find("token", 1, true) + or lower:find("secret", 1, true) or lower:find("password", 1, true) then + for _, word in ipairs(SECRET_WORDS) do + local name = "[%w_%-]*" .. word .. "[%w_%-]*" + -- name=value: a query string or a shell assignment. + text = text:gsub("(" .. name .. "=)" .. SECRET_VALUE, "%1") + -- name: value: an HTTP header or a JSON field. + text = text:gsub("(" .. name .. "\"?%s*:%s*\"?)" .. SECRET_VALUE, "%1") + end end - if text:find("earer", 1, true) then - text = text:gsub("([Bb]earer%s+)[^%s]+", "%1") + + if lower:find("bearer", 1, true) then + text = text:gsub("([Bb][Ee][Aa][Rr][Ee][Rr]%s+)" .. SECRET_VALUE, "%1") end + + -- Credentials in the userinfo half of a URL the CLI echoed back. + if lower:find("://", 1, true) then + text = text:gsub("(://)[^%s/@]+:[^%s/@]+(@)", "%1%2") + end + + -- The provider key shape this plugin sits next to all day. Anchored at a + -- word start, so "desk-top" is not a key. + if lower:find("sk-", 1, true) then + text = text:gsub("%f[%w](sk%-)" .. KEY_TAIL .. "[%w_%-]*", "%1") + end + if #text > 200 then text = string.sub(text, 1, 200) .. "..." end return text end diff --git a/ai-usagebar/tests/scrub_test.lua b/ai-usagebar/tests/scrub_test.lua new file mode 100644 index 00000000..f7932de0 --- /dev/null +++ b/ai-usagebar/tests/scrub_test.lua @@ -0,0 +1,108 @@ +-- Redaction test for service.luau's safeText(). +-- +-- Everything the CLI writes reaches the screen, and a CLI that fails an HTTP +-- request tends to quote the request. safeText is the only thing standing +-- between that and a rendered label, so it gets a test. +-- +-- The function is read out of service.luau rather than copied here: a copy +-- would keep passing after the real one changed. +-- +-- lua tests/scrub_test.lua (or luajit) +-- +-- Run it from the plugin directory. Exits non-zero on the first failure. + +local SOURCE = "service.luau" + +local function loadSafeText() + local file = io.open(SOURCE, "r") + if file == nil then + error("run this from the plugin directory: " .. SOURCE .. " not found") + end + local source = file:read("*a") + file:close() + + -- The slice runs from the redaction constants to the end of the function. + local chunk = source:match("(local SECRET_VALUE.-\nend)\n") + if chunk == nil then + error("could not find safeText in " .. SOURCE .. "; update the markers here") + end + + -- The only host API the function touches. + local env = { + string = string, + ipairs = ipairs, + tostring = tostring, + noctalia = { string = { trim = function(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end } }, + } + local loaded = load(chunk .. "\nreturn safeText", "safeText", "t", env) + return loaded() +end + +local safeText = loadSafeText() + +-- Each case names the material that must not survive. +local SECRETS = { + { "GET /v1/usage?api_key=sk-ant-abc123456 failed", "abc123456" }, + { "request token=eyJhbGciOiJIUzI1NiJ9.SIGNATURE failed", "SIGNATURE" }, + { "client_secret=hunter2 rejected", "hunter2" }, + { "Authorization: Bearer sk-ant-api03-REALKEY", "REALKEY" }, + { '{"api_key": "sk-ant-api03-REALKEY"}', "REALKEY" }, + { '{"token":"eyJhbGciOiJIUzI1NiJ9.PAYLOAD.SIG"}', "PAYLOAD" }, + { "-H 'X-Api-Key: sk-ant-api03-REALKEY'", "REALKEY" }, + { "curl https://user:hunter2@api.anthropic.com/v1/usage", "hunter2" }, + { "authorization: bearer sk-ant-api03-REALKEY", "REALKEY" }, + { "OPENAI_API_KEY sk-proj-REALKEYVALUE not accepted", "REALKEY" }, + { "password=hunter2", "hunter2" }, +} + +-- Readings the plugin draws every minute. A scrubber that eats these is worse +-- than the leak it prevents. +local BENIGN = { + "Claude Pro", + "Session (5h)", + "Weekly (7d)", + "69% of the window elapsed", + "Resets in 4h 01m at 12:40", + "62% of monthly limit consumed", + "10pts under", + "https://github.com/akitaonrails/ai-usagebar", + "ai-usagebar exited with code 2", + "2026-08-20T11:29:59.872624Z", + "Desk-top mode", + "ChatGPT Free", +} + +local failures = 0 + +local function fail(message) + failures = failures + 1 + io.write("FAIL ", message, "\n") +end + +for _, case in ipairs(SECRETS) do + local input, material = case[1], case[2] + local output = safeText(input) + if output:find(material, 1, true) then + fail(material .. " survived: " .. output) + end +end + +for _, input in ipairs(BENIGN) do + local output = safeText(input) + if output ~= input then + fail("mangled a normal reading: " .. input .. " -> " .. output) + end +end + +-- A runaway line would push a bar capsule off the screen. +local long = safeText(string.rep("x", 500)) +if #long > 210 then + fail("long text was not capped: " .. #long .. " characters") +end + +if failures > 0 then + io.write(failures, " failure(s)\n") + os.exit(1) +end + +io.write("ok: ", #SECRETS, " secrets redacted, ", #BENIGN, " readings untouched, length capped\n") From 9ddfa48ead489e5b7f18254df9bdfcb80fdd89f4 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 20 Aug 2026 09:05:20 -0300 Subject: [PATCH 02/35] ai-usagebar: rework the capsule and panel, v1.2.0 The readings now line up. Every percentage is right-aligned in a fixed column, so a stack of cards reads as one ruler and the bar capsule keeps its width from 9% to 100% instead of nudging its neighbours on every read. Capsule: a read in flight dims the row. It used to append a spinner, which shoved every widget to its right once per cycle. A failure draws the plugin glyph in the error colour; the old pair of glyphs read as two problems. Panel: - Selection is a tint. A filled `primary` row had to invert every colour inside it and shouted over the reading it was meant to mark. - Severity ships a word next to the colour, so the tier is readable without separating two accents. - The time story is one line: what is left of the window, when it lands, how much is gone, and whether the spend is running ahead. - `ui.button` for the header refresh and the error actions, replacing rows hand-built to look like buttons. The refresh button becomes the spinner in place. - Skeletons while the first read lands, and an empty state that names what is missing. - Dropped the provider id and a "ready" status from the detail pane. The id is the row that was just clicked and a healthy read is the default. Two layout bugs came out of testing it against the running shell. The root row had no flexGrow, so neither pane was given a bounded height and their ui.scroll children asked for their natural one, which clipped the cards. A bare ui.column also takes a column's free space for itself, which parked the detail title above a hundred pixels of nothing; the wrapping row that prevents it is back, with a comment saying why it is there. textRole and barRole differed only in their resting colour and existed in both entries. They are one severityRole(x, calm). The two skeleton shapes are one. 25 lines lighter. --- ai-usagebar/README.md | 29 +-- ai-usagebar/bar.luau | 42 ++--- ai-usagebar/panel.luau | 307 ++++++++++++++++++++----------- ai-usagebar/plugin.toml | 2 +- ai-usagebar/translations/en.json | 7 +- 5 files changed, 244 insertions(+), 143 deletions(-) diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index 840a493c..5fe25c0c 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -24,9 +24,9 @@ tarballs on the project's GitHub Releases page. Configure your providers once in `~/.config/ai-usagebar/config.toml`; the CLI owns the credentials and the endpoints, and this plugin never sees them. -`xdg-open` is optional. It is spawned by one row in the panel, the link to the -CLI's project page offered when `ai-usagebar` is not on `PATH`. Without -xdg-utils that row does nothing and the rest of the plugin is unaffected. +`xdg-open` is optional. It is spawned by one button in the panel, the link to +the CLI's project page offered when `ai-usagebar` is not on `PATH`. Without +xdg-utils that button is not drawn and the rest of the plugin is unaffected. ## Usage @@ -81,21 +81,22 @@ with its headline percentage. On the right is the selected one in detail: one card per reported metric, with a quota bar over a thinner "window elapsed" bar, so a fill that outruns the clock bar means quota is burning ahead of pace. Credit balances and free text rows the CLI reports get rendered as well. -Opening the panel asks the CLI for fresh numbers, and the header says how old -the reading is. There is no refresh button and no close button: the read -happens on open, and the panel closes when you click away from it or press the -same widget again. +Opening the panel asks the CLI for fresh numbers, and the detail pane says how +old the reading is. The refresh button in the header asks again; it turns into +a spinner while the CLI is answering. There is no close button: the panel +closes when you click away from it or press the same widget again. The list follows the CLI. A provider that `ai-usagebar` has no credential for never appears, while one that is set up and failing keeps its row and shows the error. -The detail pane spells out everything the CLI reports for that provider instead -of implying it: the plan and account name, the provider id, its status, a stale -flag when the reading is old, and when it was fetched. Each window gets its -label, the severity the CLI assigned it, the percentage, the raw value string -when that says more than the percentage, how much of the window has elapsed, the -time left with the clock time (or date) its reset lands on, and the pace line. +The detail pane spells out what the CLI reports for that provider instead of +implying it: the plan and account name, when it was fetched, a stale flag when +the reading is old, and the status when it is anything other than a healthy +read. Each window gets its label, the percentage, the raw value string when +that says more than the percentage, how much of the window has elapsed, the +time left with the clock time (or date) its reset lands on, the pace line, and +the severity as a word whenever the CLI calls the window high or critical. Credit blocks and free text rows appear as the CLI writes them. To open the panel from a terminal: @@ -148,7 +149,7 @@ noctalia msg plugin felipeartur/ai-usagebar:poller all select anthropic it knows arrives on that command's stdout. - A provider that fails still comes back as an entry with `status = "error"`, so one broken provider does not blank the others. A reading the CLI marks stale - keeps showing, flagged in the capsule and in the panel header. + keeps showing, flagged in the capsule and in the panel's detail pane. - The file watcher follows the `.luau` entries only, so the files in `translations/` are read once, when the plugin loads. Editing a string takes a reload before the new text shows up: diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index eb8a992e..210eea44 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -166,20 +166,14 @@ end -- would be a second source of truth. Text stays in the bar's own colour until -- the reading is high or critical, and the accent colour is used on the bar -- fill only. -local function textRole(metric) - if not colorByUsage then return "on_surface" end +-- `calm` is the colour when the CLI has raised nothing. With the tint switched +-- off it is the colour for everything. +local function severityRole(metric, calm) + if not colorByUsage then return calm end local severity = metric ~= nil and tostring(metric.severity or "") or "" if severity == "critical" then return "error" end if severity == "high" then return "tertiary" end - return "on_surface" -end - -local function barRole(metric) - if not colorByUsage then return "primary" end - local severity = metric ~= nil and tostring(metric.severity or "") or "" - if severity == "critical" then return "error" end - if severity == "high" then return "tertiary" end - return "primary" + return calm end local function shortName(entry) @@ -237,12 +231,15 @@ end -- appended to whatever it produced. local function chip(entry) local metric = headline(entry) - local tint = textRole(metric) - local fill = barRole(metric) + local tint = severityRole(metric, "on_surface") + local fill = severityRole(metric, "primary") local percent = metric ~= nil and tonumber(metric.percent) or nil local text = percent ~= nil and string.format("%d%%", percent) or "—" local glyph = ui.glyph({ name = GLYPHS[tostring(entry.id)] or "brain", size = 13, color = tint }) - local pct = ui.label({ text = text, fontSize = 11, fontWeight = "semibold", color = tint, maxLines = 1 }) + -- Right-aligned in a fixed column, so the capsule is the same width at 9% + -- as at 100% and stops nudging its neighbours on the bar once per read. + local pct = ui.label({ text = text, fontSize = 11, fontWeight = "semibold", color = tint, + maxLines = 1, width = 30, textAlign = "end" }) local name = showName and ui.label({ text = shortName(entry), fontSize = 11, color = "on_surface_variant", maxLines = 1 }) or nil @@ -346,21 +343,22 @@ local function render() children[#children + 1] = chip(entry) end - if polling then - children[#children + 1] = ui.glyph({ name = "loader-2", size = 11, color = "on_surface_variant" }) - end - if #children == 0 then - children[1] = ui.row({ gap = 4, align = "center" }, { - ui.glyph({ name = "brain", size = 13, color = "on_surface_variant" }), - ui.glyph({ name = "alert-circle", size = 12, color = "error" }), + -- One glyph, coloured by the state. A second icon beside it reads as a + -- second problem, and the plugin's own mark in the error colour says + -- the same thing in the space of one. + children[1] = ui.glyph({ + name = "brain", size = 13, + color = failure.code ~= "" and "error" or "on_surface_variant", }) elseif hidden > 0 then children[#children + 1] = ui.label({ text = "+" .. tostring(hidden), fontSize = 10, color = "on_surface_variant", maxLines = 1 }) end - barWidget.render(ui.row({ gap = 7, align = "center" }, children)) + -- A read in flight dims the capsule rather than appending a spinner to it: + -- a node that comes and goes every cycle shoves every widget to its right. + barWidget.render(ui.row({ gap = 6, align = "center", opacity = polling and 0.55 or 1 }, children)) barWidget.setTooltip(tooltip(picked, hidden)) end diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 0a94d839..11982edc 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -62,20 +62,14 @@ local function resetClock(section) return os.date("%a", at) .. " " .. clock end --- Text stays on the surface colour until the CLI calls the window high or --- critical. The accent colour is used on the bar fill only. -local function textRole(section) +-- The CLI tiers every percentage; copying its thresholds here would be a second +-- source of truth. `calm` is what to use when it has raised nothing: text stays +-- on the surface colour, and the accent is kept for bar fills. +local function severityRole(section, calm) local severity = tostring(section and section.severity or "") if severity == "critical" then return "error" end if severity == "high" then return "tertiary" end - return "on_surface" -end - -local function barRole(section) - local severity = tostring(section and section.severity or "") - if severity == "critical" then return "error" end - if severity == "high" then return "tertiary" end - return "primary" + return calm end -- The CLI reports a vendor it has no credential for as a `credentials error`. @@ -159,37 +153,49 @@ end -- ── Cards ───────────────────────────────────────────────────────────────────── +-- A severity word, and only when the CLI raised one. Colour on its own leaves +-- the reading to anyone who can tell the two accents apart. +local function severityWord(section) + local severity = tostring(section and section.severity or "") + if severity ~= "high" and severity ~= "critical" then return nil end + return noctalia.tr("ui.severity." .. severity) +end + local function metricCard(section) local percent = tonumber(section.percent) or 0 - local tint = textRole(section) - local fill = barRole(section) + local tint = severityRole(section, "on_surface") + local fill = severityRole(section, "primary") local value = tostring(section.value or ""):gsub(" of ", " / ") -- Only worth a column of its own when it says more than the percentage. local showValue = value ~= "" and value ~= string.format("%d%%", percent) local header = { - ui.glyph({ name = metricIcon(section.label), size = 14, color = tint }), - ui.label({ text = tostring(section.label or ""), fontSize = 11, color = "on_surface_variant" }), - ui.label({ - text = tostring(section.severity or ""), - fontSize = 9, fontWeight = "semibold", color = tint, - visible = tostring(section.severity or "") ~= "", - }), + ui.glyph({ name = metricIcon(section.label), size = 14, color = "on_surface_variant" }), + ui.label({ text = tostring(section.label or ""), fontSize = 12, fontWeight = "semibold", + color = "on_surface", maxLines = 1 }), ui.spacer({ flexGrow = 1 }), } + local word = severityWord(section) + if word ~= nil then + header[#header + 1] = ui.label({ text = word, fontSize = 10, fontWeight = "semibold", color = tint }) + end if showValue then - header[#header + 1] = ui.label({ text = value, fontSize = 11, color = "on_surface_variant" }) + header[#header + 1] = ui.label({ text = value, fontSize = 11, color = "on_surface_variant", maxLines = 1 }) end + -- Every card ends on the same right edge, so a column of them reads as one + -- ruler instead of a ragged margin. header[#header + 1] = ui.label({ text = string.format("%d%%", percent), fontSize = 15, fontWeight = "bold", color = tint, + width = 46, + textAlign = "end", }) local body = { ui.row({ gap = 6, align = "center" }, header), - ui.progress({ progress = ratio(percent), fill = fill, track = "on_surface/0.16", radius = 3, height = 5 }), + ui.progress({ progress = ratio(percent), fill = fill, track = "on_surface/0.16", radius = 3, height = 6 }), } -- Two readings: quota spent above, window elapsed below. A shorter clock bar @@ -201,26 +207,39 @@ local function metricCard(section) fill = "on_surface/0.45", track = "on_surface/0.10", radius = 2, - height = 2, - }) - body[#body + 1] = ui.label({ - text = noctalia.tr("ui.elapsed", { percent = elapsed }), - fontSize = 10, color = "on_surface_variant", + height = 3, }) end + -- One line under the bars carries the whole time story: what is left of the + -- window, when it lands, how much of it is gone, and whether the spend is + -- running ahead. Four separate lines said the same thing four times taller. local left = countdown(section) local clock = resetClock(section) local paceText, paceColor = pace(section.detail) - if left ~= "" or paceText ~= "" then - local footer = {} - if left ~= "" then - footer[#footer + 1] = ui.glyph({ name = "clock", size = 12, color = "on_surface_variant" }) - footer[#footer + 1] = ui.label({ text = left, fontSize = 11, color = "on_surface_variant" }) - if clock ~= "" then - footer[#footer + 1] = ui.label({ text = clock, fontSize = 11, fontWeight = "bold", color = "primary" }) - end + local footer = {} + if left ~= "" then + footer[#footer + 1] = ui.glyph({ name = "clock", size = 12, color = "on_surface_variant" }) + footer[#footer + 1] = ui.label({ text = left, fontSize = 11, color = "on_surface_variant" }) + if clock ~= "" then + -- Parenthesised and muted: it is the time the countdown beside it + -- lands on, not a reading of its own. In the accent colour it was + -- the loudest thing in the card after the percentage. + footer[#footer + 1] = ui.label({ + text = "(" .. clock .. ")", fontSize = 11, color = "on_surface_variant", + }) end + end + if elapsed ~= nil then + if #footer > 0 then + footer[#footer + 1] = ui.label({ text = "·", fontSize = 11, color = "on_surface_variant" }) + end + footer[#footer + 1] = ui.label({ + text = noctalia.tr("ui.elapsed", { percent = elapsed }), + fontSize = 11, color = "on_surface_variant", maxLines = 1, + }) + end + if #footer > 0 or paceText ~= "" then footer[#footer + 1] = ui.spacer({ flexGrow = 1 }) if paceText ~= "" then footer[#footer + 1] = ui.label({ text = paceText, fontSize = 11, fontWeight = "semibold", color = paceColor }) @@ -239,19 +258,24 @@ end local function blockCard(section) local body = { ui.row({ gap = 6, align = "center" }, { - ui.glyph({ name = metricIcon(section.label), size = 14, color = "primary" }), - ui.label({ text = tostring(section.label or ""), fontWeight = "bold", color = "on_surface" }), + ui.glyph({ name = metricIcon(section.label), size = 14, color = "on_surface_variant" }), + ui.label({ text = tostring(section.label or ""), fontSize = 12, fontWeight = "semibold", + color = "on_surface", maxLines = 1 }), }), } for _, line in ipairs(section.body or {}) do local text = noctalia.string.trim(tostring(line)) + -- A line the CLI left as a bare "balance:" reads as a row that failed + -- to render. Nothing is a value, and it is spelled the same way here as + -- it is everywhere else in the panel. + if text:find(":$") then text = text .. " —" end body[#body + 1] = ui.label({ text = text ~= "" and text or "—", fontSize = 11, color = "on_surface_variant", }) end - return ui.column({ gap = 4, padding = 10, radius = 8, fill = "surface_variant" }, body) + return ui.column({ gap = 4, padding = 10, radius = 8, fill = "surface_variant/0.45" }, body) end local function textRow(section) @@ -295,17 +319,21 @@ local function providerRow(entry, selected) local metric = headline(entry) local percent = metric ~= nil and tonumber(metric.percent) or nil local broken = entry.status == "error" - local tint = selected and "on_primary" or textRole(metric) - local fill = selected and "on_primary" or barRole(metric) - local muted = selected and "on_primary" or "on_surface_variant" + local tint = severityRole(metric, "on_surface") + -- The reading keeps its own severity colour whether or not the row is + -- selected. A selected row that recolours its number hides the one thing + -- the list exists to compare. local right if broken then - right = ui.glyph({ name = "alert-circle", size = 14, color = selected and "on_primary" or "error" }) + right = ui.row({ width = 34, justify = "end" }, { + ui.glyph({ name = "alert-circle", size = 14, color = "error" }), + }) else right = ui.label({ text = percent ~= nil and string.format("%d%%", percent) or "—", fontSize = 13, fontWeight = "bold", color = tint, + width = 34, textAlign = "end", }) end @@ -313,25 +341,30 @@ local function providerRow(entry, selected) ui.label({ text = tostring(entry.display_name or entry.id), fontSize = 12, fontWeight = "semibold", - color = selected and "on_primary" or "on_surface", maxLines = 1, + color = selected and "primary" or "on_surface", maxLines = 1, }), } if percent ~= nil and not broken then lines[#lines + 1] = ui.progress({ progress = ratio(percent), - fill = fill, - track = selected and "on_primary/0.25" or "on_surface/0.16", + fill = severityRole(metric, "primary"), + track = "on_surface/0.16", radius = 2, height = 3, }) end lines[#lines + 1] = ui.label({ text = broken and noctalia.tr("ui.unavailable") or tostring(entry.plan or entry.id or ""), - fontSize = 10, color = muted, maxLines = 1, + fontSize = 10, color = "on_surface_variant", maxLines = 1, }) return ui.row({ + -- Keyed, so the click handler survives the second tick the countdowns + -- ride on rather than being rebuilt under the pointer once a second. + key = "provider-" .. tostring(entry.id), gap = 8, align = "center", padding = 8, radius = 8, - fill = selected and "primary" or "surface_variant", + -- Selection is a tint, not a slab of accent: a filled `primary` row has + -- to invert every colour inside it, and then it shouts over the reading. + fill = selected and "primary/0.14" or "surface_variant/0.45", onClick = function() -- currentEntry() reads this back, so the panel and the capsule that -- opened it stay on the same provider. @@ -339,7 +372,8 @@ local function providerRow(entry, selected) render() end, }, { - ui.glyph({ name = GLYPHS[tostring(entry.id)] or "brain", size = 16, color = tint }), + ui.glyph({ name = GLYPHS[tostring(entry.id)] or "brain", size = 16, + color = selected and "primary" or "on_surface_variant" }), ui.column({ gap = 3, flexGrow = 1 }, lines), right, }) @@ -347,14 +381,8 @@ end -- ── Render ──────────────────────────────────────────────────────────────────── -local function actionRow(glyph, text, onClick) - return ui.row({ - gap = 5, align = "center", padding = 6, radius = 6, - fill = "surface_variant", onClick = onClick, - }, { - ui.glyph({ name = glyph, size = 12, color = "primary" }), - ui.label({ text = text, fontSize = 11, color = "primary" }), - }) +local function requestRefresh() + noctalia.state.set("command", { action = "refresh", at = os.time() }) end -- The failure and the suggested fix read first; the CLI's own words come last @@ -375,20 +403,47 @@ local function errorBlock() }) end - children[#children + 1] = actionRow("refresh", noctalia.tr("ui.retry"), function() - noctalia.state.set("command", { action = "refresh", at = os.time() }) - end) + -- The shell's own button, so a retry here looks like every other retry in + -- Noctalia and follows the user's theme without being told to. + local actions = { + ui.button({ + text = noctalia.tr("ui.retry"), glyph = "refresh", + variant = "outline", controlSize = "sm", + enabled = not polling, + onClick = requestRefresh, + }), + } -- Retrying is pointless until the CLI exists, so that one failure gets the -- install page as well. The URL is a literal, so there is nothing to quote, - -- and the row is only offered where something can open it. + -- and the button is only offered where something can open it. It reads as a + -- label with the address in its tooltip: a raw URL is not a button caption. if failure.code == "not_installed" and HAS_OPENER then - children[#children + 1] = actionRow("external-link", "github.com/akitaonrails/ai-usagebar", function() - noctalia.runAsync("xdg-open https://github.com/akitaonrails/ai-usagebar") - end) + actions[#actions + 1] = ui.button({ + text = noctalia.tr("ui.install"), glyph = "external-link", + variant = "ghost", controlSize = "sm", + tooltip = "github.com/akitaonrails/ai-usagebar", + onClick = function() + noctalia.runAsync("xdg-open https://github.com/akitaonrails/ai-usagebar") + end, + }) end + children[#children + 1] = ui.row({ gap = 6, align = "center" }, actions) - return ui.column({ gap = 6 }, children) + return ui.column({ gap = 8 }, children) +end + +-- A muted stand-in at the shape of what is coming, so a cold read is not a +-- spinner parked where the content is about to land. One shape serves both +-- panes: it is a placeholder, and two kinds of placeholder is one too many. +local function skeleton(key) + return ui.column({ + key = "skeleton-" .. key, + gap = 6, padding = 10, radius = 8, fill = "surface_variant/0.45", + }, { + ui.box({ width = 96, height = 10, radius = 3, fill = "on_surface/0.10" }), + ui.box({ height = 4, radius = 2, fill = "on_surface/0.06" }), + }) end local function listPane(entry) @@ -399,17 +454,26 @@ local function listPane(entry) end end if #rows == 0 then - rows[1] = ui.label({ text = noctalia.tr("ui.loading"), fontSize = 11, color = "on_surface_variant" }) + for index = 1, 3 do rows[index] = skeleton("row-" .. index) end end return ui.column({ gap = 10, padding = 14, width = 250 }, { ui.row({ gap = 8, align = "center" }, { ui.glyph({ name = "brain", size = 18, color = "primary" }), - ui.label({ text = noctalia.tr("ui.title"), fontSize = 15, fontWeight = "bold", color = "primary" }), + -- The accent belongs to the selection and the bars. A title that + -- takes it too leaves the panel with no quiet level to fall back to. + ui.label({ text = noctalia.tr("ui.title"), fontSize = 15, fontWeight = "bold", color = "on_surface" }), ui.spacer({ flexGrow = 1 }), - -- The panel refreshes when it opens, so the header only has to - -- show whether that read is still running. - ui.glyph({ name = "loader-2", size = 16, color = "primary", visible = polling }), + -- One slot for the read: the button becomes the spinner while the + -- CLI answers, rather than a second glyph appearing beside it and + -- pushing the header around once a cycle. + ui.button({ + glyph = polling and "loader-2" or "refresh", + variant = "ghost", controlSize = "sm", + tooltip = noctalia.tr("ui.refresh"), + enabled = not polling, + onClick = requestRefresh, + }), }), ui.scroll({ gap = 6, flexGrow = 1 }, rows), }) @@ -424,44 +488,60 @@ local function detailPane(entry) if subtitle == title then subtitle = "" end end - local children = { - ui.row({ gap = 8, align = "center" }, { + -- No entry yet means the skeletons below are the whole pane. A title here + -- would only repeat the one the list pane is already showing. + local children = {} + if entry ~= nil then + -- The row keeps the title block honest about its height. A bare + -- ui.column dropped into a column takes the pane's free space for + -- itself, which parks the title at the top of a hundred pixels of + -- nothing and pushes the rest of the header down. Wrapped, the block is + -- only as tall as the two labels in it. + children[#children + 1] = ui.row({ gap = 8, align = "center" }, { ui.column({ gap = 0, flexGrow = 1 }, { - ui.label({ text = title, fontSize = 15, fontWeight = "bold", color = "on_surface" }), - ui.label({ text = subtitle, fontSize = 11, color = "on_surface_variant", visible = subtitle ~= "" }), + ui.label({ text = title, fontSize = 15, fontWeight = "bold", + color = "on_surface", maxLines = 1 }), + ui.label({ text = subtitle, fontSize = 11, color = "on_surface_variant", + maxLines = 1, visible = subtitle ~= "" }), }), - }), - } + }) + end - -- The entry's own fields, spelled out rather than implied by a colour. + -- What the entry says about itself, in words rather than a colour. The + -- provider id and a "ready" status are the plugin talking to itself: the id + -- is the row that was just clicked, and a healthy read is the default. if entry ~= nil then - local chips = { - ui.label({ text = tostring(entry.id or ""), fontSize = 10, color = "on_surface_variant" }), - ui.label({ text = "·", fontSize = 10, color = "on_surface_variant" }), - ui.label({ - text = tostring(entry.status or ""), - fontSize = 10, - color = entry.status == "ready" and "on_surface_variant" or "error", - }), - } + local chips = {} + local function separate() + if #chips > 0 then + chips[#chips + 1] = ui.label({ text = "·", fontSize = 10, color = "on_surface_variant" }) + end + end + if entry.status ~= "ready" then + chips[#chips + 1] = ui.label({ + text = tostring(entry.status or ""), fontSize = 10, color = "error", maxLines = 1, + }) + end if entry.stale == true then - chips[#chips + 1] = ui.label({ text = "·", fontSize = 10, color = "on_surface_variant" }) + separate() chips[#chips + 1] = ui.label({ text = noctalia.tr("ui.stale"), fontSize = 10, color = "tertiary" }) end local fetched = parseIso(entry.fetched_at) if fetched ~= nil then - chips[#chips + 1] = ui.spacer({ flexGrow = 1 }) + separate() chips[#chips + 1] = ui.glyph({ name = "clock", size = 11, color = "on_surface_variant" }) chips[#chips + 1] = ui.label({ text = updatedText(entry) .. " · " .. noctalia.formatTime(noctalia.timeFormat(), fetched), fontSize = 10, color = "on_surface_variant", }) end - children[#children + 1] = ui.row({ gap = 4, align = "center" }, chips) + if #chips > 0 then + children[#children + 1] = ui.row({ gap = 4, align = "center" }, chips) + end end - local status = entry == nil and noctalia.tr("ui.loading") - or entry.status == "error" and tostring(entry.error or noctalia.tr("ui.unavailable")) + local status = entry ~= nil and entry.status == "error" + and tostring(entry.error or noctalia.tr("ui.unavailable")) or nil if status then children[#children + 1] = ui.label({ text = status, fontSize = 11, color = "on_surface_variant" }) end @@ -478,8 +558,18 @@ local function detailPane(entry) end if #cards > 0 then children[#children + 1] = ui.scroll({ gap = 8, flexGrow = 1 }, cards) - else + elseif entry ~= nil then + -- A provider with nothing to draw says so. Half an empty panel is not + -- an answer to the question the panel was opened to answer. + children[#children + 1] = ui.row({ gap = 6, align = "center" }, { + ui.glyph({ name = "info-circle", size = 14, color = "on_surface_variant" }), + ui.label({ text = noctalia.tr("ui.no_usage"), fontSize = 11, color = "on_surface_variant" }), + }) children[#children + 1] = ui.spacer({ flexGrow = 1 }) + else + children[#children + 1] = ui.column({ gap = 8, flexGrow = 1 }, { + skeleton("card-1"), skeleton("card-2"), + }) end return ui.column({ gap = 10, padding = 14, flexGrow = 1 }, children) @@ -487,27 +577,34 @@ end function render() local entry = currentEntry() - -- A failure replaces the report rather than sitting above it. The numbers - -- are from a read that is no longer happening, and leaving them up puts a - -- provider list and a percentage next to an alert saying neither can be - -- trusted. + -- A failure replaces the report. The numbers are from a read that is no + -- longer happening, and leaving them up puts a provider list and a + -- percentage next to an alert saying neither can be trusted. if failure.code ~= "" then - -- The panel keeps the fixed size the manifest gives it, so the block - -- is width-bounded rather than stretched across 720px of button. - panel.render(ui.column({ gap = 10, padding = 14, width = 320 }, { - ui.row({ gap = 8, align = "center" }, { - ui.glyph({ name = "brain", size = 18, color = "primary" }), - ui.label({ text = noctalia.tr("ui.title"), fontSize = 15, fontWeight = "bold", color = "primary" }), + -- The panel keeps the fixed size the manifest gives it, and a failure + -- has nowhere near 720x400 of things to say. The block stays bounded to + -- a readable width and sits in the middle of the panel, where an empty + -- surround reads as composition instead of a half-drawn frame. + panel.render(ui.column({ flexGrow = 1, padding = 14, align = "center", justify = "center" }, { + ui.column({ gap = 10, width = 320 }, { + ui.row({ gap = 8, align = "center" }, { + ui.glyph({ name = "brain", size = 18, color = "primary" }), + ui.label({ text = noctalia.tr("ui.title"), fontSize = 15, + fontWeight = "bold", color = "on_surface" }), + }), + errorBlock(), }), - errorBlock(), })) return end - panel.render(ui.row({ gap = 0 }, { + -- Both panes have to be told to fill the panel, or their ui.scroll children + -- ask for their natural height instead of the height they were given: the + -- cards then overflow the panel and the free space is handed to whatever + -- else in the column will take it, which pushes the header away from them. + panel.render(ui.row({ gap = 0, flexGrow = 1, align = "stretch" }, { listPane(entry), - -- ui.separator is horizontal only; a one-pixel column is the divider. - ui.column({ width = 1, fill = "on_surface/0.12" }, {}), + ui.separator({ orientation = "vertical", color = "outline", opacity = 0.28 }), detailPane(entry), })) end diff --git a/ai-usagebar/plugin.toml b/ai-usagebar/plugin.toml index f4ae1437..0dc13313 100644 --- a/ai-usagebar/plugin.toml +++ b/ai-usagebar/plugin.toml @@ -1,6 +1,6 @@ id = "felipeartur/ai-usagebar" name = "AI Usage" -version = "1.1.0" +version = "1.2.0" plugin_api = 9 author = "felipeartur" license = "MIT" diff --git a/ai-usagebar/translations/en.json b/ai-usagebar/translations/en.json index 6127d2ce..4c0f0e98 100644 --- a/ai-usagebar/translations/en.json +++ b/ai-usagebar/translations/en.json @@ -77,11 +77,16 @@ }, "hidden_label": "Not shown", "hidden_value": "{count} more, click to open the panel", - "loading": "Loading…", + "install": "Install page", "no_usage": "No usage reported", "not_configured": "`{vendor}` is not configured in ai-usagebar", "now": "now", + "refresh": "Refresh now", "retry": "Try again", + "severity": { + "critical": "critical", + "high": "high" + }, "stale": "stale", "stale_hint": "showing last known data", "title": "AI Usage", From 32700dd984fe7f27b49da51370c8e8ea0cd75db0 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 20 Aug 2026 09:05:32 -0300 Subject: [PATCH 03/35] ai-usagebar: retake the thumbnail against the new panel The card was run through the official generator in 5d8b559, but the image it was given was already a composed card with its own title and subtitle. The generator nested that inside its frame, so the name and the description appeared twice and the inner copy was too small to read. Same frame and the same title, tag and accent it was given there. The payload is now a plain screenshot of the panel, cropped to the geometry the compositor reports for the panel layer. --- ai-usagebar/thumbnail.webp | Bin 51412 -> 29280 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ai-usagebar/thumbnail.webp b/ai-usagebar/thumbnail.webp index 6a5ca136aba3eec8d60944be5001fd331a8d6908..d3410ad72195e45a53020fbd1a79f7d2e1157a1e 100644 GIT binary patch literal 29280 zcmV(>K-j-hNk&FkasU8VMM6+kP&gn=asU9Z%mJMND!>CA0zN4eh(jTvAt0er=^!u# z328$_^N|0V4iWb|q8_}Y?_1#+tPpre!Aveq;#0mcO4+yk->#nN|LVhN?Du=#`#bZO z-fE_v75!iR?=Mb^`HuOo|4a6--%s@~^*+G=*uT4almFf9AN%M2KgMr>|JFac{@HuZ z|H=K!_hJ3F)F=Aq`CnW=RB!Nq_x*zZWPN4-^8VrcJ%51zvHLOYH~&A_1OKnKKkyEo ze+mEH{Uh;H_SfroH9x-ozkg%@x#nx=pRE6L{e=GC|Cjyu-(Tea!}|*Pue^^#f0BJ` z`#<(?@n7Y?x&GsQ4gUwi|I7c3{|EWG{kP-`@qgmK)<3lV(0_CNy!v|kp835G{%@d1 zsvk4|5&gsb|NC$9AH2U--vRy8{1@*3y}zS>NB;-;x&3$S1NhJL5AQ$Tzi$7!fBgS( z@hjma_K);m_PxM=k^emZGyTW?C;kubzyJUCeldR)|0n#{`)~dq*Y|h-yWIEgXZ}xh5803XA``nBcP&ZnP}2(| zWy%IVGiPnlGy1;8yH-uui&QTOBv)8?`{1<$st znW2cBE84&$eyWS~A&$QC@w_vP8M#Sv+ZWL%lyxs>xn)^JRoKu4Z{Bhk75nz4Ci^a&FR+ zO&6-RAhpH&AY*&!rp^?~QG_GUJ8~E$!ecFCvS`cuLR5)b zL!i`qy6--p=vIi&ig54nJb|qCg4xtpwuT`CN~?c0v?Z#WX*3a!=4pn$JHETjN?q$R zOAKgk?_Dm_pQ41Pwc0)Kl%93pIt^FtN(f*_#)F{lTziN;0y^ z{~DL`EI9LSME`IJe9u{yJd=M~vMXWIbwP*Z#RefC!2}R-6qK_o-2vmFJG!H9m`cZJ zCY?6S(QTRl2C`w;xDm!bxKvyo<^g#XXHrS#bju&mu~~7`5W@v$^_pGAxX-dAT17JW zA}pE6xh-48LI5in8PrKmjU`!a87H@4nK}ZDeo2PVUVnO>B6Kb%Q#eEQ4egzY%cvDCPop$rk3VE{pLfbIzl1k7X57UeMaXL`eDs6NOp0UQV z03piD)zodUWlzxDcG3!OKJ_N8TEK~>=T!8W4`_a;4Mj>&#;ENWn|b`f4lZo_FWqF{ zfsFeI5)20)yMv=?sp4(2I5UYACKBT`d#yxVE@c7uJ!*cFNp^GnA5|#JfZjj%)Ge@Y{P>=0w2ZYRgcqef~l3K0?F7%LJXAf=B;El$TP3R zmWeqq7H4m+eJJBh_VDlL=hLgb&n>L5x^=4NM4Fs@mG^B&oVXaQpxS|;t+NMhm1uJ* zAFy%E{sk7BYtIb#gFJ%yBh(g|q4+b) zL>VUzF8E>hrXyMzgPG0xS~`Hbwj_8yeZr{}9-89hEA)}rtWG$QnNp>v?>|{%Q7W?#B|*3B4y%hYiEMF+nEif$B|29%Hut#I(k6BE75!pkMOj zTnnAaTK01wxJoy%0P5&PjF?+sV8F@;vGB^qck$r>5DZ% z-0?%i%8<;$QAl9ej2()^hZZryKp)k5;yJ31fcj|`jQyz1t! zSa#$yD*B#5*B+aKMihmsh@ib(vNtrT9`y}5xxKC(IgBE2)3T&hy32g|C$Kd9rhZX{ zd7~!Me#z57dWr!R)B}Wvgw-C8?Q0B)i7`5M)S^ki1SYwoKZyb_N)}@t|8g`KTip8i zbQ`N`SR2xO12}6WVG7(?AZBh{D`jmxu@fGNi>^75z>FMDrm80co7@%vxtw)AX-cqcngooQ>yxuu2cZAte?_o*>? z<_K!@N}0oIHb0$smc+v{^CsifBxzBo4@C?$_G=O!p5 z+|s0b(-amBi}3lgrM+W;gk!FiBOMOGoG>e|&}gOl(R2(N!R?*|wEM!c>!oWLS9^=Z z`4vr65{E7n3ODNxf5H(QmKgy)JGwHuDyc;U{4)XHRY&nNCownVw1-jxm*`p;qqihp zrIS?v;94lgdBZ$;X#CQoe42D8=1Z+AZqywk-k6}u1Zj#4C97YBw2xLPF(;y;s^Pb& zroi>bAg=$BoVNRs>LMXAuPw?)gcL?{%jL;TP)Wz_K_=#vBi@*xlXFUvXSRShbTeWd zv|3h<;a?494!=4T`>eCM9Ou1SDYkT4m7{@z-Y-<2AA=ks*^U&|g4) z{WK7vxumTQ2pa=N#GtN$Jh2Av#%$LRdIX#&QHLUIuud-+$xp%7QDg%ehu6C1j5$Op zp$+PBE0hy1K_LQ6P)qAs$l(f$$PD>sp7uodcuzU|a9&HN#N0bJYM!lIU?l zH+uA`=){lnkueI<#tEj+0YEa9%_3Z&mY|1C*E~vjzr1dxa~;_5C_Qv%t4UvX6b*lt=nX0$1Db*y&n#k*z;1JX(K;Qi-XXp z_T&&SJGyt2OHWgdG>}5)(;kOUBJkHw(2JB#9Pe&wy2xROLHisQ1By{f5{-x@qSpRR zUdizR?)hrwu3IQB(+bO>Q$I8n37T5T<2N5dAOQaS==1(n`M2$o+R-yH9sPSe8Nhx; zF~H2ciKqNp>?sEYQw~{tty>R$xb?fB{%Z@q#hDur)RRJ~`sMi%zPTMYzt@xQ1QoQK zHO;XZd{4o1{`9f+OxNEgnK$ZE9nS?ynGSy!EJg}6v^W)NY zEDTpugwwFbfJKyC2~TDrTm>iRQW!6Ocqm9`?**uuU(+0G={or2wL*2)8GzW<4x5Sv3uJ+F3vY3x5gT)-<-Nhf zfjXgr1eZ}>NR*ia_(&noOj{>3|1=E zK)WjeIne>h*LHzZ36>osUQR8a&$#GIk;WK9)ieV6ErA8V zC-7ynaOn2)8pTT{-TS6JetOHqJ5io?AKt}z!9fi^Irv~OycNa<&gWS~POk8Hc@kJM{qbElZVZ#AZ1eptLj z7RE6vIn)x@b!XBqxSLP!caWVI>^_2joUvzaPtPM<=6uKVmMqH8`GOiC-}V)3hQ^Mv zELcRn-ZX+&(4n@yRAn1wGi^OHYCoWPJ3XIu?vs?g*&U%~J0d#v=;NH?KaR_rj5}nY zqCpO>G$Qcg8r1WK0SLLa*gVNv!%mLN{sHX8?g;Z@srLS!pz4D3Gk-)V5C%&IoW2sY z2t$93SFGBsYDw2t&hBqn>FNtK*4mZ$p! zj(BVFQPH)O=|ZYw?KC=dd~A;pQus_tr^4=)0nN8z{|B9~l&fCSM2g@$CP;)_43i`| z4^|IrBS%Vpc07&KrzkT;b~|;!Hy8B=bbS3i)rjxndp`h_j2IelP$M$U{t;e70&D&@ zKXp+%`Bixv%Ag|P?WfNZA|7|9t;u=#Bd{pb2>SztVZ{WV(6@3Db? z=)i`Vr&{ppyOf$DpLCyS$#BJ3@k5?boWQ<0^!E_y#F&s4M`zmR8tPvcgLuXtYDi7* z)6InAEy&mdIgA_0HXG-M@aLkbu_~6|6@f0VoY+In(FUBV)CXh|!>Bl%h9}-kqK~uw zXcJyP{OLp<{|F@mVoImLb_kV_#%aZm|INqY_mcF>xtj!KKa5l1xOKo3sh!1pI|-vq zT{Yz;WI?n^ySh(TF2?$m>p}Sq7xTz)%6bf4RDpc7nRdg7Qp3ZACtPzw*495Srx>)Qvgwf}|nw+j?~+R*nKzB0Q1<0vle%V;l~pU6G4+2Bq? zu&vcu`SBc6Z4cR8#Gdb`CkL)~V8zNEjPZur9(51=gYZwH@%2`qquNp@`I!j%3^Br( z72w8Ts_}FW@MXzn%_>%v+Z(nIs*X()Vk)X|JDsCjnj^_+4H!j7bm}~R97lgjMCZLm z!tn7^bYFX_zOrh%C!V_VvMI0ANn2J}+32Yc9r3#0JafB=Rg8cD*nSr$Mls9Vij)iU zd$EV|q)gYe{Wc1=2~<5C_qQ&cAf6V6_kzBx#M%SLKEM9g{|Q4EXQirU-9!psP?(x# zOHGc{jAsNc{G=;dOgVMW#=j;-^?tgnT@_Y(j0+xr&c5lJv251Bk z{$7TCOTGzj!_hy5I@IDqVbhP-8PcaP zILQ<5*O<$N9V3utLCpp>ufHT;1|eFIZ=%;Wp1AVaV-AES$1n@6M@98_F>lSP`LD8j zO64zSlbgtX!V)ptt-!68wGQVgRaV4c_d#zDw;1zbvf^@i+az&U%-;Y=G&m1q#QgwR zT~m(L+U~su(HtG?921q@$v;tB?0_a%7?LAdV|z2NugkwOue-r)6mpM=3iN^YTfial zrT{zt80bOZVd{2qE*r#-5DAf%)CmeZ2AY#tVwoTYeY^jIlWu2ifP^qj*_5+^d5Ut^ z45xFc^P7fc%8w~8DJ5%I&jbp8dNI7t%Q*O&l0ohgWiMU}9((iQX;T8!F`31T01qoO zV%pl|*x)`jt9?jbFD!jQdvIVAlx#3);f*#0Q>4505# z6?0xBX$SBSqU4-b6xDl#Y(VnpiC9pC63wqq2#;w?qW6B=1c{Pth8Ach=bvA7zPM(0 zLDHgeKS{xtz*!lNSX;1AEB+i1>6s5EV#H=qi-^r*(4FOCq@(SCJa;nbYu~pz-t^l) zBq6M|>1#ihBY2Iay$WaRx?(M(v>oN>KfQK(&b%XrJ!UgD3{QU}i`DuFSeTlnv61yH zj#HRRcwU^hze+g=7otUfkq`p)Eqn)kTNtO$a{ct3>&(|c|4^88w)e(SdcZl$|Li&| z$kGC9dLF)%BjA&5m;ViZEczSlK@mY-LJPlPKl|{{YsV1Sg(L{yQ zEY+ih1`_+!v_lFw{Vt{e!y%wY_a>3WHt}1Hp_+!+wwy?s_J+(Z4sF1pUfRsz4>5t& zIj0>PWTZ35cg1)6yGqrC4V@G7z^aLzeJcu$m`ZXilr!YwXz4%)(p=t-Ds7TE`;DM~ zNP^$UbB;V0?;)inPas{ombNGsdzY~o@UwWVd~6Cs%&wG%V&rjZ#8wq z)g`w(&@Uv29pj`ris&LL39n|&)NPxT2>u>^(Z?XEFfPKNBx|1USk>3BE{a~*Sq}*n zFXObd7o*~2RAg{EzpW2xX6y1xlT(2-ZoqK|=YQsbwk`F;P-dChpkkO$?A}PC=Q5U9 zcQKf5`$N%wpVWM`*qs=*jPqbwdWOr2v%UjVgcxk%dfkgpQY#GIfCRhZQ)E?o$08dF)Y;F3zS>3xIir&jO)KS z!Uq)8ZeCEQErd4gmufaPa^+PfDmK!~ODqu>5NqOl3Hwvd(?ZL^+P;{|v8GMe@KPZD zlmFzEtB56G0>C2(4OFxd(AMn952;@IZQI|_HtJf?-`5}iV^%eqK0+7L=SED@6L1F_ zB({c+LtXo3SWCYg@J&u}DI8?j%23`uoyWW{>A-Md-M5HAtqAJ=(MlFJy{Lhm`(^6XD9Ca3$ z_=WJ|;EMu#3CCv^XXW;gF(|LN01Gtu(wCzikLCT=F*bjnXOF`ZvF5%c8fql_YC}f} z_vKYUj@qIH+1!=N{gU(Un`;!&Bf}{&ozL|4=)yc>uRO@U--_eWMt`$f2;z2=Y%QE- zik+hF7pNnUOxR@cj&G}_LK?C&jGhyze$Kok3Q?e_zKT=Q0I9%Ve}I){uCR9)qolv3b4Yxsv60?SLnbujlpZ?aNaAZL$wOabC)NClw;j z9fb2N(}whEQ)jW#zY~%e`REZ824`H>?CETubG=^Jrg@vWnjim_hm$UegchhXmWoJDfk!v)Np*WW3>Qg-zZjlpZ3q<> zp8t4;sz$HAq1+%I20cOX2HV1nPj52T7LVPY&5m3Q-W+0OdEC5Y&c*3n2^=DD?<__7 zw2!hI^qaN|3q1cgjg(W%a#L63E)V0tq`$-G>F`ZAM`^u5%WX^9<084JTq z94vz1%XtV>TGNftU4MrIs{3)f!PR&c!9UuzOpY3p>n-nE9wM9eOU&pQw@UTL6XKT4 zIf%5r4|S9HxB)T>6w(Qq(3EV*Tx^jU=Lxb7#0gM^3K} zu?2EB9inY|)7%P({%+n9&F_-Z7pOIhC!8dA)@zcSr7zZ(Do}hLMzYBA2tAG#?yk>H zxkt_v@UpA?v6hoHM`$Whs+Ls`CFSZZCzvH?(l!>9W<5y*Q_)R_poOLRw1IKWI!z36 z3wJ5<8CvGLy|lB9qGbXg{~!>uuSJUN@E41 z5oCiPY_D=%-s9UBLw9Fi8@aCp3k(7436Mc5Nr2Ic^-T37WafD9^xRJTc@&Elte9y_ zSee&Mjx%wBx7f6!%Q7s2;A%K4XG}e`BG$;BZ9ZIU-gDoGr`i!Io?R;T-NjilI${M| z{T-0S$l$MOj^4v@cJsFI=SU)e6ndeDA$aS3uzaJcJ5}^-T)BozDR5>{9kcc>99MHl zXUKQ`+f%6SMwPqf5^&SPmqv*^(Qd-p%bTX1)&U+i7i{?|`KKOzHLDCPgI)GqPl02M z7t!D@$&71zo4xhr&~HDZfTV_YV=abR$X;X()a`T5C&_Fvy@lvip0pK-O||WoAY`lh z0F4oXA#2!d!8B5Y?$tV1Ji~?1$ca8J?N(YpI0-un;~!Gt5Jd?r&`BeMNni75e&7?Z z|Hv`CeD-qkrTlI>LF^}o<&84p2S8EqZ7ZkE655Ao(SurzQg#VG4LHC~!sBuGC9E4s zkz#m8Gmj(fY8j3(0O85~8Q8iun(m&iKaWEmYWhEFutDoa>TawU14Q&Q1`CpDgF|r1;23?E5PALT z=XrXgv;V=RO^ZPVadz>s#IABsku5{1_yQV+(f$x9Kx0bC?P#Q&%>mn{_<6Q<-+;L~ z2)ixUebp^&Ugt-;mL6=an_Pb1;IFvW>(*1?u7g4V8Xj<4L4Fiz4}1E%*RaZuReifU z2><+p97z4#SCFI2h7AfnBz&!^Ycni~2#DP@tk)!JzbSnB$vNf_A%Hxe z(#xwX1^ZZ>+Y`m{+?3&_S?_{t@}O3{8l)>4F}$`$_aTLDu)rpIj}Q@1cfGU+5l$+u za7(HomM?rsdT#LqS1Zv2f*&T4@1squ)Wu2^E_f|wBa{mp9^?@_SUntpdN04Us>9ngDJ}TGaq0=8F!fJ4fVjImM+^Xmo3dVrkzOXsM*+=$!l z$z7M!`0P0A7dtP&$_hr3Uw$jEnWhX%IA%B6;aaaZ?8*Nn%{CZb32{$43)GwnC0RaZ zi%cR%q3dTE-Xj=eFCXmL{yb07^C;uwuUk0YmKh+pQmR~J_`AizWa&u{%fDFftVS))i1FV5HX54c~|}ac>kK|esp|7RPhYtU=RO4dMrBR z7{n1`LE8)hPF7c#A!Y2^`@h%1HCP!Uu_vc6@sK#=28=(LKf~R%xH)1)UA@V}q1tVC z?{|;fNK8Rf^hU+5Qmuri^UhrNMgnqRt3vB*i_&lG8r}M-u$|oS%OO-u1bXwy82L}q zb>Au^yIlX+u&Alo&F`kjcF$=UItO9b6yYjZU!DO)n5+4j%X%q}pNC!j_9hiHsU%mZJe0B;gYC@*vPTMi z@-~`EMYiYZ{mIiaqXZ-N7aZk{P&M=w$qK;(?F%CTlOvY7SD;$~hnM(>bb+ThGpxVG#(a*?T3g&+Tu^b@wm@GYkfc> z+q}`|_l|4{t7k%-+Yo0dT(L8_qFVC*=4!He5NuZx{bF{e`q>jV4WcVNfdvRlo}k5-HPu+gA|LIci@c_LU8*31EqX z2{ApEfmjvHryZF{2H~(dj?+kaU)HeKBbnk8i=d1_34wDarc70 zKD?e6&3_e4vI0ju&6cq~7hsH7>c2D+9vZls6#Qh$3R-qD%%u)4L2#nXC9$|L?4%T0 zXpAkM$LEK4$XlMq`)UnksRqG9GaQ#BI?HWh)DCRfW%xNEq4e^{Nz)k=n5nX{?uGqPP zBi$)LvjS~Slh_uWXe z@hj&A`|vXN*&MR7+sD{loo>v81*o5&+Nn)QH53!yFkAp#(01G1Z@bqIW?MmmdgT?* zl-P)hm<|>1Iekv(w!q)h*OYZ?L@2RuXTVBID9$eYTVvT$AlRl>fU!S4)LHr{wb#y> z4PLm3VhLp{bZPqzlk1>{{dokVl_I9Uw>mF@1j-F*hy?xGg+@G{PL9~{GDnt)s2i;8 z(vBPRat)&)cFW5{Im#2K^LuHNf7}9S^R@!=#pFl@Y#2Lg0cV@e%$HeU}_ldYDwu-mzY=xD1g5@$wlX@iUHrp+yR zW~FTj8(jL8z7S-R9n5Ke$5|Kgx9;d(>9PRaKz>B(>M6hE9bYyu7kJzI;(S(L z?_TCDucnV5o}|2s^3|!@g*MTm#OD(FW%Jv|a3I;lxHXBeUznnm|7U>5#6U_&yRr_@X=tv zpKLev>UtI*m-iV$pdDKuE!g`P2px9fwH73l0rDc`GKSgbh(?|bNwS;MeR<`XGdvaQ zIq%35(Id)krnM}^1MRp#D@9+D&FJ8i=A>X@aIJ z)~Mpo1lo&`T zyVmq3C6CIG!KeNKxVSoiN;{UGWRwff^k?ha-BCzPM1^y4D=!!%L{1yMsAk7jC#N0@ z?5_p;5^x822`xQf9213dIF%|hM`G7{#Q8QyI$P?~tI2Jm8oPdK4T?pyUwQYJ;Go8ES94x0AU8?^*(mN6-h!oQ=&;2n;M zta}plP{{Ik{ZB{`@nAX*Rb4W;5o+WkemoS_i&yt8zX{$4?I3xHun9S&rzP&q@2(9NvPL{=y&$gbWyx)N|t z6%2js&QO>CNi_vEC81J(W0o@&@F4S5|75kjKecvN9P;S?yKdC$c{xY0*oDKSoHE{= zpv2*SpDWIT+0JS_Ckx(X3lh64V9bVqrxDCWf+#WkrtY&ew0v`&IC<5u-PYJye5j$Y zSt^N6W%TN;tzw`sB?IgN$IeoUCI7?!K&UnI+?BS|W7=5oZ;SjZZ)s4jv3u^r#wfxV ziGaaC4x~ym!V>!W0_X=B)@|4~K(T+4yhv10;vGl+2wl-vgzcw@%B!@HhrYRj(1~-; z_LidKD6*FC+XfEn`Qwy13lcOze<5Ak}7C3Fl@U^&qWuuEdU< z0rnFa=Rc92LsnbdRA5_wvOB(_%NDd29l8vT3l#N!fJEwGv!I>|4ZmrmfZMH~6gH^a z`Ow&g$2m6*!g3%j|A}=etgdY6lCVB^1Es6seJJvH>B^F(8rYZTZ^24gpvT|yZ(SSO z2w-J^m6xvuZ---awm}7B`l}wfpumsTEb)~KiY1{hsUhE?C!$O7Vu$Q#4>~VvN5U6_ zw;u*adk@`eA%DJQ)Xid-*2@FMBUbVUmgY_-bc zVGdO2#bcf=kAvqt-*Xi(K)b0@@#}!VD9{#LZ;jCe<1*Ao$|!$|&oGAG9bFGmG}a7Y zIk`%71Di#6(r~oHjVn1>BaY#i!~TQ_(I0i;6z;}8u@Buh3TQ(#cFZ7;Z#EKZm-Yo4 zi!!uLpq=L>@HrH(82>`(4^|H7V%UA)XV1nPlh)$h zob#<6QPL6)W;OId>+kl%Asn5g(L03Jp}DedWZuCvg671N2$+QZ3fj-gqvCAee@S36 za`r^@ZuTbf=W^Bawr`)4ETMcRG15ttAVTJHcpSCE* zykZBU82Oc0??0|R@ICemizWENvy3jqekQaHGG<{oJ17+c7XTQG_#oC1<2ekE`;RYD z)hBg_PyL&cs8Acj2{(OT^+d2-U_kDWI1vU5yQO5uQKuNWOr2wr)=K(9jZ%q8kbm8{ z?*WK~>pteh#tr}URBSU-cqrGbxrU!`Q}UAz1O8ro$!JQFiW83GQ8sg_UCgbW%O1tg zIFn*L^Uiv4VyLia5;{cOsdB#7<9s^iBpfTWu<-^ov8l7Q2Ppn~H!|JX~@ zS@8Qz5$@leaC^hsM{+(90=BsV686$3WsqkW|0Mg^lqs=og8SN~7^ckmyB>E>WBTv> z@QjqlO>9`mzbOYaV!HTB-DKl~CErbbOK78^ag^eGYD>s-FWBHA6nmIplS%vYL?3nX z?UdTB{7>72N;@MNe+@dzn}_r)vpUtGlo3T$Ca=h2j?>`&_feLqz$qy9$qZxZ^G8tB z+U|Ska075yhYtOs(5&fe{+$+~Z{TG-GpIugj}1vpNOMC4UzJJyG?jMHilSs#x>K`y z-jc*5yKXHMA)4{#D*m<}&MRP^dg>*K&|`F&(@Jv+W4Q)OH#l@l^1T$g`Y*f=O^s#={H_s zMp9>|64*)g+>5q1BgC-J>Pk_M%co?!NkidQlpHk8z4>h6r&tWl9W{1bcs(`gPBq0n z@qvNpZ*L|4Eb=afo?RBs`PtMX;J7K@Npw^Fi*o5~4__hM=ME+-3%6laa>Yxm9y4y+ z>#HtpN7r!W`@NE>zdD@#QipbRHx#_6ix6?(nZUPGI1!J{t9#l}`H)T;p<=`i3_OK? zWVcFJs7UU2duXs%qmIN~QZAorYLsWHgN zn-We4boKP!TY(Be#urW9a5ECEa6;{A6S;?BI0JeXCYePJ>FOHLJ%CkH<-|)Cwc{c(A!L9omRAi*T8EntPAclXE)pvoNDxT69Bdj zOeL%;0lCJ@ou0>Wq(fz#Po9ba?>P6B3iW)et!+P;oiR7q3#qhTBT_b$U#C%mgZIyM zAS1IFr~E3#a`>}icm-euRd*>8yIA7zwPqhKqB8cCXp^NRotSec5zL8cli}0A(xdZ; zc~SeyN-P|X)zgio4oB0*m&S}t-RXaPZYviF(3%#z2)6kLrOw}e3+EB)qIYkWX0atl z`rFPt{=$=Ky(duWk90Am%N0UhF_6uYLL4gqY9`uG>+A0bweXJC0n^76HFewy^!a?G z%(1!Cg!ZnVIpnCK+k!OZr#jAp?=k;n4Zs4ddc$55IJ^9cg2^swIR0EU-yS`61CA~$ zY_TG<&WeJbGZuVsk%gf|VbHc?h^=gh_-;apWl+!Nb~SqJhB$8PabmnCRg9YY4*cXf z>gLwtvF0yKCq8K}C2*^Q?RkiH1ic2PFw?6B7#m-%`gS6-@6Tq)U+AOYat++1$J%|2 z9)m{~N}gzOqoWZ&5UwzCA*aa_X81RSFesP7UifTVnlu9#UG`I<+&~Pj(yogW&(xki zE)$oyg}xCL^ot3WxF%-@(V2!h;z0Qx0(N8R)94LazGm#yG@2RD0@JdPtS}97k$Euv z;n`NED@gXH-G*&0UiCNBMm*oQyegfN8y{mgVS0o^CSAI8N-DjTa1Ge|7pP4|Ho}vqVdz)o;p#}#3izMQ!zn= zz5ZMvPU%V(kF*Ef8;@O62U z1zFz&{^+!69du2W6;W|4%kJ7(8;g+qo2r)MG1}<6g)uu)`w3^^2n66>6jl6U#zgPD z^m5l35z=N%wNT6Kf?(}4hodG~N+o{E!{yrsCn*>kDAXS{m(sT%Xwd)#Qdae7~st#3G+FY+u08podJ9*V3NxE^wrpT zdjEwYxO_U6VTSE`VyqgYOpZ`y$C8sUsT9z8=%(WL+Rt)dX?qbCO*)KRJ(YIGuqu*?Rx0NMmPWvd~3M>O9X zNj?h0U0Pp~Sc5L)L@vXYq|SK^5bA9}(qm`3Ra1#dfES$EA)|S*1sXYiVrSHPwR{MB z)EgN5zY;p4m#ePFIS~=PUzEq_eWl5oU1%0Wl#SZ&Wz~`i8h9e#JknBC4pm6X4u_R0f(6;Y)2v@W-Mid&hKp+UK z(CF#>TIDO;F)z-#YMiw=+=YfV9CW64oRM23#+SfJt#U~R78(P7)b+StQ;oy??XiIb zx?*JS5E$iwU*}7)!;AO1nk%M{fb(lxZ0ls>;Z6f2@0aiAOvQbfFJbUt__;X}4XYDTILroozj#kTcKCw(La(b6i8*$fZ!>31 zeV?joUHA0YeZuTr#{+#5I__JhFgO@zvJeyFXw&`;0wz{^+T4_2l2%wKP4rEI#?qb* zaPzwqkU z!U|Y^c?>!?0!%_Aeo@F_b?VlpKy(4X7SBtJ*!I12qrk?Yzqr&AUTJwY3VS42Jz(r_%7c+i z84vDe7vs`Mx2n%Tji0C~2H7s9Bi9Sc91sKVmsV_xTZQgC^%h|Mgd!D{8`F(mRNK7V za9`-s@<(LR^gg;m@KvZ;&=vTTY8h{Lfc4~El=g&ka1TZ$5RXuhV${mMFpY|7T0{|A zv(qRIFOEv8`Vw4>tsc?xzIkorpEUuYVLx~@=*FZlPjUFtpb%bvf$h9VqNx=2J*R9D zR$*o)xP;BS`YOZgvdhI}j1Z^)J0<#Ody+wd(^kFJ$!Nz^wYv75k!ta%?+fxO)weLAvAlB1QoAYxYTd@b6StGB0 zxo;%d0)4T5Mlf}$=8P3K&%?G5o-==fP%I>n_ac~qK?78g!^6HnA zj~!Rs3P3O$o?JD~JzS_>^L6IiYPp58{+~Be#qpC`y&eywO;V3TwgZF=GYlAs@jiQq z+f{Muqp|rXSPjkAW#pF3jdCN@oPc4bedjY*qrsz(mTMUgve`^PU@YqZ*?|X;Tu&f= zBU=yrL!L=eB&>LoEyX9-!<+5r8=aA;C<}7$#Z2d%+=$8|wMcS} z77b}6?8{mGd0-2SD$R#GovzkMB#pu);Tl>F@v~vAp$+puuWMt4C|oH&1D&23qR&1F zv=jN*bJ@=eE(|?uHx9W>B5Bg-Uj2T7419HDsDvxx(SlDq#LRHw=5Eth$oDL_Dlb+M zZNG!_St~~k3SaBfJ5&2KOBDjCs5QelE!-&pB#|RX_YrFGr1gNQI$|;DvsA0}TJHfl zGRDnv(`(`<*Np^6x@f$fg=ck3#numQ%BzWTJ%Xdh^E*<|HriO`& zz+m#K$m_m)rH?vB)=wO1_9c(fC_Pwpm!lN@nd`mcg+9q7x2(WGDwFev@^^~`@(l*E z0B9tp1WxK6RW5EM>w&jU?UBD$UCldUsU2`vL9BqKkA1-rMmYywcR9*uDUJ3~E-!hQ z9JTFd1NSbk0}?5eE<-)5-H+l`Z zyHfe5NOjk47aGr)uuIxzjPs#THCi!_V=J77w{*o*IG1sp-{!?;=(f9(l zf$0l3W#;#Qow&@UTzK9%enE|~zmoYGPEk2_garm;q$WWCyQp+K1IvB_fTedP;UG` ztWh=4R{yT8yL7Snv&8i({>llZ=Xg7?{c{;+?vznW0w+C+Gbp8`m$eiGG8*gvsw3$d z(Ck;}ph8+i39y{HwTu9zLChj``<`!3W7s`T5V(ttBl8q_+&H!gtx7ch9%rN&sfF|1 zw($;U#|n8bp&Evg^=WgdsAZiWC{NUhJV5E~^}bC`TlS_{z2z-0{FK>({%jTq&4~72 zdC4bW4KRr)cq+OZ}y{o@<2B`!i-Q4*h^fs+H=s@qFrlZPv zx}E2{QFzF&>hwT4-93OopEm6A+(uQlzO)st+B}t<)|Xe7SQ1vr)>a2T|MbphEdVj( z^qz@fUBYM4bkz0=FURa|pJH{l2Hcl>uoU!Bc*znG99 zdgaPYdY6BIq6vSYn+(^Lz}n-MueA6C>P$;JY&ccPdlV2~!ev~>I>uF3(3f`Xhm#R0 zl=R0Z!q+F|w0PoP=V%tLi`XX;L<7?3J_OcJnmWv>@ou#E4q16_L4l@4hiP@q|L@GF zS|w3e=d%W$0M46ex0({-6SWL?2;_%Jl;O@|O#^P!e)km4^~Z~TsknMY|ID#PLx>{U z*Lv*V5CZ%u#`4r~ZLyD^^1?m^ue9rV>K<>jU@U86!hzLQM<~tIPDbg_|-Uq`XJPI29&?S?pSkG{es+5s) zKlm`P)(&hb(fuOd%=K1JQ|lz~_1hYj-1fi$NIH#oL<31UQC;^;UpKq)|6gOfv0pC* zyRwS-1udu+A2?5}R`A4$L)ZL#kDgNLKyhHw~hP-@EU=S#JG6t&e22}A1mV~a$>e!0D?eqtq8|&&c*=GJ z+E3GFAVi&l5XaM{py>!QVr{X%9C|bo;B&H>m%$WC4NfwycbBU8dDczdo;6QUY}He<9ON1$O70M`8AV$)FgyEDZT4`P*FvRp zE^yY3JS-C6UB%_Ct()S?f*tX0Ae1d)RqM2AJaEL8`gJ=Tfa4Y+2DQ1`YGWkeLyyF+ zQwDF$ua-CD4>$I=*Q!pO?vp!kAW6NdOxChf*H|CQcB-Qsg=x*k)i~T{arY*APlJ|* z()xOLyZ=^r&7pH?NK2oe)@g-js9E9^iX}3SL>Ip?cwLL85FbQGrKQly9+l^Q_Y9M; zdu1Q7SfD|VeJ$O9rZ}NdeIOty9R2<$KGrB~i2}c0*R*!4Ap3!3`dWI;5zZBN)-2Ww zdd0TLh=!ald)`{EXd()(|L8*ZHw#I+sV+Y725pj@WF|)l7#?8mF1)S-FlB;oR%X69 zg6+kqKZ8Wf%2^SaEstl#t(pSeXI*aho5!A~H4z}iZ#Iql&{Q1V%!G}FeI30*_LZwu zhT!k3mzLrgpw*B-D@7J7RCf6fJPgD!m|}(|!5p6TYABxS)xVt8%n1o6Qhr&CdEqiT z#p$f;k7UXrx`w=0gTK3nVd9VTSk)NIac)xYxS}#t;nlvCv}MekukYvD+EH`wXZ$YP z5HgJ#$hB>j;z-tL74ReHOY2a_8b!NvqrVe5LNoF)&JNzsGSUlZX-4cKG@8W-@xYNG z%mc)Yz(pckBCSd1`3P#F*uwcz{#HPi(o*FTg*hCak;#7?g2JFyo862+Gy>|qJW9)S zm$^X;&vjYu&aMxsHB#$&4Eh-X!-3XVaNH-$wI|F$z>=vAsUhc{P%f|2**l;5_O_gq95E( zI;$-m`{^8z7MO$H(6TxGl;|(M_C~;LmSvSr@5dML7$9n}deMz!A}5IJx~m9gxiA(~ zN9(b>++$Qu%v19?h)ltGFRL|Gg&(<-3*Iz;1=xix133wQ z4bbD)IV>}S&VJ|5>`xaEFgRh(^1umRA7D}exov%~?zkGD|M-LG8>telvUVq%Kz&kB znS$f@A-+&uV)H+CQoWH!6MK2;^B3v~KDHuWH~F&0fJ`V}mh*BBaj7Mc6j*>`Sb#Rx z5Ah!_;8L>zduxbqy?q_+P<|0qO&{ZnxvH_sRKCfA5R0uyH96$@B44`d9!y7J*LWnk zl$6NUB;YD^^ICY}zd){}rf}Tnh?MPrU9* zlNtgxgUL?I#7gX>9vY@|DQ7`dlc(MSe=)~%?~{5+4&7mfe}H<3ct<~3x&hyNzO7RR zjAQy#IN?le@Er(6*&*$Jn)nlb3yh=2_0`+zYq)ENQcdo3e0x+W+FvjHh5J}z3wJ>b zVl%VzLo%6m*Ni|r1jMnD*l?K91s&= zEk0tRE$yCHgT5qmVs$PC{$eS8u5&`lGD<8tHaRKD%Zn zHD8>{e5!^Lv*v^OPJmLC%WN< zAO|30wYn0r?eXJJ9a+A+WS7W5w_91QUh|KCL!fJ)snbl0p8Fs+KK(xpy`WzTuGK~s zIMjY5+SnsR3_L3v zv;6*^Uxd9B`$!h4%&higmP6m%hL3*}znWeKx5QiOe+OrT!EImQJVSCStsoW$AU!~0nbS1w9y#J-qJ~N-dUwdgY%t*o2@Xx*}_flHB+c zx^?+)yJE>8R-ge%=S!$e!q$R2#A$7z>RCvKz3XmA?+pN5(J1iFt;oY=$f==!fH6`m zTb|!YDD2I>SxA!G2AD1B=WCy98cjflR1rE}-wS7`{6VBUzoCjuU!e@d@WmIYUjt)+ zs>42zcHcZ;&g}+FQbHNm|90KNoAy0qb=Xt`wutx%frIZ@&A`fnUyt3v73V z@wGCS0=0frl-9q-$Sy(TB@ucxb0W3sm+6ETS|C7`(I%z${yaXdn-D%4r=^_MvsoO?cvW?8K}Gl4RV{8F z08b1lXuPQ=RIS-%ivSe|#uzTuQaYoeMX+~>D5}U5HK8dz7$<_tJ7+`_2CBH_iyFKk zo06xl+$i)I zpavK_U-MhqBA@&VQ|MJoJx?v6X)YWmwi4c8Iz0UWKi`Au#cdv5Yf!Bso}b&Tr&XYQ zkz`k9iiT2-H$9ZFGh7Dl+Sniu5Kx1J{q@@nwXZkj;;cyooS4=G740_KTCw`1nmgpV zbmIqSXLPV!>9W^jDik#jalo!mbNe*CU;T$i5oszQ$%1P4E`DtQ^|~LRz`?guu zX?Hwq+24KQ&Wo2-XS^;n8Xcu+jm~Yzc{WM1&*J51MSfFr3R{CSdiRR$h{7-0L6pN_2geKJXjh26s_Kb1 zM_=G02b76L_0{z`v)#y8F3)~UD&f>z0*W})3a^BhQ2U^Yaon1Ef2D2AB<*Tfm@y_ zfoS5X>OAiKoB{Iap;Fpk+LoH$10TUd&j3coj0%W)u+jA%7cC#VxrZhY%Q&r=TG)xe zrj4BOuK}GM}jWY=#|GVqfXXFcWajkW!zS)K7<$o99`HtPpbL zOxTjtf{JbF4BJ^>pAd?wN=3?8(o|!RPiH}Rdf;S^yWNl-haE5_H$Yz;8sD5x8Xl=4 zKz@X7c`79#7i3^uyY?;OOa72t=fU6sz>CW%$-C3MR2O8+iJffCcit|&80EpT3YS^_ zbyj&Q?z->1JCL_jxAKIh9sCv7nmntK)i6BJK3S`x6~o-Zx{;1&D3kGQ-7j61tH8OT zVED|(SF-Eg-avt{;V{fBZ}H9-YOAD82?p%V^YvgM2h8KC1^uO{=NX#(I=G|o(VF?0 zb)J_WFr#3I5hnb3@kk+N{#N`<;9DX|{eZJP=F7uABOM_|JfBv~k&Q%kZX9&N1MQr5 zUkMAwerwo{O!+pJwMe{!lDrKR&)h~uQQj~>1(DQ~mGra2+S@tO3MHeyU$H|VAA=EK z-upO_V4W2q)+nNo3$naXyZn+9E0A~WSoX2&Fi0o7mH_Wug{G$x_5{a@td%&g8J_Dv zqk^DyODi~r09R=%SbmDV$eB{;a1spBg$I3J&ZZAmEzJxN0m+#F&c5u{zJ_aYHva(~ zkVMEND#oCu3>dvI1(js_!WdTfDjWSJ<6;@%6c(Mu7z9bJ86j%-ln-IGZupQma+RS@ug;d0cwQNTSNgM24C**9XKOe1*kU_QE z6b_9ABVJ)d&_yl>T>kAU9%(=ni62a*N^RMI2+t~&eBiO{%`=TPe~^7mJ-p+Ybz=8$ z%|=&+^L#PMGj9LNoUL3a5fs1ojaf7a6j2q}Z%Z2#&{Zg)m@{P|4C$NxU!~~{kzmqm zbp2cJAI8+n>3BP$pPGMhSqy#C%+6~JI!UvQWWjl6P!snHqXfJ1?R4i`*D!*iPaF@6 z)dlOqZl$ng#=UZ}I2*N&TpBN>FI$}+r&gw;eB@txH0F`6+^gt&Nhce__ckgsrU|Kz z=S#)yb1nCDtLb}A+QuO4A1SaMV(B(t+nwJ{kuBuU9Mk+UVUjd%O6--aQ!B0@ z4YFQ4mtuBAukitWO5WhY8nBv1`2JgjGu8`QpMdJLow&qEhErzb3No`BTfv{BKs^9!3zpmVBtOjl7tI~t#Og0$mEbToh_TWp z8hqz)!d1LL9Bf0^!)zcHQ)gbkaRTW`k+1}quvlN=k8wOa-@!Le^f01%m? zRjMYzMFT1aw@N+6>ms2s^`!$R{)!#B*w~`n6JO9ADWZ@-M8mY%Te?5RIevwkO37|< zcX41?Y~;ypm}=0KK!=|-`wl_VlY`(_%|7`Sd&0)GfaM_j#{7x_%A9Ex4eH9ZV5%D{A*1 zO0MVX&~wW|<>W8*HN?BCGOIRiw&Lw`;RbPm+bys4J^m|h;)s(WE^U7YEF z=QXtDCg;MIjzHhwLs_N8&U0prK0I}hqS{w7GceS58gG#q>zMJbxTu$*N_G_pH-yGj z4Z_}q@J{(+pl=6DNK95JJtcmz(sc(CqP!4=BpP!Zp2+=PJvjE%WKX+EgM71>80D{o0j%c3N~-?UQ~l5L!z2(5qKMm{3- z`SWGgN-}stOmo#ki?GUWUo0uLVh~g6`p$r7Q|FMX4S!)#mo?6IxsikgEng9wyA%2s zv$-A$QlMv+z?_g=Hh%bK!=fv}dO@Zt%j-TqTZw>s8swTr)v{0aPfGiWw%>cFc>Qao znt`Qx1{Wtv$GiljTI=3jrkZ@1h$f!QII6JIVNxGJ0+0kq)q3!IG%e+*MR96JF?oF# zy==@bwj8UKEP+Gqscc{2>&_Nex=^8byH_r`?kQ+9$j zoxVNLtoOJz+*YBdad*Kp##gEVe|4A_RXwyoV!G}#x<^}w!43SaeY^w7VJUZIOW~jn zM=_WlJMz}k?%xuF8F;69attSB7%`>120ZGz$@_xiincoo&^oB>zlg{Z3M#Vsi#U6`$^()ou}{z*3d469?MHcHbykN8-W zoTY7JPE8qoOV!WYrY1z=y98`l#bmTbhz|N;-r+MoR%)&fl(zfNYZHwV>H_8`0)R8$bpH}jT z4K1OKKG~R^M}mswkO>Q)mHL+hw!qe{01c_ih>`w}sf0AIl}T_nb-5O67z&rnusS$t zpO1|?*l;cnO{D6xUuW>u#a{Dn(A(@v-#f1Nx!f=RY&@=wGjMcx1dtK#LArnoQX@^whX1?o0y*>i!c`|L2Pmb|o7Omn@Xii0ZfRq3h$ zrv4|GA1ZBIvrB&Hl6-jUyw)Ff1MtLll0JtsSkx?tsOyise}JN92p4yhR~FKiCmstG z#X80SsEptF6fN4$TMhLHv;0~#+045hybp42Cte_z;w`-i5+d?ETpywk4T@wsNZ^O! zKi2uXU(1-O<*sq1&joc|DLoO7Mpbu#b3VUaEv&7RtgICU)WZ&C_{(#duezJ56Wpl% z=e4>kSZSl{W`qqLMTB{j_!mLba&3e`<+uI2ZNTJUvYV_P{M@_$FkUMKTuSHq8egly zAP~=auJkN5%3bM3KvLE+7ipo18`LeO$0|07jZ&;2$P&f}xx z&%24=fEP*KXb}Vzo;uAktpkaCkJT|_yr!jYW8;jy@JW?x)wDQ2sjI8F1@EmU_km^g z+9s7-<60P;04=sO-m+o*qjpJIZ1Vp&dBO=pmAU-Cck&6IGQxHK06zet?Vz&6@c)fz ztR!;0BH4heLl*<7v>wx?7~RSDGk`-Fs}VE%k`!C|#>-cux^%svpj)HJNZ+g;OwLyg zV3u;HfsE;-awzK43DbY%Wg*1i4bdhLx``+>z}|F(mldm)0sIz8HjtTgd71F$Iwa{$ z?|t{h6XP!vuMYmbSh*K_7dk^2`ZqIsZ>WVX>lv^yT~0hoTKW>g6vkV(I6@N)yH6hd zMvDIjlfX#xvG%LZqt5Z6H4H7Z`W45BexqWS#msIkeXm<@XxMyB@e@2*Cg6ONL@Qp) zb#%n7mDnZyXjnC$M=&+2k?~Slgi@q>p8q)JOuI~z!uQ|mlg8=6tBpk3TvC%DZK&R) z+Ofs73Nl%7LNP15VCs&WP6XBQ6%geaUD6IOiG4=wIBfe@She?*M|!Pk-NWh>`2U{as^lA{o%|6po~mXDFlGUG zzMXUDIKy{b9BGlQFImekdmkd}t+LUV!9Ij|b5V|@vLN!4)pDDsxY5e6%B{VK|0Acr zJe1-i@wqGI$i2I(6S-EE@U`^GZ3c#p%G{U|rQl~`eP>J^mWpd)q&|0-D-o&zHO68* ztOA7z3wQ|1zf~uD%+h~)s9{`%KJ75%#z;AGBVYy2T6|e6IS?zjhLlqC17|BfpVjs9 z=urKI9zkeUD+4m$EJDvBcemFrW+(fsW*7SnpY`RZwXed*A?u+%=Q>7Z74VSQy_hT zw9z_k%Kkv^>f>Rt%9?46P*VeZu*R9RFMn3|K{s{e-A{hi!EX3;Q^dpr%=Rnaj>%AL zMQDu4D`_R^oN-Je{A(~1;uIY{b=Xg>gVdFXB`)d)+)U)~+}O!~BXej3Q?>jA#Z(|f zRdm4K@$vJWz`qNL4^;}RK+Hn)vdxY3dx2;vqVqRLrN{3O7A#~oFKEi+Y`X05UV05Sa?4a(*@R&@kiF>nqZ9-jB#+)E_PGSz%w3guuI>t*sJJ= z*~9)FewNQJ)kgB-xXhFhtG*GL^R;}nxSgX3#T~PwUf`)Kk5#2sQ)jS9BMQmWdC@Lx zI@_KGC(i=5!N29;^VDEAg-hOq1)_)Nc=lqrl;qN|tq)1XAgW7wHnk5Pih%#9G$r4( zieHzQKdnDhxd!i9bis?J^YWlq8(Rt(f~-fdiO>b!TtC{?+ietc>ga@5df3p4oDuzj zI`I>RB95Zl}foSV9UzDo%l)(OB8niFK6Pglp0N_QP z2rC?2mHPjMwNG&IKX8yM`vaabZLAB1^91aU z%LD3DkuNHSnFRz+O}r>s*nk1NPt(7p{Qh-Xi}yc-2492|^>1fCbw3XoH#IMopNRnz z@x9@x$`0J{plpJNRFk`d>m?T^k?Q=hA+}7SuAu_&3>9v36pN88YY$tr=^F-yiqPM7 zHLcC0*?WJSEZ}!ERwOi_UWO?f?{1T-V=R*MT9Z;IvJ>TmClJ!3EVL}sm;LKTz?SD)eR{_5cigi|BlfSk9D zc0ddG`bIG5=K0)GN`JjF?*ZD=PMvo~d>U7z{oYGwAT6I9wd3e97ny?x>oV3yB!0uZ z@f#N)@3D^aS1X|^c5{veSu#2Q1(?%>*c(i(c3MRS#$}i^0yk10%snW%$qJd?mDphe zW0|9-5Feq2w9Y@6I4YY}_gqGB>NC%(a)921vSY8WS@>F`H-lXXX2^3kotd6~WxI;5 zBzM_ysuDLV0J&P9O;YgtqJgDi$@q2%j{~ge(4R~P-eDRXgd^Ve^nIe$Xf(^6B)$ti z&0vAMe)?{eHiL>~Y7n%1b$j4%1xM-wEgPh)=Og;_#D>gD7r2a^&$s)iHY7mwH#jXEz@rG@U&5 z_5Lr6wBBOr?|6NH?V3I&$H)=KhWeQ(vS1HLXyCxdhIC}3zI728Ky}PK=pgE(AX;VW zGt>HNPidVdQai3c%BpTBQ2XJQ_jI==eWfaHiEM8SOjpuW)43!p>F5*E5-VhyvJr4Qyo!UV) zU^leZvc?!&VPyjaT}F2a{ey;HpBF7E-yo_w99+&1@zD|da!ji^QzyAa>4y8J7Y(&7 z4e--4Czu{;pcUUx8OaPJ0e)Th?-$NgNF;F)N`1FKFvjn7cn>NTb^ z(p;{ej`R3D?*l{%NwaZoEEAscP=F(!zVuQ=_blyQde`HqBO;M;B;uaN@J5Ixx6(?a zG7EFkOzZAcDklMvbCItc;<~5?X8l(Qwe_nYI`Nh#8FBtl>Gqd9e60OP5++NBYY`JK?q{(1`bG)}Vry z`7=vqg?%6g!{fDzb(R99EMxdTi909X+O_bGYTm!L0_K4tR(=w~vz~UC!Z`#!9BlP` z^%3Nv^!8v%R^zTy2qIDy00jV~D)k+=@TtBG*lWWfPmdw_958ZQNyEe{a*F`xSNsWm zpLK?b5sX*WWO2N@fZu)Z-QOlzcqcSFu>C6aEUue4Yto{7Y(0a%Y1}Cl^M~zZJl~ri zr<({~itry7Y+BNKImvzdjqesx*W#6=u+YaVP37o^l0pbh`LhVy^>KSCK z5evQE48UQ^E}=BH6v9^hx3$9`P~r1+xsy1Wk49U!DyFq*wLgR{o>PuG}Cka+M6 zYem6J2gXZO3IezyUPGOT{S#1BTrEr!Yf}{<00>S1+@7QyFaP2T!(Ll%+A%3t7WhKF zYSajHG;W%u1RI1rDoaU23}#nh`C9|ZvH@1_@5d-&A(*-$dEIk(>3Mnkm=us8Q#u!! z_@f>@7H5El-u`=G#eg`6S^0Ieqq*iklYGVfBIpx3(Wi}}wnckQksKcgOmW;p@A>m@aNxX;;s>Bz9lL+jl8S93gE|nCo0u5gK6;B_Tp)<-FpMCO%@c?Gtl{tG+!{BKkl-KCLM;t4c&QMCbd3yfxomU7pQLFu8 z4BGaoQ&Firt>A%4%UL{ycUC5(S3+9ky({xew$LU1GnHg{gNMGW%In{CD~G~+knjfK zo|xASnx#8dzFeRrFP;hyqF3+JltJ;w;({rf+>O|pj|>`6`ZHFp34iH(?m62MIHhwm7Q6_mx^D@Zl}%_qaY94%1!}7tcZ6`VJ*pNW;+E+Il6+ zSQRaq>R&2hR2Kxcqdbn40cdV`5f~vfek|Mwl*U;GOm-QsR(05V5ob zFXLw0JVMS6l;o!*x9f3tC2X{x4;a$wy1wLiUf-#-jM>ik%x zI|qpBK(iyxMBSYeeexrsW>ML!8kh9QG;Gds`g#==uTp2ypZqi@zsVLWOPOJS;Ay1J z#E#IMS0db5E99#yQD>4@FkBkXHRdZ(H#~1*%Bg`|*7Mh6FjP@$tSfsJyXP_RmJelX zlx@v+@qf=YZD8y9Uyt;|f8s zIO1?fFxJ$JiK)I;Ue6rd82&whcz30pQCRqf|V+@TSr?HbpUdp z+Iw`k(tM0mjc;})AQlP|b1Uw}{e+9$KjzQ19t+t{jI{x>a^;Be)2K3IH^5BqE%)9H_Q z#ggS4fmkhbv8ytj&g4>j2WIKtxp^W0R@@UN>3UuOY~NSht9pehp4nbLZv_+B`J&44YK5gn$pdI%k`T307HP-V)*O9Tw03sZ8S9*I zsNPe?g!HCaeWOW%HV5`TvgH#s+eJSfNyNQ#`{`Nt*MZ;LyprH)25zxP+zJH1$@2%S zFW3H_+dI>VWBqf;khVN*H%qb1a{!|!ms~kD{GK{_Q-JKWo7t0pQ>Vy>)T6pNb9R&w z@4SovbtD00otBCat1ckcby(I7(klRSND_xfc3&GHFofdrT(F$H$I8D+kge2CH7PV* zsCp$L-ns8E24$23pOembnq&mel9C5gJEwy0G0mMWj-#b1X6X?f5Y;SqwG}R)fx>mz| z9s_qrqx&HGpu|(SQD$5DWv=pfrIj{ApM`^KQJo-QC6in%d7{8u$+@{1!)Ju@wVNE%?4#K zlV|J*)zmSaIH%#zcmn%Wt_uMh%svV*^w15f;M(MFVT^P3n`Dv-&NF&7n1_ix>ppl? z6vW^w-tt$vpQdtod)i{mZ#|XNuz=!hl2>z5IkKTERU#;OcBgC;gi8S+!|;?WF3cjV zaerL5=(vk240SpcL>y`(_v=;57UXWETCzQ+4%uiY;VX*a44c~wc9-*LAy*= zC_9EqhUI#iaQtX)q&4(roGAQH4?jf6TdT8-^3>D95G$hZNR-*RI%J1ZcY9o1zI80* zwRdigCikg7jxR~}Api1B<8vBssUQtd|BP<#JpONuzx#9#lQ2?S5C64z`7;G2nMx*Q zlno$VznM*$&B)ixR(dH>6BG*)>A)97OmGKZOjWV9fE9L#$23IKfFf&1UEuO((YdDP zCgD}lijK>ua%@B(d{K-zeh==>zwv}J?vOm_PKHbIi@>*&GZq?U`y$|Q_h-oxIQ`Iv z$ZX0gC*db=dGaD}o0@PB*2}kM54o@H5>+0IwMiil|I!&GV}>r= zcUx+|WLxm8JTBkx68Od_GSS|!K7Y6=sbqOmxOI`L#b^cz8LFDMF%0r-(LuEtYVQw{ zfZgqO^nY2}V`h@(P|=;gnl=p{gS+~#zu>*vN!2bOM?_9g>37QY;OxEA?MJ9*E1dtdB*an7mrRP}Gw`afM& z-8X&FD$)`X)8hburnrcbh7y++EC2u?`se*%09;Ujw5X`kILN;(0OX$r003tK0PG!H zomC`7h_tkIh@f@>Aph2XnX##h(|^0$r3adgyuIG z9;g5eXpm44H2{E|mNv`>*GJC$wzazK)nWoR*=OB9Y8-I@YL{K z@Ky1x|E%B5Ur6x9J1}7H3Gf*4DEO7XB8c9v{$1pI^Ko+tjNaK4Tmoj?_}(H_8Eyjq z1jK%d+%L^o{(b%NnSV(*A28(K0(1h#eB0h~ZjbGITtz%1x%p}OD+JhnHGM#QO1`E) z2#ye*6Fvq^eLak3D}90Z$-KTi&Ryr9^!EbwfivIfH~BXKABi<&WY9w z>;*eP(iiq>NuK@Et@6Iw1JbaI5*&f$ub-xgrTk@#&>= zk*5-d_7H|a^Tns$we91}Vb2KJ<~Opv$yrm$%%8;!MoP=V#*Mc<3a7h%F!xKb;X>wk z4T?b_M30&QTWeCVh{Z*_wAb9;SsG2315N1s9(E6F0v_0O3~>ptO79^Xqs6l)VvDi+ zd53BxOmwX3uyGAO#f$6m+8@J-5wu-Ay{TuajrC?L(!fh5!qpG?;W-QwlbdaZ9ELnL zxr=QEiW8>#=}8{n14Bn>cngFm|A3_pSt;4Yq+?v1n^3#wgS)hZc0>n4G`7cV+ZmJm;zIQ=>(NZQzGedvXv)lOPNvH zt4zADW(EHQa!s5t{1A^h`uvpC;x2y-(YyP?sB{&o zo_bZyAkFg?Qf_~EAcCE97Bvl_yhxc#E~>49wS2^krCoAMK;?X~2eGR!Bdu<~F=XpE z8oRN#8;!67mOHfuCeNQL-ZYc|r{eGVmds*=L-53Ln(GNZnF_Cct{eHBK_0jwx_Y;h4mOr?<^oN`lr>9eHQ~uF05dF@||gCiSu~?_$Ot4JT+dDcSuMsCI|RJ`GN?iA<~vi!o$(C z1=JS#=;*17-UXAQh!wJa%=g#lN!4&rG#5t|icRUXVfK~DL%oSsg17OWos|eUI~q=k zoqMf41K-oY#~&RvQ*TydZw=(3;|;2y@1zv*cei${{Jr}rDl1f&d%%JLY2~PTHB%?? z^GC08((j+JrLnXGyq+#Lcaz>OiVFeV^;zDQx&jnZ&V0R0C;k?kx$x1;J|gTGix|sP z2v&47Z#R5Wy`axU;;+!rFCdL;0Y1s=xMN%%$_HF(i&9V1hNqXY%=IiUe^=M{3h6hM z?2=$ftKlIil;+BaHB6ozWyHJMGb^<&cFR*t(ClJ7SIP+QN173NpvGJagU5ONua)!T z9`3Oko_f6Us4Me&$mpYfp%@R5nEFw$z`UF*FgiTZVO86NnsZX){t=r-_oHJ<)8(E3 z4X+VoQoI&REe8YbgDbMW*`=Gi@5NST6ljV%okl;>=b_o%`3k5${i#CaHtUAgaE-40 ztiydIu#)^Ld-QWiW9xMKC|1+=Z64R>#z6+^y@r<};-{ypeK;*2Eg7iLh`X?a+!=*4 zUV1GdtaePHaI6M+uGoAK67Kl?H+-Hb?S-xFv z)E%k3Lr&3sG8@!;Rp5PSYk_uMx(rI~Uu0QY^63DOq;ksuYV9>o@+@L8Q_r&!qVxWe z#POL4XPvm0!28>$m{?(_nU$|7Spybxf`=|t@QFs>;o9KepehG8YD9}n{C&BDqW0x8 zv$*9iU$`kfx%aeDV7k0H_mu_c?ob1gc{KRES36FidzS&d{-RlF2G>Nn?!DNbYw0Fy zSmWVrbW0!gxn!TDmntl#^(IP-7F+>;Q@+iZig9~BLQ8g+MZ!G4-PF}K%3G%%0-{CK zNuJ<1fA00iEd5@V-glZkb_Ung%UZQMxt9?tTqj7bqfnE(7>2fA=w$|_iVLlS-S#d} zpUBdRjti*RtnyBaR5~NgGY=07;}5Jg8yObife*y6JuEu}3>O`o93|9>SJnEA&wo%a zs+0Yt>$OO>i%p|FTf!p0-liPgsZ9#B@8y#YH+4&n4F(;(?`29K7vl#>rRO7F+2hl$ycz!5R=lE`p`8>qLv zEa=zN?Pyv;Vurk!?)~y$t|alj-f7aXG>_BU_nReBDOGn3jvPOu3|n;Sd~hKe#DaB$ zN0VIRy5)DgMiySKm&YGIyc&bXENL(nEihaibv9BU1!xBG84@d|ZgluP93A}yYIb=s z(t>nqTC*Mn_Pp{!fTPUMld?CzpaJh`4pI0>F&d>-ug+8Lp9uszvJo<_*4p&qAl1yK zN!~1c85CeW<~qhE*+LILnglK0EOBpqliH8f1l?!o!2OG9(XAC_Azd6p2PpAhA;KQ5 zpXRf^^H~z{(g=8>JHLdzP1d^i6~T90|LV%W5EOdUi^BCV!4_YfJ0Fyn@N|ZW+d1CJ z%l7cMTdJoiEWFCWO~zhMJXcenZi5)TKr#ba)Dgj9y^B}Ge(XX*?V3})b4ic2B-FUF zg@)6cx#ZUwl0i%A)Jp%N)ELnWb(_hT z{>F(uI^uB_MWz5dv|BSq zybFCB+0WTcM0_=FpUKMW&9sfKc#fD|_WH&5JUCm6BSZA|dM9WH{AKgZ7P77w1{!c0 zeJ+{{Hh^K=&>$?X}z=BG&!c}IQw}$P3vka9r~6SwPbgvU^&86H`0%9+Wg9lLhCu$@`%$JWCH%D)I9+zU z^r5Cx@@7=IN!}<^Fh2@;a09C!^;fW3#U~=nbQ+n+@sacHw@2%?O1vVjMh9AY;DSD1 zW&|SMnh_Odb?$CR$46#sNmis|gHm4{gB|nl5=Gza9g_n@xSZUzV(s_+_R55KUi_tk zF=-L6{EoWb#fQmK74q0Bcom1S)tsqPWEC|d9sPO$rYVL*2@FI7*UM=gh8?)l2ti?6 zsceqQmzV?NLKg`lEc*hI)!eWP1Wf#$5-Z_ObqOkA#zzs5R zvxI_C2*Lw0Ci_Mz3WcKAv#{7TD1|S1l5v@dsSg_#DPI_3CCt#C=#OQ(-jvk(6i$Bd z@8psG2)>df6eb9huRhzKm+Pi?Jt#uJkmDAVR zH8-A3=j7&ALl&Z_R)V}EcU$~f@hDZoxDMC<^&*+9T(V?d<9+N6+3*4V`Fo_#PD36P zDcHddeJ6-gSBU?T$Msui&wQIfNuwOFpEQxht@{TL`9cy2bS?o8Be|4K&%4|MB@x@u z8MfjVSQYM}RsOBQSnhh6e@O1sa2Kj4__zaETn2(!fu%Q`D8IP#NsXpaip2=%S?mxR>^~LuS+t7fFJ*VSyH(ED2jYXzFTTeO zJ~ViJ?JrMTJ)p4a!4Q^M!aD+1Ov;f!W6nLih>G=TaM5QGQLOc7OzqM9mXLT?Z1BnG zOltrOq=VHyk({rn5Li0FWhWQpP&8r0Qe|k8!7Ep8vvl+>tEuPrOAQ<^m@r^!MUpqiC) zjr?Hv2;oCcDG z9>_T$YQ@IP?y8CVs}dVGFcUdI7I;_51D{d6pUYlU(iTQX<90=DgVEA-D^@%mLh0bh zwBC}7J9gDb!C!e8qszSo^+j^={tW>xB<8PoEzvZ{Mj4&;ZhxO4t_K8L)m4u^7DVL? z(33fTBHoj2P3<%PpoYB4o}6< zxT5Vq;T>=Pc1s~>C!1tLO~pwPr<0Pfbq|gSS#f1jYa_Q&F?TrL#pz5W(#5Ggi3Ai{ zhGxW~S#!&#INXmub)pvd>b||wJMp;-{DQZ(I1)l)n_&_f#YdmC7!b+4^XnrCuI#vU ztup)>oY8-m?iUOJiA+pS!+J*Mj>yZ3ZyKhQw8hl?c0t~gs9u6bq*F|#sPv7zSj zB?qP)?~-C%5LN6WNdcFOdDvGV%s2k{ zhx3#czH%Rgg`6mXz9#Hxuc3J$=;o_p?YU2|&rKdOLPGXMXNdsqr6LBBOw3lqW>F!e z!Cm~&TR>s%-YC@fyE@mj1TFy!Hua5lY8<8km~EHEuqc$k_h8!MeEf?hdA3w)2Z zdusn)Zw8wq+sHqJeOSkzVBfZ99v;EhLym@D-2X|S-M{hlI;w|c_0`Fa1Y?w#MAglx zZh*RsMxk~(#iUQ-w>3Lc4QKLEn5ce94|;p$f;zumoO$m>pJY1Bb+62#XHkhFv z_2Kx_<{47|#EOECZl!x_lddN9wHL=B^v({_C%bFztei^~*laf_Xw7RJ%s}-<#|P)7 zqx`~Cr!o8J%HHVDbNNM-@}gH3W}})zh1RYlb6PCZ zuNlLE23b>D@$Gh`+<5-E4oDSd-F8~_WM!c zrRxnVg}xQqkRNCpAI_(Wopn0qb~MnQOG&<2^f`6i=V=UpW18dh%~fks)Kt>$WK`$6 zfh9gV@xnSH{%Q%{-GfbOKjOsUt)j&M>wf3i%hsfW`mU2K5CVJy`z*N9?)+!bZ2JI? z3H*Xd>Pp&Hy-Vgnvi2r)XKt0aW@Q-McF2{-F1a5j3Ewl*O(qJ+&rHGHY(xJlZYg9} z_Pmh7gT98C6cHIQL8@tf6R%(KGwlv-!S}H~)d{kC{=mm&i>}FA)}5y~hb`+QNNGxf zYp(0N+JBLGvAinv(H_G`vN~olwoIJ8F2+vGeJ+ty)l_JZwUb`{eZFeYDgRkvN&2I1 z>Re9MrZQ)5Fn~#O*)X?b_vfJjxfY_DhV>nqs{axVo@L8EuvJOSZMJ%kwfre#2eY`p z)n-IRSE{$fxK?9`Kd~g0M=itAYfyz>uk&w`1;LJrJswnL(zDT-)~p)3H8Yc!NnIFb zC$Yr?wIo&tUeF4@+rC@rm~9CrDRnoH7B$1HP$8ttmv$XM!3zCoGJSVlpvMBTzEYNYx*TP$1OYrwW1m`*g(^wy?bfq<*9#&@#P83Mz?cr;A_G ztl};uE=%pCp%~n_$uI745bp~W=F0?jQII?ajGl$4fN$~-9$Q2c&-~wxR|-u951YAO zX8wyL3hshtGgOY|i?uvdE~}Y+^4c<_ zBK-w>!ZZ3lC{UUW==I&`r!Eb=3z5Tt2uU{EnY*1pz1VpAb7-p6# z#1MM;`Pr`bg_G-@-33LHXdTjG4;&TW?AuK-xL*;DBa3wp%+yn2gEQ*txs z8aC={^|_)H+Ocs1R$+Dtx$S{H0oMcCW=D{789Md=mG&t4Bwkbz8_Zw%A#pfu}XDWYOlwY#R0rg#egF+2JcO_Byt8SQEFeyU4e0IMMtcROwndj zb1|z86~xn3cr(sSJfqzVowvQa2ZQGAlOJWg5z8Kt8d}-t@wQ%Qb7)9agTG+XVq2qG%q8v#b*v6 z3AM2*1EK(b&v{#x9iUBwfx`UKbufGr_}06=TF$RwQ@xse?38MODyT_P_6ADqm9l^h zG~9H{pFCzmKsh5}5*`k84yi0SUY>eA!cYvMz08A|)Il$FXCuXMB`bm33% zen~c*cIQvF@^Fjtns;+!QnLx3>LyAuP{Y!C5ZLpuB4E5H3^lLpS0DPeqr1<`=%p(2 zIeU$v_f1HdoX_iA;a_0@x^N%-mH5d!IX;@xD3H$HR>^r^IKDxOt+kM{qg6L%VAM!= zXE8Y#!}>W;J4ez#*lx9Af4K|c_^HS;khI|eWa+)-V4C&x3H$pll3R(Rg{uUrU~s|u zj~MX!Ioh&Mn7JRRvAkD19s|_>lq)if$0#`9(ZK$PNX95W-9x*s$nH>HowKWwzs_v&}BqZiLCA-c5U|If@sJz3Ux%U zd`{SVkP>whkG^35hhawx@KQ#Xl-j+cN2}u9;;8Zfi>e>Jk)^9Vd%?4C;wR0?1?MrZ zl$$EVDqFd=Y~HgNBPT+7o|)yB(|K(2pU7(KPCs!r zukR-R9!k&`scf^Le%f<)!~T|_Nkz za3Zxh79!xID$H5tmBt-ZC(*d!pFvEQOaf85D_0ZE?B&b|>Rr~&td4x%d@>3)E(OP)_qXpJ_1}#(GV`Ma zbfL(Q`n-EbZq*=`EhEQV5gKhiG=AU--`|fL?EaGxY$S4gq{$y)J7W4bE~h#DZY_5= z!Mv`oL%}x%DIi2dXNKi?x}h;1yRqn`OC5X$D|7-KTuJmv_2jP3ALw1-$#<>F+P^^pP+H zP245E#_rUM^bGP^I^R1j(^mLIQpv{%l0V<48%2E5YP`ozMqb>%7b)zj{PosF{1=?7qc({0G{8J^ap zqNF#69<^HH8bbAzhM=u5A)DH*Vc1uFUsqttqZPA~QPFoLH) zMysgnIKRg>J6=~3u@T7E^RtC_6o^AD&IT-3XJbbw-1x#@VxjJ}lbn_k-gNpw0TUV*DC{;jT5|L`=umhS;tG{|9l#d~pn~ z4N8yTNKVh$_Q>nqz_Z26XNsX)j#yuKu`mj~ZW&^7E~UnsKJcO!#g{HD=x5h2mFNd< zJS_4q^CERi$^KnXcEk*efcWIPgpU=j^i5^DZbWd=Es`DJgPgdvWvzT;8cjP9otv+$ zi9|+{g{JFOGcBLV@P|*@v9Sr${OJl7R7u}cjyxI{PS=(l?B-L|9c=^XcyT92^}MS=FH^BdG@;=;c0XTfi0^cjPt=)1eqYZd~e3 zv@0V&V94DIAv2lQ=84_Q4t?&{k_2YKMKsOJpV>--$emv3A?3$s#u2){FinR+k99-V zM(p+g>QGos_SFL|GeN=4qS(e73~}U}Uf)FA?H%{J-@+-QnNCcBwJe&nA8Il&+#Suv zL$WnOK;K`ubE(S-DoF2fSr$yPi|$e48yEYD@4uyucM%j@wlR9&9;6{Z&4wwF7B%R| zb;8^smO98>(52y{s$e@d!Oc|?KcxJqboh-ae_K{WP6bs4yeuRK62vJE4kuP4N0w+QYBeyeWXsk6V#iBGe5d#gv< zVA*q)`HqEo!HeqXNNj)h;_t--IY=WQw;j1RXL&UD7r?G70v4+2AS z7zbiC}P8^NIHmD5>|6i9=M?Eq4dG&cw(z6`1p zg&x7f%XN&K`i=|?Wmwx@wNXDBlHVbQNa?+y{Yo*_8rn^Q&)sRdiIQ16`??2;i;jml zb(T{WmTnd)SDUK7p(@v;(hND2QRuJKy#WU7Md*{u+|lhU7=EgpyZ441vRXi>^K zp@q)B!RH1(KSfp=+*unE^_pc>5NMn(zFAV9ekyUj~!^2T9(j=sf*zC(IlP16fNXl#?HN4Hx~_u_Ars|OH)L`AVzdGrhES?W7) zq6yiTIBHAba?lCG#R4-Z$q5z#lYeTPCeny*LS$nk%oidRUvCnW=KRz5>fA|8r zDX_E}zwNGpr&gm9=l(RFq*fsxA;!#J4#HUhfSN6I@V6H_^IAe(zwkS^*{-jUrzSRa z>d8CISd<%yp>;XYqq!^ZW3`O_EgL3Ub3A{ft=#=33HH8rcgC2km7V;ne#cj_uZ2lD zy@UNDCe2zBlC_*jSwr;YB>H$mVUlm*9i+L6C$T`h!m3K`qALR8n%r?dYlwlnVne6v z_Al;aMby6b)_CU<~OJ4Whd&y6vi%L}vMG{b7w%UO2i>rujj>Bb>ce~u{BnV`a$ z`>T>9TT}8*i8p=Mysn5*aiT~_)^rnzspdn+t9iFlEQWwMx(T67xsGhF)$`n;N$BO^jc|NidY5Db8@TA{cN~nt+A?4 z>F&WD*`k{#FxzkQk@NaK%e9epE*}WXOi=23k>N)h{K6m_>59Zn!5+!Qr|6MVu9~D+ zfdXF+yjCzZ<`IrlQ+Pcyd^peNs)dStC_|ehkbs7CZS;;%Y ztLq64qO4{8ZxwFso@R8>weM;AX!1L`f|Rwh5asUz$;yVf&1f1{t$=ZNu0VuVkK=r#~&Z83qCx?CTp|9I>aC8 zgZL&gUcChtSP|^9QV1Cal*;T;r6@I){`$jBH79Y#og%;)Tnc8kzV&<-#>;p=NtAw4 zwe+yalDSrw{(A6s58<0JtHGYhK`2c@M;F6}jJ4*ZDP1o|$1htK=rcp%-ZDKR&?e$t z8C6{_HVd(f`5EN}Mrx%d%JUr{K0mt%wHS&w@vNM4{NY{C1>Fe^(G+7(J;^}POBn6v zx3E(Gxzn#$xz$q1xbxuUYcsQ@3(OYhtRWHBwfSqJa`*)6jKwb@A zfK8*x>5dOvLn^2EuhHw7LpkKC(30t)CHO@r+{>-Uhz-o>@@E9vd~H-z^vWF-@)YE^ zbP=lRYiAVO!W=zo3ezp_M7X~= z`)RrUo*IH+8caz8>x}5SbJ$_%RWZt|M!)=I`;FN)>P* z$v2^El$cCJVJZ#$4GzeT70^gX))RkLbqp&%^C!m0R*{g$A3!(H&PeU;hG{+G)vymp zl-o6&APTJTBOOY&7}DO|@ygho8yT4*JO7#>8e?O{za(Ryr(dE~!hv1FE{qT5f(V(e zTD^UeYp!yJh1&P`B-CB>=yf$rb=Ks99#w9!b(RGW+7??X>pep!%cwvs2wQ_`ZF#Yq zuq-hSOW?{4wqC?cVp=*2!2AB2ju_g%IAk)WM>q+YUEf_r&Pc<4)Bt(((;1&i*t>_9`aeJCI%0*;|L5{jnDPkUS24X>;+pfM(Db^R(g#K60PukIOU(>Z(oAUE^ zc`RAPnvIn7bD<#Kg=&2!_1;q|$FLZ%Z;+SQ5(u>-0>t;UCAF_GE>0pLlG-{1!;DJ) zIJ4-Ki@PK-nUkae!fvp7`9^AAjgeUjx#xfNO98)koQJjAv=N*BBomnplErgEKZW1F z5c6FU2g2$<+n;Jyv>e(}M|7=d_|+IF#I2 z)~2M;I7y#&i%~X2h!s%>!w*;9dy_TjEOmz_*Sx;ni*2x{F9GgF)~lsF^y z^P*Nl4rVwBrJh5tY;VH3T#VPn>}#@}j`?4Y$|*{Uzobs!0tv>=9R%uEF)uoFm%|=o zEU$0FK>N7#%*lxqRp3jcH{>TO*IgQITZ*BbEIm<^B;13R;IuzJ=V5;KHH-Kau1ELs zA0L_cdA7*gm3v!{CTmqEip<+Lc2&0hRFCMeicxwhp=_i$9rg)&A%i&0|2@juhCt1V zja|y+x@6ItrhG5u=sNRb+lLDjSg&ElBw&P2tB&efk!JKR4LII)X~pFk7%#1U+kj_v zT1;;Kp@hPam?nV(dmfEE!^QrWsw^kpZ%e55yK2c9l}F||xkz=~@2?3)=xh1NULY-G zYhvjGC~L&avbfgxqNgoiz{PLV0BN-b2j4l`!lIiKExik8cvXqo_10se*<|SJlkpktD)~Vx^K}q+tHEkDM^(=Z} zLs=@Gs$MHorh|{Gv@}LNdPIXJGJ`X|gN|@o2dD=R5)@;Ygwq)2xag%~H{_V7$ZbZ3 zTb;!z8yyR4CvtwoJ}B8+d2LF*s)PPv+{4V<#<<7HV~Vop4d8Pk!!6}uERZ!DxAeYY z^)Jxbq?L`8A;n0c{M=5N^Z)Il{U|#G`edCabtveW(b>AfI|hBcPoQ3=EZQ7awv_%5 z5z|HmjYO{E5u9@zC>(NI%&;}LRs zKX^R9c=)Ae|94P3|BotkV9Qw`BxLqb>ph4lBtl0A_)}QmpZEY+b`dnA$@sPDZDhG^ z-c@93W8Wmbo1t~qaL)#Z?I<9Nu#Bkj7fHZOh z<+i6&c)*V_g9);t@fBFCN)L_O82|;bRuSq#%~B%+SE*JWg=1QYW?6eLB}qwB%!j>UoFx@^ zbKt*t{SJtVN%uM!oO^y?kaV%B95OWV8EDW{qiOJvo^M{F;dWAAAdMA9`r_$%v6jX zL3!KizTzm%b%zp@6zInrUE7THRsN_Zz~$|WPP^=R!@#KBp5())+u8fGMs_}LsKTKq zK`f;j)M;$WNzd!7V`6xX_C@ZPymYYgxD$#flH!Heb$Yswr4)(|r<*=}SvdZkme9K0 z14+0`4LLKTa_SFF=Ca%X30x%6Q6jn)$55Bg^Y(YH^m~nmf&%3WYaunfZ@4MH9_FYf z{5>Ss6P zWbg}V0~KxE?NSNlxB&q-qL^Cd9wh+4yRmzJH+H<3sERUx{mB!=~4wuyC zMBD_p_Mo?xw4YhxOCn&3ALI`;3iC?VM}g6Ar)4XF6^bNALAVGl>m;{1^&5%A>8#{d zURL1cVcLF|QOIKk4d@#YpVdz|+KZh`U@k;*cvdbkvSc&F)y+P$=`LT@9ifStERS_^ z!=?`CWi_zT=m!%Df z21@|6b$d{h-5k$zFuKmXXu z2=x$W{xb78aNfu&sO?iV%;CHzeVEte=UZO7JEvy>g=4EAWj(Lu4zVNNg^+7hpWh6% znX(ntvkG4YyUgjSq;Gx4ePZO|iOpNlbwyiF!KVr>0bgglu9nrDdNicxqfu6Z!y8G_ zTZ{{%a-AvULIR7n;rt|awLzoeM;nXVP!BODg~G6s_H55O;VVChpgf$W3S-jJoPku0 z8;W=0P2k8sh=RE;Gzr*j%0YHddV0)E)!W*!)JB=o6`{o5Z0+g|v{1*XMbRD@a}HmK z$I&P5%MOFnk|s;AbiZ%H_w8~#Ly4k{RPVb#xviXkKhAtnKUuP&2!0lwANxW-Jg1qq zUdo?D#@SS|PU=Wf__8YO9a^$53Q`ZdYZR3`CmIy|^4=U_@i%UsLz_#WOt>%`Hs^=H z4TPt<(9OMfwxm6?H8t*>bZrG23)h%zoQ#Ga=Mp5jwJZ>b4o}7W#1#q7HyA2FDu<2Hzolkadj}We^}NGk#8jQUG6Kz=P_G$l4|g@=k-_lO-K%KW783&Y~YnjnlaJDYmbM|nK55W5yat908=HMNO7ehc(iQ8v=oNmNKR z_~&ObO*Qg}n0vh<&yYsjcOmx`Fc~^*UQ-XM?wh^Ei)gwHC#8bbKG?i%c~0T0?}sxm zv4(#BGnJ7Ipd1XuIeHY)WK#Iao=GGjFpf;P58(j+dzM&()-AK+mkrFFfV7v! z>r_t+|DPwBoajcdK(N>+Z_#JYC1)Q-2L^Wq%3YHtwit)f+O(PkW5*Cp;gR?XM?9V_ zQFIrEM{%S4L6{=JS%RZyb^FjPT9*_$DIllVrXcAmP1HRfFq?p(qEQU;+D)zVx+QZi ze!tT{9a4-duyv*X^|-c;*uzEQ8O9sUBR>>nI;aDFe7^2b0{g@Gs~F4M82Ew%!`7fn z>wjkXKCg=M4vrsHvz`mP!-y}wd+m(Jfn`}M+VmxfaNTAv3+TU0M?>}rv&^?m6sWig zo_K;g@tzk_$f#FpHbOB%QkTFLUzq~U5scM2s_K&EvM){gbin`{h5-vW4i%GK&~LEB zPo94p`Rwp35k^(q5ya&<(vP`l-C=CfM;s7gt)T-U62W5jX6R@Jwo-qu=oN$KvdPU* z30lJ|UX+d@T+Daf4KPh&{t)r0@5XUExol&e$X676{W@nyhWuc-)-y79Vw}AZvk;$I zkrQYU)b(&qoii~d>E*a|=_G~pwZ>Uk=3=EMLRb^gm6Lyw^$Mx~FbZB^?!DwEGGLD- zMsOH32aOhUmc7FRk#?I64%@RHk70j-_?zWW|NKYeShFeUrdu8-UXwbQ6HBnDFQG{b zLV2TSN*K6;InAcK%hI4%WIu>H15PG?e1W|>`bgnYBhvy`w7d(quTA{Gmj6*t^+xg1aEC#k+FuVBWWL)+A+aNXtwDytG1VTd=1KLAN~|T- z19dtC?qAKMLJ#be>POJ-YM(l)kIKHIZ9(SIaXkdP%H*p~E zuSGB7RJsaPH9yOejz(}nC*IL14js^(F?wB5)UHD96WtS3djsx3=zf~*!RkHC!TfOz zJZQ;Hfh17fwaQestguVV5xcZHf`BMtHbuyBNI0zA5m5UjTiDoD>Xf1|PX{Gcq5l0F zMGGctyJaj-p}Y`-dDn7+FafwKTdW0?VN-ML2<1SdjHUL^HU0mVnS zacxXPc*&rh-A%faDD-5xH}dyMS6L8G$jokbH#@D)IQ9b?sC=R~!nQ>3>1FX8eFU-2 z&#Taz3p$psww4DPTT{F(%8P(?_7@f1E<$K?rpbv!Xu z=XYXqG-;Qm6JU2+jh%M`pkB5?VsUC#B`dm4C996=91LayUi)=_)&-|MqL)yYRP&@3 znnT1Sq+y%i#3Ka>X13`z!M0c1a)iqgd|M{7H0F3$xbTYFG2=O)zjKEWGiCxwe|Ecdom@LM(glPC+s+xvd`c6D`2RjOIzWUYJ0!QFYaI0xC&0+hh348Gp7?v8wITF&3Lg-a_etX`XIx%=^tU z%H7m%&*)?A+|zgTgjY!)UNErCCAVJ~ut}bYO99r48q}FBQWCf$Ow;RB?`?N|r0wAX z;~cWSHiuEzAHL)q6#e*(gx2!t0cNn$7U&%$Y2D%C>~!%mH+>P~s-23^x%1z~#ws;DWHZmAqMjVhu7L zfDW^5KbEky@o1@3B{-PixAsgbVo|7@yN@WRl z7~}USd6QI;uOhZF8!r!UVhF>U2tLLBOEqs06OWI;v*0}Z3*&pvT$q8C@DgpSWb0XQ zR`*~8Cw=cS8M`=N1U)t5-Fala$Gt!qA}JBkRZ@Q{%fq94C%Q}E+PO^=rUZ`V$ux0C zOnSHy->OO;{ihd2-X&lzWFDI2l8L^wO8&tLo#^o@8HI*?F+5LmQ+XT^4ly-mz`c09 zIJ5X0X~1x}ZdX+WgMCkmE~x3iXJ(PN82sij!eFXzDHw7O`W&V{4dZwqOqczxh{Z8V zU3kxm(;)n_IC*0pP(m`WeI@OczMg(f*bx+o*m?F5|Japvp|ml>$I$W2ykk-*`dA88 zHf&RmL`>iP%rJqBF0+RPX>%&eA`sayS8Urkwyy5HNkUd3r;xzkjaMzlB##OvDD`wq zt)AkC0Sd{|+irPL*vYInx@3+uEJQEW6o+tMVfnX7Lb+HXKP_ZLIj{JwE115qe2^A& zi`co!r_MN*!pCt@$_WaKF)S88X>5YBQ2Ryq+K< z=E_|e7S)4SNmou473KtVr2(-7X|ko}7=hvY?s=R&MNe|2f_M+uLw#;JXdXPTA!((D z#OC=dxJ_9BPUqi8sUL{J5kd5(+Oc|*==XmBFF?@0VaXR7$AP#H+t8S#(+p%9oywte zk|RfWgLCp;6!%EH>Too&-I*P4=_|43pS6_-c6{9aOuMvWF}txe!cEtbH1%KlrTXOR z60(@s*`TVl3DX`Po4qwpizFaWkF?bn|HE4Q%!+MeIk4^~E$bzDLpc>&cv!|Hg^K(8 zEJw>I4%3}b0j3+Q0x|D7;}a1Y=$0y&)7EZ{dVveSFb7}+Y_Gwpy?HmH&5A~Nr9V8x z41vl%qWM#dT2c3;;f-8nm>bH&p9;zo_hWbsk%D03Ia#mPa*?r~da)!m-1y3l+^&=BD>rXjqk5;6^W4~y z%|WdT2@_V~8R5 zN#Zdt4#E-uSRno+4%yld)luL`O9Wj+@|muB=IV(vMA45`R*s z;g+?6<5NF|%ltfl4x`GBHZKaOKvFL+ZdS%eg)R{alLofh42%~gheVEgpjDnmiD{JTfYjyneW+ zMvV+o0hxIKVqUvXn z3x5!zJpgIL>U5S`Y)V42VveR9UG;JNn=B3Pt45A@?i;T&LX&o#wDy6X(1 zAD9395PG(#o9{hoF(wx2dJ*MK}|=b#@iBOfF>X@J}#x`DV*8-gM$As*1TipGJ4 zw~o%0_-h05aIMg8iQ~RfLcbJ-nz|9HR^WOgBDKAPYW#!Ttz)HtdGF)iV5n*E?S=W| zT-!m1^dU|VvI9kX(3X)7u7V@B!^XxoJeeJ-fhFYIO$!gZ9CqNWB=yPl1eih|%}XHw z(W$G;GfEIP80Q2cgtgh=-|EJWd2tHn(DDt2UP4CDq6UYZOo^QH_`v6 zx*G?hz`GOLdOY=f#tTn=epdw7r|mCvAupF+31+>G4evd|q0@|mwowW1vN|%C9d&F$ z=U4y&f1PN1PfMP=K8+Osk3iTGTd~!nEl^xl;IaTH&V8q}JEDqA=BfRmHS4XY9Z8*! zItsDWs6`445?I?e3WGXjyb7(CI~~~M(=YgY(%o@h1zhM$3zYCOWVZo)fKuSSR(y$q#~7M(2up6w1e?6-w*uQLE*L*} zp{>eKn@s|UuZ+#_G%#zc{nTZWR%T@#vrcR#fjZUV4ZL!$m_-r;t72zwo>B_B%^xqd z5uWlgF}GU6Zu_ED0GKbhMyG?iNFDJk%4&C$tHvc!3RPi#d2>9ySTR%K80)DXisHBWL0r>TJ8pDSeqX(%hR@kP4V zI{n~b_~Q9vrcsT*SLLs#q(Pp(!(iMKR7FMdD}Q$0%G5ZzUACRMd-62nCKgyG{Hiu$#u0K&+r^X{zs+p^HNS?H5FdM8pYO+?MmDvlmg`AAn|z~?VAP< z6WFs1qHXhfwP3y=N3EGQ-lfEIu!RvqsGz60clTZS?O^)Wi(JR{MkuYDgf+=&{*iIh zbt;6~K+8Sr2r|3RjgT4f`&&+wg|pNyYWbI9eT#dM&|JE~HP!e?LYPt=#lj&oI=3F` zg?#&%XbRV*O11SCV5fT0`euO4)Z0$CmA5m;9V-u`vq|rH(l_Y@^ke%o&Bs z)I;W7pH_a(0F$PiSSU9#p&I9H`KB{gKpF~9cc=Ydixg(+s)y0{cErPxO|v^yxmd-S z@R)R?EM^k|hC(`Q27VRMV2r}{DfN!d!c7|w`tuCWz{pV?u9LOODdoAowH;GFLo#{0 zT5L^Q`3r0EH3sGw47In)e8@i^jJ^zhC9HM~%g~+uDup>`IY?tVy{cQ$FL$a`j+SLT*s2> z-8bRX45@}Lnb@F=I9s|aB3>_w*@qZT7wNtPM!P%X++w*7g5*9?x@v~Se}-Em<_&oS z_#Qc+JTy=H@zH8Pja9Z#f|Gw+q{R(tv{PNQa`T+loQXj?s2!tEdzSfIt}Hgl61mw0 z9qE`R-4;|Sbzf#0(k=$Sw)ylj1>fnA_7l^8sdMAMchp;G(vKjfgs*^swJf$s^#spS z&EkuI37Jt5=aSGDbZ`oJD7*c}?Fu{{AEsGZQY?)u+jm}3QoIJP&pW?XNgc-jl+0S& z%jBZfB1966%?i%w2jRZXxolmzM8=A#GERE>L^-ZM&$kS9wYzs08pS@l$t)W!QWw`DUfC4E<emIY=zPF3C;^gIxN}6e4&CLU|?N%0IT< z03=H`oczZIs`0FIzEGFUB_9S7ZhA; zdQh-C^O-;gLxvAM?NbG=EGz*~i1d40vE3$Z>({huLgYS!PeKE(%bnzToL2y#u^cXy zbkXc$wpSs!VqoGDGnzvzn;unB0xRebDyo#K72{*dB*Pce8-Qp2%n(SqO}{mPC!SYu z3fqg@B{~{TFGAsrSobwNi{YxHjBt-eU6Yz_@;{#$q}f9|(%8ZE{dI64z1ES*C`;$C zgcbPl+Z$K{*3_ywyA(dH+cpTi z988A*HR%RJ2&O|Sp1X<@JS#K&GFEr(1$Rzu_nSMQ%W@#9`?YMofys>yLyjRLAz>-Q zYcVWSC3ES00%F(p^tPtYSv|oR#D<0$#rUQd3AnjTnC$y#o9c=f!ag#(5W`%AE9BEK z7w&*Nc9_N+8&56wEVyagghhuu#w=RpEwv?ZalejSnXjg8;rh(brH_dQ6|_>x+f4CE zP);3oct^cZBWf>tjR5v^jwIeUlk>0THT_t1gAlkF9PqVI_ItSCRAMc!KKb3zB=}m=rufNR`Dk5qE|yRcC66Kq|4E;r{#Ns0 zU2D3~M{?}ce&1$-iw%O7k~V>QHoz}dYY$krTt9LG+zO4aN*9Dz6Sqjq&qbgnvkVFN zAW68Vx|pM395(7G9S6mq+wW`X2Ltf+1q#*V;Li>?@co`OJDMZZ!_**J?Q4KXY%j<; z3@>dZ5#3-F4|RH%oTxX7ll6z20^h$8|EDEE^K>u;Q-

K5^e~bRs_sD$u$!5j6k> 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 XoA$vnz4oL2%N6B9D@EnVOGW?y$=E*W From 7744b37f2ee4dff1f073e29bf8f4bada65b0fdbc Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 20 Aug 2026 09:12:42 -0300 Subject: [PATCH 04/35] ai-usagebar: share the helpers, drop the busy hold Two entries kept their own copy of the ISO parsing, the duration and clock formatting, the provider glyphs, the severity tiers and the clamp, because require() needs plugin_api 22 and the manifest asked for 9. It asks for 22 now. That is the cost of this commit: the plugin stops installing on a shell older than the one that shipped API 22. shared.luau holds the copies that were identical. resetClock was not: the capsule's version named only a weekday, so a reset three weeks out read as "Sat 02:00" and named no particular Saturday. Both entries use the panel's version, which falls back to a date once a weekday stops being enough, so that is a fix to the capsule tooltip as well as a merge. severityRole stays wrapped in bar.luau, where color_by_usage can still turn the whole thing off, and delegates the thresholds. The busy hold is gone with it: MIN_BUSY_MS, the pending-clear bookkeeping and the 120 ms tick existed to keep `polling` true for 600 ms so a spinner could be seen when the CLI answers from its cache in about ten. The capsule dims now and the panel button swaps glyph in place, and a cold read takes long enough to show either without help. 1055 lines to 1178 across four files, but 123 of those are the new module and its header; the two entries lost 176 lines between them. --- ai-usagebar/bar.luau | 106 +++--------------------------------- ai-usagebar/panel.luau | 97 ++------------------------------- ai-usagebar/plugin.toml | 2 +- ai-usagebar/service.luau | 31 ++--------- ai-usagebar/shared.luau | 112 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 219 deletions(-) create mode 100644 ai-usagebar/shared.luau diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 210eea44..ec96c409 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -14,87 +14,16 @@ local colorByUsage = noctalia.getConfig("color_by_usage") ~= false local report = nil local polling = false --- The poller names the failure, so the capsule only has a code to translate. --- Anything else in that state slot reads as no failure at all. -local NO_FAILURE = { code = "", detail = "" } - -local function asFailure(value) - return type(value) == "table" and value or NO_FAILURE -end +local shared = require("./shared.luau") +local GLYPHS, parseIso = shared.GLYPHS, shared.parseIso +local countdown, resetClock = shared.countdown, shared.resetClock +local ratio, headline = shared.ratio, shared.headline +local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure local failure = NO_FAILURE --- Tabler has no Anthropic mark, so providers without a brand glyph get a --- semantic one. Same approach the other CLI-backed meters in this repo take. -local GLYPHS = { - anthropic = "asterisk-simple", - anthropic_api = "asterisk-simple", - openai = "brand-openai", - zai = "bolt", - openrouter = "route", - deepseek = "fish", - kimi = "moon", - moonshot = "moon", - kilo = "robot", - novita = "cloud", - grok = "brand-x", - supergrok = "brand-x", - antigravity = "sparkles", - cursor = "cursor-text", - minimax = "wave-square", - kiro = "ghost", - copilot = "brand-github-copilot", - gemini = "brand-google", -} - -- ── Report helpers ──────────────────────────────────────────────────────────── --- "2026-08-15T11:29:59.872624Z" -> unix seconds. The stamps are UTC, so the --- naive os.time() reading (which assumes local time) is corrected by the local --- offset measured at that same instant. -local function parseIso(value) - if type(value) ~= "string" then return nil end - local y, mo, d, h, mi, s = value:match("^(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)") - if y == nil then return nil end - local asLocal = os.time({ - year = tonumber(y), month = tonumber(mo), day = tonumber(d), - hour = tonumber(h), min = tonumber(mi), sec = tonumber(s), - }) - local utcAsLocal = os.time(os.date("!*t", asLocal)) - return asLocal + (asLocal - utcAsLocal) -end - -local function formatDuration(seconds) - if seconds <= 0 then return noctalia.tr("ui.now") end - local minutes = math.floor(seconds / 60) - local days = math.floor(minutes / 1440) - local hours = math.floor((minutes % 1440) / 60) - local rest = minutes % 60 - if days > 0 then return string.format("%dd %dh", days, hours) end - if hours > 0 then return string.format("%dh %dm", hours, rest) end - return string.format("%dm", rest) -end - -local function countdown(metric) - local at = parseIso(metric and metric.reset_at) - if at == nil then return "" end - return formatDuration(at - os.time()) -end - --- The clock time the countdown lands on: "14:20", or "Sat 14:20" past midnight. -local function resetClock(metric) - local at = parseIso(metric and metric.reset_at) - if at == nil then return "" end - local clock = noctalia.formatTime(noctalia.timeFormat(), at) - -- The weekday is prepended here rather than folded into the pattern: the - -- host's format grammar passes unknown text through verbatim, so a "ddd" - -- prefix would render as the literal word. - if os.date("%Y-%m-%d", at) ~= os.date("%Y-%m-%d") then - return os.date("%a", at) .. " " .. clock - end - return clock -end - -- "Resets in 4h 01m · 19% elapsed · 2pts ahead" says how much of the window is -- gone and how far the spend is from that line. local function elapsedPercent(metric) @@ -114,11 +43,6 @@ local function entries() return report.entries end -local function headline(entry) - if type(entry) ~= "table" or type(entry.metrics) ~= "table" then return nil end - return entry.metrics[1] -end - local SEVERITY_RANK = { critical = 3, high = 2, medium = 1, low = 0 } local function rank(entry) @@ -162,18 +86,13 @@ local function shown() return picked, #ready - #picked end --- The CLI already tiers every percentage, and copying its thresholds here --- would be a second source of truth. Text stays in the bar's own colour until --- the reading is high or critical, and the accent colour is used on the bar --- fill only. +-- `calm` is the colour when the CLI has raised nothing. With the tint switched +-- off it is the colour for everything. -- `calm` is the colour when the CLI has raised nothing. With the tint switched -- off it is the colour for everything. local function severityRole(metric, calm) if not colorByUsage then return calm end - local severity = metric ~= nil and tostring(metric.severity or "") or "" - if severity == "critical" then return "error" end - if severity == "high" then return "tertiary" end - return calm + return shared.severityRole(metric, calm) end local function shortName(entry) @@ -184,15 +103,6 @@ end -- ── Rendering ───────────────────────────────────────────────────────────────── --- A provider can report more than it was given, so the reading is clamped --- before it becomes a bar width. -local function ratio(percent) - local value = (tonumber(percent) or 0) / 100 - if value < 0 then return 0 end - if value > 1 then return 1 end - return value -end - -- Quota above, window elapsed below: a fill longer than the clock bar is spend -- running ahead of time. local function bars(percent, elapsed, tint, width) diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 11982edc..77a4e3dd 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -7,13 +7,11 @@ local report = nil local polling = false --- The poller names the failure, so the panel only has a code to translate. --- Anything else in that state slot reads as no failure at all. -local NO_FAILURE = { code = "", detail = "" } - -local function asFailure(value) - return type(value) == "table" and value or NO_FAILURE -end +local shared = require("./shared.luau") +local GLYPHS, parseIso = shared.GLYPHS, shared.parseIso +local countdown, resetClock = shared.countdown, shared.resetClock +local ratio, headline, severityRole = shared.ratio, shared.headline, shared.severityRole +local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure local failure = NO_FAILURE @@ -21,57 +19,6 @@ local failure = NO_FAILURE -- would otherwise stat PATH on every second tick it spends in a failure. local HAS_OPENER = noctalia.commandExists("xdg-open") --- Same parsing the capsule does. There is no require() below API 22, so the --- four helpers below are copied instead of shared. -local function parseIso(value) - if type(value) ~= "string" then return nil end - local y, mo, d, h, mi, s = value:match("^(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)") - if y == nil then return nil end - local asLocal = os.time({ - year = tonumber(y), month = tonumber(mo), day = tonumber(d), - hour = tonumber(h), min = tonumber(mi), sec = tonumber(s), - }) - local utcAsLocal = os.time(os.date("!*t", asLocal)) - return asLocal + (asLocal - utcAsLocal) -end - -local function formatDuration(seconds) - if seconds <= 0 then return noctalia.tr("ui.now") end - local minutes = math.floor(seconds / 60) - local days = math.floor(minutes / 1440) - local hours = math.floor((minutes % 1440) / 60) - local rest = minutes % 60 - if days > 0 then return string.format("%dd %dh", days, hours) end - if hours > 0 then return string.format("%dh %dm", hours, rest) end - return string.format("%dm", rest) -end - -local function countdown(section) - local at = parseIso(section and section.reset_at) - if at == nil then return "" end - return formatDuration(at - os.time()) -end - -local function resetClock(section) - local at = parseIso(section and section.reset_at) - if at == nil then return "" end - local clock = noctalia.formatTime(noctalia.timeFormat(), at) - if os.date("%Y-%m-%d", at) == os.date("%Y-%m-%d") then return clock end - -- A weekday alone is ambiguous once the window is more than a week out. - if at - os.time() > 6 * 86400 then return os.date("%d %b", at) .. " " .. clock end - return os.date("%a", at) .. " " .. clock -end - --- The CLI tiers every percentage; copying its thresholds here would be a second --- source of truth. `calm` is what to use when it has raised nothing: text stays --- on the surface colour, and the accent is kept for bar fills. -local function severityRole(section, calm) - local severity = tostring(section and section.severity or "") - if severity == "critical" then return "error" end - if severity == "high" then return "tertiary" end - return calm -end - -- The CLI reports a vendor it has no credential for as a `credentials error`. -- Those are not listed, because they were never set up. A configured provider -- that fails for any other reason keeps its row. @@ -137,13 +84,6 @@ local function updatedText(entry) return noctalia.tr("ui.updated_ago", { minutes = minutes }) end -local function ratio(percent) - local value = (tonumber(percent) or 0) / 100 - if value < 0 then return 0 end - if value > 1 then return 1 end - return value -end - local function metricIcon(label) local text = tostring(label or ""):lower() if text:find("week") or text:find("month") then return "calendar" end @@ -288,33 +228,6 @@ end -- ── Provider list ───────────────────────────────────────────────────────────── --- Same map the capsule uses; no require() below API 22, so it is duplicated. -local GLYPHS = { - anthropic = "asterisk-simple", - anthropic_api = "asterisk-simple", - openai = "brand-openai", - zai = "bolt", - openrouter = "route", - deepseek = "fish", - kimi = "moon", - moonshot = "moon", - kilo = "robot", - novita = "cloud", - grok = "brand-x", - supergrok = "brand-x", - antigravity = "sparkles", - cursor = "cursor-text", - minimax = "wave-square", - kiro = "ghost", - copilot = "brand-github-copilot", - gemini = "brand-google", -} - -local function headline(entry) - if type(entry) ~= "table" or type(entry.metrics) ~= "table" then return nil end - return entry.metrics[1] -end - local function providerRow(entry, selected) local metric = headline(entry) local percent = metric ~= nil and tonumber(metric.percent) or nil diff --git a/ai-usagebar/plugin.toml b/ai-usagebar/plugin.toml index 0dc13313..660aadf0 100644 --- a/ai-usagebar/plugin.toml +++ b/ai-usagebar/plugin.toml @@ -1,7 +1,7 @@ id = "felipeartur/ai-usagebar" name = "AI Usage" version = "1.2.0" -plugin_api = 9 +plugin_api = 22 author = "felipeartur" license = "MIT" icon = "brain" diff --git a/ai-usagebar/service.luau b/ai-usagebar/service.luau index 3b55351e..8effb5da 100644 --- a/ai-usagebar/service.luau +++ b/ai-usagebar/service.luau @@ -118,43 +118,23 @@ end local inFlight = false --- The CLI caches for a minute, so a manual refresh usually answers in about ten --- milliseconds, too fast for the loader to survive a frame. The busy state is --- held for a beat instead, timed by the service's own tick. -local MIN_BUSY_MS = 600 -local busyUntil = 0 -local clearPending = false - -- A floor between spawns. Opening the panel asks for a read, and a panel can be -- opened as fast as a pointer can click, so this bounds how often the plugin -- can start a process no matter how the request arrives. local MIN_GAP_MS = 2000 local lastStart = 0 -local function stopPolling() - clearPending = false - noctalia.state.set("polling", false) - noctalia.setUpdateInterval(intervalMs()) -end - local function refresh() if inFlight then return end local now = noctalia.nowMs() if now - lastStart < MIN_GAP_MS then return end lastStart = now inFlight = true - busyUntil = now + MIN_BUSY_MS - clearPending = false noctalia.state.set("polling", true) - noctalia.setUpdateInterval(120) local started = noctalia.runAsync(COMMAND, function(result) inFlight = false - if noctalia.nowMs() >= busyUntil then - stopPolling() - else - clearPending = true - end + noctalia.state.set("polling", false) local decoded = result ~= nil and noctalia.json.decode(result.stdout or "") or nil if type(decoded) == "table" and type(decoded.entries) == "table" then @@ -172,8 +152,8 @@ local function refresh() -- sit in flight forever and stop asking. if not started then inFlight = false + noctalia.state.set("polling", false) noctalia.state.set("error", failure("spawn_failed")) - stopPolling() end end @@ -183,13 +163,8 @@ noctalia.state.watch("command", function(value) end) function update() - -- While a read is in flight the fast tick is the busy timer, not a poll. + -- A read in flight answers on its own callback; the tick only starts them. if inFlight then return end - if clearPending then - if noctalia.nowMs() >= busyUntil then stopPolling() end - return - end - noctalia.setUpdateInterval(intervalMs()) refresh() end diff --git a/ai-usagebar/shared.luau b/ai-usagebar/shared.luau new file mode 100644 index 00000000..7130e6cf --- /dev/null +++ b/ai-usagebar/shared.luau @@ -0,0 +1,112 @@ +--!nonstrict +-- What the capsule and the panel both need. +-- +-- One copy, so the ISO parsing, the severity tiers and the provider glyphs +-- cannot drift between two entries that have to agree with each other on +-- screen. Needs plugin_api 22, which is where require() arrived. + +local M = {} + +-- Tabler has no Anthropic mark, so providers without a brand glyph get a +-- semantic one. Same approach the other CLI-backed meters in this repo take. +M.GLYPHS = { + anthropic = "asterisk-simple", + anthropic_api = "asterisk-simple", + openai = "brand-openai", + zai = "bolt", + openrouter = "route", + deepseek = "fish", + kimi = "moon", + moonshot = "moon", + kilo = "robot", + novita = "cloud", + grok = "brand-x", + supergrok = "brand-x", + antigravity = "sparkles", + cursor = "cursor-text", + minimax = "wave-square", + kiro = "ghost", + copilot = "brand-github-copilot", + gemini = "brand-google", +} + +-- The poller names the failure, so a subscriber only ever has a code to +-- translate. Anything else in that state slot reads as no failure at all. +M.NO_FAILURE = { code = "", detail = "" } + +function M.asFailure(value) + return type(value) == "table" and value or M.NO_FAILURE +end + +-- "2026-08-15T11:29:59.872624Z" -> unix seconds. The stamps are UTC, so the +-- naive os.time() reading (which assumes local time) is corrected by the local +-- offset measured at that same instant. +function M.parseIso(value) + if type(value) ~= "string" then return nil end + local y, mo, d, h, mi, s = value:match("^(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)") + if y == nil then return nil end + local asLocal = os.time({ + year = tonumber(y), month = tonumber(mo), day = tonumber(d), + hour = tonumber(h), min = tonumber(mi), sec = tonumber(s), + }) + local utcAsLocal = os.time(os.date("!*t", asLocal)) + return asLocal + (asLocal - utcAsLocal) +end + +function M.formatDuration(seconds) + if seconds <= 0 then return noctalia.tr("ui.now") end + local minutes = math.floor(seconds / 60) + local days = math.floor(minutes / 1440) + local hours = math.floor((minutes % 1440) / 60) + local rest = minutes % 60 + if days > 0 then return string.format("%dd %dh", days, hours) end + if hours > 0 then return string.format("%dh %dm", hours, rest) end + return string.format("%dm", rest) +end + +-- How long the window this section describes has left. +function M.countdown(section) + local at = M.parseIso(section and section.reset_at) + if at == nil then return "" end + return M.formatDuration(at - os.time()) +end + +-- The clock time the countdown lands on: "14:20" today, "Sat 14:20" past +-- midnight, and a date once a weekday alone stops naming one day. +function M.resetClock(section) + local at = M.parseIso(section and section.reset_at) + if at == nil then return "" end + local clock = noctalia.formatTime(noctalia.timeFormat(), at) + if os.date("%Y-%m-%d", at) == os.date("%Y-%m-%d") then return clock end + -- The weekday is prepended here instead of folded into the pattern: the + -- host's format grammar passes unknown text through verbatim, so a "ddd" + -- prefix would render as the literal word. + if at - os.time() > 6 * 86400 then return os.date("%d %b", at) .. " " .. clock end + return os.date("%a", at) .. " " .. clock +end + +-- A provider can report more than it was given, so the reading is clamped +-- before it becomes a bar width. +function M.ratio(percent) + local value = (tonumber(percent) or 0) / 100 + if value < 0 then return 0 end + if value > 1 then return 1 end + return value +end + +function M.headline(entry) + if type(entry) ~= "table" or type(entry.metrics) ~= "table" then return nil end + return entry.metrics[1] +end + +-- The CLI tiers every percentage; copying its thresholds here would be a second +-- source of truth. `calm` is what to use when it has raised nothing: text stays +-- on the surface colour, and the accent is kept for bar fills. +function M.severityRole(section, calm) + local severity = tostring(section and section.severity or "") + if severity == "critical" then return "error" end + if severity == "high" then return "tertiary" end + return calm +end + +return M From 18e613066d8b725c1915aa55d0c25660e5e32045 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 20 Aug 2026 09:20:12 -0300 Subject: [PATCH 05/35] ai-usagebar: settings button, v1.3.0 openSettings() needs plugin API 15, so the panel could not offer it while the manifest asked for 9. The move to 22 makes it available, and the panel is where someone is already looking at one provider and deciding the capsule should follow another. The capsule still answers a middle click the same way. The version is 1.3.0 rather than another 1.2.x because asking for API 22 is a compatibility break: on a shell older than that the plugin no longer installs. Requirements says so, since that is the page people read before installing. Dropped the note about reloading the plugin to pick up an edited translation. README.md is the plugin's page on noctalia.dev, written for someone installing it; which files the shell's watcher follows is only of interest to whoever is editing the plugin, and the note prescribed a full disable/enable when touching any .luau entry is enough. --- ai-usagebar/README.md | 17 +++++++---------- ai-usagebar/panel.luau | 9 +++++++++ ai-usagebar/plugin.toml | 2 +- ai-usagebar/translations/en.json | 1 + 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index 5fe25c0c..93ac1519 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -28,6 +28,10 @@ endpoints, and this plugin never sees them. the CLI's project page offered when `ai-usagebar` is not on `PATH`. Without xdg-utils that button is not drawn and the rest of the plugin is unaffected. +The plugin asks for **plugin API 22**, which is where Noctalia gained +`require()`. On a shell older than that it will not install. Version 1.1.0 asked +for API 9 and still runs there. + ## Usage Add `felipeartur/ai-usagebar:bar` to a bar in Settings, Bar. The capsule shows @@ -83,8 +87,9 @@ so a fill that outruns the clock bar means quota is burning ahead of pace. Credit balances and free text rows the CLI reports get rendered as well. Opening the panel asks the CLI for fresh numbers, and the detail pane says how old the reading is. The refresh button in the header asks again; it turns into -a spinner while the CLI is answering. There is no close button: the panel -closes when you click away from it or press the same widget again. +a spinner while the CLI is answering. The gear beside it opens this plugin's +settings. There is no close button: the panel closes when you click away from +it or press the same widget again. The list follows the CLI. A provider that `ai-usagebar` has no credential for never appears, while one that is set up and failing keeps its row and shows the @@ -150,11 +155,3 @@ noctalia msg plugin felipeartur/ai-usagebar:poller all select anthropic - A provider that fails still comes back as an entry with `status = "error"`, so one broken provider does not blank the others. A reading the CLI marks stale keeps showing, flagged in the capsule and in the panel's detail pane. -- The file watcher follows the `.luau` entries only, so the files in - `translations/` are read once, when the plugin loads. Editing a string takes - a reload before the new text shows up: - - ```sh - noctalia msg plugins disable felipeartur/ai-usagebar - noctalia msg plugins enable felipeartur/ai-usagebar - ``` diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 77a4e3dd..2f2559b9 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -387,6 +387,15 @@ local function listPane(entry) enabled = not polling, onClick = requestRefresh, }), + -- The capsule already answers a middle click with this, but the + -- panel is where someone is looking at a provider and deciding the + -- capsule should show a different one. + ui.button({ + glyph = "settings", + variant = "ghost", controlSize = "sm", + tooltip = noctalia.tr("ui.settings"), + onClick = function() noctalia.openSettings() end, + }), }), ui.scroll({ gap = 6, flexGrow = 1 }, rows), }) diff --git a/ai-usagebar/plugin.toml b/ai-usagebar/plugin.toml index 660aadf0..dcce3e5f 100644 --- a/ai-usagebar/plugin.toml +++ b/ai-usagebar/plugin.toml @@ -1,6 +1,6 @@ id = "felipeartur/ai-usagebar" name = "AI Usage" -version = "1.2.0" +version = "1.3.0" plugin_api = 22 author = "felipeartur" license = "MIT" diff --git a/ai-usagebar/translations/en.json b/ai-usagebar/translations/en.json index 4c0f0e98..f5a5074f 100644 --- a/ai-usagebar/translations/en.json +++ b/ai-usagebar/translations/en.json @@ -83,6 +83,7 @@ "now": "now", "refresh": "Refresh now", "retry": "Try again", + "settings": "Plugin settings", "severity": { "critical": "critical", "high": "high" From 36521867153f596f3e3d188413fabdf766fce29b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 21 Aug 2026 01:31:38 -0300 Subject: [PATCH 06/35] ai-usagebar: keep the redaction inside the callback's CPU budget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the panel rework the poller has been losing every read: the async callback overran its CPU budget partway through scrubbing the report, so `state.set("report", ...)` never ran and the capsule sat on nothing. The shell named the line each time, always inside safeText. Three things made it expensive, and the report itself is not big — 165 strings for a two-vendor read. The four keyword pattern pairs were concatenated on every call, so they were rebuilt 165 times per report. They are constants; they are now built once, at load. The gate was one test for all four keywords, so a string carrying "key" — which is most of what an AI usage CLI writes about, along with "tokens" — ran all eight substitutions instead of the two belonging to its own keyword. Each keyword now opens only its own pair. The 200-character cap ran after the redaction rather than before it, which left the patterns scanning a runaway line in full. Capping first bounds their work by what the plugin was going to draw anyway; a secret past the cut is not truncated into view, it is gone with the rest of the line. Measured against a real `usage --json`: 0.676 ms down to 0.393 ms for the whole report, and 6.44 ms down to 0.37 ms for a 4.4 KB line. No budget overruns in eleven cycles on the running shell, against one on nearly every cycle before. --- ai-usagebar/service.luau | 51 +++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/ai-usagebar/service.luau b/ai-usagebar/service.luau index 8effb5da..62ed6284 100644 --- a/ai-usagebar/service.luau +++ b/ai-usagebar/service.luau @@ -20,12 +20,21 @@ end -- A secret's value runs until whitespace or the quote or brace that closes it, -- so a JSON field loses its value and keeps its punctuation. local SECRET_VALUE = "[^%s\"',}]+" -local SECRET_WORDS = { - "[Kk][Ee][Yy]", - "[Tt][Oo][Kk][Ee][Nn]", - "[Ss][Ee][Cc][Rr][Ee][Tt]", - "[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd]", -} +-- Built once. `scrub` calls safeText on every string in the report — ~165 for a +-- two-vendor read — and rebuilding these four pattern pairs on each of those +-- calls cost more than matching them did. +local SECRET_PATTERNS = {} +for _, word in ipairs({ "key", "token", "secret", "password" }) do + local anyCase = (word:gsub("%a", function(c) return "[" .. c:upper() .. c .. "]" end)) + local name = "[%w_%-]*" .. anyCase .. "[%w_%-]*" + SECRET_PATTERNS[#SECRET_PATTERNS + 1] = { + word = word, + -- name=value: a query string or a shell assignment. + assign = "(" .. name .. "=)" .. SECRET_VALUE, + -- name: value: an HTTP header or a JSON field. + colon = "(" .. name .. "\"?%s*:%s*\"?)" .. SECRET_VALUE, + } +end -- Nine characters before the rest of a provider key, so a bare "sk-" in prose -- is not mistaken for one. local KEY_TAIL = string.rep("[%w_%-]", 9) @@ -34,22 +43,23 @@ local function safeText(value) local text = noctalia.string.trim(tostring(value or "")) text = text:gsub("%s+", " ") - -- `scrub` runs this over ~165 strings per two-vendor read, and backtracking - -- patterns on every one of them exhaust the callback's CPU budget, which - -- costs the whole report. So nothing expensive runs until a literal search - -- says it could match. The keyword is what opens the gate. A separator will - -- not do: `=` and `:` both turn up in ordinary readings, in a ratio, a clock - -- time, a URL, so gating on those ran the patterns over almost every string. + -- Capped before the redaction runs rather than after. The patterns are the + -- expensive part of the callback, and a callback that overruns its CPU + -- budget loses the whole report — so the work they do is bounded by what + -- the plugin would draw anyway. + if #text > 200 then text = string.sub(text, 1, 200) .. "..." end + + -- Nothing expensive runs until a literal search says it could match, and + -- each keyword opens only its own two patterns. The keyword is what opens + -- the gate. A separator will not do: `=` and `:` both turn up in ordinary + -- readings, in a ratio, a clock time, a URL, so gating on those ran the + -- patterns over almost every string. local lower = text:lower() - if lower:find("key", 1, true) or lower:find("token", 1, true) - or lower:find("secret", 1, true) or lower:find("password", 1, true) then - for _, word in ipairs(SECRET_WORDS) do - local name = "[%w_%-]*" .. word .. "[%w_%-]*" - -- name=value: a query string or a shell assignment. - text = text:gsub("(" .. name .. "=)" .. SECRET_VALUE, "%1") - -- name: value: an HTTP header or a JSON field. - text = text:gsub("(" .. name .. "\"?%s*:%s*\"?)" .. SECRET_VALUE, "%1") + for _, secret in ipairs(SECRET_PATTERNS) do + if lower:find(secret.word, 1, true) then + text = text:gsub(secret.assign, "%1") + text = text:gsub(secret.colon, "%1") end end @@ -68,7 +78,6 @@ local function safeText(value) text = text:gsub("%f[%w](sk%-)" .. KEY_TAIL .. "[%w_%-]*", "%1") end - if #text > 200 then text = string.sub(text, 1, 200) .. "..." end return text end From 4cf6a84f05e5da0d9797982d9abaf522191d4d1a Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 21 Aug 2026 01:43:45 -0300 Subject: [PATCH 07/35] ai-usagebar: put a CPU budget under the redaction test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test covered what the scrubber redacts and what it leaves alone, which is why the rewrite that just landed could be checked at all. It did not cover what the scrubber costs, which is the half that broke: the output was correct on every string right up to the point the shell killed the callback for overrunning its budget, and a correct answer nobody receives is not one. So the test now scrubs a report shaped like a real `usage --json` — four vendors, six metrics each, and the credential error the CLI writes for a provider it has no key for, which is the string that opens the redaction patterns on an otherwise healthy run — and asserts what that costs. The meter is `string.gsub`, wrapped for the length of the call. Counting VM instructions the way keymap's budget tests do reads nothing useful here: the work happens inside the C matcher, where the count hook is blind, and the old scrubber and the new one came out one block apart. What separates them is how much text the patterns are handed: 37352 bytes for this report before, 17664 now. The ceiling sits between the two, near enough that either half of the regression trips it on its own. The slice the test loads was widened to take `scrub` along with `safeText`, so the recursion over the report is measured rather than assumed, and README gained the section that says how to run it, as keymap and udiskie do. --- ai-usagebar/README.md | 16 ++++++ ai-usagebar/tests/scrub_test.lua | 89 +++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index 93ac1519..757fb07f 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -155,3 +155,19 @@ noctalia msg plugin felipeartur/ai-usagebar:poller all select anthropic - A provider that fails still comes back as an entry with `status = "error"`, so one broken provider does not blank the others. A reading the CLI marks stale keeps showing, flagged in the capsule and in the panel's detail pane. + +## Tests + +The redaction that stands between the CLI's output and the screen is the one +part of this plugin worth a test, so it has one. From the `ai-usagebar` +directory: + +```sh +lua tests/scrub_test.lua +``` + +It reads `safeText` and `scrub` out of `service.luau` rather than copying them, +and checks three things: that a set of real credential shapes never survive, that +ordinary readings pass through unchanged, and that scrubbing a four-vendor report +stays inside the CPU budget the poller's async callback is given — an overrun +there loses the whole reading, not just time. diff --git a/ai-usagebar/tests/scrub_test.lua b/ai-usagebar/tests/scrub_test.lua index f7932de0..0bfe8237 100644 --- a/ai-usagebar/tests/scrub_test.lua +++ b/ai-usagebar/tests/scrub_test.lua @@ -4,7 +4,7 @@ -- request tends to quote the request. safeText is the only thing standing -- between that and a rendered label, so it gets a test. -- --- The function is read out of service.luau rather than copied here: a copy +-- The functions are read out of service.luau rather than copied here: a copy -- would keep passing after the real one changed. -- -- lua tests/scrub_test.lua (or luajit) @@ -21,8 +21,9 @@ local function loadSafeText() local source = file:read("*a") file:close() - -- The slice runs from the redaction constants to the end of the function. - local chunk = source:match("(local SECRET_VALUE.-\nend)\n") + -- The slice runs from the redaction constants through scrub, which is what + -- the poller's callback actually calls. + local chunk = source:match("(local SECRET_VALUE.-)\nlocal function failure") if chunk == nil then error("could not find safeText in " .. SOURCE .. "; update the markers here") end @@ -31,14 +32,16 @@ local function loadSafeText() local env = { string = string, ipairs = ipairs, + pairs = pairs, + type = type, tostring = tostring, noctalia = { string = { trim = function(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end } }, } - local loaded = load(chunk .. "\nreturn safeText", "safeText", "t", env) + local loaded = load(chunk .. "\nreturn safeText, scrub", "scrubber", "t", env) return loaded() end -local safeText = loadSafeText() +local safeText, scrub = loadSafeText() -- Each case names the material that must not survive. local SECRETS = { @@ -53,6 +56,9 @@ local SECRETS = { { "authorization: bearer sk-ant-api03-REALKEY", "REALKEY" }, { "OPENAI_API_KEY sk-proj-REALKEYVALUE not accepted", "REALKEY" }, { "password=hunter2", "hunter2" }, + -- The cap runs before the patterns, so a secret in a runaway line has to + -- survive being truncated around. + { "api_key=sk-ant-REALKEY123 " .. string.rep("noise ", 60), "REALKEY123" }, } -- Readings the plugin draws every minute. A scrubber that eats these is worse @@ -100,9 +106,80 @@ if #long > 210 then fail("long text was not capped: " .. #long .. " characters") end +-- The poller scrubs the whole report inside one async callback, and a callback +-- that overruns its CPU budget is killed by the shell: the reading is lost, not +-- merely late. So the cost of a scrub is asserted, not just its output. +-- +-- The meter is `string.gsub`: every pattern in safeText runs through it, and the +-- work happens inside the C matcher, where an instruction-count hook sees +-- nothing. Counting the calls and the bytes handed to them measures the two +-- things that made the 1.2.0 scrubber overrun — a keyword opening all four +-- keywords' substitutions, and the length cap running after them instead of +-- before. +-- +-- The report below is the shape of a real `usage --json`: four vendors, six +-- metrics each, and the credential error the CLI writes for a provider it has no +-- key for — the string that opens the redaction patterns on an otherwise healthy +-- run. +local function sampleReport() + local entries = {} + for _, vendor in ipairs({ "anthropic", "openai", "zai", "openrouter" }) do + local metrics = {} + for index = 1, 6 do + metrics[index] = { + label = "Session (5h)", + value = "62% of monthly limit consumed", + detail = "Resets in 4h 01m at 12:40", + reset_at = "2026-08-20T11:29:59.872624Z", + severity = "normal", + percent = 62, + } + end + entries[#entries + 1] = { + id = vendor, + name = vendor, + display_name = "Claude Pro", + plan = "Claude Pro", + status = "ok", + stale = false, + fetched_at = "2026-08-20T11:29:59.872624Z", + metrics = metrics, + sections = { { type = "session" }, { type = "weekly" } }, + error = "credentials error: " .. vendor .. ": no API key. Either set an API key in a" + .. " valid environment variable or set `api_key` under [" .. vendor .. "] in the" + .. " config file. " .. string.rep("Retry later. ", 40), + } + end + return { entries = entries } +end + +local calls, bytes = 0, 0 +local realGsub = string.gsub +string.gsub = function(subject, ...) + calls = calls + 1 + bytes = bytes + #subject + return realGsub(subject, ...) +end +scrub(sampleReport()) +string.gsub = realGsub + +-- Bytes, not calls: the count barely moves, because normalising whitespace is one +-- gsub per string either way. What moved is how much text the redaction patterns +-- were handed — 37352 bytes for this report in 1.2.0, against 17664 now. The +-- ceiling sits between the two, close enough that widening the gate back to all +-- four keywords at once (22400) trips it as surely as putting the length cap back +-- after the patterns (37352) does. +local MAX_BYTES = 20000 +if bytes > MAX_BYTES then + fail("the redaction patterns were handed " .. bytes .. " bytes of a four-vendor report" + .. " in " .. calls .. " gsub calls, past the " .. MAX_BYTES .. " bytes this callback" + .. " budgets for") +end + if failures > 0 then io.write(failures, " failure(s)\n") os.exit(1) end -io.write("ok: ", #SECRETS, " secrets redacted, ", #BENIGN, " readings untouched, length capped\n") +io.write("ok: ", #SECRETS, " secrets redacted, ", #BENIGN, " readings untouched, length capped, ", + calls, " gsub calls over ", bytes, " bytes per report\n") From bd7169ddb025c1a9bd7176cde32c65c737dbdccd Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 21 Aug 2026 01:52:40 -0300 Subject: [PATCH 08/35] ai-usagebar: rank mid severity, share the detail parser, cut the comments Three passes over the plugin. The CLI tiers severity as low, mid, high and critical. The capsule's rank table answered "medium", which nothing ever sends, so a mid provider sorted level with a low one and "auto" could put the calmer plan on the bar. The table now keys on what the CLI actually writes, and drops the two rows that were already the default. `elapsedPercent` was parsed the same way in both entries. It belongs with the other shared readings, and the capsule's copy of `parseIso` was left over from before the split. The panel's pace lookup had a branch that returned exactly what the branch under it returns. The rest is prose. The comments had grown into an argument for each decision rather than a note about it, and the argument is what a reader has to skip to reach the fact. What survives is what the code cannot say for itself: why the patterns are built once, why the cap runs before them, why the title block is wrapped in a row, why a row is keyed, why status 127 has to agree with its message. The rest went, along with the em dashes; the ones left are the "no reading" placeholder the panel and the capsule both draw. No behaviour changed beyond the severity rank. Verified against the running shell: eleven cycles, no errors, no budget overruns. --- ai-usagebar/README.md | 13 ++- ai-usagebar/bar.luau | 65 ++++++--------- ai-usagebar/panel.luau | 137 ++++++++++++------------------- ai-usagebar/service.luau | 79 ++++++++---------- ai-usagebar/shared.luau | 47 ++++++----- ai-usagebar/tests/scrub_test.lua | 48 +++++------ 6 files changed, 161 insertions(+), 228 deletions(-) diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index 757fb07f..6ba749c5 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -158,16 +158,15 @@ noctalia msg plugin felipeartur/ai-usagebar:poller all select anthropic ## Tests -The redaction that stands between the CLI's output and the screen is the one -part of this plugin worth a test, so it has one. From the `ai-usagebar` -directory: +Everything the CLI prints is redacted on its way to the screen, and that is the +part worth a test. From the `ai-usagebar` directory: ```sh lua tests/scrub_test.lua ``` It reads `safeText` and `scrub` out of `service.luau` rather than copying them, -and checks three things: that a set of real credential shapes never survive, that -ordinary readings pass through unchanged, and that scrubbing a four-vendor report -stays inside the CPU budget the poller's async callback is given — an overrun -there loses the whole reading, not just time. +then checks that real credential shapes never survive, that ordinary readings +pass through unchanged, and that scrubbing a four-vendor report stays inside the +CPU budget the poller's async callback is given. An overrun there loses the whole +reading, not just time. diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index ec96c409..7c1bfb45 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -1,8 +1,7 @@ --!nonstrict --- Bar capsule. Reads whatever the poller published and draws one provider, or --- the busiest few when `provider_limit` is raised. --- --- Per-instance settings, so two capsules can follow two different providers. +-- Bar capsule. Draws what the poller published: one provider, or the busiest few +-- when `provider_limit` is raised. Settings are per-instance, so a second capsule +-- can follow a second provider. local vendor = tostring(noctalia.getConfig("vendor") or "auto") local style = tostring(noctalia.getConfig("style") or "pill") @@ -15,22 +14,15 @@ local report = nil local polling = false local shared = require("./shared.luau") -local GLYPHS, parseIso = shared.GLYPHS, shared.parseIso +local GLYPHS = shared.GLYPHS local countdown, resetClock = shared.countdown, shared.resetClock -local ratio, headline = shared.ratio, shared.headline +local ratio, headline, elapsedPercent = shared.ratio, shared.headline, shared.elapsedPercent local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure local failure = NO_FAILURE -- ── Report helpers ──────────────────────────────────────────────────────────── --- "Resets in 4h 01m · 19% elapsed · 2pts ahead" says how much of the window is --- gone and how far the spend is from that line. -local function elapsedPercent(metric) - local value = tostring(metric and metric.detail or ""):match("(%d+)%%%s*elapsed") - return value ~= nil and tonumber(value) or nil -end - -- Returns points and direction: 2, "ahead" is burning faster than the clock. local function pace(metric) local points, word = tostring(metric and metric.detail or ""):match("(%d+)pts%s+(%a+)") @@ -43,7 +35,7 @@ local function entries() return report.entries end -local SEVERITY_RANK = { critical = 3, high = 2, medium = 1, low = 0 } +local SEVERITY_RANK = { critical = 3, high = 2, mid = 1 } local function rank(entry) local metric = headline(entry) @@ -51,8 +43,9 @@ local function rank(entry) return SEVERITY_RANK[tostring(metric.severity or "")] or 0, tonumber(metric.percent) or 0 end --- A pinned vendor shows only itself. "auto" shows the busiest providers, so --- the one closest to running out is the one on the bar. `primary` breaks ties. +-- A pinned vendor shows only itself. "auto" ranks by severity then percentage, +-- so the provider closest to running out is the one on the bar. `primary` breaks +-- ties. local function shown() local all = entries() if vendor ~= "auto" then @@ -80,16 +73,12 @@ local function shown() local picked = {} for i = 1, math.min(limit, #ready) do picked[i] = ready[i] end if #picked == 0 then return {}, 0 end - -- Someone who asked for one provider does not need a count of the others, - -- so the "+N" only appears once the capsule carries more than one. if limit == 1 then return picked, 0 end return picked, #ready - #picked end --- `calm` is the colour when the CLI has raised nothing. With the tint switched --- off it is the colour for everything. --- `calm` is the colour when the CLI has raised nothing. With the tint switched --- off it is the colour for everything. +-- `calm` is the colour when the CLI has raised nothing, and every colour when +-- the tint is switched off. local function severityRole(metric, calm) if not colorByUsage then return calm end return shared.severityRole(metric, calm) @@ -97,13 +86,14 @@ end local function shortName(entry) local name = tostring(entry.display_name or entry.name or entry.id or "") - -- "Claude · gmail" is the panel's business; the bar has room for the product. + -- "Claude · gmail" is the panel's business; the bar only has room for the + -- product name. return (name:gsub("%s*·.*$", "")) end -- ── Rendering ───────────────────────────────────────────────────────────────── --- Quota above, window elapsed below: a fill longer than the clock bar is spend +-- Quota above, window elapsed below: a longer fill than clock bar is spend -- running ahead of time. local function bars(percent, elapsed, tint, width) local stack = { @@ -137,8 +127,6 @@ local function countdownNode(metric) return ui.label({ text = left, fontSize = 10, color = "on_surface_variant", maxLines = 1 }) end --- One provider's chip. The style decides the shape, and the extras are --- appended to whatever it produced. local function chip(entry) local metric = headline(entry) local tint = severityRole(metric, "on_surface") @@ -146,8 +134,8 @@ local function chip(entry) local percent = metric ~= nil and tonumber(metric.percent) or nil local text = percent ~= nil and string.format("%d%%", percent) or "—" local glyph = ui.glyph({ name = GLYPHS[tostring(entry.id)] or "brain", size = 13, color = tint }) - -- Right-aligned in a fixed column, so the capsule is the same width at 9% - -- as at 100% and stops nudging its neighbours on the bar once per read. + -- Fixed width, right-aligned: the capsule is the same size at 9% as at 100% + -- and stops nudging its neighbours once per read. local pct = ui.label({ text = text, fontSize = 11, fontWeight = "semibold", color = tint, maxLines = 1, width = 30, textAlign = "end" }) local name = showName and ui.label({ text = shortName(entry), fontSize = 11, @@ -157,7 +145,6 @@ local function chip(entry) local function add(node) if node ~= nil then nodes[#nodes + 1] = node end end if style == "meter" and percent ~= nil then - -- Five ticks instead of digits: the reading at a glance, no numbers. local ticks = {} for i = 0, 4 do ticks[#ticks + 1] = ui.box({ @@ -168,18 +155,17 @@ local function chip(entry) add(glyph); add(name) add(ui.row({ gap = 2, align = "center" }, ticks)) elseif style == "label" and percent ~= nil then - -- Name and number stacked over the bar, for a bar with room to spare. add(glyph) add(ui.column({ gap = 1, align = "center" }, { ui.row({ gap = 3, align = "center" }, { ui.label({ text = shortName(entry), fontSize = 10, color = "on_surface_variant", maxLines = 1 }), pct, }), - bars(percent, elapsedPercent(metric), fill, 44), + bars(percent, elapsedPercent(metric and metric.detail), fill, 44), })) elseif style == "gauge" and percent ~= nil then add(glyph); add(name) - add(bars(percent, elapsedPercent(metric), fill, 26)) + add(bars(percent, elapsedPercent(metric and metric.detail), fill, 26)) add(pct) else add(glyph); add(name); add(pct) @@ -243,9 +229,9 @@ end local function render() local picked, hidden = shown() - -- A failure drops the reading here too, so the bar cannot be read as a - -- live percentage while the panel behind it says the CLI is unreachable. - -- Empty is already the shape that draws the alert glyph. + -- A failure drops the reading, so the capsule cannot show a live percentage + -- while the panel behind it says the CLI is unreachable. Empty already draws + -- the alert glyph. if failure.code ~= "" then picked, hidden = {}, 0 end local children = {} @@ -254,9 +240,8 @@ local function render() end if #children == 0 then - -- One glyph, coloured by the state. A second icon beside it reads as a - -- second problem, and the plugin's own mark in the error colour says - -- the same thing in the space of one. + -- One glyph, coloured by the state. A second icon beside it would read as + -- a second problem. children[1] = ui.glyph({ name = "brain", size = 13, color = failure.code ~= "" and "error" or "on_surface_variant", @@ -266,8 +251,8 @@ local function render() color = "on_surface_variant", maxLines = 1 }) end - -- A read in flight dims the capsule rather than appending a spinner to it: - -- a node that comes and goes every cycle shoves every widget to its right. + -- A read in flight dims the capsule instead of appending a spinner: a node + -- that comes and goes every cycle shoves every widget to its right. barWidget.render(ui.row({ gap = 6, align = "center", opacity = polling and 0.55 or 1 }, children)) barWidget.setTooltip(tooltip(picked, hidden)) end diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 2f2559b9..26f2a2a8 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -1,8 +1,6 @@ --!nonstrict --- Expanded panel for one provider. --- --- It renders `sections[]`, which is the CLI's lossless view, so credit blocks --- and free text that the shorter `metrics[]` view drops still show up. +-- Expanded panel for one provider. It renders `sections[]`, the CLI's lossless +-- view, so the credit blocks and free text that `metrics[]` drops still show up. local report = nil local polling = false @@ -11,17 +9,18 @@ local shared = require("./shared.luau") local GLYPHS, parseIso = shared.GLYPHS, shared.parseIso local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, severityRole = shared.ratio, shared.headline, shared.severityRole +local elapsedPercent = shared.elapsedPercent local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure local failure = NO_FAILURE --- Read once: a session either has xdg-utils or it does not, and the panel --- would otherwise stat PATH on every second tick it spends in a failure. +-- Read once: a session either has xdg-utils or it does not, and the panel would +-- otherwise stat PATH on every second tick it spends in a failure. local HAS_OPENER = noctalia.commandExists("xdg-open") --- The CLI reports a vendor it has no credential for as a `credentials error`. --- Those are not listed, because they were never set up. A configured provider --- that fails for any other reason keeps its row. +-- A vendor with no credential comes back as a `credentials error`. It was never +-- set up, so it is not listed. A configured provider that fails for any other +-- reason keeps its row. local function configured(entry) if entry.status ~= "error" then return true end return not tostring(entry.error or ""):lower():find("credentials error") @@ -29,12 +28,7 @@ end -- ── Detail line parsing ─────────────────────────────────────────────────────── -- "Resets in 1h 58m · 60% elapsed · 30pts ahead". The reset half is already in --- `reset_at`; what is left is the pace pair. - -local function elapsedPercent(detail) - local value = tostring(detail or ""):match("(%d+)%%%s*elapsed") - return value ~= nil and tonumber(value) or nil -end +-- `reset_at`; what is left is the pace. local function pace(detail) local text = tostring(detail or "") @@ -43,9 +37,8 @@ local function pace(detail) for part in text:gmatch("[^·]+") do last = part end last = noctalia.string.trim(last) if last:find("elapsed") then return "", "on_surface_variant" end - -- Ahead of the clock is worth flagging. Under it means there is room left. + -- Ahead of the clock is worth flagging; under it means there is room left. if last:find("ahead") then return last, "tertiary" end - if last:find("under") then return last, "on_surface_variant" end return last, "on_surface_variant" end @@ -93,8 +86,6 @@ end -- ── Cards ───────────────────────────────────────────────────────────────────── --- A severity word, and only when the CLI raised one. Colour on its own leaves --- the reading to anyone who can tell the two accents apart. local function severityWord(section) local severity = tostring(section and section.severity or "") if severity ~= "high" and severity ~= "critical" then return nil end @@ -122,8 +113,7 @@ local function metricCard(section) if showValue then header[#header + 1] = ui.label({ text = value, fontSize = 11, color = "on_surface_variant", maxLines = 1 }) end - -- Every card ends on the same right edge, so a column of them reads as one - -- ruler instead of a ragged margin. + -- Fixed width, so a column of cards ends on one right edge. header[#header + 1] = ui.label({ text = string.format("%d%%", percent), fontSize = 15, @@ -138,8 +128,6 @@ local function metricCard(section) ui.progress({ progress = ratio(percent), fill = fill, track = "on_surface/0.16", radius = 3, height = 6 }), } - -- Two readings: quota spent above, window elapsed below. A shorter clock bar - -- than fill bar is quota burning ahead of time. local elapsed = elapsedPercent(section.detail) if elapsed ~= nil then body[#body + 1] = ui.progress({ @@ -151,9 +139,6 @@ local function metricCard(section) }) end - -- One line under the bars carries the whole time story: what is left of the - -- window, when it lands, how much of it is gone, and whether the spend is - -- running ahead. Four separate lines said the same thing four times taller. local left = countdown(section) local clock = resetClock(section) local paceText, paceColor = pace(section.detail) @@ -162,9 +147,8 @@ local function metricCard(section) footer[#footer + 1] = ui.glyph({ name = "clock", size = 12, color = "on_surface_variant" }) footer[#footer + 1] = ui.label({ text = left, fontSize = 11, color = "on_surface_variant" }) if clock ~= "" then - -- Parenthesised and muted: it is the time the countdown beside it - -- lands on, not a reading of its own. In the accent colour it was - -- the loudest thing in the card after the percentage. + -- Parenthesised and muted: it is where the countdown beside it lands, + -- not a reading of its own. footer[#footer + 1] = ui.label({ text = "(" .. clock .. ")", fontSize = 11, color = "on_surface_variant", }) @@ -205,9 +189,8 @@ local function blockCard(section) } for _, line in ipairs(section.body or {}) do local text = noctalia.string.trim(tostring(line)) - -- A line the CLI left as a bare "balance:" reads as a row that failed - -- to render. Nothing is a value, and it is spelled the same way here as - -- it is everywhere else in the panel. + -- A bare "balance:" from the CLI would read as a row that failed to + -- render, so nothing gets spelled the way it is everywhere else. if text:find(":$") then text = text .. " —" end body[#body + 1] = ui.label({ text = text ~= "" and text or "—", @@ -234,9 +217,8 @@ local function providerRow(entry, selected) local broken = entry.status == "error" local tint = severityRole(metric, "on_surface") - -- The reading keeps its own severity colour whether or not the row is - -- selected. A selected row that recolours its number hides the one thing - -- the list exists to compare. + -- The reading keeps its severity colour whether or not the row is selected: + -- recolouring it hides the one thing the list exists to compare. local right if broken then right = ui.row({ width = 34, justify = "end" }, { @@ -271,16 +253,13 @@ local function providerRow(entry, selected) }) return ui.row({ - -- Keyed, so the click handler survives the second tick the countdowns - -- ride on rather than being rebuilt under the pointer once a second. + -- Keyed, so the click handler survives the second tick the countdowns ride + -- on instead of being rebuilt under the pointer. key = "provider-" .. tostring(entry.id), gap = 8, align = "center", padding = 8, radius = 8, - -- Selection is a tint, not a slab of accent: a filled `primary` row has - -- to invert every colour inside it, and then it shouts over the reading. + -- A tint, not a slab: a filled `primary` row inverts every colour in it. fill = selected and "primary/0.14" or "surface_variant/0.45", onClick = function() - -- currentEntry() reads this back, so the panel and the capsule that - -- opened it stay on the same provider. noctalia.state.set("selected", tostring(entry.id)) render() end, @@ -298,8 +277,7 @@ local function requestRefresh() noctalia.state.set("command", { action = "refresh", at = os.time() }) end --- The failure and the suggested fix read first; the CLI's own words come last --- and smallest, where a bug report can still quote them. +-- The CLI's own words come last and smallest, where a bug report can quote them. local function errorBlock() local key = "ui.error." .. failure.code local children = { @@ -316,8 +294,6 @@ local function errorBlock() }) end - -- The shell's own button, so a retry here looks like every other retry in - -- Noctalia and follows the user's theme without being told to. local actions = { ui.button({ text = noctalia.tr("ui.retry"), glyph = "refresh", @@ -327,10 +303,10 @@ local function errorBlock() }), } - -- Retrying is pointless until the CLI exists, so that one failure gets the - -- install page as well. The URL is a literal, so there is nothing to quote, - -- and the button is only offered where something can open it. It reads as a - -- label with the address in its tooltip: a raw URL is not a button caption. + -- Retrying is pointless until the CLI exists, so that failure gets the install + -- page too. The URL is a literal, and the button is only offered where + -- something can open it. The address lives in the tooltip: a raw URL is not a + -- button caption. if failure.code == "not_installed" and HAS_OPENER then actions[#actions + 1] = ui.button({ text = noctalia.tr("ui.install"), glyph = "external-link", @@ -346,9 +322,8 @@ local function errorBlock() return ui.column({ gap = 8 }, children) end --- A muted stand-in at the shape of what is coming, so a cold read is not a --- spinner parked where the content is about to land. One shape serves both --- panes: it is a placeholder, and two kinds of placeholder is one too many. +-- A muted stand-in shaped like what is coming, so a cold read is not a spinner +-- parked where the content will land. One shape serves both panes. local function skeleton(key) return ui.column({ key = "skeleton-" .. key, @@ -373,13 +348,12 @@ local function listPane(entry) return ui.column({ gap = 10, padding = 14, width = 250 }, { ui.row({ gap = 8, align = "center" }, { ui.glyph({ name = "brain", size = 18, color = "primary" }), - -- The accent belongs to the selection and the bars. A title that - -- takes it too leaves the panel with no quiet level to fall back to. + -- The accent belongs to the selection and the bars. A title that took + -- it too would leave the panel with no quiet level. ui.label({ text = noctalia.tr("ui.title"), fontSize = 15, fontWeight = "bold", color = "on_surface" }), ui.spacer({ flexGrow = 1 }), - -- One slot for the read: the button becomes the spinner while the - -- CLI answers, rather than a second glyph appearing beside it and - -- pushing the header around once a cycle. + -- One slot for the read: the button becomes the spinner while the CLI + -- answers, instead of a second glyph pushing the header around. ui.button({ glyph = polling and "loader-2" or "refresh", variant = "ghost", controlSize = "sm", @@ -387,9 +361,8 @@ local function listPane(entry) enabled = not polling, onClick = requestRefresh, }), - -- The capsule already answers a middle click with this, but the - -- panel is where someone is looking at a provider and deciding the - -- capsule should show a different one. + -- The capsule answers a middle click with this too, but the panel is + -- where someone decides the capsule should follow another provider. ui.button({ glyph = "settings", variant = "ghost", controlSize = "sm", @@ -410,15 +383,14 @@ local function detailPane(entry) if subtitle == title then subtitle = "" end end - -- No entry yet means the skeletons below are the whole pane. A title here - -- would only repeat the one the list pane is already showing. + -- With no entry the skeletons below are the whole pane, and a title here would + -- repeat the list pane's. local children = {} if entry ~= nil then - -- The row keeps the title block honest about its height. A bare - -- ui.column dropped into a column takes the pane's free space for - -- itself, which parks the title at the top of a hundred pixels of - -- nothing and pushes the rest of the header down. Wrapped, the block is - -- only as tall as the two labels in it. + -- The row keeps the title block honest about its height: a bare ui.column + -- dropped into a column claims the pane's free space, parking the title at + -- the top of a hundred pixels of nothing. Wrapped, it is as tall as the two + -- labels in it. children[#children + 1] = ui.row({ gap = 8, align = "center" }, { ui.column({ gap = 0, flexGrow = 1 }, { ui.label({ text = title, fontSize = 15, fontWeight = "bold", @@ -429,9 +401,8 @@ local function detailPane(entry) }) end - -- What the entry says about itself, in words rather than a colour. The - -- provider id and a "ready" status are the plugin talking to itself: the id - -- is the row that was just clicked, and a healthy read is the default. + -- The id and a "ready" status are skipped: the id is the row that was just + -- clicked, and a healthy read is the default. if entry ~= nil then local chips = {} local function separate() @@ -481,8 +452,6 @@ local function detailPane(entry) if #cards > 0 then children[#children + 1] = ui.scroll({ gap = 8, flexGrow = 1 }, cards) elseif entry ~= nil then - -- A provider with nothing to draw says so. Half an empty panel is not - -- an answer to the question the panel was opened to answer. children[#children + 1] = ui.row({ gap = 6, align = "center" }, { ui.glyph({ name = "info-circle", size = 14, color = "on_surface_variant" }), ui.label({ text = noctalia.tr("ui.no_usage"), fontSize = 11, color = "on_surface_variant" }), @@ -499,14 +468,12 @@ end function render() local entry = currentEntry() - -- A failure replaces the report. The numbers are from a read that is no - -- longer happening, and leaving them up puts a provider list and a - -- percentage next to an alert saying neither can be trusted. + -- A failure replaces the report: those numbers came from a read that is no + -- longer happening. if failure.code ~= "" then - -- The panel keeps the fixed size the manifest gives it, and a failure - -- has nowhere near 720x400 of things to say. The block stays bounded to - -- a readable width and sits in the middle of the panel, where an empty - -- surround reads as composition instead of a half-drawn frame. + -- The panel keeps the fixed size the manifest gives it, and a failure has + -- nowhere near 720x400 to say. Bounded to a readable width and centred, the + -- empty surround reads as composition rather than a half-drawn frame. panel.render(ui.column({ flexGrow = 1, padding = 14, align = "center", justify = "center" }, { ui.column({ gap = 10, width = 320 }, { ui.row({ gap = 8, align = "center" }, { @@ -520,10 +487,9 @@ function render() return end - -- Both panes have to be told to fill the panel, or their ui.scroll children - -- ask for their natural height instead of the height they were given: the - -- cards then overflow the panel and the free space is handed to whatever - -- else in the column will take it, which pushes the header away from them. + -- Both panes have to be told to fill the panel, or their ui.scroll children ask + -- for their natural height: the cards overflow, and the free space goes to + -- whatever else in the column will take it. panel.render(ui.row({ gap = 0, flexGrow = 1, align = "stretch" }, { listPane(entry), ui.separator({ orientation = "vertical", color = "outline", opacity = 0.28 }), @@ -552,9 +518,8 @@ noctalia.state.watch("polling", function(value) end) function onOpen(_context) - -- Every open asks for fresh numbers. The CLI answers from its own cache - -- when it has one, and the poller drops requests that arrive too close - -- together, so reopening the panel repeatedly is cheap. + -- Every open asks for fresh numbers. The CLI answers from its own cache when it + -- has one, and the poller drops requests that arrive too close together. noctalia.state.set("command", { action = "refresh", at = os.time() }) report = noctalia.state.get("report") failure = asFailure(noctalia.state.get("error")) diff --git a/ai-usagebar/service.luau b/ai-usagebar/service.luau index 62ed6284..bb87438d 100644 --- a/ai-usagebar/service.luau +++ b/ai-usagebar/service.luau @@ -1,8 +1,7 @@ --!nonstrict --- Headless poller: the single owner of `ai-usagebar usage --json`. --- --- One call returns every configured vendor, so the capsules and the panel are --- pure subscribers of noctalia.state and never spawn a process of their own. +-- Headless poller: the single owner of `ai-usagebar usage --json`. One call +-- returns every configured vendor, so the capsules and the panel are subscribers +-- of noctalia.state and never spawn a process of their own. -- `ai-usagebar` is a declared dependency, so it is expected on PATH. local COMMAND = "ai-usagebar usage --json" @@ -13,16 +12,14 @@ local function intervalMs() return math.floor(minutes * 60 * 1000) end --- Everything the CLI produces ends up on screen, so all of it is cleaned once, --- here, where it enters the plugin: --- an error can quote the request that failed, and a request can carry a key in --- its query string. A runaway line would also push a bar capsule off screen. --- A secret's value runs until whitespace or the quote or brace that closes it, +-- Everything the CLI prints reaches the screen, so it is cleaned here, on the +-- way in: an error can quote the request that failed, and that request can carry +-- a key. A secret's value runs to the whitespace, quote or brace that closes it, -- so a JSON field loses its value and keeps its punctuation. local SECRET_VALUE = "[^%s\"',}]+" --- Built once. `scrub` calls safeText on every string in the report — ~165 for a --- two-vendor read — and rebuilding these four pattern pairs on each of those --- calls cost more than matching them did. +-- Built once: safeText runs on every string in the report, about 165 of them for +-- a two-vendor read, and rebuilding these pairs each time cost more than matching +-- them. local SECRET_PATTERNS = {} for _, word in ipairs({ "key", "token", "secret", "password" }) do local anyCase = (word:gsub("%a", function(c) return "[" .. c:upper() .. c .. "]" end)) @@ -43,17 +40,14 @@ local function safeText(value) local text = noctalia.string.trim(tostring(value or "")) text = text:gsub("%s+", " ") - -- Capped before the redaction runs rather than after. The patterns are the - -- expensive part of the callback, and a callback that overruns its CPU - -- budget loses the whole report — so the work they do is bounded by what - -- the plugin would draw anyway. + -- Capped before the redaction, not after. The patterns are the expensive part + -- of the callback, and a callback that overruns its CPU budget loses the whole + -- report, so they only ever scan what the plugin would draw. if #text > 200 then text = string.sub(text, 1, 200) .. "..." end - -- Nothing expensive runs until a literal search says it could match, and - -- each keyword opens only its own two patterns. The keyword is what opens - -- the gate. A separator will not do: `=` and `:` both turn up in ordinary - -- readings, in a ratio, a clock time, a URL, so gating on those ran the - -- patterns over almost every string. + -- A literal search gates each keyword's own two patterns. Gating on the + -- separator instead does not work: `=` and `:` turn up in ordinary readings, + -- in ratios, clock times and URLs. local lower = text:lower() for _, secret in ipairs(SECRET_PATTERNS) do @@ -72,8 +66,7 @@ local function safeText(value) text = text:gsub("(://)[^%s/@]+:[^%s/@]+(@)", "%1%2") end - -- The provider key shape this plugin sits next to all day. Anchored at a - -- word start, so "desk-top" is not a key. + -- Anchored at a word start, so "desk-top" is not a key. if lower:find("sk-", 1, true) then text = text:gsub("%f[%w](sk%-)" .. KEY_TAIL .. "[%w_%-]*", "%1") end @@ -81,8 +74,8 @@ local function safeText(value) return text end --- Every string in the report, not just the error: a plan name, an account name --- or a metric detail is CLI text too, and any of them can arrive long. +-- Every string, not just the error: a plan name or a metric detail is CLI text +-- too, and any of them can arrive long. local function scrub(value) if type(value) == "string" then return safeText(value) end if type(value) ~= "table" then return value end @@ -90,25 +83,21 @@ local function scrub(value) return value end --- The one place a failure is named. Subscribers translate the code, and the --- CLI's own text travels with it as `detail`, redacted like any other string --- that reaches the screen. +-- The one place a failure is named. Subscribers translate the code; the CLI's +-- own words travel with it as `detail`. local function failure(code, detail) return { code = code, detail = safeText(detail) } end --- The run's outcome as one code. A missing binary is split out from the --- generic failure because the panel can offer an install link for that one, --- and shells report it as one of two messages. Both arrive with status 127, --- which a CLI that merely cannot open its own config file does not use, so the --- code has to agree with the message before the install link is offered. +-- The run's outcome as one code. A missing binary gets its own, because the +-- panel offers an install link for that one. Shells report it as one of two +-- messages, both with status 127, so code and message have to agree. local function classify(result) if result == nil then return failure("spawn_failed") end if result.timedOut then return failure("timed_out") end - -- Matched raw. `failure` scrubs what it is given, and scrubbing first would - -- mean matching against text already capped at 200 characters, so a noisy - -- run could push the message that names the failure out of reach. + -- Matched raw: `failure` scrubs what it is given, and matching after the + -- 200-character cap would let a noisy run push the message out of reach. local stderr = tostring(result.stderr or "") local lower = stderr:lower() if result.exitCode == 127 @@ -117,8 +106,8 @@ local function classify(result) return failure("not_installed", stderr) end if result.exitCode ~= 0 then - -- Whitespace-only stderr scrubs down to nothing, so the exit code has - -- to answer for it rather than a detail that arrives on screen empty. + -- Whitespace-only stderr scrubs down to nothing, so the exit code answers + -- for it instead. return failure("failed", stderr:find("%S") and stderr or ("ai-usagebar exited with code " .. tostring(result.exitCode))) end @@ -127,9 +116,8 @@ end local inFlight = false --- A floor between spawns. Opening the panel asks for a read, and a panel can be --- opened as fast as a pointer can click, so this bounds how often the plugin --- can start a process no matter how the request arrives. +-- A floor between spawns. Opening the panel asks for a read, and a panel opens as +-- fast as a pointer can click. local MIN_GAP_MS = 2000 local lastStart = 0 @@ -147,8 +135,8 @@ local function refresh() local decoded = result ~= nil and noctalia.json.decode(result.stdout or "") or nil if type(decoded) == "table" and type(decoded.entries) == "table" then - -- A vendor that failed still comes back as an entry with `status = - -- "error"`, so a non-zero exit is not a reason to drop the report. + -- A failed vendor still comes back as an entry with `status = "error"`, + -- so a non-zero exit is no reason to drop the report. noctalia.state.set("report", scrub(decoded)) noctalia.state.set("error", failure("")) return @@ -157,8 +145,8 @@ local function refresh() noctalia.state.set("error", classify(result)) end, 30000) - -- A refusal to spawn never calls back, and without this the poller would - -- sit in flight forever and stop asking. + -- A refusal to spawn never calls back, and the poller would sit in flight + -- forever. if not started then inFlight = false noctalia.state.set("polling", false) @@ -166,7 +154,6 @@ local function refresh() end end --- Manual refresh from a capsule or the panel. noctalia.state.watch("command", function(value) if type(value) == "table" and value.action == "refresh" then refresh() end end) diff --git a/ai-usagebar/shared.luau b/ai-usagebar/shared.luau index 7130e6cf..12823f0e 100644 --- a/ai-usagebar/shared.luau +++ b/ai-usagebar/shared.luau @@ -1,14 +1,12 @@ --!nonstrict --- What the capsule and the panel both need. --- --- One copy, so the ISO parsing, the severity tiers and the provider glyphs --- cannot drift between two entries that have to agree with each other on --- screen. Needs plugin_api 22, which is where require() arrived. +-- What the capsule and the panel both need, in one copy, so the ISO parsing and +-- the severity tiers cannot drift between two entries that have to agree on +-- screen. Needs plugin_api 22, where require() arrived. local M = {} --- Tabler has no Anthropic mark, so providers without a brand glyph get a --- semantic one. Same approach the other CLI-backed meters in this repo take. +-- Tabler has no Anthropic mark, so a provider without a brand glyph gets a +-- semantic one. M.GLYPHS = { anthropic = "asterisk-simple", anthropic_api = "asterisk-simple", @@ -30,17 +28,16 @@ M.GLYPHS = { gemini = "brand-google", } --- The poller names the failure, so a subscriber only ever has a code to --- translate. Anything else in that state slot reads as no failure at all. +-- Anything else in the `error` slot means no failure. M.NO_FAILURE = { code = "", detail = "" } function M.asFailure(value) return type(value) == "table" and value or M.NO_FAILURE end --- "2026-08-15T11:29:59.872624Z" -> unix seconds. The stamps are UTC, so the --- naive os.time() reading (which assumes local time) is corrected by the local --- offset measured at that same instant. +-- "2026-08-15T11:29:59.872624Z" -> unix seconds. The stamps are UTC, and +-- os.time() reads its table as local, so the offset is measured at that same +-- instant and added back. function M.parseIso(value) if type(value) ~= "string" then return nil end local y, mo, d, h, mi, s = value:match("^(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)") @@ -71,22 +68,28 @@ function M.countdown(section) return M.formatDuration(at - os.time()) end --- The clock time the countdown lands on: "14:20" today, "Sat 14:20" past --- midnight, and a date once a weekday alone stops naming one day. +-- Where the countdown lands: "14:20" today, "Sat 14:20" past midnight, and a date +-- once a weekday alone stops naming one day. function M.resetClock(section) local at = M.parseIso(section and section.reset_at) if at == nil then return "" end local clock = noctalia.formatTime(noctalia.timeFormat(), at) if os.date("%Y-%m-%d", at) == os.date("%Y-%m-%d") then return clock end - -- The weekday is prepended here instead of folded into the pattern: the - -- host's format grammar passes unknown text through verbatim, so a "ddd" - -- prefix would render as the literal word. + -- Prepended rather than folded into the pattern: the host's format grammar + -- passes unknown text through verbatim, so "ddd" would render as the word. if at - os.time() > 6 * 86400 then return os.date("%d %b", at) .. " " .. clock end return os.date("%a", at) .. " " .. clock end --- A provider can report more than it was given, so the reading is clamped --- before it becomes a bar width. +-- How much of the window is gone, out of "Resets in 1h 58m · 60% elapsed · 30pts +-- ahead". Both entries draw it under the quota bar. +function M.elapsedPercent(detail) + local value = tostring(detail or ""):match("(%d+)%%%s*elapsed") + return value ~= nil and tonumber(value) or nil +end + +-- A provider can report more than it was given, so clamp before this becomes a +-- bar width. function M.ratio(percent) local value = (tonumber(percent) or 0) / 100 if value < 0 then return 0 end @@ -99,9 +102,9 @@ function M.headline(entry) return entry.metrics[1] end --- The CLI tiers every percentage; copying its thresholds here would be a second --- source of truth. `calm` is what to use when it has raised nothing: text stays --- on the surface colour, and the accent is kept for bar fills. +-- The CLI tiers every percentage, and copying its thresholds here would be a +-- second source of truth. `calm` is for when it raised nothing: text stays on the +-- surface colour, and the accent is kept for bar fills. function M.severityRole(section, calm) local severity = tostring(section and section.severity or "") if severity == "critical" then return "error" end diff --git a/ai-usagebar/tests/scrub_test.lua b/ai-usagebar/tests/scrub_test.lua index 0bfe8237..0fca3171 100644 --- a/ai-usagebar/tests/scrub_test.lua +++ b/ai-usagebar/tests/scrub_test.lua @@ -1,15 +1,13 @@ --- Redaction test for service.luau's safeText(). +-- Redaction test for service.luau's safeText() and scrub(). -- --- Everything the CLI writes reaches the screen, and a CLI that fails an HTTP --- request tends to quote the request. safeText is the only thing standing --- between that and a rendered label, so it gets a test. --- --- The functions are read out of service.luau rather than copied here: a copy --- would keep passing after the real one changed. +-- A CLI that fails an HTTP request tends to quote the request, and safeText is +-- the only thing between that and a rendered label. The functions are read out of +-- service.luau rather than copied, so a copy cannot keep passing after the real +-- one changes. -- -- lua tests/scrub_test.lua (or luajit) -- --- Run it from the plugin directory. Exits non-zero on the first failure. +-- Run it from the plugin directory. Exits non-zero if anything fails. local SOURCE = "service.luau" @@ -57,7 +55,7 @@ local SECRETS = { { "OPENAI_API_KEY sk-proj-REALKEYVALUE not accepted", "REALKEY" }, { "password=hunter2", "hunter2" }, -- The cap runs before the patterns, so a secret in a runaway line has to - -- survive being truncated around. + -- survive the truncation. { "api_key=sk-ant-REALKEY123 " .. string.rep("noise ", 60), "REALKEY123" }, } @@ -106,21 +104,17 @@ if #long > 210 then fail("long text was not capped: " .. #long .. " characters") end --- The poller scrubs the whole report inside one async callback, and a callback --- that overruns its CPU budget is killed by the shell: the reading is lost, not --- merely late. So the cost of a scrub is asserted, not just its output. +-- The poller scrubs the whole report inside one async callback, and the shell +-- kills a callback that overruns its CPU budget: the reading is lost, not just +-- late. So the cost is asserted, not only the output. -- --- The meter is `string.gsub`: every pattern in safeText runs through it, and the --- work happens inside the C matcher, where an instruction-count hook sees --- nothing. Counting the calls and the bytes handed to them measures the two --- things that made the 1.2.0 scrubber overrun — a keyword opening all four --- keywords' substitutions, and the length cap running after them instead of --- before. +-- The meter is `string.gsub`, which every pattern in safeText runs through. The +-- work itself happens inside the C matcher, where an instruction-count hook sees +-- nothing, so what gets counted is the calls and the bytes handed to them. -- --- The report below is the shape of a real `usage --json`: four vendors, six +-- The report below has the shape of a real `usage --json`: four vendors, six -- metrics each, and the credential error the CLI writes for a provider it has no --- key for — the string that opens the redaction patterns on an otherwise healthy --- run. +-- key for, which is the string that opens the redaction patterns. local function sampleReport() local entries = {} for _, vendor in ipairs({ "anthropic", "openai", "zai", "openrouter" }) do @@ -163,12 +157,12 @@ end scrub(sampleReport()) string.gsub = realGsub --- Bytes, not calls: the count barely moves, because normalising whitespace is one --- gsub per string either way. What moved is how much text the redaction patterns --- were handed — 37352 bytes for this report in 1.2.0, against 17664 now. The --- ceiling sits between the two, close enough that widening the gate back to all --- four keywords at once (22400) trips it as surely as putting the length cap back --- after the patterns (37352) does. +-- Bytes, not calls: the count barely moves, since normalising whitespace is one +-- gsub per string either way. What moves is how much text the patterns are handed, +-- 37352 bytes for this report before the rewrite against 17664 after. The ceiling +-- sits between the two, near enough that widening the gate back to all four +-- keywords at once (22400) trips it as surely as moving the cap back after the +-- patterns (37352). local MAX_BYTES = 20000 if bytes > MAX_BYTES then fail("the redaction patterns were handed " .. bytes .. " bytes of a four-vendor report" From 8875f0298b60f19c591dfeecaddb9f50ce453eb6 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 21 Aug 2026 11:41:54 -0300 Subject: [PATCH 09/35] ai-usagebar: retake the thumbnail in the official generator The card now shows the panel the way this release draws it: both providers in the list, the two quota bars, and the severity word beside the weekly reading. The previous one was assembled by hand, which the contribution checklist asks against, and it was cropped loose enough that the percentages did not survive being scaled into a catalog card. --- ai-usagebar/thumbnail.webp | Bin 29280 -> 47798 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ai-usagebar/thumbnail.webp b/ai-usagebar/thumbnail.webp index d3410ad72195e45a53020fbd1a79f7d2e1157a1e..6398d3a6effa7ae93c193a74839ef134ad195529 100644 GIT binary patch literal 47798 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 literal 29280 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 Date: Fri, 21 Aug 2026 12:04:40 -0300 Subject: [PATCH 10/35] ai-usagebar: stamp the refresh command with a millisecond clock `at` is in that payload for one reason: to make each request distinct from the one before it, so a watcher has something to tell them apart by. `os.time()` is whole-second, which is a weak way to promise that, and the state store's contract says nothing either way about what it does with a repeated value. `noctalia.nowMs()` is the only sub-second clock the API offers, and it is already what the poller measures MIN_GAP_MS with. Two clocks for one question was the oversight. The rate limit belongs in the poller, where it is written down and can be read; a coarse stamp in the transport is a second limit nobody declared. Reported by Copilot on #427, which reached the same line by a different route. --- ai-usagebar/bar.luau | 2 +- ai-usagebar/panel.luau | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 7c1bfb45..5c1075fa 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -285,7 +285,7 @@ function onClick() end function onRightClick() - noctalia.state.set("command", { action = "refresh", at = os.time() }) + noctalia.state.set("command", { action = "refresh", at = noctalia.nowMs() }) end report = noctalia.state.get("report") diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 26f2a2a8..1c93cd7c 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -274,7 +274,7 @@ end -- ── Render ──────────────────────────────────────────────────────────────────── local function requestRefresh() - noctalia.state.set("command", { action = "refresh", at = os.time() }) + noctalia.state.set("command", { action = "refresh", at = noctalia.nowMs() }) end -- The CLI's own words come last and smallest, where a bug report can quote them. @@ -520,7 +520,7 @@ end) function onOpen(_context) -- Every open asks for fresh numbers. The CLI answers from its own cache when it -- has one, and the poller drops requests that arrive too close together. - noctalia.state.set("command", { action = "refresh", at = os.time() }) + noctalia.state.set("command", { action = "refresh", at = noctalia.nowMs() }) report = noctalia.state.get("report") failure = asFailure(noctalia.state.get("error")) polling = noctalia.state.get("polling") == true From 31c0a9c7d78471d19fda599c4775c4a3af6ba596 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 21 Aug 2026 12:15:59 -0300 Subject: [PATCH 11/35] ai-usagebar: keep the refresh request in one place The payload was written out three times, in two files, in the release whose point was to stop both entries from carrying their own copy of things. The millisecond fix had to be made in all three, which is how the duplication announced itself. `shared.requestRefresh()` now owns it, and with it the note that `at` is never read: the poller looks at `action` and nothing else, so the field is there to keep two requests in a row from being the same value. Written down once, in the place a reader will find it, rather than inferred three times from a literal. --- ai-usagebar/bar.luau | 3 ++- ai-usagebar/panel.luau | 7 ++----- ai-usagebar/shared.luau | 8 ++++++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 5c1075fa..39042c10 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -18,6 +18,7 @@ local GLYPHS = shared.GLYPHS local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, elapsedPercent = shared.ratio, shared.headline, shared.elapsedPercent local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure +local requestRefresh = shared.requestRefresh local failure = NO_FAILURE @@ -285,7 +286,7 @@ function onClick() end function onRightClick() - noctalia.state.set("command", { action = "refresh", at = noctalia.nowMs() }) + requestRefresh() end report = noctalia.state.get("report") diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 1c93cd7c..82acf75c 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -10,6 +10,7 @@ local GLYPHS, parseIso = shared.GLYPHS, shared.parseIso local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, severityRole = shared.ratio, shared.headline, shared.severityRole local elapsedPercent = shared.elapsedPercent +local requestRefresh = shared.requestRefresh local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure local failure = NO_FAILURE @@ -273,10 +274,6 @@ end -- ── Render ──────────────────────────────────────────────────────────────────── -local function requestRefresh() - noctalia.state.set("command", { action = "refresh", at = noctalia.nowMs() }) -end - -- The CLI's own words come last and smallest, where a bug report can quote them. local function errorBlock() local key = "ui.error." .. failure.code @@ -520,7 +517,7 @@ end) function onOpen(_context) -- Every open asks for fresh numbers. The CLI answers from its own cache when it -- has one, and the poller drops requests that arrive too close together. - noctalia.state.set("command", { action = "refresh", at = noctalia.nowMs() }) + requestRefresh() report = noctalia.state.get("report") failure = asFailure(noctalia.state.get("error")) polling = noctalia.state.get("polling") == true diff --git a/ai-usagebar/shared.luau b/ai-usagebar/shared.luau index 12823f0e..85439fb3 100644 --- a/ai-usagebar/shared.luau +++ b/ai-usagebar/shared.luau @@ -28,6 +28,14 @@ M.GLYPHS = { gemini = "brand-google", } +-- Ask the poller for a read. It only looks at `action`; `at` is never read, and +-- is there so two requests in a row are not the same value. nowMs is the only +-- sub-second clock the API has, so os.time() would stamp two clicks in the same +-- second identically. +function M.requestRefresh() + noctalia.state.set("command", { action = "refresh", at = noctalia.nowMs() }) +end + -- Anything else in the `error` slot means no failure. M.NO_FAILURE = { code = "", detail = "" } From 87a3a04f8353effecd647e0ca48559aa64f37a37 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 21 Aug 2026 13:14:54 -0300 Subject: [PATCH 12/35] ai-usagebar: make the right click a gesture binding Reported as a broken button. It was not broken: a probe on the callback and on the poller's chain caught 39 requests from a burst of right clicks, every one of them reaching the watcher. MIN_GAP_MS honoured five and dropped thirty-five, in silence, and the five that ran came back from the CLI's cache fast enough that the capsule's dim was over before it could be seen. On the `meter` style, which draws ticks rather than digits, a fresh reading of the same number looks like nothing happened at all. So the gesture worked and had no way to say so. The half of that worth fixing is not the feedback. It is that the gesture was invisible: `onRightClick` does not appear in the widget's settings, so there was nothing to discover it by and no way to point it elsewhere. Every other plugin in this repo that answers a gesture declares it, and the API notes say why, that a declared action is listed where a Luau callback is not. This one now declares it too, and the callback is gone rather than left to shadow it. Left stays in the script. It sets `selected` before opening the panel so the panel lands on the provider that capsule tracks, which `panel-toggle` on its own cannot do, and the manifest says so next to the binding. README claimed the click "refreshes immediately", which stops being true the second time you press it. It now says a read is asked for, that one process serves every capsule, and that the poller will not start another within two seconds. Also that right is a binding, so it can be reassigned or turned off. --- ai-usagebar/README.md | 7 ++++++- ai-usagebar/bar.luau | 4 ---- ai-usagebar/plugin.toml | 10 ++++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index 6ba749c5..77445014 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -77,9 +77,14 @@ start = [ "clock", "ai_usage" ] clock time the reset lands on. - **Left click** opens the `AI Usage` panel for the provider that capsule tracks. -- **Right click** refreshes immediately. +- **Right click** asks the poller for a read. One process serves every capsule, + and it will not start a second one within two seconds of the last, so holding + the button down does not spawn a queue of them. - **Middle click** opens the widget's settings, as everywhere else in the shell. +Left and middle are the script's; right is a gesture binding, so it is listed in +the widget's settings and can be pointed at any other action, or at `none`. + The panel is a two pane view. On the left is every provider you have set up, with its headline percentage. On the right is the selected one in detail: one card per reported metric, with a quota bar over a thinner "window elapsed" bar, diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 39042c10..0d33091a 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -18,7 +18,6 @@ local GLYPHS = shared.GLYPHS local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, elapsedPercent = shared.ratio, shared.headline, shared.elapsedPercent local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure -local requestRefresh = shared.requestRefresh local failure = NO_FAILURE @@ -285,9 +284,6 @@ function onClick() noctalia.togglePanel("felipeartur/ai-usagebar:panel") end -function onRightClick() - requestRefresh() -end report = noctalia.state.get("report") failure = asFailure(noctalia.state.get("error")) diff --git a/ai-usagebar/plugin.toml b/ai-usagebar/plugin.toml index dcce3e5f..969172e4 100644 --- a/ai-usagebar/plugin.toml +++ b/ai-usagebar/plugin.toml @@ -36,6 +36,16 @@ entry = "service.luau" id = "bar" entry = "bar.luau" +# Right click asks the poller for a read. Declared rather than handled in +# bar.luau because a binding is what the settings editor lists and what a user +# can point somewhere else; an onRightClick callback is neither. +# +# Left stays in the script: it sets `selected` before opening the panel, so the +# panel lands on the provider this capsule tracks, which `panel-toggle` alone +# cannot do. + [widget.actions] + right = "plugin felipeartur/ai-usagebar:poller all refresh" + # Per-instance, so a second capsule can track a second provider. [[widget.setting]] key = "vendor" From 7997ce046c66894b6e42b4706237d52c4e399799 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 12:34:52 -0300 Subject: [PATCH 13/35] feat(ai-usagebar): improve refresh and provider visuals --- ai-usagebar/README.md | 19 +++-- ai-usagebar/bar.luau | 6 +- ai-usagebar/panel.luau | 127 +++++++++++++++++++++-------- ai-usagebar/plugin.toml | 4 +- ai-usagebar/service.luau | 32 ++++++-- ai-usagebar/shared.luau | 48 ++++++----- ai-usagebar/tests/refresh_test.lua | 66 +++++++++++++++ ai-usagebar/tests/scrub_test.lua | 39 +++++++++ ai-usagebar/translations/en.json | 2 + catalog.toml | 4 +- 10 files changed, 271 insertions(+), 76 deletions(-) create mode 100644 ai-usagebar/tests/refresh_test.lua diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index 77445014..6273fe91 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -78,8 +78,8 @@ start = [ "clock", "ai_usage" ] - **Left click** opens the `AI Usage` panel for the provider that capsule tracks. - **Right click** asks the poller for a read. One process serves every capsule, - and it will not start a second one within two seconds of the last, so holding - the button down does not spawn a queue of them. + and it coalesces repeated clicks into at most one pending read, so holding the + button down does not spawn a queue of processes. - **Middle click** opens the widget's settings, as everywhere else in the shell. Left and middle are the script's; right is a gesture binding, so it is listed in @@ -159,7 +159,8 @@ noctalia msg plugin felipeartur/ai-usagebar:poller all select anthropic it knows arrives on that command's stdout. - A provider that fails still comes back as an entry with `status = "error"`, so one broken provider does not blank the others. A reading the CLI marks stale - keeps showing, flagged in the capsule and in the panel's detail pane. + keeps showing, flagged by an icon in the list, the capsule, and the panel's + detail pane. ## Tests @@ -168,10 +169,12 @@ part worth a test. From the `ai-usagebar` directory: ```sh lua tests/scrub_test.lua +lua tests/refresh_test.lua ``` -It reads `safeText` and `scrub` out of `service.luau` rather than copying them, -then checks that real credential shapes never survive, that ordinary readings -pass through unchanged, and that scrubbing a four-vendor report stays inside the -CPU budget the poller's async callback is given. An overrun there loses the whole -reading, not just time. +The first test reads `safeText` and `scrub` out of `service.luau` rather than +copying them, then checks that real credential shapes never survive, that ordinary +readings pass through unchanged, and that scrubbing a four-vendor report stays +inside the CPU budget the poller's async callback is given. The second exercises +the coalesced refresh state and checks that every configured provider has visual +metadata. An overrun in the first test loses the whole reading, not just time. diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 0d33091a..6950b390 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -14,7 +14,7 @@ local report = nil local polling = false local shared = require("./shared.luau") -local GLYPHS = shared.GLYPHS +local providerVisual = shared.providerVisual local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, elapsedPercent = shared.ratio, shared.headline, shared.elapsedPercent local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure @@ -131,9 +131,11 @@ local function chip(entry) local metric = headline(entry) local tint = severityRole(metric, "on_surface") local fill = severityRole(metric, "primary") + local visual = providerVisual(entry.id) local percent = metric ~= nil and tonumber(metric.percent) or nil local text = percent ~= nil and string.format("%d%%", percent) or "—" - local glyph = ui.glyph({ name = GLYPHS[tostring(entry.id)] or "brain", size = 13, color = tint }) + local glyph = ui.glyph({ name = visual.glyph, size = 13, + color = severityRole(metric, visual.role) }) -- Fixed width, right-aligned: the capsule is the same size at 9% as at 100% -- and stops nudging its neighbours once per read. local pct = ui.label({ text = text, fontSize = 11, fontWeight = "semibold", color = tint, diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 82acf75c..04d3c3ac 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -4,9 +4,12 @@ local report = nil local polling = false +local refreshQueued = false +local refreshPhase = 0 +local refreshFrameMs = 0 local shared = require("./shared.luau") -local GLYPHS, parseIso = shared.GLYPHS, shared.parseIso +local providerVisual, parseIso = shared.providerVisual, shared.parseIso local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, severityRole = shared.ratio, shared.headline, shared.severityRole local elapsedPercent = shared.elapsedPercent @@ -24,7 +27,7 @@ local HAS_OPENER = noctalia.commandExists("xdg-open") -- reason keeps its row. local function configured(entry) if entry.status ~= "error" then return true end - return not tostring(entry.error or ""):lower():find("credentials error") + return not tostring(entry.error or ""):lower():find("^credentials error:") end -- ── Detail line parsing ─────────────────────────────────────────────────────── @@ -143,33 +146,36 @@ local function metricCard(section) local left = countdown(section) local clock = resetClock(section) local paceText, paceColor = pace(section.detail) - local footer = {} + local timing = {} if left ~= "" then - footer[#footer + 1] = ui.glyph({ name = "clock", size = 12, color = "on_surface_variant" }) - footer[#footer + 1] = ui.label({ text = left, fontSize = 11, color = "on_surface_variant" }) + timing[#timing + 1] = ui.glyph({ name = "clock", size = 12, color = "on_surface_variant" }) + timing[#timing + 1] = ui.label({ text = left, fontSize = 11, color = "on_surface_variant" }) if clock ~= "" then -- Parenthesised and muted: it is where the countdown beside it lands, -- not a reading of its own. - footer[#footer + 1] = ui.label({ + timing[#timing + 1] = ui.label({ text = "(" .. clock .. ")", fontSize = 11, color = "on_surface_variant", }) end end + local context = {} if elapsed ~= nil then - if #footer > 0 then - footer[#footer + 1] = ui.label({ text = "·", fontSize = 11, color = "on_surface_variant" }) - end - footer[#footer + 1] = ui.label({ + context[#context + 1] = ui.label({ text = noctalia.tr("ui.elapsed", { percent = elapsed }), - fontSize = 11, color = "on_surface_variant", maxLines = 1, + fontSize = 10, color = "on_surface_variant", maxLines = 1, }) end - if #footer > 0 or paceText ~= "" then - footer[#footer + 1] = ui.spacer({ flexGrow = 1 }) - if paceText ~= "" then - footer[#footer + 1] = ui.label({ text = paceText, fontSize = 11, fontWeight = "semibold", color = paceColor }) - end - body[#body + 1] = ui.row({ gap = 5, align = "center" }, footer) + if paceText ~= "" then + context[#context + 1] = ui.spacer({ flexGrow = 1 }) + context[#context + 1] = ui.label({ + text = paceText, fontSize = 10, fontWeight = "semibold", color = paceColor, maxLines = 1, + }) + end + if #timing > 0 then + body[#body + 1] = ui.row({ gap = 5, align = "center" }, timing) + end + if #context > 0 then + body[#body + 1] = ui.row({ gap = 5, align = "center" }, context) end local rest = plainDetail(section.detail) @@ -177,7 +183,8 @@ local function metricCard(section) body[#body + 1] = ui.label({ text = rest, fontSize = 11, color = "on_surface_variant" }) end - return ui.column({ gap = 6, padding = 10, radius = 8, fill = "surface_variant" }, body) + return ui.column({ gap = 6, padding = 10, radius = 8, fill = "surface_variant/0.45", + border = "outline/0.12", borderWidth = 1 }, body) end local function blockCard(section) @@ -197,16 +204,18 @@ local function blockCard(section) text = text ~= "" and text or "—", fontSize = 11, color = "on_surface_variant", + maxLines = 1, }) end - return ui.column({ gap = 4, padding = 10, radius = 8, fill = "surface_variant/0.45" }, body) + return ui.column({ gap = 4, padding = 10, radius = 8, fill = "surface_variant/0.38", + border = "outline/0.18", borderWidth = 1 }, body) end local function textRow(section) return ui.row({ gap = 6, align = "center" }, { ui.label({ text = tostring(section.label or ""), fontSize = 11, color = "on_surface_variant" }), ui.spacer({ flexGrow = 1 }), - ui.label({ text = tostring(section.value or ""), fontSize = 11, color = "on_surface" }), + ui.label({ text = tostring(section.value or ""), fontSize = 11, color = "on_surface", maxLines = 1 }), }) end @@ -222,15 +231,23 @@ local function providerRow(entry, selected) -- recolouring it hides the one thing the list exists to compare. local right if broken then - right = ui.row({ width = 34, justify = "end" }, { + right = ui.row({ width = 48, justify = "end" }, { ui.glyph({ name = "alert-circle", size = 14, color = "error" }), }) else - right = ui.label({ - text = percent ~= nil and string.format("%d%%", percent) or "—", - fontSize = 13, fontWeight = "bold", color = tint, - width = 34, textAlign = "end", - }) + local rightChildren = { + ui.label({ + text = percent ~= nil and string.format("%d%%", percent) or "—", + fontSize = 13, fontWeight = "bold", color = tint, + width = 34, textAlign = "end", + }), + } + if entry.stale == true then + rightChildren[#rightChildren + 1] = ui.glyph({ + name = "clock-exclamation", size = 12, color = "tertiary", + }) + end + right = ui.row({ gap = 3, width = 48, justify = "end", align = "center" }, rightChildren) end local lines = { @@ -253,20 +270,23 @@ local function providerRow(entry, selected) fontSize = 10, color = "on_surface_variant", maxLines = 1, }) + local visual = providerVisual(entry.id) return ui.row({ -- Keyed, so the click handler survives the second tick the countdowns ride -- on instead of being rebuilt under the pointer. key = "provider-" .. tostring(entry.id), gap = 8, align = "center", padding = 8, radius = 8, -- A tint, not a slab: a filled `primary` row inverts every colour in it. - fill = selected and "primary/0.14" or "surface_variant/0.45", + fill = selected and "primary/0.10" or "surface_variant/0.30", + border = selected and "primary/0.55" or "outline/0.12", + borderWidth = 1, onClick = function() noctalia.state.set("selected", tostring(entry.id)) render() end, }, { - ui.glyph({ name = GLYPHS[tostring(entry.id)] or "brain", size = 16, - color = selected and "primary" or "on_surface_variant" }), + ui.glyph({ name = visual.glyph, size = 16, + color = selected and "primary" or visual.role }), ui.column({ gap = 3, flexGrow = 1 }, lines), right, }) @@ -352,9 +372,12 @@ local function listPane(entry) -- One slot for the read: the button becomes the spinner while the CLI -- answers, instead of a second glyph pushing the header around. ui.button({ - glyph = polling and "loader-2" or "refresh", + glyph = polling and "loader-2" or refreshQueued and "clock" or "refresh", variant = "ghost", controlSize = "sm", - tooltip = noctalia.tr("ui.refresh"), + opacity = polling and (0.72 + 0.28 * (0.5 + 0.5 * math.cos(refreshPhase))) + or refreshQueued and 0.78 or 1, + tooltip = noctalia.tr(refreshQueued and "ui.refresh_queued" + or polling and "ui.refreshing" or "ui.refresh"), enabled = not polling, onClick = requestRefresh, }), @@ -389,6 +412,8 @@ local function detailPane(entry) -- the top of a hundred pixels of nothing. Wrapped, it is as tall as the two -- labels in it. children[#children + 1] = ui.row({ gap = 8, align = "center" }, { + ui.glyph({ name = providerVisual(entry.id).glyph, size = 18, + color = providerVisual(entry.id).role }), ui.column({ gap = 0, flexGrow = 1 }, { ui.label({ text = title, fontSize = 15, fontWeight = "bold", color = "on_surface", maxLines = 1 }), @@ -407,7 +432,7 @@ local function detailPane(entry) chips[#chips + 1] = ui.label({ text = "·", fontSize = 10, color = "on_surface_variant" }) end end - if entry.status ~= "ready" then + if entry.status == "error" then chips[#chips + 1] = ui.label({ text = tostring(entry.status or ""), fontSize = 10, color = "error", maxLines = 1, }) @@ -444,6 +469,19 @@ local function detailPane(entry) cards[#cards + 1] = blockCard(section) elseif section.type == "text" then cards[#cards + 1] = textRow(section) + elseif section.type == "spacer" then + cards[#cards + 1] = ui.spacer({ height = tonumber(section.height) or 8 }) + elseif section.type == "title" then + cards[#cards + 1] = ui.label({ + text = tostring(section.value or section.label or ""), + fontSize = 12, fontWeight = "semibold", color = "on_surface", + }) + else + -- Keep schema additions visible instead of silently losing report data. + cards[#cards + 1] = ui.label({ + text = tostring(section.value or section.label or section.type or "—"), + fontSize = 11, color = "on_surface_variant", + }) end end if #cards > 0 then @@ -460,7 +498,7 @@ local function detailPane(entry) }) end - return ui.column({ gap = 10, padding = 14, flexGrow = 1 }, children) + return ui.column({ gap = 8, padding = 14, flexGrow = 1 }, children) end function render() @@ -511,6 +549,13 @@ end) noctalia.state.watch("polling", function(value) polling = value == true + panel.setNeedsFrameTick(polling or refreshQueued) + render() +end) + +noctalia.state.watch("refresh_queued", function(value) + refreshQueued = value == true + panel.setNeedsFrameTick(polling or refreshQueued) render() end) @@ -521,8 +566,10 @@ function onOpen(_context) report = noctalia.state.get("report") failure = asFailure(noctalia.state.get("error")) polling = noctalia.state.get("polling") == true + refreshQueued = noctalia.state.get("refresh_queued") == true -- Countdowns tick locally; the CLI is only woken by the poller's interval. panel.setWantsSecondTicks(true) + panel.setNeedsFrameTick(polling or refreshQueued) render() end @@ -531,4 +578,18 @@ function update() render() end +function onFrameTick(deltaMs) + if not polling and not refreshQueued then return end + refreshFrameMs = refreshFrameMs + (tonumber(deltaMs) or 0) + if refreshFrameMs < 50 then return end + refreshPhase = (refreshPhase + refreshFrameMs / 1000) % (math.pi * 2) + refreshFrameMs = 0 + render() +end + +function onClose() + panel.setNeedsFrameTick(false) + panel.setWantsSecondTicks(false) +end + render() diff --git a/ai-usagebar/plugin.toml b/ai-usagebar/plugin.toml index 969172e4..19096aec 100644 --- a/ai-usagebar/plugin.toml +++ b/ai-usagebar/plugin.toml @@ -1,6 +1,6 @@ id = "felipeartur/ai-usagebar" name = "AI Usage" -version = "1.3.0" +version = "1.4.0" plugin_api = 22 author = "felipeartur" license = "MIT" @@ -9,7 +9,7 @@ description = "AI plan usage in the bar, powered by the ai-usagebar CLI." tags = ["bar", "panel", "ai", "indicator", "utility"] # The CLI owns credentials, vendor endpoints and caching. This plugin only runs # `ai-usagebar usage --json` and draws the result. -dependencies = ["ai-usagebar", "xdg-open"] +dependencies = ["ai-usagebar"] # ── Plugin-level settings (shared by the poller, every capsule and the panel) ── diff --git a/ai-usagebar/service.luau b/ai-usagebar/service.luau index bb87438d..1726a095 100644 --- a/ai-usagebar/service.luau +++ b/ai-usagebar/service.luau @@ -99,10 +99,7 @@ local function classify(result) -- Matched raw: `failure` scrubs what it is given, and matching after the -- 200-character cap would let a noisy run push the message out of reach. local stderr = tostring(result.stderr or "") - local lower = stderr:lower() - if result.exitCode == 127 - and (lower:find("command not found", 1, true) - or lower:find("no such file or directory", 1, true)) then + if result.exitCode == 127 then return failure("not_installed", stderr) end if result.exitCode ~= 0 then @@ -115,18 +112,34 @@ local function classify(result) end local inFlight = false +local pendingRefresh = false -- A floor between spawns. Opening the panel asks for a read, and a panel opens as -- fast as a pointer can click. local MIN_GAP_MS = 2000 local lastStart = 0 +local function queueRefresh() + pendingRefresh = true + noctalia.state.set("refresh_queued", true) + noctalia.setUpdateInterval(250) +end + local function refresh() - if inFlight then return end + if inFlight then + queueRefresh() + return + end local now = noctalia.nowMs() - if now - lastStart < MIN_GAP_MS then return end + if MIN_GAP_MS - (now - lastStart) > 0 then + queueRefresh() + return + end lastStart = now + pendingRefresh = false + noctalia.setUpdateInterval(intervalMs()) inFlight = true + noctalia.state.set("refresh_queued", false) noctalia.state.set("polling", true) local started = noctalia.runAsync(COMMAND, function(result) @@ -139,16 +152,19 @@ local function refresh() -- so a non-zero exit is no reason to drop the report. noctalia.state.set("report", scrub(decoded)) noctalia.state.set("error", failure("")) - return + else + noctalia.state.set("error", classify(result)) end - noctalia.state.set("error", classify(result)) + if pendingRefresh then refresh() end end, 30000) -- A refusal to spawn never calls back, and the poller would sit in flight -- forever. if not started then inFlight = false + pendingRefresh = false + noctalia.state.set("refresh_queued", false) noctalia.state.set("polling", false) noctalia.state.set("error", failure("spawn_failed")) end diff --git a/ai-usagebar/shared.luau b/ai-usagebar/shared.luau index 85439fb3..99093c93 100644 --- a/ai-usagebar/shared.luau +++ b/ai-usagebar/shared.luau @@ -5,29 +5,35 @@ local M = {} --- Tabler has no Anthropic mark, so a provider without a brand glyph gets a --- semantic one. -M.GLYPHS = { - anthropic = "asterisk-simple", - anthropic_api = "asterisk-simple", - openai = "brand-openai", - zai = "bolt", - openrouter = "route", - deepseek = "fish", - kimi = "moon", - moonshot = "moon", - kilo = "robot", - novita = "cloud", - grok = "brand-x", - supergrok = "brand-x", - antigravity = "sparkles", - cursor = "cursor-text", - minimax = "wave-square", - kiro = "ghost", - copilot = "brand-github-copilot", - gemini = "brand-google", +-- Tabler has no Anthropic mark, so providers without a brand glyph use a +-- semantic one. Roles keep service identity distinct without hardcoded colors. +M.PROVIDER_VISUALS = { + anthropic = { glyph = "asterisk-simple", role = "tertiary" }, + anthropic_api = { glyph = "asterisk-simple", role = "tertiary" }, + openai = { glyph = "brand-openai", role = "primary" }, + zai = { glyph = "bolt", role = "tertiary" }, + openrouter = { glyph = "route", role = "secondary" }, + deepseek = { glyph = "fish", role = "primary" }, + kimi = { glyph = "moon", role = "tertiary" }, + moonshot = { glyph = "moon", role = "tertiary" }, + kilo = { glyph = "robot", role = "secondary" }, + novita = { glyph = "cloud", role = "secondary" }, + grok = { glyph = "brand-x", role = "on_surface" }, + supergrok = { glyph = "brand-x", role = "on_surface" }, + antigravity = { glyph = "sparkles", role = "secondary" }, + cursor = { glyph = "cursor-text", role = "primary" }, + minimax = { glyph = "wave-square", role = "secondary" }, + kiro = { glyph = "ghost", role = "secondary" }, + copilot = { glyph = "brand-github-copilot", role = "secondary" }, + gemini = { glyph = "brand-google", role = "secondary" }, } +local DEFAULT_PROVIDER_VISUAL = { glyph = "brain", role = "on_surface_variant" } + +function M.providerVisual(id) + return M.PROVIDER_VISUALS[tostring(id)] or DEFAULT_PROVIDER_VISUAL +end + -- Ask the poller for a read. It only looks at `action`; `at` is never read, and -- is there so two requests in a row are not the same value. nowMs is the only -- sub-second clock the API has, so os.time() would stamp two clicks in the same diff --git a/ai-usagebar/tests/refresh_test.lua b/ai-usagebar/tests/refresh_test.lua new file mode 100644 index 00000000..68db1c4c --- /dev/null +++ b/ai-usagebar/tests/refresh_test.lua @@ -0,0 +1,66 @@ +-- Small host harness for the poller state machine and provider visual metadata. + +local function read(path) + local file = assert(io.open(path, "r")) + local source = file:read("*a") + file:close() + return source +end + +local values, watchers = {}, {} +local commands, callbacks = {}, {} +local now = 5000 +local intervals = {} + +local state = { + get = function(key) return values[key] end, + set = function(key, value) + values[key] = value + if watchers[key] then watchers[key](value) end + end, + watch = function(key, callback) watchers[key] = callback end, +} + +local noctalia = { + state = state, + nowMs = function() return now end, + getConfig = function(key) return key == "refresh_minutes" and 5 or nil end, + setUpdateInterval = function(ms) intervals[#intervals + 1] = ms end, + runAsync = function(command, callback) + commands[#commands + 1] = command + callbacks[#callbacks + 1] = callback + return true + end, + json = { decode = function() return { entries = {} } end }, + string = { trim = function(value) return value end }, +} + +local env = setmetatable({ noctalia = noctalia }, { __index = _G }) +local service = assert(load(read("service.luau"), "service", "t", env)) +service() + +assert(#callbacks == 1, "service should start one initial refresh") +env.onIpc("refresh") +assert(#callbacks == 1, "refresh while busy should be coalesced") +assert(values.refresh_queued == true, "coalesced refresh should be visible as queued") + +now = 7000 +callbacks[1]({ exitCode = 0, stdout = "{}", stderr = "" }) +assert(#callbacks == 2, "queued refresh should start after the first callback") +assert(values.refresh_queued == false, "queued state should clear when refresh starts") +assert(values.polling == true, "queued refresh should become the active poll") +assert(intervals[#intervals] == 5 * 60 * 1000, "active refresh should restore the configured interval") + +local sharedEnv = setmetatable({ noctalia = noctalia }, { __index = _G }) +local shared = assert(load(read("shared.luau"), "shared", "t", sharedEnv))() +for _, id in ipairs({ + "anthropic", "openai", "anthropic_api", "zai", "openrouter", "deepseek", "kimi", + "kilo", "novita", "moonshot", "grok", "supergrok", "antigravity", "cursor", + "minimax", "kiro", +}) do + local visual = assert(shared.PROVIDER_VISUALS[id], "missing provider visual for " .. id) + assert(type(visual.glyph) == "string" and visual.glyph ~= "", "missing glyph for " .. id) + assert(type(visual.role) == "string" and visual.role ~= "", "missing role for " .. id) +end + +io.write("ok: refresh queue coalesced, provider visuals complete\n") diff --git a/ai-usagebar/tests/scrub_test.lua b/ai-usagebar/tests/scrub_test.lua index 0fca3171..42de3948 100644 --- a/ai-usagebar/tests/scrub_test.lua +++ b/ai-usagebar/tests/scrub_test.lua @@ -41,6 +41,30 @@ end local safeText, scrub = loadSafeText() +local function loadClassify() + local file = io.open(SOURCE, "r") + local source = file:read("*a") + file:close() + local chunk = source:match("(local SECRET_VALUE.-)\nlocal inFlight") + if chunk == nil then error("could not find classify in " .. SOURCE) end + local env = { + string = string, + ipairs = ipairs, + pairs = pairs, + type = type, + tostring = tostring, + noctalia = { string = { trim = function(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end } }, + } + local loaded = load(chunk .. "\nreturn classify", "classifier", "t", env) + return loaded() +end + +local classify = loadClassify() + +local panelFile = io.open("panel.luau", "r") +local panelSource = panelFile:read("*a") +panelFile:close() + -- Each case names the material that must not survive. local SECRETS = { { "GET /v1/usage?api_key=sk-ant-abc123456 failed", "abc123456" }, @@ -83,6 +107,21 @@ local function fail(message) io.write("FAIL ", message, "\n") end +-- A healthy usage entry is reported as `ok` by the CLI. Only `error` is a +-- failed provider state. +local healthy = classify({ exitCode = 0, stdout = "not json", stderr = "" }) +if healthy.code ~= "no_data" then + fail("malformed successful output should be no_data") +end +local missing = classify({ exitCode = 127, stderr = "comando não encontrado" }) +if missing.code ~= "not_installed" then + fail("exit code 127 should mean not_installed regardless of shell language") +end +if panelSource:find('entry.status ~= "ready"', 1, true) + or not panelSource:find('entry.status == "error"', 1, true) then + fail("panel must treat only error entries as failed") +end + for _, case in ipairs(SECRETS) do local input, material = case[1], case[2] local output = safeText(input) diff --git a/ai-usagebar/translations/en.json b/ai-usagebar/translations/en.json index f5a5074f..6a8d00f9 100644 --- a/ai-usagebar/translations/en.json +++ b/ai-usagebar/translations/en.json @@ -82,6 +82,8 @@ "not_configured": "`{vendor}` is not configured in ai-usagebar", "now": "now", "refresh": "Refresh now", + "refresh_queued": "Refresh queued", + "refreshing": "Refreshing usage", "retry": "Try again", "settings": "Plugin settings", "severity": { diff --git a/catalog.toml b/catalog.toml index a0fe5f12..feaa0776 100644 --- a/catalog.toml +++ b/catalog.toml @@ -1455,8 +1455,8 @@ tags = ["bar", "niri", "panel", "service", "system", "utility"] [[plugin]] id = "felipeartur/ai-usagebar" name = "AI Usage" -version = "1.1.0" -updated_at = 1787016147 +version = "1.4.0" +updated_at = 1787585275 added_at = 1786903781 author = "felipeartur" license = "MIT" From 6bb00f054a03e052ac6699d7d3ce042c8a82cff4 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 22:07:16 -0300 Subject: [PATCH 14/35] ai-usagebar: drop the catalog.toml edit, CI generates it --- catalog.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/catalog.toml b/catalog.toml index feaa0776..a0fe5f12 100644 --- a/catalog.toml +++ b/catalog.toml @@ -1455,8 +1455,8 @@ tags = ["bar", "niri", "panel", "service", "system", "utility"] [[plugin]] id = "felipeartur/ai-usagebar" name = "AI Usage" -version = "1.4.0" -updated_at = 1787585275 +version = "1.1.0" +updated_at = 1787016147 added_at = 1786903781 author = "felipeartur" license = "MIT" From 93d62654f847c2feb3d1a7448b536646f770ed48 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 22:07:36 -0300 Subject: [PATCH 15/35] ai-usagebar: make the classify comments match the check --- ai-usagebar/service.luau | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ai-usagebar/service.luau b/ai-usagebar/service.luau index 1726a095..675c9abf 100644 --- a/ai-usagebar/service.luau +++ b/ai-usagebar/service.luau @@ -90,14 +90,12 @@ local function failure(code, detail) end -- The run's outcome as one code. A missing binary gets its own, because the --- panel offers an install link for that one. Shells report it as one of two --- messages, both with status 127, so code and message have to agree. +-- panel offers an install link for that one. Status 127 is the shell's own +-- "command not found"; the CLI never exits with it once it is on PATH. local function classify(result) if result == nil then return failure("spawn_failed") end if result.timedOut then return failure("timed_out") end - -- Matched raw: `failure` scrubs what it is given, and matching after the - -- 200-character cap would let a noisy run push the message out of reach. local stderr = tostring(result.stderr or "") if result.exitCode == 127 then return failure("not_installed", stderr) From 404c3d05ac65fc17cdb7371b60e00a0f6067acd6 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 22:11:57 -0300 Subject: [PATCH 16/35] ai-usagebar: stop the redaction from eating the readings The colon pattern matched any label containing key/token/secret/password, so 'Tokens: 45000 / 100000' rendered as 'Tokens: / 100000'. The value is now captured, and a plain number keeps it. --- ai-usagebar/service.luau | 20 +++++++++++++++++--- ai-usagebar/tests/scrub_test.lua | 5 +++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/ai-usagebar/service.luau b/ai-usagebar/service.luau index 675c9abf..d545621d 100644 --- a/ai-usagebar/service.luau +++ b/ai-usagebar/service.luau @@ -28,10 +28,24 @@ for _, word in ipairs({ "key", "token", "secret", "password" }) do word = word, -- name=value: a query string or a shell assignment. assign = "(" .. name .. "=)" .. SECRET_VALUE, - -- name: value: an HTTP header or a JSON field. - colon = "(" .. name .. "\"?%s*:%s*\"?)" .. SECRET_VALUE, + -- name: value: an HTTP header or a JSON field. The value is captured + -- rather than swallowed, because this shape is also how the CLI labels a + -- reading -- "Tokens: 45000 / 100000" -- and a plugin that draws token + -- counts cannot redact them. + colon = "(" .. name .. "\"?%s*:%s*\"?)(" .. SECRET_VALUE .. ")", } end + +-- A quota, a percentage or a price is not a credential. Anything that starts +-- with a letter still is, so "password: hunter2" is redacted. +local function isReading(value) + return value:match("^%d[%d%.,]*%%?$") ~= nil +end + +local function redactColon(prefix, value) + if isReading(value) then return prefix .. value end + return prefix .. "" +end -- Nine characters before the rest of a provider key, so a bare "sk-" in prose -- is not mistaken for one. local KEY_TAIL = string.rep("[%w_%-]", 9) @@ -53,7 +67,7 @@ local function safeText(value) for _, secret in ipairs(SECRET_PATTERNS) do if lower:find(secret.word, 1, true) then text = text:gsub(secret.assign, "%1") - text = text:gsub(secret.colon, "%1") + text = text:gsub(secret.colon, redactColon) end end diff --git a/ai-usagebar/tests/scrub_test.lua b/ai-usagebar/tests/scrub_test.lua index 42de3948..389f6687 100644 --- a/ai-usagebar/tests/scrub_test.lua +++ b/ai-usagebar/tests/scrub_test.lua @@ -98,6 +98,11 @@ local BENIGN = { "2026-08-20T11:29:59.872624Z", "Desk-top mode", "ChatGPT Free", + -- The colon form of the redaction is also how the CLI labels a reading. + "Tokens: 45000 / 100000", + "tokens_used: 1500", + "Session tokens: 98%", + "Prompt tokens: 1,024", } local failures = 0 From b8583484feaa14ff28b976563a4cf4b89446555c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 22:11:57 -0300 Subject: [PATCH 17/35] ai-usagebar: only frame-tick while the panel is open The polling watchers run with the panel closed, so the poller's automatic cycle re-armed per-frame rendering every refresh_minutes for the rest of the session. Also orders the refresh tooltip like its glyph. --- ai-usagebar/panel.luau | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 04d3c3ac..f4056527 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -7,6 +7,11 @@ local polling = false local refreshQueued = false local refreshPhase = 0 local refreshFrameMs = 0 +-- The state watchers below run whether or not the panel is on screen, and the +-- poller sets `polling` on every automatic cycle. Without this, the first open +-- of the session would leave a closed panel re-arming per-frame callbacks every +-- `refresh_minutes` for as long as the session lasts. +local open = false local shared = require("./shared.luau") local providerVisual, parseIso = shared.providerVisual, shared.parseIso @@ -376,8 +381,8 @@ local function listPane(entry) variant = "ghost", controlSize = "sm", opacity = polling and (0.72 + 0.28 * (0.5 + 0.5 * math.cos(refreshPhase))) or refreshQueued and 0.78 or 1, - tooltip = noctalia.tr(refreshQueued and "ui.refresh_queued" - or polling and "ui.refreshing" or "ui.refresh"), + tooltip = noctalia.tr(polling and "ui.refreshing" + or refreshQueued and "ui.refresh_queued" or "ui.refresh"), enabled = not polling, onClick = requestRefresh, }), @@ -549,17 +554,18 @@ end) noctalia.state.watch("polling", function(value) polling = value == true - panel.setNeedsFrameTick(polling or refreshQueued) + panel.setNeedsFrameTick(open and (polling or refreshQueued)) render() end) noctalia.state.watch("refresh_queued", function(value) refreshQueued = value == true - panel.setNeedsFrameTick(polling or refreshQueued) + panel.setNeedsFrameTick(open and (polling or refreshQueued)) render() end) function onOpen(_context) + open = true -- Every open asks for fresh numbers. The CLI answers from its own cache when it -- has one, and the poller drops requests that arrive too close together. requestRefresh() @@ -579,7 +585,7 @@ function update() end function onFrameTick(deltaMs) - if not polling and not refreshQueued then return end + if not open or (not polling and not refreshQueued) then return end refreshFrameMs = refreshFrameMs + (tonumber(deltaMs) or 0) if refreshFrameMs < 50 then return end refreshPhase = (refreshPhase + refreshFrameMs / 1000) % (math.pi * 2) @@ -588,6 +594,7 @@ function onFrameTick(deltaMs) end function onClose() + open = false panel.setNeedsFrameTick(false) panel.setWantsSecondTicks(false) end From 8e9ff889b9219a41cf23ee6a5fdc09fbb5efcfeb Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 22:15:03 -0300 Subject: [PATCH 18/35] ai-usagebar: only hide providers the CLI says have no key Every unreachable provider comes back under the same `credentials error:` prefix, including ones that are configured -- Antigravity reports "no local server found ... open Antigravity" that way -- so filtering on the prefix alone dropped the row that had something to say. --- ai-usagebar/panel.luau | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index f4056527..2fb076d7 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -27,12 +27,16 @@ local failure = NO_FAILURE -- otherwise stat PATH on every second tick it spends in a failure. local HAS_OPENER = noctalia.commandExists("xdg-open") --- A vendor with no credential comes back as a `credentials error`. It was never --- set up, so it is not listed. A configured provider that fails for any other --- reason keeps its row. +-- A vendor the user never set up is not listed. The CLI prefixes every such +-- message with `credentials error:`, but that bucket also holds providers that +-- ARE set up and merely unreachable right now -- "credentials error: +-- Antigravity: no local server found ... open Antigravity" -- so the row is only +-- dropped when the CLI says there is no key at all. Anything else keeps its row +-- and shows the CLI's own words, which say what to do about it. local function configured(entry) if entry.status ~= "error" then return true end - return not tostring(entry.error or ""):lower():find("^credentials error:") + local message = tostring(entry.error or ""):lower() + return not (message:find("^credentials error:") and message:find("no api key", 1, true)) end -- ── Detail line parsing ─────────────────────────────────────────────────────── From f43a838191df642a5791d039b6044324f9d22e0b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 22:22:24 -0300 Subject: [PATCH 19/35] ai-usagebar: stop flagging things in the dimmest colour on screen `tertiary` carried high severity, staleness and a pace running ahead. In the stock dark theme it measures 2.8:1 on a card -- below the 3.9:1 of `on_surface_variant`, the muted text it is supposed to outrank, and below the 3:1 an icon needs. The flagged reading was the least legible thing in the card. Those three states and the brand glyphs that landed on the same role now use `secondary`, which measures 8.5:1. --- ai-usagebar/bar.luau | 6 +++--- ai-usagebar/panel.luau | 6 +++--- ai-usagebar/shared.luau | 21 +++++++++++++++------ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 6950b390..5520bdae 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -114,9 +114,9 @@ local function paceNodes(metric) local ahead = word == "ahead" return ui.row({ gap = 0, align = "center" }, { ui.glyph({ name = ahead and "arrow-up" or "arrow-down", size = 10, - color = ahead and "tertiary" or "on_surface_variant" }), + color = ahead and "secondary" or "on_surface_variant" }), ui.label({ text = tostring(points), fontSize = 10, - color = ahead and "tertiary" or "on_surface_variant", maxLines = 1 }), + color = ahead and "secondary" or "on_surface_variant", maxLines = 1 }), }) end @@ -177,7 +177,7 @@ local function chip(entry) add(paceNodes(metric)) if entry.stale == true then - add(ui.glyph({ name = "clock-exclamation", size = 11, color = "tertiary" })) + add(ui.glyph({ name = "clock-exclamation", size = 11, color = "secondary" })) end return ui.row({ gap = 4, align = "center" }, nodes) end diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 2fb076d7..3c7b2516 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -51,7 +51,7 @@ local function pace(detail) last = noctalia.string.trim(last) if last:find("elapsed") then return "", "on_surface_variant" end -- Ahead of the clock is worth flagging; under it means there is room left. - if last:find("ahead") then return last, "tertiary" end + if last:find("ahead") then return last, "secondary" end return last, "on_surface_variant" end @@ -253,7 +253,7 @@ local function providerRow(entry, selected) } if entry.stale == true then rightChildren[#rightChildren + 1] = ui.glyph({ - name = "clock-exclamation", size = 12, color = "tertiary", + name = "clock-exclamation", size = 12, color = "secondary", }) end right = ui.row({ gap = 3, width = 48, justify = "end", align = "center" }, rightChildren) @@ -448,7 +448,7 @@ local function detailPane(entry) end if entry.stale == true then separate() - chips[#chips + 1] = ui.label({ text = noctalia.tr("ui.stale"), fontSize = 10, color = "tertiary" }) + chips[#chips + 1] = ui.label({ text = noctalia.tr("ui.stale"), fontSize = 10, color = "secondary" }) end local fetched = parseIso(entry.fetched_at) if fetched ~= nil then diff --git a/ai-usagebar/shared.luau b/ai-usagebar/shared.luau index 99093c93..71ab3a31 100644 --- a/ai-usagebar/shared.luau +++ b/ai-usagebar/shared.luau @@ -7,15 +7,19 @@ local M = {} -- Tabler has no Anthropic mark, so providers without a brand glyph use a -- semantic one. Roles keep service identity distinct without hardcoded colors. +-- ponytail: `tertiary` is avoided here as well -- it renders at 2.8:1 in the +-- stock dark theme, under the 3:1 an icon needs. Only measured against that +-- theme; if another one darkens `secondary` the same way, this needs a role the +-- host guarantees is a foreground. M.PROVIDER_VISUALS = { - anthropic = { glyph = "asterisk-simple", role = "tertiary" }, - anthropic_api = { glyph = "asterisk-simple", role = "tertiary" }, + anthropic = { glyph = "asterisk-simple", role = "secondary" }, + anthropic_api = { glyph = "asterisk-simple", role = "secondary" }, openai = { glyph = "brand-openai", role = "primary" }, - zai = { glyph = "bolt", role = "tertiary" }, + zai = { glyph = "bolt", role = "secondary" }, openrouter = { glyph = "route", role = "secondary" }, deepseek = { glyph = "fish", role = "primary" }, - kimi = { glyph = "moon", role = "tertiary" }, - moonshot = { glyph = "moon", role = "tertiary" }, + kimi = { glyph = "moon", role = "secondary" }, + moonshot = { glyph = "moon", role = "secondary" }, kilo = { glyph = "robot", role = "secondary" }, novita = { glyph = "cloud", role = "secondary" }, grok = { glyph = "brand-x", role = "on_surface" }, @@ -119,10 +123,15 @@ end -- The CLI tiers every percentage, and copying its thresholds here would be a -- second source of truth. `calm` is for when it raised nothing: text stays on the -- surface colour, and the accent is kept for bar fills. +-- +-- `high` is deliberately not `tertiary`. Noctalia themes are free to make that +-- role a dark one -- in the stock dark theme it lands at 2.8:1 on a card, below +-- the 3.9:1 of `on_surface_variant`, the colour of the text it is supposed to +-- outrank -- so a flagged reading came out dimmer than the muted line beside it. function M.severityRole(section, calm) local severity = tostring(section and section.severity or "") if severity == "critical" then return "error" end - if severity == "high" then return "tertiary" end + if severity == "high" then return "secondary" end return calm end From e258ea91e780d2e1bda81614c6e1ffccc665855d Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 22:30:44 -0300 Subject: [PATCH 20/35] ai-usagebar: name the detail pane after the provider, not the plan The CLI keeps them apart -- display_name is "Codex", plan is "ChatGPT Free" -- and the pane had them the wrong way round, so the header read "ChatGPT Free" over "Codex" while the row in the list read the other way. --- ai-usagebar/panel.luau | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 3c7b2516..ae3321bb 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -407,8 +407,11 @@ local function detailPane(entry) local title = noctalia.tr("ui.title") local subtitle = "" if entry ~= nil then - title = tostring(entry.plan or entry.display_name or entry.id) - subtitle = tostring(entry.display_name or entry.id) + -- The provider names the pane, the plan qualifies it -- the same order the + -- row in the list uses. The CLI keeps them apart: `display_name` is + -- "Codex", `plan` is "ChatGPT Free". + title = tostring(entry.display_name or entry.id) + subtitle = tostring(entry.plan or "") if subtitle == title then subtitle = "" end end From 240bec231a48127b01a21a4c60bb02f65df28eba Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 22:33:15 -0300 Subject: [PATCH 21/35] ai-usagebar: leave the provider glyphs uncoloured Each provider carried a theme role picked for variety, so the OpenAI mark came out pink and the Claude one cyan for no reason a reader could act on. The glyph is identity; colour now belongs to severity and to the selected row alone, and the table collapses to id -> glyph. --- ai-usagebar/bar.luau | 6 ++-- ai-usagebar/panel.luau | 10 +++--- ai-usagebar/shared.luau | 52 ++++++++++++++---------------- ai-usagebar/tests/refresh_test.lua | 5 ++- 4 files changed, 32 insertions(+), 41 deletions(-) diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 5520bdae..1657a9be 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -14,7 +14,7 @@ local report = nil local polling = false local shared = require("./shared.luau") -local providerVisual = shared.providerVisual +local providerGlyph = shared.providerGlyph local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, elapsedPercent = shared.ratio, shared.headline, shared.elapsedPercent local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure @@ -131,11 +131,9 @@ local function chip(entry) local metric = headline(entry) local tint = severityRole(metric, "on_surface") local fill = severityRole(metric, "primary") - local visual = providerVisual(entry.id) local percent = metric ~= nil and tonumber(metric.percent) or nil local text = percent ~= nil and string.format("%d%%", percent) or "—" - local glyph = ui.glyph({ name = visual.glyph, size = 13, - color = severityRole(metric, visual.role) }) + local glyph = ui.glyph({ name = providerGlyph(entry.id), size = 13, color = tint }) -- Fixed width, right-aligned: the capsule is the same size at 9% as at 100% -- and stops nudging its neighbours once per read. local pct = ui.label({ text = text, fontSize = 11, fontWeight = "semibold", color = tint, diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index ae3321bb..a4a2831b 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -14,7 +14,7 @@ local refreshFrameMs = 0 local open = false local shared = require("./shared.luau") -local providerVisual, parseIso = shared.providerVisual, shared.parseIso +local providerGlyph, parseIso = shared.providerGlyph, shared.parseIso local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, severityRole = shared.ratio, shared.headline, shared.severityRole local elapsedPercent = shared.elapsedPercent @@ -279,7 +279,6 @@ local function providerRow(entry, selected) fontSize = 10, color = "on_surface_variant", maxLines = 1, }) - local visual = providerVisual(entry.id) return ui.row({ -- Keyed, so the click handler survives the second tick the countdowns ride -- on instead of being rebuilt under the pointer. @@ -294,8 +293,8 @@ local function providerRow(entry, selected) render() end, }, { - ui.glyph({ name = visual.glyph, size = 16, - color = selected and "primary" or visual.role }), + ui.glyph({ name = providerGlyph(entry.id), size = 16, + color = selected and "primary" or "on_surface" }), ui.column({ gap = 3, flexGrow = 1 }, lines), right, }) @@ -424,8 +423,7 @@ local function detailPane(entry) -- the top of a hundred pixels of nothing. Wrapped, it is as tall as the two -- labels in it. children[#children + 1] = ui.row({ gap = 8, align = "center" }, { - ui.glyph({ name = providerVisual(entry.id).glyph, size = 18, - color = providerVisual(entry.id).role }), + ui.glyph({ name = providerGlyph(entry.id), size = 18, color = "on_surface" }), ui.column({ gap = 0, flexGrow = 1 }, { ui.label({ text = title, fontSize = 15, fontWeight = "bold", color = "on_surface", maxLines = 1 }), diff --git a/ai-usagebar/shared.luau b/ai-usagebar/shared.luau index 71ab3a31..5a3dcdf7 100644 --- a/ai-usagebar/shared.luau +++ b/ai-usagebar/shared.luau @@ -6,36 +6,32 @@ local M = {} -- Tabler has no Anthropic mark, so providers without a brand glyph use a --- semantic one. Roles keep service identity distinct without hardcoded colors. --- ponytail: `tertiary` is avoided here as well -- it renders at 2.8:1 in the --- stock dark theme, under the 3:1 an icon needs. Only measured against that --- theme; if another one darkens `secondary` the same way, this needs a role the --- host guarantees is a foreground. -M.PROVIDER_VISUALS = { - anthropic = { glyph = "asterisk-simple", role = "secondary" }, - anthropic_api = { glyph = "asterisk-simple", role = "secondary" }, - openai = { glyph = "brand-openai", role = "primary" }, - zai = { glyph = "bolt", role = "secondary" }, - openrouter = { glyph = "route", role = "secondary" }, - deepseek = { glyph = "fish", role = "primary" }, - kimi = { glyph = "moon", role = "secondary" }, - moonshot = { glyph = "moon", role = "secondary" }, - kilo = { glyph = "robot", role = "secondary" }, - novita = { glyph = "cloud", role = "secondary" }, - grok = { glyph = "brand-x", role = "on_surface" }, - supergrok = { glyph = "brand-x", role = "on_surface" }, - antigravity = { glyph = "sparkles", role = "secondary" }, - cursor = { glyph = "cursor-text", role = "primary" }, - minimax = { glyph = "wave-square", role = "secondary" }, - kiro = { glyph = "ghost", role = "secondary" }, - copilot = { glyph = "brand-github-copilot", role = "secondary" }, - gemini = { glyph = "brand-google", role = "secondary" }, +-- semantic one. The glyph is identity and nothing else: it takes no colour of +-- its own, so every colour left in the panel means something -- severity on the +-- bars, and the selected row. +M.PROVIDER_GLYPHS = { + anthropic = "asterisk-simple", + anthropic_api = "asterisk-simple", + openai = "brand-openai", + zai = "bolt", + openrouter = "route", + deepseek = "fish", + kimi = "moon", + moonshot = "moon", + kilo = "robot", + novita = "cloud", + grok = "brand-x", + supergrok = "brand-x", + antigravity = "sparkles", + cursor = "cursor-text", + minimax = "wave-square", + kiro = "ghost", + copilot = "brand-github-copilot", + gemini = "brand-google", } -local DEFAULT_PROVIDER_VISUAL = { glyph = "brain", role = "on_surface_variant" } - -function M.providerVisual(id) - return M.PROVIDER_VISUALS[tostring(id)] or DEFAULT_PROVIDER_VISUAL +function M.providerGlyph(id) + return M.PROVIDER_GLYPHS[tostring(id)] or "brain" end -- Ask the poller for a read. It only looks at `action`; `at` is never read, and diff --git a/ai-usagebar/tests/refresh_test.lua b/ai-usagebar/tests/refresh_test.lua index 68db1c4c..51973acc 100644 --- a/ai-usagebar/tests/refresh_test.lua +++ b/ai-usagebar/tests/refresh_test.lua @@ -58,9 +58,8 @@ for _, id in ipairs({ "kilo", "novita", "moonshot", "grok", "supergrok", "antigravity", "cursor", "minimax", "kiro", }) do - local visual = assert(shared.PROVIDER_VISUALS[id], "missing provider visual for " .. id) - assert(type(visual.glyph) == "string" and visual.glyph ~= "", "missing glyph for " .. id) - assert(type(visual.role) == "string" and visual.role ~= "", "missing role for " .. id) + local glyph = shared.PROVIDER_GLYPHS[id] + assert(type(glyph) == "string" and glyph ~= "", "missing glyph for " .. id) end io.write("ok: refresh queue coalesced, provider visuals complete\n") From 43be9fc70927f8edcdae90707d1b7b6a6e8451ad Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 22:36:49 -0300 Subject: [PATCH 22/35] ai-usagebar: keep the number exemption away from the real secrets The exemption that lets "Tokens: 45000" through was offered to every keyword, so "password: 1234" survived redaction too. It is now limited to `token` -- the only keyword that also names a metric -- and to nine characters, since a long run of digits is a credential whatever labels it. --- ai-usagebar/service.luau | 12 ++++++++---- ai-usagebar/tests/scrub_test.lua | 5 +++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ai-usagebar/service.luau b/ai-usagebar/service.luau index d545621d..d0680ac1 100644 --- a/ai-usagebar/service.luau +++ b/ai-usagebar/service.luau @@ -26,6 +26,8 @@ for _, word in ipairs({ "key", "token", "secret", "password" }) do local name = "[%w_%-]*" .. anyCase .. "[%w_%-]*" SECRET_PATTERNS[#SECRET_PATTERNS + 1] = { word = word, + -- Only the keyword that collides with a metric label keeps its numbers. + keepsReadings = word == "token", -- name=value: a query string or a shell assignment. assign = "(" .. name .. "=)" .. SECRET_VALUE, -- name: value: an HTTP header or a JSON field. The value is captured @@ -36,10 +38,12 @@ for _, word in ipairs({ "key", "token", "secret", "password" }) do } end --- A quota, a percentage or a price is not a credential. Anything that starts --- with a letter still is, so "password: hunter2" is redacted. +-- A quota or a percentage is not a credential. The exemption is deliberately +-- narrow: short, digits only, and offered to `token` alone. "Tokens: 45000" is a +-- reading this plugin exists to draw, while a bare number after `password` or +-- `secret` never is, and a long run of digits is a credential whatever labels it. local function isReading(value) - return value:match("^%d[%d%.,]*%%?$") ~= nil + return #value <= 9 and value:match("^%d[%d%.,]*%%?$") ~= nil end local function redactColon(prefix, value) @@ -67,7 +71,7 @@ local function safeText(value) for _, secret in ipairs(SECRET_PATTERNS) do if lower:find(secret.word, 1, true) then text = text:gsub(secret.assign, "%1") - text = text:gsub(secret.colon, redactColon) + text = text:gsub(secret.colon, secret.keepsReadings and redactColon or "%1") end end diff --git a/ai-usagebar/tests/scrub_test.lua b/ai-usagebar/tests/scrub_test.lua index 389f6687..ec0ac9e7 100644 --- a/ai-usagebar/tests/scrub_test.lua +++ b/ai-usagebar/tests/scrub_test.lua @@ -76,6 +76,11 @@ local SECRETS = { { "-H 'X-Api-Key: sk-ant-api03-REALKEY'", "REALKEY" }, { "curl https://user:hunter2@api.anthropic.com/v1/usage", "hunter2" }, { "authorization: bearer sk-ant-api03-REALKEY", "REALKEY" }, + -- The numeric exemption that keeps "Tokens: 45000" readable is offered to + -- `token` alone, and never to a long run of digits. + { "password: 1234", "1234" }, + { "secret: 99", "99" }, + { "api_key: 123456789012345", "123456789012345" }, { "OPENAI_API_KEY sk-proj-REALKEYVALUE not accepted", "REALKEY" }, { "password=hunter2", "hunter2" }, -- The cap runs before the patterns, so a secret in a runaway line has to From b34812fa3ff8e2896869dfb7cfc3b814a266d974 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 23:09:49 -0300 Subject: [PATCH 23/35] ai-usagebar: retake the thumbnail after the panel fixes The old one showed the detail pane naming the plan over the provider, the bug e258ea9 fixed, and the provider glyphs still tinted. --- ai-usagebar/thumbnail.webp | Bin 47798 -> 46732 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ai-usagebar/thumbnail.webp b/ai-usagebar/thumbnail.webp index 6398d3a6effa7ae93c193a74839ef134ad195529..dd6f294b49923f37eb05d7284bc74ab197dc77ce 100644 GIT binary patch literal 46732 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 E0QYlsQ2+n{ literal 47798 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 From 78141768000077cf801a2bc0558e930d50dca398 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 23:12:16 -0300 Subject: [PATCH 24/35] ai-usagebar: drop the refresh pulse Twenty-three lines of `refreshPhase`, `onFrameTick` and an `open` flag drove one button's opacity at 20fps. The glyph already swaps to `loader-2` and the button already disables, and DESIGN.md's motion budget was "None" all along. This also removes b858348 by deletion: the closed panel cannot re-arm frame callbacks it no longer asks for. --- ai-usagebar/panel.luau | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index a4a2831b..08990ad8 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -5,13 +5,6 @@ local report = nil local polling = false local refreshQueued = false -local refreshPhase = 0 -local refreshFrameMs = 0 --- The state watchers below run whether or not the panel is on screen, and the --- poller sets `polling` on every automatic cycle. Without this, the first open --- of the session would leave a closed panel re-arming per-frame callbacks every --- `refresh_minutes` for as long as the session lasts. -local open = false local shared = require("./shared.luau") local providerGlyph, parseIso = shared.providerGlyph, shared.parseIso @@ -382,8 +375,6 @@ local function listPane(entry) ui.button({ glyph = polling and "loader-2" or refreshQueued and "clock" or "refresh", variant = "ghost", controlSize = "sm", - opacity = polling and (0.72 + 0.28 * (0.5 + 0.5 * math.cos(refreshPhase))) - or refreshQueued and 0.78 or 1, tooltip = noctalia.tr(polling and "ui.refreshing" or refreshQueued and "ui.refresh_queued" or "ui.refresh"), enabled = not polling, @@ -559,18 +550,15 @@ end) noctalia.state.watch("polling", function(value) polling = value == true - panel.setNeedsFrameTick(open and (polling or refreshQueued)) render() end) noctalia.state.watch("refresh_queued", function(value) refreshQueued = value == true - panel.setNeedsFrameTick(open and (polling or refreshQueued)) render() end) function onOpen(_context) - open = true -- Every open asks for fresh numbers. The CLI answers from its own cache when it -- has one, and the poller drops requests that arrive too close together. requestRefresh() @@ -580,7 +568,6 @@ function onOpen(_context) refreshQueued = noctalia.state.get("refresh_queued") == true -- Countdowns tick locally; the CLI is only woken by the poller's interval. panel.setWantsSecondTicks(true) - panel.setNeedsFrameTick(polling or refreshQueued) render() end @@ -589,18 +576,8 @@ function update() render() end -function onFrameTick(deltaMs) - if not open or (not polling and not refreshQueued) then return end - refreshFrameMs = refreshFrameMs + (tonumber(deltaMs) or 0) - if refreshFrameMs < 50 then return end - refreshPhase = (refreshPhase + refreshFrameMs / 1000) % (math.pi * 2) - refreshFrameMs = 0 - render() -end function onClose() - open = false - panel.setNeedsFrameTick(false) panel.setWantsSecondTicks(false) end From 12a6a68cee7ac7f4cd812f26aaa7437a8d678c73 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 24 Aug 2026 23:56:54 -0300 Subject: [PATCH 25/35] ai-usagebar: cut the skeletons and the guessed card icons metricIcon picked a decorative glyph by substring-matching English prose ("week", "credit", "balance") while the label it guessed from sat in text right beside it. It would have died silently the day the CLI localised one. The skeleton cards stood in for a read the CLI answers from its own cache in about ten milliseconds. With them gone the pane is briefly empty instead, which is what it actually is. Unwrapping the row that held blockCard's icon took the last of it. --- ai-usagebar/panel.luau | 38 +++----------------------------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 08990ad8..e86fe330 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -83,13 +83,6 @@ local function updatedText(entry) return noctalia.tr("ui.updated_ago", { minutes = minutes }) end -local function metricIcon(label) - local text = tostring(label or ""):lower() - if text:find("week") or text:find("month") then return "calendar" end - if text:find("credit") or text:find("balance") or text:find("extra") then return "shopping-cart" end - return "hourglass" -end - -- ── Cards ───────────────────────────────────────────────────────────────────── local function severityWord(section) @@ -107,7 +100,6 @@ local function metricCard(section) local showValue = value ~= "" and value ~= string.format("%d%%", percent) local header = { - ui.glyph({ name = metricIcon(section.label), size = 14, color = "on_surface_variant" }), ui.label({ text = tostring(section.label or ""), fontSize = 12, fontWeight = "semibold", color = "on_surface", maxLines = 1 }), ui.spacer({ flexGrow = 1 }), @@ -191,11 +183,8 @@ end local function blockCard(section) local body = { - ui.row({ gap = 6, align = "center" }, { - ui.glyph({ name = metricIcon(section.label), size = 14, color = "on_surface_variant" }), - ui.label({ text = tostring(section.label or ""), fontSize = 12, fontWeight = "semibold", - color = "on_surface", maxLines = 1 }), - }), + ui.label({ text = tostring(section.label or ""), fontSize = 12, fontWeight = "semibold", + color = "on_surface", maxLines = 1 }), } for _, line in ipairs(section.body or {}) do local text = noctalia.string.trim(tostring(line)) @@ -340,18 +329,6 @@ local function errorBlock() return ui.column({ gap = 8 }, children) end --- A muted stand-in shaped like what is coming, so a cold read is not a spinner --- parked where the content will land. One shape serves both panes. -local function skeleton(key) - return ui.column({ - key = "skeleton-" .. key, - gap = 6, padding = 10, radius = 8, fill = "surface_variant/0.45", - }, { - ui.box({ width = 96, height = 10, radius = 3, fill = "on_surface/0.10" }), - ui.box({ height = 4, radius = 2, fill = "on_surface/0.06" }), - }) -end - local function listPane(entry) local rows = {} for _, candidate in ipairs(entries()) do @@ -359,10 +336,6 @@ local function listPane(entry) rows[#rows + 1] = providerRow(candidate, entry ~= nil and candidate.id == entry.id) end end - if #rows == 0 then - for index = 1, 3 do rows[index] = skeleton("row-" .. index) end - end - return ui.column({ gap = 10, padding = 14, width = 250 }, { ui.row({ gap = 8, align = "center" }, { ui.glyph({ name = "brain", size = 18, color = "primary" }), @@ -405,8 +378,7 @@ local function detailPane(entry) if subtitle == title then subtitle = "" end end - -- With no entry the skeletons below are the whole pane, and a title here would - -- repeat the list pane's. + -- With no entry the pane stays empty rather than repeating the list's title. local children = {} if entry ~= nil then -- The row keeps the title block honest about its height: a bare ui.column @@ -493,10 +465,6 @@ local function detailPane(entry) ui.label({ text = noctalia.tr("ui.no_usage"), fontSize = 11, color = "on_surface_variant" }), }) children[#children + 1] = ui.spacer({ flexGrow = 1 }) - else - children[#children + 1] = ui.column({ gap = 8, flexGrow = 1 }, { - skeleton("card-1"), skeleton("card-2"), - }) end return ui.column({ gap = 8, padding = 14, flexGrow = 1 }, children) From 11f3628ce626e0906388189922648f683e04c4b1 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 00:13:49 -0300 Subject: [PATCH 26/35] ai-usagebar: one capsule shape, v2.0.0 The style setting drew the same number four ways: pill, gauge, meter and label. pill stays and the branch, bars(), the manifest block, the six translation keys and the README table go with the other three. Major, not minor: the setting shipped in 1.1.0, so anyone who picked gauge or meter loses their choice and the saved value stops meaning anything. Also local-ises formatDuration and PROVIDER_GLYPHS, which nothing outside shared.luau read; drops the header chip that printed the raw word "error" next to the CLI's own error text; inlines separate() at its one remaining call site; and fixes the README, which still named tertiary as the high severity colour. --- ai-usagebar/README.md | 22 ++++----------- ai-usagebar/bar.luau | 45 ++---------------------------- ai-usagebar/panel.luau | 15 ++-------- ai-usagebar/plugin.toml | 15 +--------- ai-usagebar/shared.luau | 8 +++--- ai-usagebar/tests/refresh_test.lua | 4 +-- ai-usagebar/translations/en.json | 10 ------- 7 files changed, 17 insertions(+), 102 deletions(-) diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index 6273fe91..ec0f4e53 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -36,9 +36,8 @@ for API 9 and still runs there. Add `felipeartur/ai-usagebar:bar` to a bar in Settings, Bar. The capsule shows the headline percentage of a provider, behind that provider's icon. It reads in -the bar's own colour while there is room, picks up the theme's `tertiary` when -the CLI calls the window high, and `error` when it calls it critical. The accent -stays on the gauge fill, so a calm capsule looks like the widgets beside it. +the bar's own colour while there is room, picks up the theme's `secondary` when +the CLI calls the window high, and `error` when it calls it critical. Left on `Automatic`, the capsule follows the busiest provider, so what sits in the bar is the plan closest to running out. Raise `provider_limit` and it @@ -46,18 +45,9 @@ carries the next busiest ones too, with a `+N` for whatever did not fit. Pin a provider instead, or add the widget twice, when you want two fixed plans side by side. -Four styles, all with the same reading: - -| Style | Shape | -| --- | --- | -| `pill` | Icon and percentage. The compact one. | -| `gauge` | Icon, a small quota bar over a thinner "window elapsed" bar, percentage. | -| `meter` | Icon and five segments, filled in twenties, with no percentage. | -| `label` | Icon, provider name and percentage stacked over the bars. | - -Next to that, `extras` puts the time left in the window (`3h 51m`), the pace -against the clock (`↑3` is three points ahead of where the window says you -should be, `↓3` is three under), both, or neither. +`extras` puts the time left in the window (`3h 51m`), the pace against the +clock (`↑3` is three points ahead of where the window says you should be, `↓3` +is three under), both, or neither. If you add the widget by hand in `config.toml`, give it a name. A bar list entry that is a raw widget id becomes an anonymous instance, and an anonymous instance @@ -66,7 +56,6 @@ has no settings of its own, so the gear opens empty: ```toml [widget.ai_usage] type = "felipeartur/ai-usagebar:bar" -style = "gauge" provider_limit = 2 [bar.default] @@ -128,7 +117,6 @@ Per widget instance, so two capsules can follow two providers: | Setting | Type | Default | Description | | --- | --- | --- | --- | | `vendor` | `select` | `auto` | Which plan this capsule tracks. `auto` follows the busiest provider, with the CLI's own `[ui] primary` breaking ties. | -| `style` | `select` | `pill` | `pill`, `gauge`, `meter` or `label`, as described in the table above. | | `provider_limit` | `int` | `1` | How many providers one capsule carries, busiest first, from 1 to 4. Only applies on `auto`. | | `extras` | `select` | `countdown` | What rides beside the percentage: `countdown`, `pace`, `both` or `none`. | | `show_name` | `bool` | `false` | Adds the product name, so two capsules do not look alike. | diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 1657a9be..be6c6fcb 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -4,7 +4,6 @@ -- can follow a second provider. local vendor = tostring(noctalia.getConfig("vendor") or "auto") -local style = tostring(noctalia.getConfig("style") or "pill") local extras = tostring(noctalia.getConfig("extras") or "countdown") local limit = math.max(1, math.min(4, tonumber(noctalia.getConfig("provider_limit")) or 1)) local showName = noctalia.getConfig("show_name") == true @@ -16,7 +15,7 @@ local polling = false local shared = require("./shared.luau") local providerGlyph = shared.providerGlyph local countdown, resetClock = shared.countdown, shared.resetClock -local ratio, headline, elapsedPercent = shared.ratio, shared.headline, shared.elapsedPercent +local headline = shared.headline local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure local failure = NO_FAILURE @@ -93,20 +92,6 @@ end -- ── Rendering ───────────────────────────────────────────────────────────────── --- Quota above, window elapsed below: a longer fill than clock bar is spend --- running ahead of time. -local function bars(percent, elapsed, tint, width) - local stack = { - ui.progress({ progress = ratio(percent), fill = tint, track = "on_surface/0.16", - radius = 3, width = width, height = 4 }), - } - if elapsed ~= nil then - stack[#stack + 1] = ui.progress({ progress = ratio(elapsed), fill = "on_surface/0.45", - track = "on_surface/0.10", radius = 1, width = width, height = 2 }) - end - return ui.column({ gap = 1, align = "center" }, stack) -end - local function paceNodes(metric) if extras ~= "pace" and extras ~= "both" then return nil end local points, word = pace(metric) @@ -130,7 +115,6 @@ end local function chip(entry) local metric = headline(entry) local tint = severityRole(metric, "on_surface") - local fill = severityRole(metric, "primary") local percent = metric ~= nil and tonumber(metric.percent) or nil local text = percent ~= nil and string.format("%d%%", percent) or "—" local glyph = ui.glyph({ name = providerGlyph(entry.id), size = 13, color = tint }) @@ -144,32 +128,7 @@ local function chip(entry) local nodes = {} local function add(node) if node ~= nil then nodes[#nodes + 1] = node end end - if style == "meter" and percent ~= nil then - local ticks = {} - for i = 0, 4 do - ticks[#ticks + 1] = ui.box({ - width = 3, height = 11, radius = 1, - fill = percent > i * 20 and fill or "on_surface/0.22", - }) - end - add(glyph); add(name) - add(ui.row({ gap = 2, align = "center" }, ticks)) - elseif style == "label" and percent ~= nil then - add(glyph) - add(ui.column({ gap = 1, align = "center" }, { - ui.row({ gap = 3, align = "center" }, { - ui.label({ text = shortName(entry), fontSize = 10, color = "on_surface_variant", maxLines = 1 }), - pct, - }), - bars(percent, elapsedPercent(metric and metric.detail), fill, 44), - })) - elseif style == "gauge" and percent ~= nil then - add(glyph); add(name) - add(bars(percent, elapsedPercent(metric and metric.detail), fill, 26)) - add(pct) - else - add(glyph); add(name); add(pct) - end + add(glyph); add(name); add(pct) add(countdownNode(metric)) add(paceNodes(metric)) diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index e86fe330..43a9f09d 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -400,23 +400,14 @@ local function detailPane(entry) -- clicked, and a healthy read is the default. if entry ~= nil then local chips = {} - local function separate() - if #chips > 0 then - chips[#chips + 1] = ui.label({ text = "·", fontSize = 10, color = "on_surface_variant" }) - end - end - if entry.status == "error" then - chips[#chips + 1] = ui.label({ - text = tostring(entry.status or ""), fontSize = 10, color = "error", maxLines = 1, - }) - end if entry.stale == true then - separate() chips[#chips + 1] = ui.label({ text = noctalia.tr("ui.stale"), fontSize = 10, color = "secondary" }) end local fetched = parseIso(entry.fetched_at) if fetched ~= nil then - separate() + if #chips > 0 then + chips[#chips + 1] = ui.label({ text = "·", fontSize = 10, color = "on_surface_variant" }) + end chips[#chips + 1] = ui.glyph({ name = "clock", size = 11, color = "on_surface_variant" }) chips[#chips + 1] = ui.label({ text = updatedText(entry) .. " · " .. noctalia.formatTime(noctalia.timeFormat(), fetched), diff --git a/ai-usagebar/plugin.toml b/ai-usagebar/plugin.toml index 19096aec..062e447c 100644 --- a/ai-usagebar/plugin.toml +++ b/ai-usagebar/plugin.toml @@ -1,6 +1,6 @@ id = "felipeartur/ai-usagebar" name = "AI Usage" -version = "1.4.0" +version = "2.0.0" plugin_api = 22 author = "felipeartur" license = "MIT" @@ -73,19 +73,6 @@ options = [ { value = "kiro", label_key = "settings.vendor.option.kiro" }, ] -[[widget.setting]] -key = "style" -type = "select" -label_key = "settings.style.label" -description_key = "settings.style.description" -default = "pill" -options = [ - { value = "pill", label_key = "settings.style.option.pill" }, - { value = "gauge", label_key = "settings.style.option.gauge" }, - { value = "meter", label_key = "settings.style.option.meter" }, - { value = "label", label_key = "settings.style.option.label" }, -] - # One capsule can carry more than one provider; "auto" fills it with the busiest. [[widget.setting]] key = "provider_limit" diff --git a/ai-usagebar/shared.luau b/ai-usagebar/shared.luau index 5a3dcdf7..9445c0aa 100644 --- a/ai-usagebar/shared.luau +++ b/ai-usagebar/shared.luau @@ -9,7 +9,7 @@ local M = {} -- semantic one. The glyph is identity and nothing else: it takes no colour of -- its own, so every colour left in the panel means something -- severity on the -- bars, and the selected row. -M.PROVIDER_GLYPHS = { +local PROVIDER_GLYPHS = { anthropic = "asterisk-simple", anthropic_api = "asterisk-simple", openai = "brand-openai", @@ -31,7 +31,7 @@ M.PROVIDER_GLYPHS = { } function M.providerGlyph(id) - return M.PROVIDER_GLYPHS[tostring(id)] or "brain" + return PROVIDER_GLYPHS[tostring(id)] or "brain" end -- Ask the poller for a read. It only looks at `action`; `at` is never read, and @@ -64,7 +64,7 @@ function M.parseIso(value) return asLocal + (asLocal - utcAsLocal) end -function M.formatDuration(seconds) +local function formatDuration(seconds) if seconds <= 0 then return noctalia.tr("ui.now") end local minutes = math.floor(seconds / 60) local days = math.floor(minutes / 1440) @@ -79,7 +79,7 @@ end function M.countdown(section) local at = M.parseIso(section and section.reset_at) if at == nil then return "" end - return M.formatDuration(at - os.time()) + return formatDuration(at - os.time()) end -- Where the countdown lands: "14:20" today, "Sat 14:20" past midnight, and a date diff --git a/ai-usagebar/tests/refresh_test.lua b/ai-usagebar/tests/refresh_test.lua index 51973acc..2cb121f8 100644 --- a/ai-usagebar/tests/refresh_test.lua +++ b/ai-usagebar/tests/refresh_test.lua @@ -58,8 +58,8 @@ for _, id in ipairs({ "kilo", "novita", "moonshot", "grok", "supergrok", "antigravity", "cursor", "minimax", "kiro", }) do - local glyph = shared.PROVIDER_GLYPHS[id] - assert(type(glyph) == "string" and glyph ~= "", "missing glyph for " .. id) + -- "brain" is the fallback, so a provider still on it has no glyph of its own. + assert(shared.providerGlyph(id) ~= "brain", "missing glyph for " .. id) end io.write("ok: refresh queue coalesced, provider visuals complete\n") diff --git a/ai-usagebar/translations/en.json b/ai-usagebar/translations/en.json index 6a8d00f9..ac21ceb1 100644 --- a/ai-usagebar/translations/en.json +++ b/ai-usagebar/translations/en.json @@ -26,16 +26,6 @@ "description": "Adds the product name next to the percentage, so two capsules do not look alike.", "label": "Show provider name" }, - "style": { - "description": "How this capsule looks in the bar.", - "label": "Style", - "option": { - "gauge": "Gauge and percentage", - "label": "Name, gauge and percentage", - "meter": "Segments", - "pill": "Percentage" - } - }, "vendor": { "description": "Which plan this capsule tracks. Add the widget twice to watch two.", "label": "Provider", From b48f53cd7b305b88647f0ea38b9d50b68876844a Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 00:26:48 -0300 Subject: [PATCH 27/35] ai-usagebar: configure the capsule the way sysmon does The style setting was four names for three independent choices. The core sysmon widget already solved this with orthogonal keys, so the capsule now borrows them verbatim: visualization (gauge, meter or none), show_value, show_glyph and glyph_position. A user who has configured the CPU reading beside this one already knows the vocabulary. gauge is the default and draws the quota bar over a thinner bar for how much of the window has gone, so a fill longer than the clock is spend running ahead. graph is left out: sysmon samples every few seconds and this reads every refresh_minutes, so the line would be flat between points. --- ai-usagebar/README.md | 13 +++++++++ ai-usagebar/bar.luau | 48 ++++++++++++++++++++++++++++---- ai-usagebar/plugin.toml | 39 ++++++++++++++++++++++++++ ai-usagebar/translations/en.json | 25 +++++++++++++++++ 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index ec0f4e53..ff2c8536 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -45,6 +45,14 @@ carries the next busiest ones too, with a `+N` for whatever did not fit. Pin a provider instead, or add the widget twice, when you want two fixed plans side by side. +The capsule is put together the way the core `sysmon` widget is, with the same +key names, so the CPU reading beside it is configured with the same vocabulary. +`visualization` draws a `gauge` (a quota bar over a thinner bar for how much of +the window has gone, so a longer fill than clock is spend running ahead), five +`meter` segments filled in twenties, or `none`. `show_value`, `show_glyph` and +`glyph_position` decide whether the percentage and the icon are there and which +side the icon sits on. + `extras` puts the time left in the window (`3h 51m`), the pace against the clock (`↑3` is three points ahead of where the window says you should be, `↓3` is three under), both, or neither. @@ -56,6 +64,7 @@ has no settings of its own, so the gear opens empty: ```toml [widget.ai_usage] type = "felipeartur/ai-usagebar:bar" +visualization = "meter" provider_limit = 2 [bar.default] @@ -117,6 +126,10 @@ Per widget instance, so two capsules can follow two providers: | Setting | Type | Default | Description | | --- | --- | --- | --- | | `vendor` | `select` | `auto` | Which plan this capsule tracks. `auto` follows the busiest provider, with the CLI's own `[ui] primary` breaking ties. | +| `visualization` | `select` | `gauge` | `gauge`, `meter` or `none`, as described above. | +| `show_value` | `bool` | `true` | Show the percentage as text. | +| `show_glyph` | `bool` | `true` | Show the provider's icon. | +| `glyph_position` | `select` | `before` | `before` or `after` the reading. | | `provider_limit` | `int` | `1` | How many providers one capsule carries, busiest first, from 1 to 4. Only applies on `auto`. | | `extras` | `select` | `countdown` | What rides beside the percentage: `countdown`, `pace`, `both` or `none`. | | `show_name` | `bool` | `false` | Adds the product name, so two capsules do not look alike. | diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index be6c6fcb..5cf24519 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -5,6 +5,12 @@ local vendor = tostring(noctalia.getConfig("vendor") or "auto") local extras = tostring(noctalia.getConfig("extras") or "countdown") +-- Named after the core `sysmon` widget's own keys, so a reading in this capsule +-- is configured the same way as the CPU one beside it. +local visualization = tostring(noctalia.getConfig("visualization") or "gauge") +local showValue = noctalia.getConfig("show_value") ~= false +local showGlyph = noctalia.getConfig("show_glyph") ~= false +local glyphAfter = tostring(noctalia.getConfig("glyph_position") or "before") == "after" local limit = math.max(1, math.min(4, tonumber(noctalia.getConfig("provider_limit")) or 1)) local showName = noctalia.getConfig("show_name") == true local colorByUsage = noctalia.getConfig("color_by_usage") ~= false @@ -15,7 +21,7 @@ local polling = false local shared = require("./shared.luau") local providerGlyph = shared.providerGlyph local countdown, resetClock = shared.countdown, shared.resetClock -local headline = shared.headline +local ratio, headline, elapsedPercent = shared.ratio, shared.headline, shared.elapsedPercent local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure local failure = NO_FAILURE @@ -92,6 +98,20 @@ end -- ── Rendering ───────────────────────────────────────────────────────────────── +-- Quota above, window elapsed below: a fill longer than the clock bar is spend +-- running ahead of time. +local function bars(percent, elapsed, tint, width) + local stack = { + ui.progress({ progress = ratio(percent), fill = tint, track = "on_surface/0.16", + radius = 3, width = width, height = 4 }), + } + if elapsed ~= nil then + stack[#stack + 1] = ui.progress({ progress = ratio(elapsed), fill = "on_surface/0.45", + track = "on_surface/0.10", radius = 1, width = width, height = 2 }) + end + return ui.column({ gap = 1, align = "center" }, stack) +end + local function paceNodes(metric) if extras ~= "pace" and extras ~= "both" then return nil end local points, word = pace(metric) @@ -117,18 +137,36 @@ local function chip(entry) local tint = severityRole(metric, "on_surface") local percent = metric ~= nil and tonumber(metric.percent) or nil local text = percent ~= nil and string.format("%d%%", percent) or "—" - local glyph = ui.glyph({ name = providerGlyph(entry.id), size = 13, color = tint }) + local fill = severityRole(metric, "primary") + local glyph = showGlyph + and ui.glyph({ name = providerGlyph(entry.id), size = 13, color = tint }) or nil -- Fixed width, right-aligned: the capsule is the same size at 9% as at 100% -- and stops nudging its neighbours once per read. - local pct = ui.label({ text = text, fontSize = 11, fontWeight = "semibold", color = tint, - maxLines = 1, width = 30, textAlign = "end" }) + local pct = showValue and ui.label({ text = text, fontSize = 11, fontWeight = "semibold", + color = tint, maxLines = 1, width = 30, textAlign = "end" }) or nil local name = showName and ui.label({ text = shortName(entry), fontSize = 11, color = "on_surface_variant", maxLines = 1 }) or nil local nodes = {} local function add(node) if node ~= nil then nodes[#nodes + 1] = node end end - add(glyph); add(name); add(pct) + if not glyphAfter then add(glyph) end + add(name) + if percent ~= nil and visualization == "gauge" then + add(bars(percent, elapsedPercent(metric and metric.detail), fill, 26)) + elseif percent ~= nil and visualization == "meter" then + -- Five segments filled in twenties: the reading at a glance, no digits. + local ticks = {} + for i = 0, 4 do + ticks[#ticks + 1] = ui.box({ + width = 3, height = 11, radius = 1, + fill = percent > i * 20 and fill or "on_surface/0.22", + }) + end + add(ui.row({ gap = 2, align = "center" }, ticks)) + end + add(pct) + if glyphAfter then add(glyph) end add(countdownNode(metric)) add(paceNodes(metric)) diff --git a/ai-usagebar/plugin.toml b/ai-usagebar/plugin.toml index 062e447c..2fb509b9 100644 --- a/ai-usagebar/plugin.toml +++ b/ai-usagebar/plugin.toml @@ -73,6 +73,45 @@ options = [ { value = "kiro", label_key = "settings.vendor.option.kiro" }, ] +# Keys named after the core `sysmon` widget, so the CPU capsule and this one are +# configured with the same vocabulary. +[[widget.setting]] +key = "visualization" +type = "select" +label_key = "settings.visualization.label" +description_key = "settings.visualization.description" +default = "gauge" +options = [ + { value = "gauge", label_key = "settings.visualization.option.gauge" }, + { value = "meter", label_key = "settings.visualization.option.meter" }, + { value = "none", label_key = "settings.visualization.option.none" }, +] + +[[widget.setting]] +key = "show_value" +type = "bool" +label_key = "settings.show_value.label" +description_key = "settings.show_value.description" +default = true + +[[widget.setting]] +key = "show_glyph" +type = "bool" +label_key = "settings.show_glyph.label" +description_key = "settings.show_glyph.description" +default = true + +[[widget.setting]] +key = "glyph_position" +type = "select" +label_key = "settings.glyph_position.label" +description_key = "settings.glyph_position.description" +default = "before" +options = [ + { value = "before", label_key = "settings.glyph_position.option.before" }, + { value = "after", label_key = "settings.glyph_position.option.after" }, +] + # One capsule can carry more than one provider; "auto" fills it with the busiest. [[widget.setting]] key = "provider_limit" diff --git a/ai-usagebar/translations/en.json b/ai-usagebar/translations/en.json index ac21ceb1..a983c5fe 100644 --- a/ai-usagebar/translations/en.json +++ b/ai-usagebar/translations/en.json @@ -14,6 +14,14 @@ "pace": "Pace against the clock" } }, + "glyph_position": { + "description": "Which side of the reading the icon sits on.", + "label": "Icon position", + "option": { + "before": "Before", + "after": "After" + } + }, "provider_limit": { "description": "How many providers one capsule shows, busiest first, with a +N for the rest. Only applies when the provider is Automatic.", "label": "Providers in the capsule" @@ -22,10 +30,18 @@ "description": "How often the CLI is asked for fresh usage. Countdowns tick locally between calls.", "label": "Refresh interval (minutes)" }, + "show_glyph": { + "description": "Show the provider's icon.", + "label": "Show icon" + }, "show_name": { "description": "Adds the product name next to the percentage, so two capsules do not look alike.", "label": "Show provider name" }, + "show_value": { + "description": "Show the percentage as text.", + "label": "Show value" + }, "vendor": { "description": "Which plan this capsule tracks. Add the widget twice to watch two.", "label": "Provider", @@ -48,6 +64,15 @@ "supergrok": "SuperGrok", "zai": "Z.AI" } + }, + "visualization": { + "description": "What the capsule draws beside the reading.", + "label": "Visualization", + "option": { + "gauge": "Quota bar over the window clock", + "meter": "Five segments", + "none": "Nothing" + } } }, "ui": { From 14ce9bc90c886c1be94a8b0da09e221275b1a958 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 00:42:00 -0300 Subject: [PATCH 28/35] ai-usagebar: drop the meter, default to no visualization, stop the dim Five segments could not tell 85% from 97%: percent > i * 20 fills all of them anywhere above 80. Beside the digits and the countdown they read as a barcode rather than a reading, so gauge and none are the whole set. The default is none, which is what the capsule looked like before this PR, so an upgrade changes nothing until someone picks the gauge. The capsule also stopped dropping to 55% opacity on every read. That was a blink every refresh_minutes with no transition behind it, and the plugin API gives bar widgets no frame tick to smooth it with. Settings copy rewritten while the keys were open. color_by_usage promised 'Primary, then amber, then red', which named a role at the user and was wrong twice over after the severity colours moved to secondary and error. --- ai-usagebar/README.md | 13 ++++++------- ai-usagebar/bar.luau | 16 ++-------------- ai-usagebar/plugin.toml | 5 ++--- ai-usagebar/translations/en.json | 11 +++++------ 4 files changed, 15 insertions(+), 30 deletions(-) diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index ff2c8536..0433cda8 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -47,11 +47,10 @@ side. The capsule is put together the way the core `sysmon` widget is, with the same key names, so the CPU reading beside it is configured with the same vocabulary. -`visualization` draws a `gauge` (a quota bar over a thinner bar for how much of -the window has gone, so a longer fill than clock is spend running ahead), five -`meter` segments filled in twenties, or `none`. `show_value`, `show_glyph` and -`glyph_position` decide whether the percentage and the icon are there and which -side the icon sits on. +`visualization` draws a `gauge`, a quota bar over a thinner bar for how much of +the window has gone, so a longer fill than clock is spend running ahead, or +`none`. `show_value`, `show_glyph` and `glyph_position` decide whether the +percentage and the icon are there and which side the icon sits on. `extras` puts the time left in the window (`3h 51m`), the pace against the clock (`↑3` is three points ahead of where the window says you should be, `↓3` @@ -64,7 +63,7 @@ has no settings of its own, so the gear opens empty: ```toml [widget.ai_usage] type = "felipeartur/ai-usagebar:bar" -visualization = "meter" +visualization = "gauge" provider_limit = 2 [bar.default] @@ -126,7 +125,7 @@ Per widget instance, so two capsules can follow two providers: | Setting | Type | Default | Description | | --- | --- | --- | --- | | `vendor` | `select` | `auto` | Which plan this capsule tracks. `auto` follows the busiest provider, with the CLI's own `[ui] primary` breaking ties. | -| `visualization` | `select` | `gauge` | `gauge`, `meter` or `none`, as described above. | +| `visualization` | `select` | `none` | `gauge` or `none`, as described above. | | `show_value` | `bool` | `true` | Show the percentage as text. | | `show_glyph` | `bool` | `true` | Show the provider's icon. | | `glyph_position` | `select` | `before` | `before` or `after` the reading. | diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 5cf24519..c9b9d7b9 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -7,7 +7,7 @@ local vendor = tostring(noctalia.getConfig("vendor") or "auto") local extras = tostring(noctalia.getConfig("extras") or "countdown") -- Named after the core `sysmon` widget's own keys, so a reading in this capsule -- is configured the same way as the CPU one beside it. -local visualization = tostring(noctalia.getConfig("visualization") or "gauge") +local visualization = tostring(noctalia.getConfig("visualization") or "none") local showValue = noctalia.getConfig("show_value") ~= false local showGlyph = noctalia.getConfig("show_glyph") ~= false local glyphAfter = tostring(noctalia.getConfig("glyph_position") or "before") == "after" @@ -154,16 +154,6 @@ local function chip(entry) add(name) if percent ~= nil and visualization == "gauge" then add(bars(percent, elapsedPercent(metric and metric.detail), fill, 26)) - elseif percent ~= nil and visualization == "meter" then - -- Five segments filled in twenties: the reading at a glance, no digits. - local ticks = {} - for i = 0, 4 do - ticks[#ticks + 1] = ui.box({ - width = 3, height = 11, radius = 1, - fill = percent > i * 20 and fill or "on_surface/0.22", - }) - end - add(ui.row({ gap = 2, align = "center" }, ticks)) end add(pct) if glyphAfter then add(glyph) end @@ -248,9 +238,7 @@ local function render() color = "on_surface_variant", maxLines = 1 }) end - -- A read in flight dims the capsule instead of appending a spinner: a node - -- that comes and goes every cycle shoves every widget to its right. - barWidget.render(ui.row({ gap = 6, align = "center", opacity = polling and 0.55 or 1 }, children)) + barWidget.render(ui.row({ gap = 6, align = "center" }, children)) barWidget.setTooltip(tooltip(picked, hidden)) end diff --git a/ai-usagebar/plugin.toml b/ai-usagebar/plugin.toml index 2fb509b9..b984790a 100644 --- a/ai-usagebar/plugin.toml +++ b/ai-usagebar/plugin.toml @@ -80,11 +80,10 @@ key = "visualization" type = "select" label_key = "settings.visualization.label" description_key = "settings.visualization.description" -default = "gauge" +default = "none" options = [ - { value = "gauge", label_key = "settings.visualization.option.gauge" }, - { value = "meter", label_key = "settings.visualization.option.meter" }, { value = "none", label_key = "settings.visualization.option.none" }, + { value = "gauge", label_key = "settings.visualization.option.gauge" }, ] [[widget.setting]] diff --git a/ai-usagebar/translations/en.json b/ai-usagebar/translations/en.json index a983c5fe..90d2702f 100644 --- a/ai-usagebar/translations/en.json +++ b/ai-usagebar/translations/en.json @@ -1,11 +1,11 @@ { "settings": { "color_by_usage": { - "description": "Primary while there is room, then amber, then red as the quota fills.", + "description": "Tints the reading as the quota gets tight. Off keeps the capsule in the bar's own colour.", "label": "Color by usage" }, "extras": { - "description": "What rides next to the percentage: the time left in the window, how far the spend is from the clock, or neither.", + "description": "The time left in the window, how far the spend is from the clock, both, or neither.", "label": "Extra reading", "option": { "both": "Both", @@ -31,7 +31,7 @@ "label": "Refresh interval (minutes)" }, "show_glyph": { - "description": "Show the provider's icon.", + "description": "The provider's icon. Off leaves the reading on its own.", "label": "Show icon" }, "show_name": { @@ -39,7 +39,7 @@ "label": "Show provider name" }, "show_value": { - "description": "Show the percentage as text.", + "description": "The percentage as text. Off leaves the gauge to say it.", "label": "Show value" }, "vendor": { @@ -66,11 +66,10 @@ } }, "visualization": { - "description": "What the capsule draws beside the reading.", + "description": "A gauge beside the reading, or nothing.", "label": "Visualization", "option": { "gauge": "Quota bar over the window clock", - "meter": "Five segments", "none": "Nothing" } } From 6b13c15d47faada43d9a9c5bfd71b1b73fbb6d2a Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 00:52:11 -0300 Subject: [PATCH 29/35] ai-usagebar: rule between providers, plain provider marks A capsule carrying two providers ran them together on a six-pixel gap, so the first one's countdown read as part of the second. A vertical rule now sits between them, and only between them. The provider mark also stopped taking the severity colour. It says which provider, never how full the plan is; the reading and the gauge already say that. Same split the panel settled on in 240bec2. The gauge option was labelled 'Quota bar over the window clock', which is a sentence in a dropdown. It is 'Quota bar'. --- ai-usagebar/bar.luau | 10 +++++++++- ai-usagebar/translations/en.json | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index c9b9d7b9..51b723ea 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -138,8 +138,10 @@ local function chip(entry) local percent = metric ~= nil and tonumber(metric.percent) or nil local text = percent ~= nil and string.format("%d%%", percent) or "—" local fill = severityRole(metric, "primary") + -- The mark says which provider, never how full it is. Severity rides on the + -- reading and the gauge, the same split the panel uses. local glyph = showGlyph - and ui.glyph({ name = providerGlyph(entry.id), size = 13, color = tint }) or nil + and ui.glyph({ name = providerGlyph(entry.id), size = 13, color = "on_surface" }) or nil -- Fixed width, right-aligned: the capsule is the same size at 9% as at 100% -- and stops nudging its neighbours once per read. local pct = showValue and ui.label({ text = text, fontSize = 11, fontWeight = "semibold", @@ -223,6 +225,12 @@ local function render() local children = {} for _, entry in ipairs(picked) do + -- Two providers in one capsule run together on a gap alone, and the + -- countdown of the first reads as part of the second. + if #children > 0 then + children[#children + 1] = ui.separator({ orientation = "vertical", + color = "outline", spacing = 0 }) + end children[#children + 1] = chip(entry) end diff --git a/ai-usagebar/translations/en.json b/ai-usagebar/translations/en.json index 90d2702f..a4cb059b 100644 --- a/ai-usagebar/translations/en.json +++ b/ai-usagebar/translations/en.json @@ -69,7 +69,7 @@ "description": "A gauge beside the reading, or nothing.", "label": "Visualization", "option": { - "gauge": "Quota bar over the window clock", + "gauge": "Quota bar", "none": "Nothing" } } From 1848947f26038a7ac8b5a4169ed33429c8cfba02 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 00:58:44 -0300 Subject: [PATCH 30/35] ai-usagebar: redact secrets whose separator is padded The assign pattern wanted the = flush against the name, so API_KEY = "..." survived. That spacing is not exotic: it is how the CLI's own TOML config spells api_key, and the CLI quotes that file back when it cannot read a credential. --- ai-usagebar/service.luau | 5 +++-- ai-usagebar/tests/scrub_test.lua | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ai-usagebar/service.luau b/ai-usagebar/service.luau index d0680ac1..96359f1b 100644 --- a/ai-usagebar/service.luau +++ b/ai-usagebar/service.luau @@ -28,8 +28,9 @@ for _, word in ipairs({ "key", "token", "secret", "password" }) do word = word, -- Only the keyword that collides with a metric label keeps its numbers. keepsReadings = word == "token", - -- name=value: a query string or a shell assignment. - assign = "(" .. name .. "=)" .. SECRET_VALUE, + -- name=value: a query string, a shell assignment, or a line out of the + -- CLI's own TOML, where the separator is padded and the value quoted. + assign = "(" .. name .. "%s*=%s*\"?)" .. SECRET_VALUE, -- name: value: an HTTP header or a JSON field. The value is captured -- rather than swallowed, because this shape is also how the CLI labels a -- reading -- "Tokens: 45000 / 100000" -- and a plugin that draws token diff --git a/ai-usagebar/tests/scrub_test.lua b/ai-usagebar/tests/scrub_test.lua index ec0ac9e7..3f0c126e 100644 --- a/ai-usagebar/tests/scrub_test.lua +++ b/ai-usagebar/tests/scrub_test.lua @@ -79,6 +79,9 @@ local SECRETS = { -- The numeric exemption that keeps "Tokens: 45000" readable is offered to -- `token` alone, and never to a long run of digits. { "password: 1234", "1234" }, + -- A padded separator is how the CLI's own config file spells it. + { 'api_key = "sk-ant-api03-REALKEY"', "REALKEY" }, + { "export ANTHROPIC_API_KEY = sk-ant-api03-REALKEY", "REALKEY" }, { "secret: 99", "99" }, { "api_key: 123456789012345", "123456789012345" }, { "OPENAI_API_KEY sk-proj-REALKEYVALUE not accepted", "REALKEY" }, From 50e0532e378ecdb87a4961f2990154e55173903b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 01:07:54 -0300 Subject: [PATCH 31/35] ai-usagebar: move the last duplicated parsing into shared Three things the capsule and the panel both did, each in its own copy: pace() existed twice with different grammars. The capsule matched (%d+)pts%s+(%a+) anywhere in the string; the panel split on the separator and looked at the tail. One shared.pace(detail) returns the tail, the points and the direction, and the two drawings stay where they belong. SEVERITY_RANK sat in bar.luau while severityRole sat in shared.luau. That split is what let 'medium' go on not matching a tier the CLI never emitted. entries() was written out twice, identical but for the report it closed over. paceNodes, countdownNode and textRow were one-caller functions and are now inlined at their single call site. --- ai-usagebar/bar.luau | 54 +++++++++++++++-------------------------- ai-usagebar/panel.luau | 39 +++++++++-------------------- ai-usagebar/shared.luau | 25 +++++++++++++++++++ 3 files changed, 56 insertions(+), 62 deletions(-) diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index 51b723ea..ec0060b2 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -22,25 +22,14 @@ local shared = require("./shared.luau") local providerGlyph = shared.providerGlyph local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, elapsedPercent = shared.ratio, shared.headline, shared.elapsedPercent +local pace, SEVERITY_RANK = shared.pace, shared.SEVERITY_RANK local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure local failure = NO_FAILURE -- ── Report helpers ──────────────────────────────────────────────────────────── --- Returns points and direction: 2, "ahead" is burning faster than the clock. -local function pace(metric) - local points, word = tostring(metric and metric.detail or ""):match("(%d+)pts%s+(%a+)") - if points == nil then return nil, nil end - return tonumber(points), word -end - -local function entries() - if type(report) ~= "table" or type(report.entries) ~= "table" then return {} end - return report.entries -end - -local SEVERITY_RANK = { critical = 3, high = 2, mid = 1 } +local function entries() return shared.entries(report) end local function rank(entry) local metric = headline(entry) @@ -112,26 +101,6 @@ local function bars(percent, elapsed, tint, width) return ui.column({ gap = 1, align = "center" }, stack) end -local function paceNodes(metric) - if extras ~= "pace" and extras ~= "both" then return nil end - local points, word = pace(metric) - if points == nil then return nil end - local ahead = word == "ahead" - return ui.row({ gap = 0, align = "center" }, { - ui.glyph({ name = ahead and "arrow-up" or "arrow-down", size = 10, - color = ahead and "secondary" or "on_surface_variant" }), - ui.label({ text = tostring(points), fontSize = 10, - color = ahead and "secondary" or "on_surface_variant", maxLines = 1 }), - }) -end - -local function countdownNode(metric) - if extras ~= "countdown" and extras ~= "both" then return nil end - local left = countdown(metric) - if left == "" then return nil end - return ui.label({ text = left, fontSize = 10, color = "on_surface_variant", maxLines = 1 }) -end - local function chip(entry) local metric = headline(entry) local tint = severityRole(metric, "on_surface") @@ -160,8 +129,23 @@ local function chip(entry) add(pct) if glyphAfter then add(glyph) end - add(countdownNode(metric)) - add(paceNodes(metric)) + if extras == "countdown" or extras == "both" then + local left = countdown(metric) + if left ~= "" then + add(ui.label({ text = left, fontSize = 10, color = "on_surface_variant", maxLines = 1 })) + end + end + if extras == "pace" or extras == "both" then + local _, points, word = pace(metric and metric.detail) + if points ~= nil then + local ahead = word == "ahead" + local role = ahead and "secondary" or "on_surface_variant" + add(ui.row({ gap = 0, align = "center" }, { + ui.glyph({ name = ahead and "arrow-up" or "arrow-down", size = 10, color = role }), + ui.label({ text = tostring(points), fontSize = 10, color = role, maxLines = 1 }), + })) + end + end if entry.stale == true then add(ui.glyph({ name = "clock-exclamation", size = 11, color = "secondary" })) diff --git a/ai-usagebar/panel.luau b/ai-usagebar/panel.luau index 43a9f09d..9f759188 100644 --- a/ai-usagebar/panel.luau +++ b/ai-usagebar/panel.luau @@ -10,7 +10,7 @@ local shared = require("./shared.luau") local providerGlyph, parseIso = shared.providerGlyph, shared.parseIso local countdown, resetClock = shared.countdown, shared.resetClock local ratio, headline, severityRole = shared.ratio, shared.headline, shared.severityRole -local elapsedPercent = shared.elapsedPercent +local elapsedPercent, pace = shared.elapsedPercent, shared.pace local requestRefresh = shared.requestRefresh local NO_FAILURE, asFailure = shared.NO_FAILURE, shared.asFailure @@ -36,18 +36,6 @@ end -- "Resets in 1h 58m · 60% elapsed · 30pts ahead". The reset half is already in -- `reset_at`; what is left is the pace. -local function pace(detail) - local text = tostring(detail or "") - if not text:find("·") then return "", "on_surface_variant" end - local last = "" - for part in text:gmatch("[^·]+") do last = part end - last = noctalia.string.trim(last) - if last:find("elapsed") then return "", "on_surface_variant" end - -- Ahead of the clock is worth flagging; under it means there is room left. - if last:find("ahead") then return last, "secondary" end - return last, "on_surface_variant" -end - -- Details that carry no reset at all, e.g. "62% of monthly limit consumed". local function plainDetail(detail) local head = tostring(detail or ""):match("^[^·]*") or "" @@ -58,10 +46,7 @@ end -- ── Entry selection ─────────────────────────────────────────────────────────── -local function entries() - if type(report) ~= "table" or type(report.entries) ~= "table" then return {} end - return report.entries -end +local function entries() return shared.entries(report) end local function currentEntry() local wanted = noctalia.state.get("selected") @@ -139,7 +124,9 @@ local function metricCard(section) local left = countdown(section) local clock = resetClock(section) - local paceText, paceColor = pace(section.detail) + -- Ahead of the clock is worth flagging; under it means there is room left. + local paceText, _, paceWord = pace(section.detail) + local paceColor = paceWord == "ahead" and "secondary" or "on_surface_variant" local timing = {} if left ~= "" then timing[#timing + 1] = ui.glyph({ name = "clock", size = 12, color = "on_surface_variant" }) @@ -202,14 +189,6 @@ local function blockCard(section) border = "outline/0.18", borderWidth = 1 }, body) end -local function textRow(section) - return ui.row({ gap = 6, align = "center" }, { - ui.label({ text = tostring(section.label or ""), fontSize = 11, color = "on_surface_variant" }), - ui.spacer({ flexGrow = 1 }), - ui.label({ text = tostring(section.value or ""), fontSize = 11, color = "on_surface", maxLines = 1 }), - }) -end - -- ── Provider list ───────────────────────────────────────────────────────────── local function providerRow(entry, selected) @@ -432,7 +411,13 @@ local function detailPane(entry) elseif section.type == "block" then cards[#cards + 1] = blockCard(section) elseif section.type == "text" then - cards[#cards + 1] = textRow(section) + cards[#cards + 1] = ui.row({ gap = 6, align = "center" }, { + ui.label({ text = tostring(section.label or ""), fontSize = 11, + color = "on_surface_variant" }), + ui.spacer({ flexGrow = 1 }), + ui.label({ text = tostring(section.value or ""), fontSize = 11, + color = "on_surface", maxLines = 1 }), + }) elseif section.type == "spacer" then cards[#cards + 1] = ui.spacer({ height = tonumber(section.height) or 8 }) elseif section.type == "title" then diff --git a/ai-usagebar/shared.luau b/ai-usagebar/shared.luau index 9445c0aa..3030aeaa 100644 --- a/ai-usagebar/shared.luau +++ b/ai-usagebar/shared.luau @@ -102,6 +102,26 @@ function M.elapsedPercent(detail) return value ~= nil and tonumber(value) or nil end +-- The pace is the trailing segment of "Resets in 1h 58m · 60% elapsed · 30pts +-- ahead". `elapsed` sitting there means the reading carries no pace at all. +-- Returns that segment's text, then the points and the direction inside it. +function M.pace(detail) + local text = tostring(detail or "") + local points, word = text:match("(%d+)pts%s+(%a+)") + points = tonumber(points) + if not text:find("·") then return "", points, word end + local last = "" + for part in text:gmatch("[^·]+") do last = part end + last = noctalia.string.trim(last) + if last:find("elapsed") then return "", points, word end + return last, points, word +end + +function M.entries(report) + if type(report) ~= "table" or type(report.entries) ~= "table" then return {} end + return report.entries +end + -- A provider can report more than it was given, so clamp before this becomes a -- bar width. function M.ratio(percent) @@ -116,6 +136,11 @@ function M.headline(entry) return entry.metrics[1] end +-- The rank the capsule sorts by, next to the role both entries colour with, so +-- the tiers cannot be edited in one file and forgotten in the other. `low` is +-- absent on purpose: it falls through to 0 like anything the CLI adds later. +M.SEVERITY_RANK = { critical = 3, high = 2, mid = 1 } + -- The CLI tiers every percentage, and copying its thresholds here would be a -- second source of truth. `calm` is for when it raised nothing: text stays on the -- surface colour, and the accent is kept for bar fills. From 4ec2adc725c72eb8a9fcf09c70739575c17d8e32 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 01:16:57 -0300 Subject: [PATCH 32/35] ai-usagebar: bring the README back in line with the plugin Three passages described something that no longer exists. The capsule paragraph put the reading behind the provider's mark and had that mark taking the severity colour, which stopped being true when the mark went neutral. The provider list was said to hide anything without a credential, when it now hides only what the CLI calls a missing API key, so a configured but unreachable provider keeps its row. And the refresh test was described as checking 'visual metadata', which was the name of a table that is now a glyph lookup. Also names the actor on the xdg-open sentence and hyphenates two-pane. --- ai-usagebar/README.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/ai-usagebar/README.md b/ai-usagebar/README.md index 0433cda8..9bf6d8d0 100644 --- a/ai-usagebar/README.md +++ b/ai-usagebar/README.md @@ -24,9 +24,9 @@ tarballs on the project's GitHub Releases page. Configure your providers once in `~/.config/ai-usagebar/config.toml`; the CLI owns the credentials and the endpoints, and this plugin never sees them. -`xdg-open` is optional. It is spawned by one button in the panel, the link to -the CLI's project page offered when `ai-usagebar` is not on `PATH`. Without -xdg-utils that button is not drawn and the rest of the plugin is unaffected. +`xdg-open` is optional. The panel spawns it for one button, the link to the +CLI's project page it offers when `ai-usagebar` is not on `PATH`. Without +xdg-utils the panel leaves that button out and nothing else changes. The plugin asks for **plugin API 22**, which is where Noctalia gained `require()`. On a shell older than that it will not install. Version 1.1.0 asked @@ -35,9 +35,11 @@ for API 9 and still runs there. ## Usage Add `felipeartur/ai-usagebar:bar` to a bar in Settings, Bar. The capsule shows -the headline percentage of a provider, behind that provider's icon. It reads in -the bar's own colour while there is room, picks up the theme's `secondary` when -the CLI calls the window high, and `error` when it calls it critical. +one provider's headline percentage next to that provider's mark. The reading +sits in the bar's own colour while there is room, picks up the theme's +`secondary` when the CLI calls the window high, and `error` when it calls it +critical. The mark itself never changes colour: it says which provider, not how +full the plan is. Left on `Automatic`, the capsule follows the busiest provider, so what sits in the bar is the plan closest to running out. Raise `provider_limit` and it @@ -82,7 +84,7 @@ start = [ "clock", "ai_usage" ] Left and middle are the script's; right is a gesture binding, so it is listed in the widget's settings and can be pointed at any other action, or at `none`. -The panel is a two pane view. On the left is every provider you have set up, +The panel is a two-pane view. On the left is every provider you have set up, with its headline percentage. On the right is the selected one in detail: one card per reported metric, with a quota bar over a thinner "window elapsed" bar, so a fill that outruns the clock bar means quota is burning ahead of pace. @@ -93,9 +95,10 @@ a spinner while the CLI is answering. The gear beside it opens this plugin's settings. There is no close button: the panel closes when you click away from it or press the same widget again. -The list follows the CLI. A provider that `ai-usagebar` has no credential for -never appears, while one that is set up and failing keeps its row and shows the -error. +The list follows the CLI. A provider the CLI reports no API key for never +appears, because it was never set up. One that is set up and unreachable keeps +its row and shows the CLI's own words, so Antigravity with its local server +down says to open Antigravity rather than vanishing. The detail pane spells out what the CLI reports for that provider instead of implying it: the plan and account name, when it was fetched, a stale flag when @@ -176,5 +179,5 @@ The first test reads `safeText` and `scrub` out of `service.luau` rather than copying them, then checks that real credential shapes never survive, that ordinary readings pass through unchanged, and that scrubbing a four-vendor report stays inside the CPU budget the poller's async callback is given. The second exercises -the coalesced refresh state and checks that every configured provider has visual -metadata. An overrun in the first test loses the whole reading, not just time. +the coalesced refresh state and checks that every provider it knows about has a +glyph of its own rather than the fallback. An overrun in the first test loses the whole reading, not just time. From 60a5e79bad0b2250a86399aeec665537759c5bf9 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 15:36:55 -0300 Subject: [PATCH 33/35] ai-usagebar: retake the thumbnail against the current panel The previous one still drew the hourglass and calendar that 12a6a68 removed from the metric card headers. --- ai-usagebar/thumbnail.webp | Bin 46732 -> 46400 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ai-usagebar/thumbnail.webp b/ai-usagebar/thumbnail.webp index dd6f294b49923f37eb05d7284bc74ab197dc77ce..2696ddbf320751411d6a0087db42fe4f37b67711 100644 GIT binary patch literal 46400 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

?@VelIWHcoVgk?}S>5YvrBbt` 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 E0QYlsQ2+n{ From 0bb6c31e70462e553267a97585193cdf17036ade Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 15:41:18 -0300 Subject: [PATCH 34/35] ai-usagebar: give the provider rule something to divide with At outline and no spacing of its own the separator landed as a stray pixel between two readings, which is what it was there to stop. It now sits at on_surface/0.35 with three pixels either side. --- ai-usagebar/bar.luau | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index ec0060b2..b58dfe79 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -210,10 +210,13 @@ local function render() local children = {} for _, entry in ipairs(picked) do -- Two providers in one capsule run together on a gap alone, and the - -- countdown of the first reads as part of the second. + -- countdown of the first reads as part of the second. `outline` at the + -- row's own gap was too faint to divide anything, so the rule gets a + -- brighter tint and air of its own. if #children > 0 then children[#children + 1] = ui.separator({ orientation = "vertical", - color = "outline", spacing = 0 }) + color = "on_surface/0.35", + thickness = 1, spacing = 3 }) end children[#children + 1] = chip(entry) end From 852e12ecd994496dcf66adbac37bb9473edcc32b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 25 Aug 2026 15:44:10 -0300 Subject: [PATCH 35/35] ai-usagebar: divide providers with a dot, not a rule The vertical separator was measured at two pixels wide by one tall in a screenshot of the bar: ui.separator gets no height of its own inside a row, so no colour or spacing was ever going to make it visible. A label carries the font's height, and the panel already divides its chips with the same dot. --- ai-usagebar/bar.luau | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ai-usagebar/bar.luau b/ai-usagebar/bar.luau index b58dfe79..c39bfa43 100644 --- a/ai-usagebar/bar.luau +++ b/ai-usagebar/bar.luau @@ -210,13 +210,13 @@ local function render() local children = {} for _, entry in ipairs(picked) do -- Two providers in one capsule run together on a gap alone, and the - -- countdown of the first reads as part of the second. `outline` at the - -- row's own gap was too faint to divide anything, so the rule gets a - -- brighter tint and air of its own. + -- countdown of the first reads as part of the second. A dot, not a rule: + -- a vertical `ui.separator` in a row gets no height of its own and came + -- out two pixels wide by one tall. The panel divides its chips the same + -- way. if #children > 0 then - children[#children + 1] = ui.separator({ orientation = "vertical", - color = "on_surface/0.35", - thickness = 1, spacing = 3 }) + children[#children + 1] = ui.label({ text = "·", fontSize = 11, + color = "on_surface_variant" }) end children[#children + 1] = chip(entry) end