# Gitea Connector Plugin — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build a Claude Code plugin that lets collaborators connect to Seth's Gitea instance (`git.sethpc.xyz`) with their own accounts, bundling a bash CLI, guided setup, commit conventions, and a push-reminder hook. **Architecture:** Claude Code plugin with `.claude-plugin/plugin.json` manifest. Slash command (`/gitea-setup`) invokes an agent for interactive credential setup. Skill provides commit conventions. Hook reminds to push after commits. Bash CLI in `bin/gitea` handles all API/git operations over HTTPS to `git.sethpc.xyz`. **Tech Stack:** Bash (CLI), Markdown with YAML frontmatter (plugin components), JSON (plugin manifest, hook config), `curl`/`jq`/`git` (CLI dependencies). **Spec:** `docs/superpowers/specs/2026-04-01-gitea-connector-design.md` --- ## File Map | File | Action | Responsibility | |------|--------|----------------| | `.claude-plugin/plugin.json` | Create | Plugin manifest — name, description, paths to commands/agents/hooks | | `bin/gitea` | Create | Bash CLI — create, remote, push, delete, list repos. HTTPS-only to `git.sethpc.xyz`. Reads username/token/host from `~/.config/gitea/` | | `commands/gitea-setup.md` | Create | `/gitea-setup` slash command — invokes the setup agent | | `agents/gitea-setup.md` | Create | Interactive setup agent — dependency check, credential collection, CLI install, token validation | | `skills/gitea-workflow/SKILL.md` | Create | Commit conventions, CLI reference, credential safety, override mechanism | | `hooks/hooks.json` | Create | Hook configuration — PostToolUse push reminder (disabled by default) | | `hooks/scripts/push-reminder.sh` | Create | Detects `git commit` in Bash tool output, echoes push reminder | | `README.md` | Create | User-facing install + usage docs | --- ### Task 1: Plugin Manifest & Scaffold **Files:** - Create: `gitea-connector/.claude-plugin/plugin.json` - [ ] **Step 1: Create the `.claude-plugin` directory** ```bash mkdir -p /home/claude/bin/gitea-connector/.claude-plugin ``` - [ ] **Step 2: Write `plugin.json`** ```json { "name": "gitea-connector", "version": "1.0.0", "description": "Connect Claude Code to Seth's Gitea instance (git.sethpc.xyz) — guided setup, CLI, commit conventions, and a gentle nudge to push.", "author": { "name": "Seth", "url": "https://git.sethpc.xyz" }, "homepage": "https://git.sethpc.xyz/Seth/gitea-connector", "repository": "https://git.sethpc.xyz/Seth/gitea-connector", "license": "MIT", "commands": ["./commands"], "agents": ["./agents"], "hooks": "./hooks/hooks.json" } ``` - [ ] **Step 3: Create remaining directory structure** ```bash mkdir -p /home/claude/bin/gitea-connector/{commands,agents,skills/gitea-workflow,hooks/scripts,bin} ``` - [ ] **Step 4: Init git repo** ```bash cd /home/claude/bin/gitea-connector && git init ``` - [ ] **Step 5: Create `.gitignore`** ``` .backup/ *.swp *.swo ``` - [ ] **Step 6: Commit scaffold** ```bash cd /home/claude/bin/gitea-connector git add .claude-plugin/plugin.json .gitignore git commit -m "chore: scaffold plugin with manifest and directory structure" ``` --- ### Task 2: Bash CLI (`bin/gitea`) **Files:** - Create: `gitea-connector/bin/gitea` - [ ] **Step 1: Write the CLI script** Adapted from Seth's `~/bin/gitea`. Key changes: no LAN detection, reads username from config, silly help text. ```bash #!/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 # --- 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' } 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 } 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 "$@" ;; help|--help|-h) cmd_help ;; *) echo "Unknown command: $1 (try 'gitea help' — I promise it's helpful)" >&2; exit 1 ;; esac ``` - [ ] **Step 2: Make it executable** ```bash chmod +x /home/claude/bin/gitea-connector/bin/gitea ``` - [ ] **Step 3: Test CLI help output** ```bash /home/claude/bin/gitea-connector/bin/gitea help ``` Expected: The help text prints with all commands listed and silly flavor text. - [ ] **Step 4: Test CLI error handling (no config)** ```bash HOME=/tmp/fakehome /home/claude/bin/gitea-connector/bin/gitea list ``` Expected: Error about missing username, exit code 1. - [ ] **Step 5: Commit** ```bash cd /home/claude/bin/gitea-connector git add bin/gitea git commit -m "feat: add gitea bash CLI with silly personality" ``` --- ### Task 3: Setup Agent **Files:** - Create: `gitea-connector/agents/gitea-setup.md` - [ ] **Step 1: Write the setup agent** ```markdown --- name: gitea-setup description: Use this agent when a user needs to connect to the Gitea instance at git.sethpc.xyz. Walks them through API key generation, stores credentials, installs the CLI, and validates everything works. model: inherit --- You are the Gitea Setup Wizard — part helpful guide, part chaos goblin, fully committed to getting this human connected to git.sethpc.xyz without anyone crying. Your job: walk the user through connecting their Claude Code environment to Seth's Gitea instance. Be silly but get the job done. ## Setup Steps Follow these steps IN ORDER. Use AskUserQuestion for each input step. Do not skip steps. ### Step 1: Dependency Check Run these commands to verify dependencies: ```bash command -v curl >/dev/null 2>&1 && echo "curl: OK" || echo "curl: MISSING" command -v jq >/dev/null 2>&1 && echo "jq: OK" || echo "jq: MISSING" command -v git >/dev/null 2>&1 && echo "git: OK" || echo "git: MISSING" ``` If anything is MISSING, tell the user what to install and stop. Be dramatic about it: - "Oh no. You don't have `jq`. That's like showing up to a sword fight with a pool noodle. Install it with `apt install jq` (or your distro's equivalent) and run /gitea-setup again." If all OK, proceed with enthusiasm. ### Step 2: Check for Existing Config ```bash ls -la ~/.config/gitea/token 2>/dev/null && echo "EXISTS" || echo "NONE" ``` If EXISTS: Ask the user "You've already got Gitea credentials on file. Want to reconfigure? (This won't delete your repos, just your local config. Repos are forever. Well, until someone runs `gitea delete`.)" If they say no, stop gracefully: "Smart. If it ain't broke, don't authenticate it." ### Step 3: Collect Username Ask the user: "What's your Gitea username? (This is your login name at git.sethpc.xyz — not your email, not your display name, not your gamer tag.)" Store the response for later. ### Step 4: API Key Generation Tell the user exactly how to generate their token. Use this message: "Time to forge your API key. Here's the quest: 1. Open **https://git.sethpc.xyz** in your browser and log in 2. Click your **profile picture** (top right corner — yes, that little circle) 3. Go to **Settings** 4. Click **Applications** in the sidebar 5. Under **Manage Access Tokens**, type `claude-code` as the token name 6. For permissions, check **repo** (Read and Write) and **user** (Read) 7. Click **Generate Token** 8. **COPY THE TOKEN NOW** — Gitea only shows it once. It's like a shooting star, except it grants repo access. Paste the token here when you've got it." Wait for the token via AskUserQuestion. ### Step 5: Store Credentials Run these commands (substitute the actual username and token): ```bash mkdir -p ~/.config/gitea echo "USERNAME_HERE" > ~/.config/gitea/username echo "TOKEN_HERE" > ~/.config/gitea/token chmod 600 ~/.config/gitea/username ~/.config/gitea/token ``` Replace USERNAME_HERE and TOKEN_HERE with the actual values collected. ### Step 6: Install CLI Copy the CLI from the plugin to ~/bin/: ```bash mkdir -p ~/bin cp "${CLAUDE_PLUGIN_ROOT}/bin/gitea" ~/bin/gitea chmod +x ~/bin/gitea ``` Check if ~/bin is on PATH: ```bash echo "$PATH" | grep -q "$HOME/bin" && echo "ON_PATH" || echo "NOT_ON_PATH" ``` If NOT_ON_PATH, tell the user: "Heads up — `~/bin` isn't on your PATH. Add this to your `~/.bashrc` or `~/.zshrc`: ```bash export PATH=\"\$HOME/bin:\$PATH\" ``` Then restart your shell or run `source ~/.bashrc`. Otherwise `gitea` will just sit there, lonely and uncallable." ### Step 7: Validate Token Test the token by calling the Gitea API: ```bash curl -sf "https://git.sethpc.xyz/api/v1/user" \ -H "Authorization: token $(cat ~/.config/gitea/token)" | jq -r '.login' ``` Expected: The username they entered in Step 3. If the returned username matches: proceed to success. If it doesn't match: "Hmm, the token says you're `` but you told me ``. One of these is a lie. Which username is correct?" Then update ~/.config/gitea/username with their answer. If the request fails entirely: "That token didn't work. Gitea rejected it like a bouncer with standards. Double-check: - Did you copy the whole token? (No leading/trailing spaces?) - Did you actually click Generate? (The token only appears once!) - Can you reach https://git.sethpc.xyz in a browser? Generate a new token and try /gitea-setup again." ### Step 8: Success Print the victory message: "You're in! Here's what just happened: - Credentials stored in `~/.config/gitea/` (locked down, chmod 600, very secure, much wow) - CLI installed at `~/bin/gitea` - Token validated against git.sethpc.xyz — you are who you say you are **Available commands:** ``` gitea create [--private] [--description "..."] gitea remote gitea push gitea delete gitea list ``` **Quick start:** ```bash mkdir my-project && cd my-project && git init gitea create my-project --description "My cool thing" gitea remote my-project # write some code... git add -A && git commit -m "feat: initial commit" gitea push ``` Now go build something. The Gitea server believes in you. I believe in you. Your commits will be legendary." ``` - [ ] **Step 2: Commit** ```bash cd /home/claude/bin/gitea-connector git add agents/gitea-setup.md git commit -m "feat: add interactive gitea-setup agent with guided credential flow" ``` --- ### Task 4: Setup Command (`/gitea-setup`) **Files:** - Create: `gitea-connector/commands/gitea-setup.md` - [ ] **Step 1: Write the slash command** ```markdown --- name: gitea-setup description: Connect to Seth's Gitea instance (git.sethpc.xyz) — guided API key setup --- Launch the gitea-setup agent to walk the user through connecting to the Gitea instance at git.sethpc.xyz. This command sets up: 1. API token credentials 2. The `gitea` CLI tool 3. Validates the connection works Dispatch the gitea-setup agent to handle this interactively. ``` - [ ] **Step 2: Commit** ```bash cd /home/claude/bin/gitea-connector git add commands/gitea-setup.md git commit -m "feat: add /gitea-setup slash command" ``` --- ### Task 5: Gitea Workflow Skill **Files:** - Create: `gitea-connector/skills/gitea-workflow/SKILL.md` - [ ] **Step 1: Write the skill** ```markdown --- name: gitea-workflow description: This skill should be used when working in a git repository with a git.sethpc.xyz remote, when the user asks to "commit", "push", "create a repo", "gitea", or when making changes to code that should be committed. Provides commit conventions, CLI reference, and credential safety rules. --- # Gitea Workflow — Commit Conventions & CLI You're working with a repo hosted on Seth's Gitea instance (`git.sethpc.xyz`). Here's how we roll. ## Commit Conventions (default-on, overridable) These conventions apply unless the project's CLAUDE.md says otherwise: - **Conventional commits:** Prefix every commit message with a type: - `feat:` — new feature - `fix:` — bug fix - `docs:` — documentation only - `refactor:` — code restructuring, no behavior change - `test:` — adding or updating tests - `chore:` — maintenance, tooling, deps - **Commit immediately** — every meaningful change gets its own commit. Don't hoard changes. - **Always push after commit** — use `gitea push` or `git push`. Code that isn't pushed is just a fancy diary entry. - **No squashing** — every commit is a record. History is sacred (and also useful for debugging). - **No batching unrelated changes** — one commit, one concern. If you changed the auth system AND updated the README, that's two commits. ## CLI Reference The `gitea` CLI handles repo operations against git.sethpc.xyz: ``` gitea create [--private] [--description "..."] # Create a new repo gitea remote # Set/update git origin with token auth gitea push # Push current branch to origin gitea delete # Delete a repo (with confirmation) gitea list # List your repos ``` For creating a new project from scratch: ```bash mkdir my-project && cd my-project && git init gitea create my-project --description "What it does" gitea remote my-project # ... write code ... git add -A && git commit -m "feat: initial commit" gitea push ``` ## Credential Safety **Never commit these:** - `~/.config/gitea/token` or `~/.config/gitea/username` - `.env` files containing secrets - API keys, tokens, or passwords in any form **Always ensure `.gitignore` includes:** ``` .env .env.* *.key *.pem ``` If you see credentials in staged files, warn the user immediately and unstage them. ## Override Mechanism Users can override any convention above in their project's CLAUDE.md. For example, if a project's CLAUDE.md says "squash commits before merge", follow that instead of these defaults. Project-level instructions always win. ``` - [ ] **Step 2: Commit** ```bash cd /home/claude/bin/gitea-connector git add skills/gitea-workflow/SKILL.md git commit -m "feat: add gitea-workflow skill with commit conventions and CLI reference" ``` --- ### Task 6: Push Reminder Hook **Files:** - Create: `gitea-connector/hooks/hooks.json` - Create: `gitea-connector/hooks/scripts/push-reminder.sh` - [ ] **Step 1: Write the hook script** ```bash #!/usr/bin/env bash # push-reminder.sh — nudges you to push after a commit # Reads Bash tool output from stdin (JSON), checks if a git commit happened. set -euo pipefail INPUT=$(cat) TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""') TOOL_RESULT=$(echo "$INPUT" | jq -r '.tool_result // ""') # Only trigger on Bash tool [[ "$TOOL_NAME" == "Bash" ]] || exit 0 # Check if the output looks like a successful git commit if echo "$TOOL_RESULT" | grep -qE '^\[.+\]\s+\S+'; then echo '{"decision":"approve","systemMessage":"Commit detected! Friendly reminder: run `gitea push` to send it to git.sethpc.xyz. Unpushed commits are just really elaborate local notes."}' else echo '{"decision":"approve"}' fi ``` - [ ] **Step 2: Make it executable** ```bash chmod +x /home/claude/bin/gitea-connector/hooks/scripts/push-reminder.sh ``` - [ ] **Step 3: Write hooks.json (hook disabled by default)** ```json { "description": "Gitea connector hooks — push reminder after commits (opt-in)", "hooks": {} } ``` Note: The hook is shipped disabled. To enable, users update `hooks.json` to: ```json { "description": "Gitea connector hooks — push reminder after commits", "hooks": { "PostToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/push-reminder.sh", "timeout": 5 } ] } ] } } ``` Document this in the README (Task 7). - [ ] **Step 4: Commit** ```bash cd /home/claude/bin/gitea-connector git add hooks/hooks.json hooks/scripts/push-reminder.sh git commit -m "feat: add push-reminder hook (opt-in, disabled by default)" ``` --- ### Task 7: README **Files:** - Create: `gitea-connector/README.md` - [ ] **Step 1: Write the README** ```markdown # gitea-connector A Claude Code plugin for connecting to Seth's Gitea instance at `git.sethpc.xyz`. Sets up your credentials, installs a CLI, and teaches Claude Code your commit conventions — so you can focus on writing code instead of remembering how to push it. ## Install Add this plugin to your Claude Code setup: ```bash claude plugin add /path/to/gitea-connector ``` Or clone it: ```bash git clone https://git.sethpc.xyz/Seth/gitea-connector.git claude plugin add ./gitea-connector ``` ## Setup Run `/gitea-setup` in Claude Code. It'll walk you through everything: 1. Checks you have `curl`, `jq`, and `git` (the holy trinity) 2. Guides you through generating an API token on git.sethpc.xyz 3. Stores your credentials securely in `~/.config/gitea/` 4. Installs the `gitea` CLI to `~/bin/` 5. Validates your connection The whole thing takes about 2 minutes. Less if you type fast. ## CLI Usage After setup, you have the `gitea` command: ```bash gitea create my-project --private --description "Top secret stuff" gitea remote my-project # wire up git origin gitea push # push current branch gitea list # see your repos gitea delete old-project # delete (with confirmation) ``` ## Commit Conventions The plugin teaches Claude Code these conventions (override in your project's CLAUDE.md): - **Conventional commits:** `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:` - **Commit immediately** — don't hoard changes - **Always push** — unpushed commits are just elaborate local notes - **No squashing** — history is sacred - **One concern per commit** — no bundling unrelated changes ## Push Reminder Hook (opt-in) Want a nudge after every commit? Edit `hooks/hooks.json` in the plugin directory: ```json { "description": "Gitea connector hooks — push reminder after commits", "hooks": { "PostToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/push-reminder.sh", "timeout": 5 } ] } ] } } ``` ## Config Files All stored in `~/.config/gitea/`: | File | Purpose | |------|---------| | `token` | Your Gitea API token | | `username` | Your Gitea username | | `host` | (Optional) Host override, defaults to `git.sethpc.xyz` | ## Requirements - `curl`, `jq`, `git` - A Gitea account on `git.sethpc.xyz` (talk to Seth) - Claude Code ``` - [ ] **Step 2: Commit** ```bash cd /home/claude/bin/gitea-connector git add README.md git commit -m "docs: add README with install, setup, and usage instructions" ``` --- ### Task 8: Create Gitea Repo & Push **Files:** - No new files - [ ] **Step 1: Create the Gitea repo** ```bash cd /home/claude/bin/gitea-connector ~/bin/gitea create gitea-connector --description "Claude Code plugin for connecting to Seth's Gitea instance" ``` - [ ] **Step 2: Set remote** ```bash cd /home/claude/bin/gitea-connector ~/bin/gitea remote gitea-connector ``` - [ ] **Step 3: Push all commits** ```bash cd /home/claude/bin/gitea-connector ~/bin/gitea push ``` Expected: All commits pushed to `https://git.sethpc.xyz/Seth/gitea-connector`.