110 lines
4.6 KiB
Lua
110 lines
4.6 KiB
Lua
-- 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)
|