diff --git a/bin/gitea b/bin/gitea new file mode 100755 index 0000000..322bcf3 --- /dev/null +++ b/bin/gitea @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +# gitea — your friendly neighborhood Gitea CLI +# It creates repos. It pushes code. It occasionally judges your commit messages. +# Usage: gitea [options] + +set -euo pipefail + +cmd_help() { + cat <<'EOF' +gitea — your friendly neighborhood Gitea CLI + +Usage: gitea [options] + +Commands: + create [--private] [--description "..."] Bring a new repo into this world + remote Wire up git origin with token auth + push Yeet current branch to origin + delete Send a repo into the void (confirms first) + list Show your repos (sorted by last updated) + +Examples: + gitea create my-project --private --description "Top secret stuff" + gitea remote my-project + gitea push + +Config files (~/.config/gitea/): + token Your API token (required) + username Your Gitea username (required) + host Gitea host override (default: git.sethpc.xyz) + +First time? Run /gitea-setup in Claude Code to get started. +EOF +} + +# Short-circuit help/unknown before loading config +case "${1:-help}" in + help|--help|-h) cmd_help; exit 0 ;; + create|remote|push|delete|list|ls) ;; + *) echo "Unknown command: $1 (try 'gitea help' — I promise it's helpful)" >&2; exit 1 ;; +esac + +# --- Config --- +GITEA_HOST="git.sethpc.xyz" +if [[ -f "$HOME/.config/gitea/host" ]]; then + GITEA_HOST="$(< "$HOME/.config/gitea/host")" + GITEA_HOST="${GITEA_HOST%%[[:space:]]}" +fi + +GITEA_USER="" +if [[ -f "$HOME/.config/gitea/username" ]]; then + GITEA_USER="$(< "$HOME/.config/gitea/username")" + GITEA_USER="${GITEA_USER%%[[:space:]]}" +fi +[[ -z "$GITEA_USER" ]] && { echo "ERROR: No username found. Run /gitea-setup or put your username in ~/.config/gitea/username" >&2; exit 1; } + +GITEA_TOKEN="${GITEA_TOKEN:-}" +if [[ -z "$GITEA_TOKEN" && -f "$HOME/.config/gitea/token" ]]; then + GITEA_TOKEN="$(< "$HOME/.config/gitea/token")" + GITEA_TOKEN="${GITEA_TOKEN%%[[:space:]]}" +fi +[[ -z "$GITEA_TOKEN" ]] && { echo "ERROR: No token found. Run /gitea-setup or put your token in ~/.config/gitea/token" >&2; exit 1; } + +API_BASE="https://${GITEA_HOST}/api/v1" + +# --- Helpers --- +api() { + local method="$1" endpoint="$2" + shift 2 + curl -sf -X "$method" "${API_BASE}${endpoint}" \ + -H "Content-Type: application/json" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + "$@" +} + +# --- Commands --- +cmd_create() { + local name="" private=false description="" + while [[ $# -gt 0 ]]; do + case "$1" in + --private|-p) private=true; shift ;; + --public) private=false; shift ;; + --description|-d) description="$2"; shift 2 ;; + -*) echo "Unknown option: $1 (I don't know what that means and I'm too proud to guess)" >&2; exit 1 ;; + *) name="$1"; shift ;; + esac + done + [[ -z "$name" ]] && { echo "Usage: gitea create [--private] [--description \"...\"]"; echo " (You forgot the name. The repo needs a name. We all need names.)" >&2; exit 1; } + + local payload + payload=$(jq -n \ + --arg name "$name" \ + --argjson private "$private" \ + --arg desc "$description" \ + '{name: $name, private: $private, description: $desc, auto_init: false}') + + local resp + resp=$(api POST "/user/repos" -d "$payload") || { echo "FAIL: Gitea said no. Check your token and network." >&2; exit 1; } + + local html_url + html_url=$(echo "$resp" | jq -r '.html_url') + echo "Created: ${html_url}" + echo "Clone: https://${GITEA_HOST}/${GITEA_USER}/${name}.git" + echo "" + echo "Next up: 'gitea remote ${name}' to wire up your origin. You know the drill." +} + +cmd_remote() { + local name="${1:-}" + [[ -z "$name" ]] && { echo "Usage: gitea remote "; echo " (Which repo? I can't read minds. Yet.)" >&2; exit 1; } + + local url="https://${GITEA_HOST}/${GITEA_USER}/${name}.git" + + git config credential.helper 'store' + local cred_file="${HOME}/.git-credentials" + local cred_entry="https://${GITEA_USER}:${GITEA_TOKEN}@${GITEA_HOST}" + if ! grep -qF "${GITEA_HOST}" "$cred_file" 2>/dev/null; then + echo "$cred_entry" >> "$cred_file" + chmod 600 "$cred_file" + fi + + if git remote get-url origin &>/dev/null; then + git remote set-url origin "$url" + echo "Updated origin -> ${url}" + else + git remote add origin "$url" + echo "Added origin -> ${url}" + fi + echo "You're locked and loaded. 'gitea push' when ready." +} + +cmd_push() { + local branch + branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || { echo "This isn't a git repo. I checked." >&2; exit 1; } + echo "Pushing ${branch} to origin... hold onto your bits." + git push -u origin "$branch" +} + +cmd_delete() { + local name="${1:-}" + [[ -z "$name" ]] && { echo "Usage: gitea delete "; echo " (Delete what? Be specific. Destruction requires precision.)" >&2; exit 1; } + + read -rp "Delete ${GITEA_USER}/${name}? This is permanent. No take-backs. [y/N] " confirm + [[ "$confirm" =~ ^[Yy]$ ]] || { echo "Crisis averted. Repo lives another day."; exit 0; } + + if api DELETE "/repos/${GITEA_USER}/${name}"; then + echo "Deleted: ${GITEA_USER}/${name}. It's gone. Pour one out." + else + echo "FAIL: Could not delete ${GITEA_USER}/${name}. Maybe it's already gone? Maybe it's fighting back?" >&2; exit 1 + fi +} + +cmd_list() { + local resp + resp=$(api GET "/user/repos?limit=50&sort=updated") || { echo "FAIL: Couldn't fetch repos. Is Gitea awake?" >&2; exit 1; } + echo "$resp" | jq -r '.[] | "\(.name)\t\(if .private then "private" else "public" end)\t\(.description // "")"' | column -t -s$'\t' +} + +case "${1:-help}" in + create) shift; cmd_create "$@" ;; + remote) shift; cmd_remote "$@" ;; + push) shift; cmd_push "$@" ;; + delete) shift; cmd_delete "$@" ;; + list|ls) shift; cmd_list "$@" ;; +esac