Initial commit of Hammerspoon config

This commit is contained in:
Franco Pellicciotti
2026-05-14 18:59:23 -04:00
commit 8a9f5c37ff
683 changed files with 180195 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
local obj = {}
obj.__index = obj
-- CONFIGURATION
local PROJECT_ID = "gen-lang-client-0842463959"
local PROJECT_NAME = "Google API Monitor"
local menu = hs.menubar.new()
local accessToken = nil
-- Data storage
local history = {}
local lastMetrics = {
requests = 0,
throughput = "0 B",
errors = 0,
latency = "0 ms",
quotaPercent = 0
}
-- Helper: Format bytes
local function formatBytes(bytes)
if not bytes or bytes == 0 then return "0 B" end
local units = {"B", "KB", "MB", "GB"}
local i = 1
while bytes > 1024 and i < #units do
bytes = bytes / 1024
i = i + 1
end
return string.format("%.2f %s", bytes, units[i])
end
-- Helper: ASCII Trend Graph (Traffic)
local function getTrendString()
if #history < 2 then return "▂ Loading..." end
local bars = {" ", "", "", "", "", "", "", ""}
local maxVal = 0
for _, v in ipairs(history) do if v > maxVal then maxVal = v end end
local res = ""
for _, v in ipairs(history) do
local idx = 1
if maxVal > 0 then
idx = math.floor((v / maxVal) * 7) + 1
end
res = res .. bars[idx]
end
return res
end
-- Helper: Token Refresh
local function refreshAuth(callback)
local scriptPath = os.getenv("HOME") .. "/.hammerspoon/get_token.py"
hs.task.new("/usr/bin/python3", function(exitCode, stdOut, stdErr)
if exitCode == 0 then
accessToken = stdOut:gsub("%s+", "")
if callback then callback() end
else
print("Python Auth Error: " .. (stdErr or "Unknown"))
end
end, {scriptPath}):start()
end
-- Generic Fetch
local function fetchMetric(metricType, callback)
if not accessToken then return end
local now = os.date("!%Y-%m-%dT%H:%M:%SZ")
local start = os.date("!%Y-%m-%dT%H:%M:%SZ", os.time() - 3600)
local baseUrl = "https://monitoring.googleapis.com/v3/projects/" .. PROJECT_ID .. "/timeSeries"
local fullUrl = baseUrl .. "?" ..
"filter=" .. hs.http.encodeForQuery('metric.type="' .. metricType .. '"') ..
"&interval.startTime=" .. hs.http.encodeForQuery(start) ..
"&interval.endTime=" .. hs.http.encodeForQuery(now)
hs.http.asyncGet(fullUrl, {Authorization = "Bearer " .. accessToken}, function(status, body)
if status == 200 then
local data = hs.json.decode(body)
local total, count = 0, 0
if data and data.timeSeries then
for _, series in ipairs(data.timeSeries) do
for _, point in ipairs(series.points or {}) do
local val = point.value.int64Value or point.value.doubleValue or 0
total = total + tonumber(val)
count = count + 1
end
end
end
callback(total, count)
elseif status == 401 or status == 403 then
accessToken = nil
end
end)
end
function obj.updateMenu()
local trend = getTrendString()
local qColor = {white=1}
if lastMetrics.quotaPercent >= 90 then qColor = {red=1}
elseif lastMetrics.quotaPercent >= 75 then qColor = {orange=1} end
menu:setMenu({
{ title = "Project: " .. PROJECT_NAME, disabled = true },
{ title = "Traffic Trend: " .. trend, disabled = true },
{ title = "-" },
{ title = "Quota Utilization: " .. lastMetrics.quotaPercent .. "%", font={color=qColor} },
{ title = "Total Requests (1h): " .. lastMetrics.requests },
{ title = "Average Latency: " .. lastMetrics.latency },
{ title = "Data Throughput: " .. lastMetrics.throughput },
{ title = "Errors (1h): " .. lastMetrics.errors, font = { color = (lastMetrics.errors > 0 and {red=1} or {white=1}) } },
{ title = "-" },
{ title = "Refresh Now", fn = function() obj.updateQuota() end },
{ title = "Open Google Console", fn = function() hs.urlevent.openURL("https://console.cloud.google.com/apis/dashboard?project=" .. PROJECT_ID) end }
})
end
function obj.updateQuota()
if not accessToken then
refreshAuth(function() obj.updateQuota() end)
return
end
-- 1. Fetch Traffic
fetchMetric("serviceruntime.googleapis.com/api/request_count", function(reqCount)
lastMetrics.requests = reqCount
-- Update history for trend bars
table.insert(history, reqCount)
if #history > 10 then table.remove(history, 1) end
-- 2. Fetch Quota Usage vs Limit
fetchMetric("serviceruntime.googleapis.com/quota/allocation/usage", function(usage)
fetchMetric("serviceruntime.googleapis.com/quota/limit", function(limit)
lastMetrics.quotaPercent = (limit > 0) and math.floor((usage / limit) * 100) or 0
-- 3. Fetch Throughput and Latency
fetchMetric("serviceruntime.googleapis.com/api/response_sizes", function(bytes)
lastMetrics.throughput = formatBytes(bytes)
fetchMetric("serviceruntime.googleapis.com/api/request_latencies", function(totalLat, count)
lastMetrics.latency = count > 0 and string.format("%.2f ms", (totalLat / count) * 1000) or "0 ms"
-- 4. Errors
local errFilter = 'serviceruntime.googleapis.com/api/request_count" AND metric.labels.response_code_class!="2xx'
fetchMetric(errFilter, function(errCount)
lastMetrics.errors = errCount
-- Title Icon Logic
local statusIcon = ""
if errCount > 0 then statusIcon = "🔴 "
elseif lastMetrics.quotaPercent >= 80 then statusIcon = "⚠️ "
end
menu:setTitle(statusIcon .. "G-API: " .. reqCount)
obj.updateMenu()
end)
end)
end)
end)
end)
end)
end
function obj.init()
if menu then
menu:setTitle("G-API: ...")
obj.updateMenu()
-- Polling every 5 minutes
obj.timer = hs.timer.doEvery(300, function() obj.updateQuota() end)
obj.updateQuota()
end
end
return obj