From fb57093a751569ef9d7264c1f13e1254877f655d Mon Sep 17 00:00:00 2001 From: SirBlobby Date: Mon, 27 Jul 2026 11:39:54 -0400 Subject: [PATCH] Add side panels with widget cards and Claude Code usage stats to quick settings --- README.md | 1 + ags/lib/claude-usage.sh | 34 ++++++++++++++++++ ags/style.css | 51 +++++++++++++++++++++----- ags/widget/ClaudeUsage.tsx | 56 +++++++++++++++++++++++++++++ ags/widget/QuickSettings.tsx | 70 ++++++++++++++++++++++++++++++++---- install.sh | 2 ++ scripts/blob_claude_key.sh | 40 +++++++++++++++++++++ 7 files changed, 238 insertions(+), 16 deletions(-) create mode 100755 ags/lib/claude-usage.sh create mode 100644 ags/widget/ClaudeUsage.tsx create mode 100755 scripts/blob_claude_key.sh diff --git a/README.md b/README.md index 97187ab..806bfe7 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ The installer automatically exposes scripts from the `scripts/` directory as glo - **`blob_glass [on|off|toggle]`**: A quick toggle to enable or disable window transparency on the fly. - **`blob_boot [path]`**: Safely updates your Plymouth boot splash image (defaults to `branding/boot_flash.png`) and rebuilds the `initramfs` (GRUB compatible via `mkinitcpio`). - **`blob_wifi`**: A streamlined script to connect to the GMU Eduroam Wi-Fi network using `iwd` and `systemd-resolved` (replaces NetworkManager). +- **`blob_claude_key `**: Stores your Claude Console Admin API key (`sk-ant-admin01-...`), used by the Claude Code Usage card in the quick settings panel to pull daily token and cost stats. ## Installation diff --git a/ags/lib/claude-usage.sh b/ags/lib/claude-usage.sh new file mode 100755 index 0000000..c0b6f8c --- /dev/null +++ b/ags/lib/claude-usage.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +KEY_FILE="$HOME/.config/ags/secrets/anthropic_admin_key" + +if [ ! -f "$KEY_FILE" ]; then + echo '{"available":false,"tokens":0,"costUsd":0}' + exit 0 +fi + +ADMIN_KEY=$(cat "$KEY_FILE") +TODAY_START=$(date -u '+%Y-%m-%dT00:00:00Z') +TODAY_END=$(date -u -d tomorrow '+%Y-%m-%dT00:00:00Z') + +USAGE_JSON=$(curl -fsS --max-time 6 \ + -H "anthropic-version: 2023-06-01" \ + -H "x-api-key: $ADMIN_KEY" \ + "https://api.anthropic.com/v1/organizations/usage_report/messages?starting_at=${TODAY_START}&ending_at=${TODAY_END}&bucket_width=1d") + +COST_JSON=$(curl -fsS --max-time 6 \ + -H "anthropic-version: 2023-06-01" \ + -H "x-api-key: $ADMIN_KEY" \ + "https://api.anthropic.com/v1/organizations/cost_report?starting_at=${TODAY_START}&ending_at=${TODAY_END}&bucket_width=1d") + +if [ -z "$USAGE_JSON" ] || [ -z "$COST_JSON" ]; then + echo '{"available":false,"tokens":0,"costUsd":0}' + exit 0 +fi + +TOKENS=$(echo "$USAGE_JSON" | jq '[(.data // [])[].results[]? | (.uncached_input_tokens // 0) + (.cache_read_input_tokens // 0) + (.cache_creation.ephemeral_1h_input_tokens // 0) + (.cache_creation.ephemeral_5m_input_tokens // 0) + (.output_tokens // 0)] | add // 0') + +COST_CENTS=$(echo "$COST_JSON" | jq '[(.data // [])[].results[]?.amount | tonumber] | add // 0') + +jq -n --argjson tokens "$TOKENS" --argjson costCents "$COST_CENTS" \ + '{available: true, tokens: $tokens, costUsd: ($costCents / 100)}' diff --git a/ags/style.css b/ags/style.css index 6544158..b63c92c 100644 --- a/ags/style.css +++ b/ags/style.css @@ -349,6 +349,48 @@ font-size: 13px; } +.qs-divider { + min-width: 1px; + background-color: alpha(@foreground, 0.15); +} + +.qs-side-panel { + min-width: 160px; +} + +.side-card { + background-color: alpha(@color0, 0.6); + border-radius: 10px; + padding: 10px; +} + +.side-card-icon { + color: @accent; + font-size: 14px; +} + +.side-card-title { + font-size: 12px; + font-weight: bold; + color: @foreground; +} + +.side-card-label { + font-size: 12px; + opacity: 0.8; +} + +.side-card-value { + font-size: 13px; + font-weight: bold; + color: @foreground; +} + +.side-card-empty { + font-size: 12px; + opacity: 0.6; +} + .control-center { min-width: 360px; } @@ -357,15 +399,6 @@ margin-bottom: 2px; } -.cc-uptime { - font-size: 13px; - color: @foreground; -} - -.cc-uptime-icon { - color: @accent; -} - .cc-clock { font-size: 22px; font-weight: bold; diff --git a/ags/widget/ClaudeUsage.tsx b/ags/widget/ClaudeUsage.tsx new file mode 100644 index 0000000..f94a0dc --- /dev/null +++ b/ags/widget/ClaudeUsage.tsx @@ -0,0 +1,56 @@ +import { Gtk } from "ags/gtk3" +import { createPoll } from "ags/time" +import { shell } from "../lib/utils" + +type ClaudeUsage = { + available: boolean + tokens: number + costUsd: number +} + +const usage = createPoll( + { available: false, tokens: 0, costUsd: 0 }, + 60000, + shell("bash $HOME/.config/ags/lib/claude-usage.sh"), + (stdout) => { + try { + return JSON.parse(stdout.trim()) + } catch { + return { available: false, tokens: 0, costUsd: 0 } + } + }, +) + +function formatTokens(tokens: number) { + if (tokens >= 1000000) return `${(tokens / 1000000).toFixed(2)}M` + if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}K` + return `${tokens}` +} + +export function ClaudeUsageCard() { + return ( + + + + + ) +} diff --git a/ags/widget/QuickSettings.tsx b/ags/widget/QuickSettings.tsx index f537853..dba4174 100644 --- a/ags/widget/QuickSettings.tsx +++ b/ags/widget/QuickSettings.tsx @@ -3,6 +3,7 @@ import { Astal, Gtk } from "ags/gtk3" import { createState } from "ags" import { createPoll, interval } from "ags/time" import { sh, shell } from "../lib/utils" +import { ClaudeUsageCard } from "./ClaudeUsage" export const QUICKSETTINGS_WINDOW = "quick-settings" @@ -306,6 +307,55 @@ function CalendarSection() { ) } +const uptime = createPoll("", 60000, shell("uptime -p"), (stdout) => + stdout.trim().replace(/^up /, ""), +) + +function UptimeCard() { + return ( + + + + + ) +} + +const weather = createPoll("Loading...", 600000, shell("omarchy-weather-status"), (stdout) => + stdout.trim(), +) + +function WeatherCard() { + return ( + + + + + ) +} + +function LeftPanel() { + return ( + + + + + ) +} + +function RightPanel() { + return ( + + + + ) +} + export default function QuickSettings() { const { TOP } = Astal.WindowAnchor @@ -321,13 +371,19 @@ export default function QuickSettings() { visible={false} application={app} > - -
- - - - - + + + + +
+ + + + + + + + ) diff --git a/install.sh b/install.sh index 38d5723..c857ce0 100755 --- a/install.sh +++ b/install.sh @@ -139,6 +139,8 @@ check_file "$SCRIPT_DIR/omarchy/hooks/theme-set" "$HOME_DIR/.config/omarchy/hook check_file "$SCRIPT_DIR/ags/app.ts" "$HOME_DIR/.config/ags/app.ts" "ags/app.ts" || check_status=1 check_file "$SCRIPT_DIR/ags/style.css" "$HOME_DIR/.config/ags/style.css" "ags/style.css" || check_status=1 check_file "$SCRIPT_DIR/ags/lib/utils.ts" "$HOME_DIR/.config/ags/lib/utils.ts" "ags/lib/utils.ts" || check_status=1 +check_file "$SCRIPT_DIR/ags/lib/claude-usage.sh" "$HOME_DIR/.config/ags/lib/claude-usage.sh" "ags/lib/claude-usage.sh" || check_status=1 +check_file "$SCRIPT_DIR/ags/widget/ClaudeUsage.tsx" "$HOME_DIR/.config/ags/widget/ClaudeUsage.tsx" "ags/widget/ClaudeUsage.tsx" || check_status=1 check_file "$SCRIPT_DIR/ags/widget/Media.tsx" "$HOME_DIR/.config/ags/widget/Media.tsx" "ags/widget/Media.tsx" || check_status=1 check_file "$SCRIPT_DIR/ags/widget/Notifications.tsx" "$HOME_DIR/.config/ags/widget/Notifications.tsx" "ags/widget/Notifications.tsx" || check_status=1 check_file "$SCRIPT_DIR/ags/widget/QuickSettings.tsx" "$HOME_DIR/.config/ags/widget/QuickSettings.tsx" "ags/widget/QuickSettings.tsx" || check_status=1 diff --git a/scripts/blob_claude_key.sh b/scripts/blob_claude_key.sh new file mode 100755 index 0000000..e8aa78d --- /dev/null +++ b/scripts/blob_claude_key.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +KEY_FILE="$HOME/.config/ags/secrets/anthropic_admin_key" + +usage() { + echo "Usage: blob_claude_key " + echo " set Store your Anthropic Admin API key (sk-ant-admin01-...)" + echo " show Print the stored key, masked" + echo " clear Remove the stored key" + exit 1 +} + +case "$1" in + set) + key="$2" + if [ -z "$key" ]; then + echo "Usage: blob_claude_key set " + exit 1 + fi + mkdir -p "$(dirname "$KEY_FILE")" + printf '%s' "$key" > "$KEY_FILE" + chmod 600 "$KEY_FILE" + echo "Admin key saved to $KEY_FILE" + ;; + show) + if [ -f "$KEY_FILE" ]; then + key=$(cat "$KEY_FILE") + echo "${key:0:14}...${key: -4}" + else + echo "No admin key stored" + fi + ;; + clear) + rm -f "$KEY_FILE" + echo "Admin key removed" + ;; + *) + usage + ;; +esac