#!/usr/bin/env bash # Refresh the agent usage records the bar panel watches. # # Each panama-agent-usage- collector prints one display-ready JSON # record; this runs the enabled ones in parallel and writes their output to # $XDG_STATE_HOME/panama/agents/usage/.json. Adding an agent is adding a # collector -- the panel picks up any record that appears in that directory, and # no QML changes. # # Adapted from Omarchy (bin/omarchy-agent-usage-update). # # Copyright (c) David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # ── Why this reads settings.json rather than being told ────────────────────── # # Per-agent collection is a preference, and a preference is read at the moment # it matters -- the same rule the power button follows. A collector the user # turned off must not run at all: it is the thing that reads a credential file # and makes a network call, so "off" has to mean "no process", not "a process # whose output is discarded". # # ── Why a disabled agent's record is deleted ───────────────────────────────── # # The panel watches the directory. Leaving yesterday's record behind after the # collector is switched off would keep showing numbers nothing is refreshing, # which is worse than an empty tab. set -uo pipefail self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Seams. The contract points all three somewhere hermetic; nothing else does. COLLECTOR_DIR="${PANAMA_AGENT_USAGE_COLLECTORS:-$self_dir}" USAGE_DIR="${PANAMA_AGENT_USAGE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/panama/agents/usage}" SETTINGS="${PANAMA_SETTINGS:-${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json}" command -v jq >/dev/null 2>&1 || { printf 'panama-agent-usage-update: jq is required\n' >&2 exit 1 } mkdir -p "$USAGE_DIR" || exit 1 flags=() only=() while (( $# > 0 )); do case "$1" in --force | --limits-only) flags+=("$1") ;; --help | -h) printf 'usage: panama-agent-usage-update [--force] [--limits-only] [agent...]\n' exit 0 ;; -*) printf 'panama-agent-usage-update: unknown option %s\n' "$1" >&2 exit 2 ;; *) only+=("$1") ;; esac shift done # `.key // true` is wrong here: jq's alternative operator fires on false as well # as on null, so a collector the user switched off would read as enabled. Absent # is the only case that means "use the default". enabled() { local agent="$1" key answer key="agentUsage$(tr '[:lower:]' '[:upper:]' <<<"${agent:0:1}")${agent:1}" [[ -r "$SETTINGS" ]] || return 0 answer="$(jq -r --arg key "$key" \ 'if has($key) then (.[$key] | tostring) else "true" end' "$SETTINGS" 2>/dev/null)" || return 0 [[ "$answer" == "true" ]] } requested() { local agent="$1" candidate (( ${#only[@]} == 0 )) && return 0 for candidate in "${only[@]}"; do [[ "$candidate" == "$agent" ]] && return 0 done return 1 } # A record is replaced only by a whole, parseable one. A collector that dies # halfway, or prints a stack trace, leaves the previous numbers standing rather # than blanking the panel: mktemp + mv makes the swap atomic, so a reader never # sees a half-written file, and the jq gate makes sure the thing being moved # into place is a record at all. collect() { local collector="$1" agent="$2" record tmp if ! record="$("$collector" "${flags[@]}" 2>/dev/null)" \ || [[ -z "$record" ]] \ || ! jq -e . >/dev/null 2>&1 <<<"$record"; then printf 'panama-agent-usage-update: the %s collector produced no usable record\n' "$agent" >&2 return 1 fi tmp="$(mktemp "$USAGE_DIR/.$agent.XXXXXX")" || return 1 printf '%s\n' "$record" >"$tmp" || { rm -f "$tmp"; return 1; } chmod 644 "$tmp" mv "$tmp" "$USAGE_DIR/$agent.json" } pids=() status=0 for collector in "$COLLECTOR_DIR"/panama-agent-usage-*; do [[ -x "$collector" ]] || continue agent="${collector##*/panama-agent-usage-}" [[ "$agent" == "update" ]] && continue requested "$agent" || continue if ! enabled "$agent"; then rm -f "$USAGE_DIR/$agent.json" continue fi collect "$collector" "$agent" & pids+=($!) done for pid in "${pids[@]}"; do wait "$pid" || status=1 done exit "$status"