24 KiB
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-plugindirectory
mkdir -p /home/claude/bin/gitea-connector/.claude-plugin
- Step 2: Write
plugin.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
mkdir -p /home/claude/bin/gitea-connector/{commands,agents,skills/gitea-workflow,hooks/scripts,bin}
- Step 4: Init git repo
cd /home/claude/bin/gitea-connector && git init
- Step 5: Create
.gitignore
.backup/
*.swp
*.swo
- Step 6: Commit scaffold
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.
#!/usr/bin/env bash
# gitea — your friendly neighborhood Gitea CLI
# It creates repos. It pushes code. It occasionally judges your commit messages.
# Usage: gitea <command> [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 <name> [--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 <repo-name>"; 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 <repo-name>"; 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 <command> [options]
Commands:
create <name> [--private] [--description "..."] Bring a new repo into this world
remote <name> Wire up git origin with token auth
push Yeet current branch to origin
delete <name> 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
chmod +x /home/claude/bin/gitea-connector/bin/gitea
- Step 3: Test CLI help output
/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)
HOME=/tmp/fakehome /home/claude/bin/gitea-connector/bin/gitea list
Expected: Error about missing username, exit code 1.
- Step 5: Commit
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
---
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 withapt install jq(or your distro's equivalent) and run /gitea-setup again."
If all OK, proceed with enthusiasm.
Step 2: Check for Existing Config
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:
- Open https://git.sethpc.xyz in your browser and log in
- Click your profile picture (top right corner — yes, that little circle)
- Go to Settings
- Click Applications in the sidebar
- Under Manage Access Tokens, type
claude-codeas the token name - For permissions, check repo (Read and Write) and user (Read)
- Click Generate Token
- 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):
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/:
mkdir -p ~/bin
cp "${CLAUDE_PLUGIN_ROOT}/bin/gitea" ~/bin/gitea
chmod +x ~/bin/gitea
Check if ~/bin is on PATH:
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:
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:
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 <returned> but you told me <entered>. 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 <name> [--private] [--description "..."]
gitea remote <name>
gitea push
gitea delete <name>
gitea list
Quick start:
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
---
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
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
---
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/tokenor~/.config/gitea/username.envfiles 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
#!/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
chmod +x /home/claude/bin/gitea-connector/hooks/scripts/push-reminder.sh
- Step 3: Write hooks.json (hook disabled by default)
{
"description": "Gitea connector hooks — push reminder after commits (opt-in)",
"hooks": {}
}
Note: The hook is shipped disabled. To enable, users update hooks.json to:
{
"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
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
# 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:
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:
- Checks you have
curl,jq, andgit(the holy trinity) - Guides you through generating an API token on git.sethpc.xyz
- Stores your credentials securely in
~/.config/gitea/ - Installs the
giteaCLI to~/bin/ - Validates your connection
The whole thing takes about 2 minutes. Less if you type fast.
CLI Usage
After setup, you have the gitea command:
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:
{
"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
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
cd /home/claude/bin/gitea-connector
~/bin/gitea remote gitea-connector
- Step 3: Push all commits
cd /home/claude/bin/gitea-connector
~/bin/gitea push
Expected: All commits pushed to https://git.sethpc.xyz/Seth/gitea-connector.