-- 1. Identify it uniquely (Fresh ID forces macOS to update the hover title) GeminiMonitorItem = hs.menubar.new(true, "GeminiMonitorV3") -- 2. Set the hover text (Tooltip) if GeminiMonitorItem then GeminiMonitorItem:setTooltip("Gemini Usage Monitor") end local JSON_KEY_PATH = os.getenv("HOME") .. "/.hammerspoon/gcp-key.json" local PROJECT_ID = "gen-lang-client-0842463959" local UPDATE_INTERVAL = 300 -- 3. Setup the Icon -- "NSActionTemplate" is the standard 'Gear' icon local geminiStar = hs.image.imageFromName("NSActionTemplate") if geminiStar then -- 'template' allows the icon to switch between black and white for Dark Mode geminiStar:template(true) GeminiMonitorItem:setIcon(geminiStar) end -- 2026 Flash Preview Pricing local PRICING_ESTIMATES = { ["Gemini 3 Flash Preview"] = 0.0010, ["Gemini 3.1 Pro"] = 0.0450, ["Gemini 2.0 Ultra"] = 0.1500 } local selectedModel = "Gemini 3 Flash Preview" local stats = { requests = 0, lastUpdate = "Never" } local authToken = nil -- POPUP ON LOAD hs.alert.show("GeminiMonitor Loaded", 2) local function getAccessToken(callback) local keyFile = io.open(JSON_KEY_PATH, "r") if not keyFile then return end local keyData = hs.json.decode(keyFile:read("*all")) keyFile:close() local now = os.time() local header = hs.base64.encode(hs.json.encode({alg="RS256", typ="JWT"})) local claim = hs.base64.encode(hs.json.encode({iss=keyData.client_email, scope="https://www.googleapis.com/auth/monitoring.read", aud="https://oauth2.googleapis.com/token", exp=now+3600, iat=now})) local sign_cmd = string.format("echo -n '%s' | openssl dgst -sha256 -sign <(echo '%s') -binary | openssl base64", header.."."..claim, keyData.private_key) hs.task.new("/bin/bash", function(c, out, e) local sig = out:gsub("%s+", ""):gsub("/", "_"):gsub("+", "-"):gsub("=", "") hs.http.asyncPost("https://oauth2.googleapis.com/token", "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion="..header.."."..claim.."."..sig, {["Content-Type"]="application/x-www-form-urlencoded"}, function(s, b) local res = hs.json.decode(b) if res and res.access_token then callback(res.access_token) end end) end, {"-c", sign_cmd}):start() end local function updateUsage() if not authToken then getAccessToken(function(token) authToken = token; updateUsage() end) return end local endTime = os.date("!%Y-%m-%dT%H:%M:%SZ") local startTime = os.date("!%Y-%m-%dT%H:%M:%SZ", os.time() - 86400) local filter = 'resource.type="consumed_api" AND metric.type="serviceruntime.googleapis.com/api/request_count" AND resource.labels.service="generativelanguage.googleapis.com"' local url = string.format("https://monitoring.googleapis.com/v3/projects/%s/timeSeries?filter=%s&interval.startTime=%s&interval.endTime=%s", PROJECT_ID, hs.http.encodeForQuery(filter), startTime, endTime) hs.http.asyncGet(url, {Authorization = "Bearer "..authToken}, function(status, body) if status ~= 200 then return end local data = hs.json.decode(body) local r = 0 if data.timeSeries then for _, s in ipairs(data.timeSeries) do for _, p in ipairs(s.points) do r = r + (tonumber(p.value.int64Value) or 0) end end end stats.requests = r stats.lastUpdate = os.date("%H:%M") local estCost = r * PRICING_ESTIMATES[selectedModel] GeminiMonitorItem:setTitle(string.format(" %d ($%0.3f)", r, estCost)) end) end GeminiMonitorItem:setMenu(function() return { { title = "GEMINI FLASH MONITOR", disabled = true }, { title = "Project: " .. PROJECT_ID, disabled = true }, { title = "-" }, { title = "24H Requests: " .. stats.requests }, { title = string.format("Est. Cost: $%0.4f", stats.requests * PRICING_ESTIMATES[selectedModel]) }, { title = "-" }, { title = "ACTIVE MODEL:", disabled = true }, { title = "Gemini 3 Flash Preview", checked = (selectedModel == "Gemini 3 Flash Preview"), fn = function() selectedModel = "Gemini 3 Flash Preview"; updateUsage() end }, { title = "Gemini 3.1 Pro", checked = (selectedModel == "Gemini 3.1 Pro"), fn = function() selectedModel = "Gemini 3.1 Pro"; updateUsage() end }, { title = "-" }, { title = "Refresh Now", fn = updateUsage }, { title = "Last Sync: " .. stats.lastUpdate, disabled = true } } end) updateUsage() if GeminiTimer then GeminiTimer:stop() end GeminiTimer = hs.timer.doEvery(UPDATE_INTERVAL, updateUsage)