Replace /api/v1/user with /api/v1/repos/{owner}/{repo} in three places:
- preflight_check() auth verification
- setup_codeberg_auth() --token flag verification
- setup_codeberg_auth() interactive flow verification
The repo endpoint only requires repo-level access, which matches the
scopes disinto actually needs (write:issue, write:repository). Tokens
without read:user scope now pass verification correctly.
Also use generic "token" as netrc login since the username is no longer
retrieved from the API (git operations authenticate via the token, not
the login field).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
617 lines
19 KiB
Bash
Executable file
617 lines
19 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# disinto — CLI entry point for the disinto code factory
|
|
#
|
|
# Commands:
|
|
# disinto init <repo-url> [options] Bootstrap a new project
|
|
# disinto status Show factory status
|
|
#
|
|
# Usage:
|
|
# disinto init https://codeberg.org/user/repo
|
|
# disinto init https://codeberg.org/user/repo --branch main --ci-id 3
|
|
# disinto status
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
FACTORY_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
source "${FACTORY_ROOT}/lib/env.sh"
|
|
|
|
# ── Helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
disinto — autonomous code factory CLI
|
|
|
|
Usage:
|
|
disinto init <repo-url> [options] Bootstrap a new project
|
|
disinto status Show factory status
|
|
|
|
Init options:
|
|
--branch <name> Primary branch (default: auto-detect)
|
|
--repo-root <path> Local clone path (default: ~/name)
|
|
--ci-id <n> Woodpecker CI repo ID (default: 0 = no CI)
|
|
--token <token> Codeberg API token (saved to ~/.netrc)
|
|
--yes Skip confirmation prompts
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
# Extract org/repo slug from various URL formats.
|
|
# Accepts: https://codeberg.org/user/repo, codeberg.org/user/repo,
|
|
# user/repo, https://codeberg.org/user/repo.git
|
|
parse_repo_slug() {
|
|
local url="$1"
|
|
url="${url#https://}"
|
|
url="${url#http://}"
|
|
url="${url#codeberg.org/}"
|
|
url="${url%.git}"
|
|
url="${url%/}"
|
|
if [[ ! "$url" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
|
|
echo "Error: invalid repo URL — expected https://codeberg.org/org/repo or org/repo" >&2
|
|
exit 1
|
|
fi
|
|
printf '%s' "$url"
|
|
}
|
|
|
|
# Build a clone-able URL from a slug.
|
|
clone_url_from_slug() {
|
|
printf 'https://codeberg.org/%s.git' "$1"
|
|
}
|
|
|
|
# Write (or update) Codeberg credentials in ~/.netrc.
|
|
write_netrc() {
|
|
local login="$1" token="$2"
|
|
local netrc="${HOME}/.netrc"
|
|
|
|
# Remove existing codeberg.org entry if present
|
|
if [ -f "$netrc" ]; then
|
|
local tmp
|
|
tmp=$(mktemp)
|
|
awk '
|
|
/^machine codeberg\.org/ { skip=1; next }
|
|
/^machine / { skip=0 }
|
|
!skip
|
|
' "$netrc" > "$tmp"
|
|
mv "$tmp" "$netrc"
|
|
fi
|
|
|
|
# Append new entry
|
|
printf 'machine codeberg.org\nlogin %s\npassword %s\n' "$login" "$token" >> "$netrc"
|
|
chmod 600 "$netrc"
|
|
}
|
|
|
|
# Interactively set up Codeberg auth if missing.
|
|
# Args: [token_from_flag]
|
|
setup_codeberg_auth() {
|
|
local token_flag="${1:-}"
|
|
local repo_slug="${2:-}"
|
|
|
|
# --token flag takes priority: verify and save
|
|
if [ -n "$token_flag" ]; then
|
|
local verify_url="https://codeberg.org/api/v1/repos/${repo_slug}"
|
|
if ! curl -sf --max-time 10 \
|
|
-H "Authorization: token ${token_flag}" \
|
|
"$verify_url" >/dev/null 2>&1; then
|
|
echo "Error: provided token failed verification" >&2
|
|
exit 1
|
|
fi
|
|
write_netrc "token" "$token_flag"
|
|
echo "Saving to ~/.netrc... done."
|
|
echo "Verified: token accepted ✓"
|
|
export CODEBERG_TOKEN="$token_flag"
|
|
return
|
|
fi
|
|
|
|
# Existing auth — skip
|
|
if [ -n "${CODEBERG_TOKEN:-}" ]; then
|
|
return
|
|
fi
|
|
if grep -q 'codeberg\.org' ~/.netrc 2>/dev/null; then
|
|
CODEBERG_TOKEN="$(awk '/codeberg.org/{getline;getline;print $2}' ~/.netrc 2>/dev/null || true)"
|
|
export CODEBERG_TOKEN
|
|
return
|
|
fi
|
|
|
|
# Non-interactive — fail with guidance
|
|
if [ ! -t 0 ]; then
|
|
echo "Error: no Codeberg auth found" >&2
|
|
echo " Set CODEBERG_TOKEN, configure ~/.netrc, or use --token <token>" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Interactive guided flow
|
|
echo ""
|
|
echo "No Codeberg authentication found."
|
|
echo ""
|
|
echo "1. Open https://codeberg.org/user/settings/applications"
|
|
echo "2. Create a token with these scopes:"
|
|
echo " - write:issue (create issues, add labels, post comments, close issues)"
|
|
echo " - write:repository (push branches, create PRs, merge PRs)"
|
|
echo "3. Paste the token below."
|
|
echo ""
|
|
|
|
while true; do
|
|
read -rsp "Codeberg token: " token_input
|
|
echo ""
|
|
|
|
if [ -z "$token_input" ]; then
|
|
echo "Token cannot be empty. Try again." >&2
|
|
continue
|
|
fi
|
|
|
|
local verify_url="https://codeberg.org/api/v1/repos/${repo_slug}"
|
|
if ! curl -sf --max-time 10 \
|
|
-H "Authorization: token ${token_input}" \
|
|
"$verify_url" >/dev/null 2>&1; then
|
|
echo "Token verification failed. Check your token and try again." >&2
|
|
read -rp "Retry? [Y/n] " retry
|
|
if [[ "$retry" =~ ^[Nn] ]]; then
|
|
echo "Aborted." >&2
|
|
exit 1
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
write_netrc "token" "$token_input"
|
|
echo "Saving to ~/.netrc... done."
|
|
echo "Verified: token accepted ✓"
|
|
export CODEBERG_TOKEN="$token_input"
|
|
return
|
|
done
|
|
}
|
|
|
|
# Preflight check — verify all factory requirements before proceeding.
|
|
preflight_check() {
|
|
local repo_slug="${1:-}"
|
|
local errors=0
|
|
|
|
# ── Required commands ──
|
|
local -A hints=(
|
|
[claude]="Install: https://docs.anthropic.com/en/docs/claude-code/overview"
|
|
[tmux]="Install: apt install tmux / brew install tmux"
|
|
[git]="Install: apt install git / brew install git"
|
|
[jq]="Install: apt install jq / brew install jq"
|
|
[python3]="Install: apt install python3 / brew install python3"
|
|
[curl]="Install: apt install curl / brew install curl"
|
|
)
|
|
|
|
local cmd
|
|
for cmd in claude tmux git jq python3 curl; do
|
|
if ! command -v "$cmd" &>/dev/null; then
|
|
echo "Error: ${cmd} not found" >&2
|
|
echo " ${hints[$cmd]}" >&2
|
|
errors=$((errors + 1))
|
|
fi
|
|
done
|
|
|
|
# ── Claude Code authentication ──
|
|
if command -v claude &>/dev/null && command -v jq &>/dev/null; then
|
|
local auth_json auth_stderr auth_rc=0
|
|
auth_stderr=$(claude auth status 2>&1 >/dev/null) || auth_rc=$?
|
|
auth_json=$(claude auth status 2>/dev/null) || auth_json=""
|
|
# Only skip check if subcommand is unrecognized (old claude version)
|
|
if printf '%s' "$auth_stderr" | grep -qi "unknown command"; then
|
|
: # claude version doesn't support auth status — skip
|
|
elif [ -z "$auth_json" ] || [ "$auth_rc" -ne 0 ]; then
|
|
echo "Error: Claude Code is not authenticated (auth check failed)" >&2
|
|
echo " Run: claude auth login" >&2
|
|
errors=$((errors + 1))
|
|
else
|
|
local logged_in
|
|
logged_in=$(printf '%s' "$auth_json" | jq -r '.loggedIn // false' 2>/dev/null) || logged_in="false"
|
|
if [ "$logged_in" != "true" ]; then
|
|
echo "Error: Claude Code is not authenticated" >&2
|
|
echo " Run: claude auth login" >&2
|
|
errors=$((errors + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# ── Codeberg auth (setup_codeberg_auth handles interactive setup;
|
|
# this verifies the API actually works) ──
|
|
if [ -n "${CODEBERG_TOKEN:-}" ] && command -v curl &>/dev/null; then
|
|
local curl_args=(-sf --max-time 10)
|
|
if [ -n "${CODEBERG_TOKEN:-}" ]; then
|
|
curl_args+=(-H "Authorization: token ${CODEBERG_TOKEN}")
|
|
else
|
|
curl_args+=(--netrc)
|
|
fi
|
|
if ! curl "${curl_args[@]}" "https://codeberg.org/api/v1/repos/${repo_slug}" >/dev/null 2>&1; then
|
|
echo "Error: Codeberg API auth failed" >&2
|
|
echo " Verify your CODEBERG_TOKEN or ~/.netrc credentials" >&2
|
|
errors=$((errors + 1))
|
|
fi
|
|
fi
|
|
|
|
# ── Codeberg SSH access ──
|
|
if command -v ssh &>/dev/null; then
|
|
local ssh_output
|
|
ssh_output=$(ssh -T -o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=accept-new \
|
|
git@codeberg.org 2>&1) || true
|
|
if ! printf '%s' "$ssh_output" | grep -qi "successfully authenticated"; then
|
|
echo "Error: Codeberg SSH access failed (agents push via SSH)" >&2
|
|
echo " Add your SSH key: https://codeberg.org/user/settings/keys" >&2
|
|
errors=$((errors + 1))
|
|
fi
|
|
fi
|
|
|
|
# ── Optional tools (warn only) ──
|
|
if ! command -v docker &>/dev/null; then
|
|
echo "Warning: docker not found (some projects may need it)" >&2
|
|
fi
|
|
|
|
if [ "$errors" -gt 0 ]; then
|
|
echo "" >&2
|
|
echo "${errors} preflight error(s) — fix the above before running disinto init" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Clone the repo if the target directory doesn't exist; validate if it does.
|
|
clone_or_validate() {
|
|
local slug="$1" target="$2"
|
|
if [ -d "${target}/.git" ]; then
|
|
echo "Repo: ${target} (existing clone)"
|
|
return
|
|
fi
|
|
local url
|
|
url=$(clone_url_from_slug "$slug")
|
|
echo "Cloning: ${url} -> ${target}"
|
|
git clone "$url" "$target"
|
|
}
|
|
|
|
# Detect the primary branch from the remote HEAD or fallback to main/master.
|
|
detect_branch() {
|
|
local repo_root="$1"
|
|
local branch
|
|
branch=$(git -C "$repo_root" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null \
|
|
| sed 's|refs/remotes/origin/||') || true
|
|
if [ -z "$branch" ]; then
|
|
if git -C "$repo_root" show-ref --verify --quiet refs/remotes/origin/main 2>/dev/null; then
|
|
branch="main"
|
|
else
|
|
branch="master"
|
|
fi
|
|
fi
|
|
printf '%s' "$branch"
|
|
}
|
|
|
|
# Generate projects/<name>.toml config file.
|
|
generate_toml() {
|
|
local path="$1" name="$2" repo="$3" root="$4" branch="$5" ci_id="$6"
|
|
cat > "$path" <<EOF
|
|
# projects/${name}.toml — Project config for ${repo}
|
|
#
|
|
# Generated by disinto init
|
|
|
|
name = "${name}"
|
|
repo = "${repo}"
|
|
repo_root = "${root}"
|
|
primary_branch = "${branch}"
|
|
|
|
[ci]
|
|
woodpecker_repo_id = ${ci_id}
|
|
stale_minutes = 60
|
|
|
|
[services]
|
|
containers = []
|
|
|
|
[monitoring]
|
|
check_prs = true
|
|
check_dev_agent = true
|
|
check_pipeline_stall = false
|
|
EOF
|
|
}
|
|
|
|
# Create standard labels on the Codeberg repo.
|
|
create_labels() {
|
|
local repo="$1"
|
|
local api="https://codeberg.org/api/v1/repos/${repo}"
|
|
|
|
local -A labels=(
|
|
["backlog"]="#0075ca"
|
|
["in-progress"]="#e4e669"
|
|
["blocked"]="#d73a4a"
|
|
["tech-debt"]="#cfd3d7"
|
|
["underspecified"]="#fbca04"
|
|
["vision"]="#0e8a16"
|
|
["action"]="#1d76db"
|
|
)
|
|
|
|
echo "Creating labels on ${repo}..."
|
|
local name color
|
|
for name in backlog in-progress blocked tech-debt underspecified vision action; do
|
|
color="${labels[$name]}"
|
|
if curl -sf -X POST \
|
|
-H "Authorization: token ${CODEBERG_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
"${api}/labels" \
|
|
-d "{\"name\":\"${name}\",\"color\":\"${color}\"}" >/dev/null 2>&1; then
|
|
echo " + ${name}"
|
|
else
|
|
echo " . ${name} (already exists)"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Generate a minimal VISION.md template in the target project.
|
|
generate_vision() {
|
|
local repo_root="$1" name="$2"
|
|
local vision_path="${repo_root}/VISION.md"
|
|
if [ -f "$vision_path" ]; then
|
|
echo "VISION: ${vision_path} (already exists, skipping)"
|
|
return
|
|
fi
|
|
cat > "$vision_path" <<EOF
|
|
# Vision
|
|
|
|
## What ${name} does
|
|
|
|
<!-- Describe the purpose of this project in one paragraph -->
|
|
|
|
## Who it's for
|
|
|
|
<!-- Describe the target audience -->
|
|
|
|
## Design principles
|
|
|
|
- <!-- Principle 1 -->
|
|
- <!-- Principle 2 -->
|
|
- <!-- Principle 3 -->
|
|
|
|
## Milestones
|
|
|
|
### Current
|
|
- <!-- What you're working on now -->
|
|
|
|
### Next
|
|
- <!-- What comes after -->
|
|
EOF
|
|
echo "Created: ${vision_path}"
|
|
echo " Commit this to your repo when ready"
|
|
}
|
|
|
|
# Generate and optionally install cron entries for the project agents.
|
|
install_cron() {
|
|
local name="$1" toml="$2" auto_yes="$3"
|
|
|
|
# Use absolute path for the TOML in cron entries
|
|
local abs_toml
|
|
abs_toml="$(cd "$(dirname "$toml")" && pwd)/$(basename "$toml")"
|
|
|
|
local cron_block
|
|
cron_block="# disinto: ${name}
|
|
2,7,12,17,22,27,32,37,42,47,52,57 * * * * ${FACTORY_ROOT}/review/review-poll.sh ${abs_toml} >/dev/null 2>&1
|
|
4,9,14,19,24,29,34,39,44,49,54,59 * * * * ${FACTORY_ROOT}/dev/dev-poll.sh ${abs_toml} >/dev/null 2>&1
|
|
0 0,6,12,18 * * * cd ${FACTORY_ROOT} && bash gardener/gardener-run.sh ${abs_toml} >/dev/null 2>&1"
|
|
|
|
echo ""
|
|
echo "Cron entries to install:"
|
|
echo "$cron_block"
|
|
echo ""
|
|
|
|
if [ "$auto_yes" = false ] && [ -t 0 ]; then
|
|
read -rp "Install these cron entries? [y/N] " confirm
|
|
if [[ ! "$confirm" =~ ^[Yy] ]]; then
|
|
echo "Skipped cron install. Add manually with: crontab -e"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Append to existing crontab
|
|
{ crontab -l 2>/dev/null || true; printf '%s\n' "$cron_block"; } | crontab -
|
|
echo "Cron entries installed"
|
|
}
|
|
|
|
# ── init command ─────────────────────────────────────────────────────────────
|
|
|
|
disinto_init() {
|
|
local repo_url="${1:-}"
|
|
if [ -z "$repo_url" ]; then
|
|
echo "Error: repo URL required" >&2
|
|
echo "Usage: disinto init <repo-url>" >&2
|
|
exit 1
|
|
fi
|
|
shift
|
|
|
|
# Parse flags
|
|
local branch="" repo_root="" ci_id="0" auto_yes=false token_flag=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--branch) branch="$2"; shift 2 ;;
|
|
--repo-root) repo_root="$2"; shift 2 ;;
|
|
--ci-id) ci_id="$2"; shift 2 ;;
|
|
--token) token_flag="$2"; shift 2 ;;
|
|
--yes) auto_yes=true; shift ;;
|
|
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# Extract org/repo slug
|
|
local codeberg_repo
|
|
codeberg_repo=$(parse_repo_slug "$repo_url")
|
|
local project_name="${codeberg_repo##*/}"
|
|
local toml_path="${FACTORY_ROOT}/projects/${project_name}.toml"
|
|
|
|
echo "=== disinto init ==="
|
|
echo "Project: ${codeberg_repo}"
|
|
echo "Name: ${project_name}"
|
|
|
|
# Check for existing config
|
|
local toml_exists=false
|
|
if [ -f "$toml_path" ]; then
|
|
toml_exists=true
|
|
echo "Config: ${toml_path} (already exists, reusing)"
|
|
|
|
# Read repo_root and branch from existing TOML
|
|
local existing_root existing_branch
|
|
existing_root=$(python3 -c "
|
|
import sys, tomllib
|
|
with open(sys.argv[1], 'rb') as f:
|
|
cfg = tomllib.load(f)
|
|
print(cfg.get('repo_root', ''))
|
|
" "$toml_path" 2>/dev/null) || existing_root=""
|
|
existing_branch=$(python3 -c "
|
|
import sys, tomllib
|
|
with open(sys.argv[1], 'rb') as f:
|
|
cfg = tomllib.load(f)
|
|
print(cfg.get('primary_branch', ''))
|
|
" "$toml_path" 2>/dev/null) || existing_branch=""
|
|
|
|
# Use existing values as defaults
|
|
if [ -n "$existing_branch" ] && [ -z "$branch" ]; then
|
|
branch="$existing_branch"
|
|
fi
|
|
|
|
# Handle repo_root: flag overrides TOML, prompt if they differ
|
|
if [ -z "$repo_root" ]; then
|
|
repo_root="${existing_root:-/home/${USER}/${project_name}}"
|
|
elif [ -n "$existing_root" ] && [ "$repo_root" != "$existing_root" ]; then
|
|
echo "Note: --repo-root (${repo_root}) differs from TOML (${existing_root})"
|
|
local update_toml=false
|
|
if [ "$auto_yes" = true ]; then
|
|
update_toml=true
|
|
elif [ -t 0 ]; then
|
|
read -rp "Update repo_root in TOML to ${repo_root}? [y/N] " confirm
|
|
if [[ "$confirm" =~ ^[Yy] ]]; then
|
|
update_toml=true
|
|
else
|
|
repo_root="$existing_root"
|
|
fi
|
|
fi
|
|
if [ "$update_toml" = true ]; then
|
|
python3 -c "
|
|
import sys, re, pathlib
|
|
p = pathlib.Path(sys.argv[1])
|
|
text = p.read_text()
|
|
text = re.sub(r'^repo_root\s*=\s*.*$', 'repo_root = \"' + sys.argv[2] + '\"', text, flags=re.MULTILINE)
|
|
p.write_text(text)
|
|
" "$toml_path" "$repo_root"
|
|
echo "Updated: repo_root in ${toml_path}"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Set up Codeberg auth (interactive if needed, before preflight)
|
|
setup_codeberg_auth "$token_flag" "$codeberg_repo"
|
|
|
|
# Preflight: verify factory requirements
|
|
preflight_check "$codeberg_repo"
|
|
|
|
# Determine repo root (for new projects)
|
|
repo_root="${repo_root:-/home/${USER}/${project_name}}"
|
|
|
|
# Clone or validate
|
|
clone_or_validate "$codeberg_repo" "$repo_root"
|
|
|
|
# Detect primary branch
|
|
if [ -z "$branch" ]; then
|
|
branch=$(detect_branch "$repo_root")
|
|
fi
|
|
echo "Branch: ${branch}"
|
|
|
|
# Generate project TOML (skip if already exists)
|
|
if [ "$toml_exists" = false ]; then
|
|
# Prompt for CI ID if interactive and not already set via flag
|
|
if [ "$ci_id" = "0" ] && [ "$auto_yes" = false ] && [ -t 0 ]; then
|
|
read -rp "Woodpecker CI repo ID (0 to skip CI): " user_ci_id
|
|
ci_id="${user_ci_id:-0}"
|
|
fi
|
|
|
|
generate_toml "$toml_path" "$project_name" "$codeberg_repo" "$repo_root" "$branch" "$ci_id"
|
|
echo "Created: ${toml_path}"
|
|
fi
|
|
|
|
# Create labels on remote
|
|
create_labels "$codeberg_repo"
|
|
|
|
# Generate VISION.md template
|
|
generate_vision "$repo_root" "$project_name"
|
|
|
|
# Install cron jobs
|
|
install_cron "$project_name" "$toml_path" "$auto_yes"
|
|
|
|
echo ""
|
|
echo "Done. Project ${project_name} is ready."
|
|
echo " Config: ${toml_path}"
|
|
echo " Clone: ${repo_root}"
|
|
echo " Run 'disinto status' to verify."
|
|
}
|
|
|
|
# ── status command ───────────────────────────────────────────────────────────
|
|
|
|
disinto_status() {
|
|
local toml_dir="${FACTORY_ROOT}/projects"
|
|
local found=false
|
|
|
|
for toml in "${toml_dir}"/*.toml; do
|
|
[ -f "$toml" ] || continue
|
|
found=true
|
|
|
|
# Parse name and repo from TOML
|
|
local pname prepo
|
|
pname=$(python3 -c "
|
|
import sys, tomllib
|
|
with open(sys.argv[1], 'rb') as f:
|
|
print(tomllib.load(f)['name'])
|
|
" "$toml" 2>/dev/null) || continue
|
|
prepo=$(python3 -c "
|
|
import sys, tomllib
|
|
with open(sys.argv[1], 'rb') as f:
|
|
print(tomllib.load(f)['repo'])
|
|
" "$toml" 2>/dev/null) || continue
|
|
|
|
echo "== ${pname} (${prepo}) =="
|
|
|
|
# Active dev sessions
|
|
local has_sessions=false
|
|
for pf in /tmp/dev-session-"${pname}"-*.phase; do
|
|
[ -f "$pf" ] || continue
|
|
has_sessions=true
|
|
local issue phase_line
|
|
issue=$(basename "$pf" | sed "s/dev-session-${pname}-//;s/\.phase//")
|
|
phase_line=$(head -1 "$pf" 2>/dev/null || echo "unknown")
|
|
echo " Session #${issue}: ${phase_line}"
|
|
done
|
|
if [ "$has_sessions" = false ]; then
|
|
echo " Sessions: none"
|
|
fi
|
|
|
|
# Backlog depth via API
|
|
if [ -n "${CODEBERG_TOKEN:-}" ]; then
|
|
local api="https://codeberg.org/api/v1/repos/${prepo}"
|
|
local backlog_count pr_count
|
|
|
|
backlog_count=$(curl -sf -I \
|
|
-H "Authorization: token ${CODEBERG_TOKEN}" \
|
|
"${api}/issues?state=open&labels=backlog&limit=1" 2>/dev/null \
|
|
| grep -i 'x-total-count' | tr -d '\r' | awk '{print $2}') || backlog_count="?"
|
|
echo " Backlog: ${backlog_count:-0} issues"
|
|
|
|
pr_count=$(curl -sf -I \
|
|
-H "Authorization: token ${CODEBERG_TOKEN}" \
|
|
"${api}/pulls?state=open&limit=1" 2>/dev/null \
|
|
| grep -i 'x-total-count' | tr -d '\r' | awk '{print $2}') || pr_count="?"
|
|
echo " Open PRs: ${pr_count:-0}"
|
|
else
|
|
echo " Backlog: (no CODEBERG_TOKEN)"
|
|
echo " Open PRs: (no CODEBERG_TOKEN)"
|
|
fi
|
|
|
|
echo ""
|
|
done
|
|
|
|
if [ "$found" = false ]; then
|
|
echo "No projects configured."
|
|
echo "Run 'disinto init <repo-url>' to get started."
|
|
fi
|
|
}
|
|
|
|
# ── Main dispatch ────────────────────────────────────────────────────────────
|
|
|
|
case "${1:-}" in
|
|
init) shift; disinto_init "$@" ;;
|
|
status) shift; disinto_status "$@" ;;
|
|
-h|--help) usage ;;
|
|
*) usage ;;
|
|
esac
|