mirror of
https://github.com/terraphim/gitea-infrastructure.git
synced 2026-07-16 01:00:33 +02:00
feat(gitea-skill): Rewrite Gitea agent skill with verified API patterns
Rewrites SKILL.md with patterns verified against live Gitea 1.22.6: - Milestones as project containers (project board API does not exist) - Scoped workflow labels with mutual exclusivity - Issue dependencies using IssueMeta format - 1Password token retrieval via op read - Default repo terraphim/agent-tasks with env var overrides Adds setup-labels.sh for idempotent repo and label creation. Includes research and design documents from disciplined development. Adds pre-commit config adapted for infrastructure project. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+409
-309
@@ -1,365 +1,465 @@
|
||||
---
|
||||
name: gitea
|
||||
description: |
|
||||
AI agent skill for interacting with Gitea. Manages users, organizations,
|
||||
repositories, issues, pull requests, and project boards via Gitea API.
|
||||
Supports both REST API and Gitea admin CLI operations.
|
||||
AI agent skill for Gitea project and task management. Provides authentication,
|
||||
milestone-based project tracking, issue lifecycle with scoped workflow labels,
|
||||
and shell helpers for agent automation. Targets Gitea 1.22.6.
|
||||
license: Apache-2.0
|
||||
version: "2.0"
|
||||
gitea_version: "1.22.6"
|
||||
---
|
||||
|
||||
You are an AI agent specializing in Gitea administration and automation. Your role is to help manage users, organizations, repositories, issues, and projects using the Gitea API and CLI tools.
|
||||
You are an AI agent specializing in Gitea project and task management. You manage milestones (projects), issues (tasks), workflow labels, and dependencies via the Gitea REST API.
|
||||
|
||||
## Configuration
|
||||
|
||||
**Gitea Instance:** https://git.terraphim.cloud
|
||||
**Gitea Instance:** https://git.terraphim.cloud (Gitea 1.22.6)
|
||||
|
||||
**Access Methods:**
|
||||
1. **REST API** - Preferred for automation and scripting
|
||||
2. **Admin CLI** - For user creation and administrative tasks inside container
|
||||
**Default Task Repository:** `terraphim/agent-tasks`
|
||||
|
||||
**Required Environment Variables:**
|
||||
- `GITEA_URL` - Gitea instance URL (default: https://git.terraphim.cloud)
|
||||
- `GITEA_TOKEN` - API token for authentication
|
||||
**Environment Variables:**
|
||||
```bash
|
||||
GITEA_URL="${GITEA_URL:-https://git.terraphim.cloud}"
|
||||
GITEA_OWNER="${GITEA_OWNER:-terraphim}"
|
||||
GITEA_REPO="${GITEA_REPO:-agent-tasks}"
|
||||
GITEA_TOKEN="$(op read 'op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential')"
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### Get API Token
|
||||
1. Login to Gitea web UI
|
||||
2. Go to Settings → Applications
|
||||
3. Create new token with appropriate scopes
|
||||
### 1Password Token Retrieval
|
||||
|
||||
### Using the Token
|
||||
```bash
|
||||
export GITEA_TOKEN="your-token-here"
|
||||
# Load 1Password service account
|
||||
source ~/op_zesticai_non_prod.sh
|
||||
|
||||
# Read the Gitea admin token
|
||||
export GITEA_TOKEN=$(op read "op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential")
|
||||
```
|
||||
|
||||
## Core Operations
|
||||
**Token scopes required:** `repo`, `admin:org`, `write:user`, `read:user`
|
||||
|
||||
**Security rules:**
|
||||
- Never echo or log `$GITEA_TOKEN`
|
||||
- Never hardcode tokens in scripts or commit them
|
||||
- Always use `op read` at runtime
|
||||
|
||||
### Verify Authentication
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" "$GITEA_URL/api/v1/user" | jq '{login, is_admin}'
|
||||
```
|
||||
|
||||
## Base API Helper
|
||||
|
||||
All operations use this helper. It outputs JSON to stdout and errors to stderr.
|
||||
|
||||
```bash
|
||||
gitea_api() {
|
||||
local method="$1" endpoint="$2" data="${3:-}"
|
||||
local args=(-s -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json")
|
||||
if [ -n "$data" ]; then
|
||||
args+=(-X "$method" -d "$data")
|
||||
else
|
||||
args+=(-X "$method")
|
||||
fi
|
||||
curl "${args[@]}" "$GITEA_URL/api/v1$endpoint"
|
||||
}
|
||||
```
|
||||
|
||||
## Initial Setup
|
||||
|
||||
Run once per repository to create labels:
|
||||
|
||||
```bash
|
||||
./setup-labels.sh # Uses defaults: terraphim/agent-tasks
|
||||
./setup-labels.sh myorg myrepo # Custom repo
|
||||
GITEA_OWNER=zestic-ai GITEA_REPO=tasks ./setup-labels.sh # Via env vars
|
||||
```
|
||||
|
||||
This creates the repo (if missing) and 12 scoped labels (workflow, priority, type).
|
||||
|
||||
## Project Management (Milestones)
|
||||
|
||||
Gitea 1.22.6 has no project board API. Milestones serve as project containers with built-in progress tracking.
|
||||
|
||||
### Create Project
|
||||
|
||||
```bash
|
||||
gitea_create_project() {
|
||||
local title="$1" desc="${2:-}" due="${3:-}"
|
||||
local owner="${4:-$GITEA_OWNER}" repo="${5:-$GITEA_REPO}"
|
||||
local data
|
||||
data=$(jq -n --arg t "$title" --arg d "$desc" --arg due "$due" \
|
||||
'{title: $t, description: $d} + (if $due != "" then {due_on: $due} else {} end)')
|
||||
gitea_api POST "/repos/$owner/$repo/milestones" "$data"
|
||||
}
|
||||
|
||||
# Example:
|
||||
gitea_create_project "Sprint 2026-W08" "Weekly sprint" "2026-02-28T00:00:00Z"
|
||||
```
|
||||
|
||||
### List Projects
|
||||
|
||||
```bash
|
||||
gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/milestones?state=open" | \
|
||||
jq '.[] | {id, title, open_issues, closed_issues}'
|
||||
```
|
||||
|
||||
### Get Project Progress
|
||||
|
||||
```bash
|
||||
gitea_progress() {
|
||||
local milestone="${1:-}" owner="${2:-$GITEA_OWNER}" repo="${3:-$GITEA_REPO}"
|
||||
if [ -n "$milestone" ]; then
|
||||
gitea_api GET "/repos/$owner/$repo/milestones/$milestone" | jq '{
|
||||
milestone: .title,
|
||||
open: .open_issues,
|
||||
closed: .closed_issues,
|
||||
total: (.open_issues + .closed_issues),
|
||||
percent_complete: (if (.open_issues + .closed_issues) > 0
|
||||
then (.closed_issues * 100 / (.open_issues + .closed_issues))
|
||||
else 0 end),
|
||||
due_on: .due_on,
|
||||
state: .state
|
||||
}'
|
||||
else
|
||||
gitea_api GET "/repos/$owner/$repo/milestones?state=open" | \
|
||||
jq '[.[] | {id, title, open: .open_issues, closed: .closed_issues,
|
||||
pct: (if (.open_issues + .closed_issues) > 0
|
||||
then (.closed_issues * 100 / (.open_issues + .closed_issues))
|
||||
else 0 end)}]'
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
### Close Project
|
||||
|
||||
```bash
|
||||
gitea_api PATCH "/repos/$GITEA_OWNER/$GITEA_REPO/milestones/{id}" '{"state": "closed"}'
|
||||
```
|
||||
|
||||
## Task Management (Issues)
|
||||
|
||||
### Create Task
|
||||
|
||||
```bash
|
||||
gitea_create_task() {
|
||||
local title="$1" body="$2" milestone="$3"
|
||||
local labels="${4:-}" owner="${5:-$GITEA_OWNER}" repo="${6:-$GITEA_REPO}"
|
||||
local data
|
||||
data=$(jq -n --arg t "$title" --arg b "$body" --argjson m "$milestone" \
|
||||
'{title: $t, body: $b, milestone: $m}')
|
||||
if [ -n "$labels" ]; then
|
||||
data=$(echo "$data" | jq --argjson l "[$labels]" '. + {labels: $l}')
|
||||
fi
|
||||
gitea_api POST "/repos/$owner/$repo/issues" "$data"
|
||||
}
|
||||
|
||||
# Example -- create task in milestone 1 with "workflow:ready" (id=4) and "type:task" (id=14):
|
||||
gitea_create_task "Implement auth module" "Design and implement OAuth2 flow" 1 "4,14"
|
||||
```
|
||||
|
||||
### List Tasks by Milestone
|
||||
|
||||
```bash
|
||||
gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues?milestones=Sprint+2026-W08&state=all" | \
|
||||
jq '[.[] | {number, title, state, labels: [.labels[].name]}]'
|
||||
```
|
||||
|
||||
### List Tasks by Workflow State
|
||||
|
||||
```bash
|
||||
# Ready tasks (available for agents to claim)
|
||||
gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=workflow:ready&state=open" | \
|
||||
jq '[.[] | {number, title, labels: [.labels[].name]}]'
|
||||
|
||||
# In-progress tasks
|
||||
gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=workflow:in-progress&state=open" | \
|
||||
jq '[.[] | {number, title, assignees: [.assignees[].login]}]'
|
||||
|
||||
# Blocked tasks
|
||||
gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=workflow:blocked&state=open" | \
|
||||
jq '[.[] | {number, title}]'
|
||||
```
|
||||
|
||||
### Update Task Labels
|
||||
|
||||
```bash
|
||||
gitea_set_labels() {
|
||||
local index="$1" labels="$2"
|
||||
local owner="${3:-$GITEA_OWNER}" repo="${4:-$GITEA_REPO}"
|
||||
gitea_api PUT "/repos/$owner/$repo/issues/$index/labels" "{\"labels\": [$labels]}"
|
||||
}
|
||||
```
|
||||
|
||||
### Add Comment
|
||||
|
||||
```bash
|
||||
gitea_api POST "/repos/$GITEA_OWNER/$GITEA_REPO/issues/{index}/comments" \
|
||||
"$(jq -n --arg b "Progress update: completed auth module" '{body: $b}')"
|
||||
```
|
||||
|
||||
## Agent Workflow
|
||||
|
||||
Inspired by the br (beads_rust) pattern: ready -> claim -> implement -> close.
|
||||
|
||||
```
|
||||
create_task
|
||||
|
|
||||
v
|
||||
+---------------+
|
||||
| workflow:ready |<--------+
|
||||
+-------+-------+ |
|
||||
| |
|
||||
gitea_claim (unblock)
|
||||
| |
|
||||
v |
|
||||
+--------------------+ +-----------------+
|
||||
| workflow:in-progress|->| workflow:blocked |
|
||||
+---------+----------+ +-----------------+
|
||||
|
|
||||
gitea_close_task
|
||||
|
|
||||
v
|
||||
+---------------+
|
||||
| workflow:done |
|
||||
| (issue closed) |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
Scoped labels enforce mutual exclusivity: applying `workflow:in-progress` auto-removes `workflow:ready`.
|
||||
|
||||
### Mark Ready
|
||||
|
||||
```bash
|
||||
gitea_ready() {
|
||||
local index="$1" owner="${2:-$GITEA_OWNER}" repo="${3:-$GITEA_REPO}"
|
||||
local ready_id
|
||||
ready_id=$(gitea_api GET "/repos/$owner/$repo/labels" | \
|
||||
jq -r '.[] | select(.name=="workflow:ready") | .id')
|
||||
gitea_api POST "/repos/$owner/$repo/issues/$index/labels" "{\"labels\": [$ready_id]}"
|
||||
}
|
||||
```
|
||||
|
||||
### Claim Task
|
||||
|
||||
```bash
|
||||
gitea_claim() {
|
||||
local index="$1" user="${2:-}" owner="${3:-$GITEA_OWNER}" repo="${4:-$GITEA_REPO}"
|
||||
local ip_id
|
||||
ip_id=$(gitea_api GET "/repos/$owner/$repo/labels" | \
|
||||
jq -r '.[] | select(.name=="workflow:in-progress") | .id')
|
||||
gitea_api POST "/repos/$owner/$repo/issues/$index/labels" "{\"labels\": [$ip_id]}"
|
||||
if [ -n "$user" ]; then
|
||||
gitea_api POST "/repos/$owner/$repo/issues/$index/assignees" "{\"assignees\": [\"$user\"]}"
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
### Close Task
|
||||
|
||||
```bash
|
||||
gitea_close_task() {
|
||||
local index="$1" comment="${2:-}" owner="${3:-$GITEA_OWNER}" repo="${4:-$GITEA_REPO}"
|
||||
local done_id
|
||||
done_id=$(gitea_api GET "/repos/$owner/$repo/labels" | \
|
||||
jq -r '.[] | select(.name=="workflow:done") | .id')
|
||||
gitea_api POST "/repos/$owner/$repo/issues/$index/labels" "{\"labels\": [$done_id]}"
|
||||
gitea_api PATCH "/repos/$owner/$repo/issues/$index" '{"state": "closed"}'
|
||||
if [ -n "$comment" ]; then
|
||||
gitea_api POST "/repos/$owner/$repo/issues/$index/comments" \
|
||||
"$(jq -n --arg b "$comment" '{body: $b}')"
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
### Workflow Command Reference
|
||||
|
||||
| br Command | Gitea Equivalent | Description |
|
||||
|------------|------------------|-------------|
|
||||
| `br ready` | `gitea_ready 1` | Mark task #1 ready |
|
||||
| `br update ID --status in_progress` | `gitea_claim 1 root` | Claim task #1 |
|
||||
| `br close ID` | `gitea_close_task 1 "Done"` | Complete task #1 |
|
||||
| `br list --status ready` | List issues `?labels=workflow:ready` | Ready tasks |
|
||||
| `br status` | `gitea_progress 1` | Milestone progress |
|
||||
|
||||
## Issue Dependencies
|
||||
|
||||
Dependencies express task ordering. Gitea uses `IssueMeta` format: `{owner, repo, index}`.
|
||||
|
||||
### Add Dependency
|
||||
|
||||
```bash
|
||||
# Make issue #3 depend on issue #2 (issue #2 blocks issue #3)
|
||||
gitea_api POST "/repos/$GITEA_OWNER/$GITEA_REPO/issues/3/dependencies" \
|
||||
"{\"owner\": \"$GITEA_OWNER\", \"repo\": \"$GITEA_REPO\", \"index\": 2}"
|
||||
```
|
||||
|
||||
### List Dependencies
|
||||
|
||||
```bash
|
||||
# What blocks issue #3?
|
||||
gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues/3/dependencies" | \
|
||||
jq '[.[] | {number, title, state}]'
|
||||
|
||||
# What does issue #2 block?
|
||||
gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues/2/blocks" | \
|
||||
jq '[.[] | {number, title}]'
|
||||
```
|
||||
|
||||
### Remove Dependency
|
||||
|
||||
```bash
|
||||
gitea_api DELETE "/repos/$GITEA_OWNER/$GITEA_REPO/issues/3/dependencies" \
|
||||
"{\"owner\": \"$GITEA_OWNER\", \"repo\": \"$GITEA_REPO\", \"index\": 2}"
|
||||
```
|
||||
|
||||
## Label Scheme
|
||||
|
||||
All labels use scoped naming (`scope:name`) with `exclusive: true` for mutual exclusivity within each scope.
|
||||
|
||||
### Workflow Labels
|
||||
|
||||
| Label | Color | Description |
|
||||
|-------|-------|-------------|
|
||||
| `workflow:ready` | `#0e8a16` green | Ready for an agent to pick up |
|
||||
| `workflow:in-progress` | `#fbca04` yellow | Actively being worked on |
|
||||
| `workflow:blocked` | `#d93f0b` red | Blocked by a dependency |
|
||||
| `workflow:done` | `#6f42c1` purple | Complete |
|
||||
|
||||
### Priority Labels
|
||||
|
||||
| Label | Color | Description |
|
||||
|-------|-------|-------------|
|
||||
| `priority:critical` | `#b60205` dark red | Immediate |
|
||||
| `priority:high` | `#d93f0b` red | This cycle |
|
||||
| `priority:medium` | `#fbca04` yellow | Normal |
|
||||
| `priority:low` | `#0e8a16` green | Nice to have |
|
||||
|
||||
### Type Labels
|
||||
|
||||
| Label | Color | Description |
|
||||
|-------|-------|-------------|
|
||||
| `type:feature` | `#1d76db` blue | New functionality |
|
||||
| `type:bug` | `#d93f0b` red | Defect fix |
|
||||
| `type:task` | `#5319e7` violet | General task |
|
||||
| `type:research` | `#006b75` teal | Investigation |
|
||||
|
||||
## Time Tracking
|
||||
|
||||
```bash
|
||||
# Start stopwatch
|
||||
gitea_api POST "/repos/$GITEA_OWNER/$GITEA_REPO/issues/{index}/stopwatch/start"
|
||||
|
||||
# Stop stopwatch
|
||||
gitea_api POST "/repos/$GITEA_OWNER/$GITEA_REPO/issues/{index}/stopwatch/stop"
|
||||
|
||||
# Get tracked times
|
||||
gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/issues/{index}/times" | jq '.[].time'
|
||||
```
|
||||
|
||||
## MCP Integration (Optional)
|
||||
|
||||
The official Gitea MCP Server provides ~80 tools for repos, issues, PRs, files, and CI/CD.
|
||||
It does NOT cover milestones or workflow labels -- use the shell helpers above for those.
|
||||
|
||||
### Claude Code Setup
|
||||
|
||||
```bash
|
||||
claude mcp add --transport stdio --scope user gitea \
|
||||
--env GITEA_ACCESS_TOKEN="$(op read 'op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential')" \
|
||||
--env GITEA_HOST=https://git.terraphim.cloud \
|
||||
-- go run gitea.com/gitea/gitea-mcp@latest -t stdio
|
||||
```
|
||||
|
||||
### Available MCP Tools
|
||||
|
||||
Repos, branches, tags, releases, files, commits, issues (CRUD + comments), PRs (CRUD + reviews + merge), org labels, CI/CD actions (secrets, variables, workflows, runs, jobs), wiki pages.
|
||||
|
||||
**Not covered by MCP:** milestones, repo labels, issue dependencies, time tracking, workflow labels.
|
||||
|
||||
## Core API Reference (Gitea 1.22.6)
|
||||
|
||||
### Users
|
||||
|
||||
#### Create Admin User (requires admin CLI)
|
||||
```bash
|
||||
# Inside Gitea container
|
||||
# Create admin (CLI inside container)
|
||||
docker exec gitea gitea admin user create \
|
||||
--username <username> \
|
||||
--password <password> \
|
||||
--email <email> \
|
||||
--admin \
|
||||
--access-token
|
||||
```
|
||||
--username <user> --password <pass> --email <email> --admin --access-token
|
||||
|
||||
#### List Users (API)
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/admin/users"
|
||||
```
|
||||
# List users
|
||||
gitea_api GET "/admin/users"
|
||||
|
||||
#### Get User Info
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/users/{username}"
|
||||
# Get user
|
||||
gitea_api GET "/users/{username}"
|
||||
```
|
||||
|
||||
### Organizations
|
||||
|
||||
#### Create Organization
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/orgs" \
|
||||
-d '{
|
||||
"username": "myorg",
|
||||
"full_name": "My Organization",
|
||||
"description": "Organization description",
|
||||
"visibility": "private"
|
||||
}'
|
||||
```
|
||||
# Create org
|
||||
gitea_api POST "/orgs" '{"username":"myorg","full_name":"My Org","visibility":"private"}'
|
||||
|
||||
#### List Organization Repos
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/orgs/{org}/repos"
|
||||
# List org repos
|
||||
gitea_api GET "/orgs/{org}/repos"
|
||||
```
|
||||
|
||||
### Repositories
|
||||
|
||||
#### Create Repository
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/user/repos" \
|
||||
-d '{
|
||||
"name": "my-repo",
|
||||
"description": "Repository description",
|
||||
"private": true,
|
||||
"auto_init": true,
|
||||
"gitignores": "Rust",
|
||||
"license": "MIT"
|
||||
}'
|
||||
```
|
||||
# Create user repo
|
||||
gitea_api POST "/user/repos" '{"name":"my-repo","auto_init":true,"private":true}'
|
||||
|
||||
#### Create Repository in Organization
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/orgs/{org}/repos" \
|
||||
-d '{
|
||||
"name": "my-repo",
|
||||
"description": "Repository description",
|
||||
"private": false
|
||||
}'
|
||||
```
|
||||
# Create org repo
|
||||
gitea_api POST "/orgs/{org}/repos" '{"name":"my-repo","private":false}'
|
||||
|
||||
#### Delete Repository
|
||||
```bash
|
||||
curl -s -X DELETE -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}"
|
||||
```
|
||||
# Delete repo
|
||||
gitea_api DELETE "/repos/{owner}/{repo}"
|
||||
|
||||
#### List User Repositories
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/user/repos"
|
||||
```
|
||||
|
||||
### Issues
|
||||
|
||||
#### Create Issue
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues" \
|
||||
-d '{
|
||||
"title": "Issue title",
|
||||
"body": "Issue description",
|
||||
"assignees": ["username"]
|
||||
}'
|
||||
```
|
||||
|
||||
#### List Issues
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues?state=all"
|
||||
```
|
||||
|
||||
#### Get Issue
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}"
|
||||
```
|
||||
|
||||
#### Update Issue
|
||||
```bash
|
||||
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}" \
|
||||
-d '{
|
||||
"title": "New title",
|
||||
"body": "Updated body",
|
||||
"state": "open"
|
||||
}'
|
||||
```
|
||||
|
||||
#### Close/Reopen Issue
|
||||
```bash
|
||||
# Close issue
|
||||
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}" \
|
||||
-d '{"state": "closed"}'
|
||||
|
||||
# Reopen issue
|
||||
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}" \
|
||||
-d '{"state": "open"}'
|
||||
```
|
||||
|
||||
#### Add Issue Comment
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}/comments" \
|
||||
-d '{"body": "Comment text"}'
|
||||
```
|
||||
|
||||
#### Add/Remove Issue Labels
|
||||
```bash
|
||||
# Add labels
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/issues/{index}/labels" \
|
||||
-d '{"labels": [1, 2, 3]}'
|
||||
|
||||
# List available labels
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/labels"
|
||||
# List user repos
|
||||
gitea_api GET "/user/repos"
|
||||
```
|
||||
|
||||
### Pull Requests
|
||||
|
||||
#### List Pull Requests
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/pulls?state=all"
|
||||
# List PRs
|
||||
gitea_api GET "/repos/{owner}/{repo}/pulls?state=all"
|
||||
|
||||
# Get PR
|
||||
gitea_api GET "/repos/{owner}/{repo}/pulls/{index}"
|
||||
|
||||
# Merge PR
|
||||
gitea_api POST "/repos/{owner}/{repo}/pulls/{index}/merge" '{"do":"merge"}'
|
||||
```
|
||||
|
||||
#### Get Pull Request
|
||||
### Releases
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/pulls/{index}"
|
||||
```
|
||||
|
||||
#### Merge Pull Request
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/pulls/{index}/merge" \
|
||||
-d '{"do": "merge", "merge_message": "Merge PR #1"}'
|
||||
```
|
||||
|
||||
### Projects
|
||||
|
||||
#### Create Project
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/user/projects" \
|
||||
-d '{
|
||||
"name": "Project Name",
|
||||
"description": "Project description",
|
||||
"visibility": "private"
|
||||
}'
|
||||
```
|
||||
|
||||
#### Create Organization Project
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/orgs/{org}/projects" \
|
||||
-d '{
|
||||
"name": "Project Name",
|
||||
"description": "Project description"
|
||||
}'
|
||||
```
|
||||
|
||||
#### List Projects
|
||||
```bash
|
||||
# User projects
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/user/projects"
|
||||
|
||||
# Organization projects
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/orgs/{org}/projects"
|
||||
```
|
||||
|
||||
#### Add Issue to Project
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/projects/{project_id}/issues" \
|
||||
-d '{"issue_id": 123}'
|
||||
```
|
||||
|
||||
### Labels
|
||||
|
||||
#### Create Label
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/labels" \
|
||||
-d '{
|
||||
"name": "bug",
|
||||
"color": "fc2929",
|
||||
"description": "Bug report"
|
||||
}'
|
||||
```
|
||||
|
||||
### Release Notes
|
||||
|
||||
#### Create Release
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/{owner}/{repo}/releases" \
|
||||
-d '{
|
||||
"tag_name": "v1.0.0",
|
||||
"name": "Release v1.0.0",
|
||||
"body": "Release notes",
|
||||
"draft": false,
|
||||
"prerelease": false
|
||||
}'
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
|
||||
### Bash Alias for Common Operations
|
||||
|
||||
Add to your shell profile:
|
||||
```bash
|
||||
gitea() {
|
||||
local cmd="$1"
|
||||
shift
|
||||
case "$cmd" in
|
||||
repos)
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" "$GITEA_URL/api/v1/user/repos" | jq -r '.[].full_name'
|
||||
;;
|
||||
issues)
|
||||
local owner="$1" repo="$2"
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/repos/$owner/$repo/issues?state=all" | \
|
||||
jq -r '.[] | "#\(.number) [\(.state)] \(.title)"'
|
||||
;;
|
||||
create-issue)
|
||||
local owner="$1" repo="$2" title="$3" body="${4:-}"
|
||||
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$GITEA_URL/api/v1/repos/$owner/$repo/issues" \
|
||||
-d "{\"title\": \"$title\", \"body\": \"$body\"}"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: gitea {repos|issues|create-issue}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
gitea_api POST "/repos/{owner}/{repo}/releases" \
|
||||
'{"tag_name":"v1.0.0","name":"Release v1.0.0","body":"Release notes"}'
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Check HTTP status codes (200 = success, 201 = created, 404 = not found, 401 = unauthorized)
|
||||
- Use `jq` to parse JSON responses
|
||||
- Rate limiting: Gitea doesn't enforce strict rate limits but be reasonable
|
||||
- Token scopes: Ensure token has required permissions (repo, read:user, write:user, etc.)
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Automated Issue Tracking
|
||||
1. Create issues for task tracking
|
||||
2. Add labels for categorization
|
||||
3. Assign to team members
|
||||
4. Link to projects for board view
|
||||
5. Update status via comments
|
||||
|
||||
### Project Management
|
||||
1. Create organization for team
|
||||
2. Create project board
|
||||
3. Add repositories to project
|
||||
4. Add issues to project columns
|
||||
5. Track progress via project API
|
||||
|
||||
### Repository Automation
|
||||
1. Initialize repositories with templates
|
||||
2. Configure branch protection
|
||||
3. Add deploy keys
|
||||
4. Set up webhooks
|
||||
5. Mirror from GitHub
|
||||
| HTTP Code | Meaning | Action |
|
||||
|-----------|---------|--------|
|
||||
| 200 | Success | Parse JSON response |
|
||||
| 201 | Created | Resource created successfully |
|
||||
| 204 | No Content | Delete succeeded |
|
||||
| 401 | Unauthorized | Check GITEA_TOKEN; re-read from 1Password |
|
||||
| 403 | Forbidden | Token lacks required scope |
|
||||
| 404 | Not Found | Check owner/repo/index; resource may not exist |
|
||||
| 409 | Conflict | Resource already exists (labels, repos) |
|
||||
| 422 | Validation Error | Check request body format |
|
||||
|
||||
## Constraints
|
||||
|
||||
- Always use HTTPS in production
|
||||
- Always use HTTPS
|
||||
- Never commit tokens to version control
|
||||
- Use environment variables for secrets
|
||||
- Use `op read` for secrets at runtime
|
||||
- Prefer API over CLI for idempotent operations
|
||||
- Handle rate limiting gracefully
|
||||
- Clean up temporary resources
|
||||
- Project board API does NOT exist in Gitea 1.22.6 -- use milestones
|
||||
- Issue dependency body uses `IssueMeta` format: `{owner, repo, index}`
|
||||
- Scoped labels require the `exclusive: true` flag on creation
|
||||
|
||||
Executable
+132
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
# setup-labels.sh -- Idempotent setup of Gitea repo and workflow labels
|
||||
# Creates the default agent-tasks repo (if missing) and 12 scoped labels.
|
||||
#
|
||||
# Usage:
|
||||
# source ~/op_zesticai_non_prod.sh # load OP_SERVICE_ACCOUNT_TOKEN
|
||||
# export GITEA_TOKEN=$(op read "op://TerraphimPlatform/git.terraphim.cloud-admin-token/credential")
|
||||
# ./setup-labels.sh [OWNER] [REPO]
|
||||
#
|
||||
# Environment:
|
||||
# GITEA_URL -- Gitea instance (default: https://git.terraphim.cloud)
|
||||
# GITEA_TOKEN -- API token (required)
|
||||
# GITEA_OWNER -- Org or user (default: terraphim)
|
||||
# GITEA_REPO -- Repository (default: agent-tasks)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GITEA_URL="${GITEA_URL:-https://git.terraphim.cloud}"
|
||||
GITEA_OWNER="${1:-${GITEA_OWNER:-terraphim}}"
|
||||
GITEA_REPO="${2:-${GITEA_REPO:-agent-tasks}}"
|
||||
|
||||
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||
echo '{"error": "GITEA_TOKEN is not set"}' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gitea_api() {
|
||||
local method="$1" endpoint="$2" data="${3:-}"
|
||||
local args=(-s -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json")
|
||||
if [ -n "$data" ]; then
|
||||
args+=(-X "$method" -d "$data")
|
||||
else
|
||||
args+=(-X "$method")
|
||||
fi
|
||||
curl "${args[@]}" "$GITEA_URL/api/v1$endpoint"
|
||||
}
|
||||
|
||||
# --- Ensure repo exists (idempotent) ---
|
||||
ensure_repo() {
|
||||
local http_code
|
||||
http_code=$(curl -s -o /dev/null -w '%{http_code}' \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_URL/api/v1/repos/$GITEA_OWNER/$GITEA_REPO")
|
||||
|
||||
if [ "$http_code" = "200" ]; then
|
||||
echo "{\"repo\": \"$GITEA_OWNER/$GITEA_REPO\", \"action\": \"exists\"}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try creating under org first, fall back to user repo
|
||||
local result
|
||||
result=$(gitea_api POST "/orgs/$GITEA_OWNER/repos" \
|
||||
"{\"name\": \"$GITEA_REPO\", \"description\": \"AI agent task tracking\", \"auto_init\": true, \"private\": false}" 2>&1)
|
||||
|
||||
if echo "$result" | jq -e '.id' > /dev/null 2>&1; then
|
||||
echo "{\"repo\": \"$GITEA_OWNER/$GITEA_REPO\", \"action\": \"created\"}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Fall back to user repo
|
||||
result=$(gitea_api POST "/user/repos" \
|
||||
"{\"name\": \"$GITEA_REPO\", \"description\": \"AI agent task tracking\", \"auto_init\": true, \"private\": false}" 2>&1)
|
||||
|
||||
if echo "$result" | jq -e '.id' > /dev/null 2>&1; then
|
||||
echo "{\"repo\": \"$GITEA_OWNER/$GITEA_REPO\", \"action\": \"created\"}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "{\"repo\": \"$GITEA_OWNER/$GITEA_REPO\", \"action\": \"error\", \"detail\": $result}" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# --- Create label if it does not exist ---
|
||||
ensure_label() {
|
||||
local name="$1" color="$2" description="$3" exclusive="${4:-false}"
|
||||
local existing
|
||||
|
||||
existing=$(gitea_api GET "/repos/$GITEA_OWNER/$GITEA_REPO/labels" \
|
||||
| jq -r --arg n "$name" '.[] | select(.name == $n) | .id')
|
||||
|
||||
if [ -n "$existing" ]; then
|
||||
echo "{\"name\": \"$name\", \"id\": $existing, \"action\": \"exists\"}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local payload
|
||||
payload=$(jq -n \
|
||||
--arg name "$name" \
|
||||
--arg color "$color" \
|
||||
--arg desc "$description" \
|
||||
--argjson excl "$exclusive" \
|
||||
'{name: $name, color: $color, description: $desc, exclusive: $excl}')
|
||||
|
||||
local result
|
||||
result=$(gitea_api POST "/repos/$GITEA_OWNER/$GITEA_REPO/labels" "$payload")
|
||||
local label_id
|
||||
label_id=$(echo "$result" | jq -r '.id // empty')
|
||||
|
||||
if [ -n "$label_id" ]; then
|
||||
echo "{\"name\": \"$name\", \"id\": $label_id, \"action\": \"created\"}"
|
||||
else
|
||||
echo "{\"name\": \"$name\", \"action\": \"error\", \"detail\": $result}" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# === Main ===
|
||||
|
||||
echo "--- Ensure repo $GITEA_OWNER/$GITEA_REPO ---"
|
||||
ensure_repo
|
||||
|
||||
echo "--- Creating labels ---"
|
||||
|
||||
# Workflow labels (mutually exclusive within scope)
|
||||
ensure_label "workflow:ready" "0e8a16" "Task is ready for an agent to pick up" true
|
||||
ensure_label "workflow:in-progress" "fbca04" "Task is actively being worked on" true
|
||||
ensure_label "workflow:blocked" "d93f0b" "Task is blocked by a dependency" true
|
||||
ensure_label "workflow:done" "6f42c1" "Task is complete" true
|
||||
|
||||
# Priority labels (mutually exclusive within scope)
|
||||
ensure_label "priority:critical" "b60205" "Must be done immediately" true
|
||||
ensure_label "priority:high" "d93f0b" "Should be done this cycle" true
|
||||
ensure_label "priority:medium" "fbca04" "Normal priority" true
|
||||
ensure_label "priority:low" "0e8a16" "Nice to have" true
|
||||
|
||||
# Type labels (mutually exclusive within scope)
|
||||
ensure_label "type:feature" "1d76db" "New functionality" true
|
||||
ensure_label "type:bug" "d93f0b" "Defect fix" true
|
||||
ensure_label "type:task" "5319e7" "General task" true
|
||||
ensure_label "type:research" "006b75" "Investigation or spike" true
|
||||
|
||||
echo "--- Done ---"
|
||||
Reference in New Issue
Block a user