Skip to content

Instantly share code, notes, and snippets.

@simoninglis
Created January 30, 2026 10:09
Show Gist options
  • Select an option

  • Save simoninglis/5f2e00d69c9042db84d35c6fbdc3d03b to your computer and use it in GitHub Desktop.

Select an option

Save simoninglis/5f2e00d69c9042db84d35c6fbdc3d03b to your computer and use it in GitHub Desktop.
teax CI status for tmux - Real-time Gitea workflow visibility
#!/usr/bin/env bash
# Description: Output Gitea CI workflow status for tmux status bar
# Author: https://github.com/simoninglis/teax
# License: MIT
#
# Dependencies:
# - teax: Gitea CLI companion (pip install git+https://github.com/simoninglis/teax.git)
# - tea: Official Gitea CLI (must be configured with `tea login add`)
# - jq: JSON processor
#
# Installation:
# 1. Place this script in your PATH (e.g., ~/.local/bin/)
# 2. Make executable: chmod +x teax-ci-status
# 3. Configure TEAX_CI_REPO below or set via environment
# 4. Add to tmux status bar:
# tmux set-option status-right '#(~/.local/bin/teax-ci-status) | %H:%M'
#
# Output format: B:✓ C:✓ D:✗ V:✓ +2
# - B = Build workflow
# - C = CI workflow
# - D = Deploy workflow
# - V = Verify workflow
# - +N = N additional workflows (all passing or count of failures)
# - ✓ = passing (green)
# - ✗ = failing (red)
# - ? = unknown/error
set -euo pipefail
#=============================================================================
# USER CONFIGURATION - Customize these settings
#=============================================================================
# Repository to monitor (required)
# Format: owner/repo (e.g., "singlis/myproject")
TEAX_CI_REPO="${TEAX_CI_REPO:-}"
# Cache settings
CACHE_TTL="${TEAX_CI_CACHE_TTL:-120}" # Seconds between API calls (default: 2 min)
# Workflow name mappings (customize for your project)
# Maps workflow filename to single-letter display code
declare -A WORKFLOW_MAP=(
["build.yml"]="B"
["ci.yml"]="C"
["deploy-staging.yml"]="D"
["deploy-prod.yml"]="P"
["verify-staging.yml"]="V"
["verify-prod.yml"]="W"
)
# Priority order for display (first N shown, rest summarized as +N)
PRIORITY_WORKFLOWS=("build.yml" "ci.yml" "deploy-staging.yml" "verify-staging.yml")
MAX_DISPLAY=4
# Tmux color styles
STYLE_PASS="${TEAX_CI_STYLE_PASS:-fg=green}"
STYLE_FAIL="${TEAX_CI_STYLE_FAIL:-fg=red,bold}"
STYLE_UNKNOWN="${TEAX_CI_STYLE_UNKNOWN:-fg=yellow}"
STYLE_DEFAULT="${TEAX_CI_STYLE_DEFAULT:-default}"
# SSL verification (set to 1 for self-signed certs)
TEAX_INSECURE="${TEAX_INSECURE:-0}"
#=============================================================================
# INTERNAL CONFIGURATION
#=============================================================================
readonly CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/teax-ci-status"
readonly CACHE_FILE="${CACHE_DIR}/status"
#=============================================================================
# FUNCTIONS
#=============================================================================
check_dependencies() {
local missing=()
for cmd in teax jq; do
if ! command -v "$cmd" &>/dev/null; then
missing+=("$cmd")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
echo "#[fg=red]ERR:${missing[*]}#[$STYLE_DEFAULT]"
exit 1
fi
}
ensure_cache_dir() {
if [[ ! -d "$CACHE_DIR" ]]; then
mkdir -p "$CACHE_DIR"
chmod 700 "$CACHE_DIR"
fi
}
cache_valid() {
if [[ ! -f "$CACHE_FILE" ]]; then
return 1
fi
local cache_age
cache_age=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) ))
[[ "$cache_age" -lt "$CACHE_TTL" ]]
}
format_status() {
local status="$1"
case "$status" in
success)
echo "#[$STYLE_PASS]✓#[$STYLE_DEFAULT]"
;;
failure|error)
echo "#[$STYLE_FAIL]✗#[$STYLE_DEFAULT]"
;;
*)
echo "#[$STYLE_UNKNOWN]?#[$STYLE_DEFAULT]"
;;
esac
}
fetch_status() {
if [[ -z "$TEAX_CI_REPO" ]]; then
echo "#[$STYLE_UNKNOWN]CI:no-repo#[$STYLE_DEFAULT]"
return 1
fi
local teax_cmd="teax"
if [[ "$TEAX_INSECURE" == "1" ]]; then
teax_cmd="TEAX_INSECURE=1 teax"
fi
# Fetch workflow status as JSON
local response
response=$(eval "$teax_cmd runs status -r '$TEAX_CI_REPO' -o json" 2>/dev/null) || {
echo "#[$STYLE_UNKNOWN]CI:err#[$STYLE_DEFAULT]"
return 1
}
if [[ -z "$response" ]] || [[ "$response" == "null" ]]; then
echo "#[$STYLE_UNKNOWN]CI:--#[$STYLE_DEFAULT]"
return 1
fi
# Parse and format output
local output=""
local shown=0
local extra_pass=0
local extra_fail=0
# Process priority workflows first
for wf in "${PRIORITY_WORKFLOWS[@]}"; do
local code="${WORKFLOW_MAP[$wf]:-}"
if [[ -z "$code" ]]; then
continue
fi
local status
status=$(echo "$response" | jq -r --arg wf "$wf" '.[] | select(.workflow == $wf) | .status' 2>/dev/null)
if [[ -n "$status" && "$status" != "null" ]]; then
if [[ $shown -lt $MAX_DISPLAY ]]; then
output+="${code}:$(format_status "$status") "
((shown++))
else
if [[ "$status" == "success" ]]; then
((extra_pass++))
else
((extra_fail++))
fi
fi
fi
done
# Count remaining workflows not in priority list
local all_workflows
all_workflows=$(echo "$response" | jq -r '.[].workflow' 2>/dev/null)
while IFS= read -r wf; do
local in_priority=0
for pwf in "${PRIORITY_WORKFLOWS[@]}"; do
if [[ "$wf" == "$pwf" ]]; then
in_priority=1
break
fi
done
if [[ $in_priority -eq 0 ]]; then
local status
status=$(echo "$response" | jq -r --arg wf "$wf" '.[] | select(.workflow == $wf) | .status' 2>/dev/null)
if [[ "$status" == "success" ]]; then
((extra_pass++))
else
((extra_fail++))
fi
fi
done <<< "$all_workflows"
# Add extra count if any
local extra_total=$((extra_pass + extra_fail))
if [[ $extra_total -gt 0 ]]; then
if [[ $extra_fail -gt 0 ]]; then
output+="#[$STYLE_FAIL]+$extra_fail✗#[$STYLE_DEFAULT]"
else
output+="+$extra_total"
fi
fi
# Trim trailing space
output="${output% }"
if [[ -z "$output" ]]; then
echo "#[$STYLE_UNKNOWN]CI:--#[$STYLE_DEFAULT]"
return 1
fi
# Cache the result
ensure_cache_dir
local tmp_file
tmp_file=$(mktemp "${CACHE_DIR}/status.XXXXXX")
echo "$output" > "$tmp_file"
mv -f "$tmp_file" "$CACHE_FILE"
chmod 600 "$CACHE_FILE"
echo "$output"
}
main() {
check_dependencies
# Return cached value if valid
if cache_valid; then
cat "$CACHE_FILE"
return 0
fi
# Fetch fresh status
if ! fetch_status; then
# On failure, use stale cache if available
if [[ -f "$CACHE_FILE" ]]; then
cat "$CACHE_FILE"
echo "~" # Stale indicator
fi
fi
}
main "$@"
#!/usr/bin/env bash
# Example tmux session script with CI status integration
#
# This shows how to integrate teax-ci-status into your project's tmux session.
# Customize TEAX_CI_REPO for your project.
SESSION="myproject"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Set the repository for CI status monitoring
export TEAX_CI_REPO="singlis/myproject"
# Optional: For self-signed Gitea certificates
# export TEAX_INSECURE=1
tmux has-session -t "=$SESSION" 2>/dev/null
if [ $? != 0 ]; then
# Create session with windows
tmux new-session -d -s $SESSION -c "$SCRIPT_DIR" -n nvim
tmux new-window -t $SESSION:1 -n claude -c "$SCRIPT_DIR"
tmux new-window -t $SESSION:2 -n shell -c "$SCRIPT_DIR"
# Launch editor in first window
tmux send-keys -t $SESSION:0 'nvim .' C-m
# Configure status bar with CI status
# Format: B:✓ C:✓ D:✓ V:✓ +2 | 14:30 30-Jan
tmux set-option -t $SESSION status-right-length 60
tmux set-option -t $SESSION status-right '#(~/.local/bin/teax-ci-status) │ %H:%M %d-%b'
# Optional: Also show Claude usage if you have that script
# tmux set-option -t $SESSION status-right '#(~/.local/bin/claude-usage) │ #(~/.local/bin/teax-ci-status) │ %H:%M %d-%b'
fi
# Attach to session
tmux attach-session -t "=$SESSION:0"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment