diff --git a/SKILL.md b/SKILL.md index ebfeca6..54370bd 100644 --- a/SKILL.md +++ b/SKILL.md @@ -56,6 +56,7 @@ Antes de cualquier query: | `pr-comments.sh` | Lista comentarios de un PR (requiere PAT con `read:issue`) | | `pr-create.sh` | Crear PR con guards anti-AI-attribution + body UTF-8 safe | | `repo-create.sh` | Crear repo (default owner = bot user; **regla dura**: bot user → siempre público) | +| `repo-list.sh` | Listar repos de un owner (default = bot user). Filtros: `--limit`, `--sort`. | | `actions-list-runs.sh` | Lista runs (filtros client-side: workflow, branch, status, event) | | `actions-view.sh` | Detalle de un run + probe del job_id | | `actions-logs.sh` | Lee logs con filtros precisos (--tail/--head/--lines/--grep/--errors) | @@ -206,6 +207,14 @@ bash ~/.claude/skills/gitea/scripts/repo-create.sh skill-foo \ # → claudeCode0/skill-foo (public), html_url + clone_url + ssh_url ``` +### "Listame los repos del bot / del org" + +```bash +bash ~/.claude/skills/gitea/scripts/repo-list.sh # bot user +bash ~/.claude/skills/gitea/scripts/repo-list.sh --owner NucleOS # org +bash ~/.claude/skills/gitea/scripts/repo-list.sh --sort alpha --limit 10 +``` + ### "Creá un repo en NucleOS" Para org, `--public` o `--private` es **requerido** (exit 6 si no pasás diff --git a/endpoints.md b/endpoints.md index 5b5d6e9..bb291df 100644 --- a/endpoints.md +++ b/endpoints.md @@ -19,8 +19,8 @@ Auth: `Authorization: token `. Todas las queries pasan por |---|---| | `POST /user/repos` | Crear repo bajo el user autenticado (claudeCode0). Body: ver schema abajo. La skill bloquea `private:true` acá (regla dura) | | `POST /orgs/{org}/repos` | Crear repo bajo una org (NucleOS). Mismo body | -| `GET /users/{user}/repos` | Listar repos públicos de un user | -| `GET /orgs/{org}/repos` | Listar repos de una org | +| `GET /users/{user}/repos` | Listar repos. Si el caller es ese user, devuelve todos (incluyendo privados); si es otro, solo públicos. Querystring: `page`, `limit`, `sort` (`updated`/`oldest`/`alphabetically`/`size`). Helper: `repo-list.sh` | +| `GET /orgs/{org}/repos` | Listar repos de la org según permisos del PAT. Mismo querystring que el de user. Helper: `repo-list.sh --owner ` | | `GET /repos/{o}/{r}` | Detalle de un repo | | `DELETE /repos/{o}/{r}` | Borrar repo (requiere PAT con `delete_repo` o admin) | diff --git a/scripts/repo-list.sh b/scripts/repo-list.sh new file mode 100644 index 0000000..069ac92 --- /dev/null +++ b/scripts/repo-list.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +# gitea skill — listar repos de un owner (user o org). +# +# Uso: +# repo-list.sh [--owner ] [--limit N] [--sort newest|oldest|alpha|size] +# +# Default owner: el bot user ($GITEA_BOT_USER del .env). +# Default limit: 30. Default sort: newest. +# +# El endpoint cambia según el tipo de owner: +# - bot user: GET /users/{user}/repos (devuelve todos los del bot, +# incluyendo privados, porque está autenticado como ese user) +# - cualquier otro: GET /orgs/{org}/repos (devuelve los visibles según +# permisos del PAT en el org) + +set -euo pipefail + +SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +QUERY="$SKILL_DIR/scripts/query.sh" +ENV_FILE="$SKILL_DIR/.env" + +if [[ ! -f "$ENV_FILE" ]]; then + echo "ERROR: $ENV_FILE no existe. Corré setup.sh primero." >&2 + exit 1 +fi + +set -a +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a + +: "${GITEA_BOT_USER:?GITEA_BOT_USER no definido en .env. Re-correr setup.sh.}" + +owner="" +limit=30 +sort="newest" + +while [[ $# -gt 0 ]]; do + case "$1" in + --owner) owner="$2"; shift 2 ;; + --limit) limit="$2"; shift 2 ;; + --sort) sort="$2"; shift 2 ;; + -h|--help) + cat < Default: bot user (\$GITEA_BOT_USER, "$GITEA_BOT_USER") + --limit N Default: 30 + --sort newest | oldest | alpha | size (default: newest) + +Ejemplos: + repo-list.sh # repos del bot + repo-list.sh --owner NucleOS # repos del org + repo-list.sh --limit 10 --sort alpha +EOF + exit 0 + ;; + -*) echo "ERROR: flag desconocida: $1" >&2; exit 2 ;; + *) echo "ERROR: argumento extra: $1" >&2; exit 2 ;; + esac +done + +[[ -z "$owner" ]] && owner="$GITEA_BOT_USER" + +# Detectar user vs. org (case-insensitive contra bot user; cualquier otro = org) +shopt -s nocasematch +if [[ "$owner" == "$GITEA_BOT_USER" ]]; then + endpoint="/users/${owner}/repos" + owner_kind="user (bot)" +else + endpoint="/orgs/${owner}/repos" + owner_kind="org" +fi +shopt -u nocasematch + +# Map sort → param de Gitea +case "$sort" in + newest) sort_param="updated" ;; + oldest) sort_param="oldest" ;; + alpha) sort_param="alphabetically" ;; + size) sort_param="size" ;; + *) echo "ERROR: --sort debe ser newest|oldest|alpha|size" >&2; exit 2 ;; +esac + +resp="$("$QUERY" "${endpoint}?limit=${limit}&sort=${sort_param}")" + +# Detectar error (objeto en vez de array) +if [[ "${resp:0:1}" == "{" ]]; then + echo "$resp" | PYTHONIOENCODING=utf-8 python -c " +import json, sys +d = json.load(sys.stdin) +print('ERROR:', d.get('message') or d, file=sys.stderr) +" >&2 + exit 1 +fi + +export PY_OWNER="$owner" PY_KIND="$owner_kind" PYTHONIOENCODING=utf-8 +echo "$resp" | python -c ' +import json, os, sys +repos = json.load(sys.stdin) +owner = os.environ.get("PY_OWNER", "?") +kind = os.environ.get("PY_KIND", "?") +print(f"{len(repos)} repo(s) en {owner} ({kind}):") +print() +for r in repos: + name = r.get("name","?") + vis = "private" if r.get("private") else "public " + archived = " [archived]" if r.get("archived") else "" + template = " [template]" if r.get("template") else "" + fork = " [fork]" if r.get("fork") else "" + size_kb = r.get("size", 0) + if size_kb < 1024: + size_str = f"{size_kb:>5}KB" + else: + size_str = f"{size_kb/1024:>5.1f}MB" + updated = (r.get("updated_at") or "")[:10] + desc = (r.get("description") or "").replace("\n", " ") + if len(desc) > 70: desc = desc[:67] + "..." + print(f" {name:<26} [{vis}] {size_str} upd {updated}{archived}{template}{fork}") + if desc: + print(f" {desc}") +'