← All skills
  • Integrations
  • v1.0.0
  • github
  • pull-requests
  • issues
  • ci

GitHub

Manage GitHub repositories, issues, pull requests, and code reviews. Search code, create branches, and track CI/CD status.

SKILL.md

GitHub Skill

Manage GitHub repositories, create and respond to issues, handle pull requests, run code reviews, and track CI/CD pipeline status. Search code and manage branches for seamless development workflow integration.

Dashboard Connection Required This skill needs GitHub connected in the MyClawBots dashboard: https://myclawbots.ai/settings/social → GitHub → Connect.

That is a one-time OAuth sign-in on the account, and every agent you own inherits it — there is nothing to connect per agent, and nothing to paste.

Your GitHub token

You never see the real token. MyClawBots keeps it and gives you $GITHUB_PAT, a stand-in (mcb_sec_…) that only works through vault-curl: it swaps in the real token on the way to https://api.github.com, and only there. There is no gh CLI on this machine.

  • Put $GITHUB_PAT only in the Authorization: Bearer header, exactly as in the commands below. Anywhere else (URL, body, another header) is refused. The one exception is git over HTTPS to github.com through the vault proxy (section 14), where it is the Basic-auth password.
  • Never print, echo or save the stand-in or any token, and never try to recover the real one. Don't use OpenClaw's built-in secrets tool: these keys are not stored there, so it always looks empty.

Prerequisites

  • GitHub account with repository access
  • GitHub connected at https://myclawbots.ai/settings/social (one-time, account-wide)
  • Repository permissions for the actions you plan to take
  • Appropriate branch protection settings understood

Dependencies

Dependency Required? Why
GitHub connected in Settings → Connectors Yes Provides $GITHUB_PAT (a stand-in) to this agent
vault-curl Yes Sends the calls; swaps the stand-in for the real token
slack skill No Optional: for PR and issue notifications

Pre-flight checks:

  • Check the stand-in is present: [ -n "$GITHUB_PAT" ] && echo present || echo missing

  • If it is missing (GitHub was connected after you started), look it up live: vault-curl --secrets. If an entry is named GITHUB_PAT, use its surrogate string in place of $GITHUB_PAT in every command. No restart is needed.

  • If vault-curl is not found (command -v vault-curl prints nothing), run bash /root/.openclaw/skills/github/scripts/vault-curl.sh in its place.

  • If none of that finds a token, do NOT reach for gh, git config, or any local credential store — none of them are configured here. Say exactly this instead:

    I don't have access to GitHub yet. Connect GitHub at https://myclawbots.ai/settings/social, then ask me again.

  • If integrating with Slack: ensure slack skill is connected

How It Works

This skill provides agent-friendly access to GitHub's core development operations via the official GitHub REST API. Every call is vault-curl against https://api.github.com with -H "Authorization: Bearer $GITHUB_PAT".

The skill supports:

  • Repository Management: List repos and view details
  • Issue Tracking: Create, search, update, and discuss issues
  • Pull Requests: Create, view, and review PRs with full diff analysis
  • Code Review: Create reviews with approval, changes requested, or comments
  • CI/CD Integration: Monitor GitHub Actions workflow runs and status
  • Code Search: Find specific code patterns across repositories
  • Branch Management: List, create, and switch branches

Available Tools

1. List Repositories

Get all repositories you have access to.

vault-curl -sS -X GET "https://api.github.com/user/repos?per_page=30&sort=updated" \
  -H "Authorization: Bearer $GITHUB_PAT"

Returns: Array of repository objects with names, URLs, descriptions, languages, stars, and fork status.


2. Get Repository Details

Retrieve comprehensive information about a specific repository.

vault-curl -sS -X GET "https://api.github.com/repos/owner/repo" \
  -H "Authorization: Bearer $GITHUB_PAT"

Parameters:

  • owner: Repository owner username (required)
  • repo: Repository name (required)

Returns: Repository metadata including description, URL, visibility, default branch, language, topics, and activity stats.


3. List Issues

Retrieve issues from a repository with filtering by state and limits.

vault-curl -sS -X GET "https://api.github.com/repos/owner/repo/issues?state=open&per_page=30" \
  -H "Authorization: Bearer $GITHUB_PAT"

Query Parameters:

  • owner: Repository owner (required)
  • repo: Repository name (required)
  • state: Filter by "open", "closed", "all" (optional, default: open)
  • labels: Comma-separated labels to filter (optional)
  • assignee: Filter by assignee username (optional)
  • limit: Max results, default 30 (optional)

Returns: Array of issue objects with numbers, titles, bodies, labels, assignees, and status.


4. Create Issue

Create a new GitHub issue with title, description, labels, and assignees.

vault-curl -sS -X POST "https://api.github.com/repos/owner/repo/issues" \
  -H "Authorization: Bearer $GITHUB_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Fix authentication flow on mobile",
    "body": "Users on iOS are unable to complete OAuth flow. Session expires after redirect.\n\n## Steps to Reproduce\n1. Open app on iOS\n2. Tap Login\n3. Complete OAuth\n\n## Expected\nUser logged in and redirected to dashboard\n\n## Actual\nSession error, redirect fails",
    "labels": ["bug", "authentication", "mobile"],
    "assignees": ["username1", "username2"]
  }'

Request Body:

  • title: Issue title (required)
  • body: Issue description in Markdown (required for clarity)
  • labels: Array of label strings (optional)
  • assignees: Array of GitHub usernames (optional)

Returns: Created issue number and full issue object.


5. Update Issue

Modify issue title, body, labels, assignees, or state.

vault-curl -sS -X PATCH "https://api.github.com/repos/owner/repo/issues/42" \
  -H "Authorization: Bearer $GITHUB_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Fix authentication flow on mobile (IN PROGRESS)",
    "state": "open",
    "labels": ["bug", "authentication", "mobile", "high-priority"],
    "assignees": ["username1"]
  }'

Parameters:

  • owner: Repository owner (required)
  • repo: Repository name (required)
  • number: Issue number (required)

Request Body:

  • Any of: title, body, state (open/closed), labels, assignees

Returns: Updated issue object.


6. Comment on Issue

Add a comment to an issue for discussion and updates.

vault-curl -sS -X POST "https://api.github.com/repos/owner/repo/issues/42/comments" \
  -H "Authorization: Bearer $GITHUB_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Started investigating. The issue appears to be in the OAuth redirect handler. Found that the session timeout is set to 5 minutes, but the OAuth flow takes longer on slow networks."
  }'

Parameters:

  • owner: Repository owner (required)
  • repo: Repository name (required)
  • number: Issue number (required)

Request Body:

  • body: Comment text in Markdown (required)

Returns: Created comment with ID, author, and timestamp.


7. List Pull Requests

Retrieve pull requests from a repository filtered by state.

vault-curl -sS -X GET "https://api.github.com/repos/owner/repo/pulls?state=open&per_page=20" \
  -H "Authorization: Bearer $GITHUB_PAT"

Query Parameters:

  • owner: Repository owner (required)
  • repo: Repository name (required)
  • state: Filter by "open", "closed", "merged", "all" (optional, default: open)
  • head: Filter by head branch (optional)
  • base: Filter by base branch (optional)
  • limit: Max results, default 20 (optional)

Returns: Array of PR objects with numbers, titles, branches, and status.


8. Get Pull Request Details

Retrieve full details about a specific PR including diff statistics and reviews.

vault-curl -sS -X GET "https://api.github.com/repos/owner/repo/pulls/128" \
  -H "Authorization: Bearer $GITHUB_PAT"

Parameters:

  • owner: Repository owner (required)
  • repo: Repository name (required)
  • number: PR number (required)

Returns: PR object with title, description, base/head branches, author, state, diff stats (additions, deletions, changed files), reviewers, and approval status.


9. Create Pull Request Review

Submit a code review on a PR with approval, requested changes, or comments.

vault-curl -sS -X POST "https://api.github.com/repos/owner/repo/pulls/128/reviews" \
  -H "Authorization: Bearer $GITHUB_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Implementation looks solid. The OAuth redirect handler properly manages session timeout and the retry logic is robust. A few suggestions for improvement:\n\n1. Consider adding exponential backoff for network failures\n2. Add more comprehensive error logging\n\nOtherwise approved.",
    "event": "APPROVE"
  }'

Parameters:

  • owner: Repository owner (required)
  • repo: Repository name (required)
  • number: PR number (required)

Request Body:

  • body: Review comments in Markdown (optional)
  • event: "APPROVE", "REQUEST_CHANGES", or "COMMENT" (required)

Returns: Created review ID and object.


10. List Recent CI Runs

Get recent GitHub Actions workflow runs for a repository.

vault-curl -sS -X GET "https://api.github.com/repos/owner/repo/actions/runs?per_page=10" \
  -H "Authorization: Bearer $GITHUB_PAT"

Query Parameters:

  • owner: Repository owner (required)
  • repo: Repository name (required)
  • limit: Max runs to return, default 10 (optional)

Returns: Array of workflow run objects with names, status (success, failure, pending), conclusion, branch, and timestamp.


11. Search Code

Search for specific code patterns, functions, or strings across a repository.

vault-curl -sS -X GET "https://api.github.com/search/code?q=function%20authenticate%20repo:owner/repo&per_page=20" \
  -H "Authorization: Bearer $GITHUB_PAT"

Query Parameters:

  • q: Search query (required)
  • repo: Repository in "owner/repo" format (optional, omit to search all)
  • limit: Max results, default 20 (optional)

Returns: Array of code search results with file paths, line numbers, and context snippets.


12. List Branches

Get all branches in a repository.

vault-curl -sS -X GET "https://api.github.com/repos/owner/repo/branches" \
  -H "Authorization: Bearer $GITHUB_PAT"

Parameters:

  • owner: Repository owner (required)
  • repo: Repository name (required)

Returns: Array of branch objects with names, commit SHAs, and protected status.


13. Create Branch

Create a new branch from an existing branch for feature development.

# First, get the SHA of the branch you are branching from:
vault-curl -sS -X GET "https://api.github.com/repos/owner/repo/git/ref/heads/main" \
  -H "Authorization: Bearer $GITHUB_PAT"

# Then create the new branch ref using that SHA:
vault-curl -sS -X POST "https://api.github.com/repos/owner/repo/git/refs" \
  -H "Authorization: Bearer $GITHUB_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "ref": "refs/heads/feature/oauth-mobile-fix",
    "sha": "<sha-from-previous-call>"
  }'

Parameters:

  • owner: Repository owner (required)
  • repo: Repository name (required)

Request Body:

  • ref: Fully qualified ref of the new branch, e.g. refs/heads/<name> (required)
  • sha: Commit SHA to branch from (required — fetch it with the git/ref/heads/<base> call above)

Returns: Created ref object with the new branch name and commit SHA.


14. Clone and Push with git (needs the vault proxy)

$GITHUB_PAT is also accepted by github.com itself, as the password of git's HTTPS Basic auth. This works only when the vault proxy is on for this agent: git honours HTTPS_PROXY, the proxy sees the Basic header git sends to github.com, and swaps the stand-in (the password) for the real token there. vault-curl cannot do this for you: git talks to GitHub itself.

# Check the proxy first. Empty output = no proxy: use section 15 instead.
printenv HTTPS_PROXY

# Clone (the stand-in is only the Basic-auth password; the user name is literal):
git clone https://x-access-token:$GITHUB_PAT@github.com/owner/repo.git /root/.openclaw/workspace/repo
cd /root/.openclaw/workspace/repo
# Don't keep the stand-in in .git/config:
git remote set-url origin https://github.com/owner/repo.git

# Push a branch (name the URL again, same form):
git push https://x-access-token:$GITHUB_PAT@github.com/owner/repo.git feature/oauth-mobile-fix
  • Only github.com, only in that https://x-access-token:$GITHUB_PAT@github.com/… form. Never put the stand-in anywhere else in a URL, and never in git config, a credential helper or a file.
  • A push is one request per pack: packs over 10 MB are refused by the proxy. Push smaller commits, or use section 15 for single files.
  • If git fails with an authentication or certificate error, the proxy is not serving this command: fall back to section 15. Don't retry with other tricks.

15. Read and Write Repository Files (API fallback)

Always available (plain vault-curl, no proxy needed). Use it when HTTPS_PROXY is empty or git fails as described above.

# Read one file (raw content) from a branch:
vault-curl -sS "https://api.github.com/repos/owner/repo/contents/src/app.py?ref=main" \
  -H "Authorization: Bearer $GITHUB_PAT" \
  -H "Accept: application/vnd.github.raw"

# Create or update a file on a branch (content is base64; "sha" is required
# when updating, from a GET of the same path without the raw Accept header):
vault-curl -sS -X PUT "https://api.github.com/repos/owner/repo/contents/docs/notes.md" \
  -H "Authorization: Bearer $GITHUB_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "docs: add notes",
    "content": "<base64 of the new file>",
    "branch": "feature/oauth-mobile-fix",
    "sha": "<current file sha, when updating>"
  }'

A public repository can always be cloned without a token for local reading: git clone https://github.com/owner/repo.git /root/.openclaw/workspace/repo.


Workflow Statuses

Common GitHub Actions workflow statuses:

Status Meaning
pending Workflow queued but not started
in_progress Workflow currently running
success All jobs passed
failure One or more jobs failed
cancelled Workflow manually cancelled
skipped Workflow skipped (no changes)

Review Events

When creating a PR review, use one of these events:

Event Meaning
APPROVE Approve the PR for merge
REQUEST_CHANGES Request changes before merging
COMMENT Comment without approval or rejection

Important Rules

  1. Always Search for Issues First: Before creating a new issue, search for existing ones using keywords. Many issues are duplicates.

  2. Confirm Before Creating Issues/PRs: When creating issues or PRs, verify they don't already exist. Double-check the repository.

  3. Include Clear Descriptions: Provide comprehensive descriptions with context, steps to reproduce (for bugs), expected vs actual behavior, and relevant error messages.

  4. Check CI Status Before Approving: Never approve a PR without verifying that all CI checks pass. Use the CI runs endpoint to validate.

  5. Review Diff Statistics: Always check the diff stats before reviewing. Large PRs (100+ files) may need different review strategies.

  6. Use Appropriate Review Events: Use "REQUEST_CHANGES" only when blocking issues exist. Use "COMMENT" for suggestions. Use "APPROVE" when ready.

  7. Reference Issues in PRs: When creating a PR that fixes an issue, reference it in the description (e.g., "Fixes #42") to auto-link them.

  8. Add Meaningful Labels: Use labels consistently (bug, feature, documentation, high-priority, etc.) for organization and filtering.

  9. Keep Branch Names Descriptive: Use branch names like "feature/oauth-mobile-fix" or "bugfix/session-timeout" that explain the work.

  10. Avoid Merging Failing Builds: Never merge a PR when CI is failing or incomplete. Wait for green status.

  11. Use Code Search Strategically: Before refactoring, search for all usages of a function or pattern to understand impact.

  12. Request Reviews Proactively: When creating PRs, ensure appropriate reviewers are assigned. Don't leave PRs in limbo.

Scripts

1 file ship with this skill. They are installed beside the SKILL.md.

scripts/vault-curl.sh10.5 KB
#!/bin/bash
# vault-curl — curl for calls that use a key saved in MyClawBots → Secrets.
#
# The agent only ever holds surrogates (mcb_sec_…). This wrapper rewrites every
# https:// URL to the api-proxy's /vault/fwd/<host>/<path> route, where the
# proxy swaps each surrogate for the real key (only toward that key's allowed
# hosts and injection points) and scrubs it from the response. Spec:
# docs/secrets-vault.md §10.1.
#
# Options are an ALLOWLIST: anything that could send the request (or the
# platform key) somewhere other than the vault — proxies, --connect-to,
# --resolve, config files, --next — is refused with exit 2.
#
# Must stay bash 3.2 compatible (the tests run it with macOS /bin/bash): no
# ${x,,}, no associative arrays, no mapfile. Never use `set -x` here, and never
# print the key. A copy ships in the gemini-image skill; keep them identical.
set -euo pipefail

VAULT_CURL_BIN="${VAULT_CURL_BIN:-/usr/bin/curl}"
VAULT_OPENCLAW_CONFIG="${VAULT_OPENCLAW_CONFIG:-/root/.openclaw/openclaw.json}"
VAULT_APPLY_ENV="${VAULT_APPLY_ENV:-/root/.openclaw/apply-env.sh}"

usage() {
    cat <<'USAGE'
vault-curl — call an API with a key saved in MyClawBots → Secrets.

Use it exactly like curl, with https:// URLs only:

    vault-curl -sS https://api.example.com/v1/things -H "Authorization: Bearer $MY_KEY"

Put $NAME only where the key's "Put it in" says (a header, a query parameter,
or -u for Basic auth). Your saved keys and where each one goes are listed in
AGENTS.md ("Saved API keys"). A key added after you started is not in your env
yet: run `vault-curl --secrets` (or call the myclawbots list_secrets tool) and
use its surrogate string in place of $NAME. OpenClaw's own secrets tool does
not see these keys.

    vault-curl --secrets     # JSON: every saved key, its surrogate, hosts, where it goes
USAGE
}

die() { echo "vault-curl: $2" >&2; exit "$1"; }

lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }

# ── --help / no arguments ─────────────────────────────────────
if [ "$#" -eq 0 ]; then
    usage >&2
    exit 2
fi
for a in "$@"; do
    [ "$a" = "--" ] && break
    if [ "$a" = "--help" ]; then usage; exit 0; fi
done

# ── Vault base URL ────────────────────────────────────────────
# VAULT_API_PROXY_URL, not VAULT_PROXY_URL: that name is the vault-proxy
# (:3128 HTTPS proxy) in control-plane env and must never be taken for the
# api-proxy base.
VAULT_BASE="${VAULT_API_PROXY_URL:-${API_PROXY_URL:-${SOCIAL_POST_PROXY_URL:-}}}"
while [ -n "$VAULT_BASE" ] && [ "${VAULT_BASE%/}" != "$VAULT_BASE" ]; do
    VAULT_BASE="${VAULT_BASE%/}"
done
[ -n "$VAULT_BASE" ] || die 1 "API_PROXY_URL is not configured"

# ── Proxy key (D17): openclaw.json MCP header → apply-env.sh → env ──
# Read at run time, so a settings save that mints a new key takes effect
# without restarting the gateway. apply-env.sh is parsed, never sourced.
# Sets KEY.
is_key() { [[ "$1" =~ ^[0-9a-f]{64}$ ]]; }
resolve_key() {
    local cand
    KEY=""
    if command -v jq >/dev/null 2>&1; then
        cand=$(jq -r '.mcp.servers.myclawbots.headers["x-api-key"] // empty' "$VAULT_OPENCLAW_CONFIG" 2>/dev/null || true)
        if is_key "$cand"; then KEY="$cand"; fi
    fi
    if [ -z "$KEY" ]; then
        cand=$(sed -n 's/^export PROXY_API_KEY="\([0-9a-f]\{64\}\)"$/\1/p' "$VAULT_APPLY_ENV" 2>/dev/null | tail -n 1 || true)
        if is_key "$cand"; then KEY="$cand"; fi
    fi
    if [ -z "$KEY" ] && is_key "${PROXY_API_KEY:-}"; then
        KEY="$PROXY_API_KEY"
    fi
    [ -n "$KEY" ] || die 1 "no MyClawBots proxy key found"
}

run_curl() {
    exec env -u http_proxy -u HTTP_PROXY -u https_proxy -u HTTPS_PROXY -u all_proxy -u ALL_PROXY \
        -u no_proxy -u NO_PROXY -u CURL_HOME \
        "$VAULT_CURL_BIN" -q --noproxy '*' --proto =http --proto-redir =http --max-redirs 10 --path-as-is \
        -H "x-mcb-proxy-key: ${KEY}" "$@"
}

# ── --secrets: the live listing (surrogates only, never values) ──
# The same GET start.sh makes at boot, so a key added after boot can be found
# without a restart or an MCP client.
if [ "$#" -eq 1 ] && [ "$1" = "--secrets" ]; then
    resolve_key
    run_curl -sS --fail-with-body --max-time 20 "${VAULT_BASE}/vault/secrets"
fi

# ── Option tables ─────────────────────────────────────────────
SHORT_FLAGS='sSfiIvLGgNOJ#'
SHORT_VALUE='AbcdDeFHmorTuwXyYzC'
LONG_FLAGS=' --silent --show-error --fail --fail-with-body --fail-early --include --head --verbose --location --get --globoff --no-buffer --compressed --no-progress-meter --progress-bar --remote-name --remote-header-name --create-dirs --http1.1 --http2 --raw --retry-all-errors --retry-connrefused --no-keepalive '
LONG_VALUE=' --data --data-ascii --data-binary --data-raw --data-urlencode --json --form --form-string --header --user --user-agent --referer --cookie --cookie-jar --output --output-dir --dump-header --write-out --request --max-time --connect-timeout --upload-file --range --retry --retry-delay --retry-max-time --limit-rate --max-filesize --oauth2-bearer --url-query --speed-limit --speed-time --time-cond --continue-at '

# in_list " --a --b " --a   (exact word match, no globbing)
in_list() {
    case "$1" in
        *" $2 "*) return 0 ;;
    esac
    return 1
}

refuse() {
    die 2 "$1 is not allowed: it could send the request somewhere the vault can't check"
}

# Named explicitly so the agent gets a clear reason (the allowlist would
# refuse them anyway).
is_refused_long() {
    case "$1" in
        --proxy|--proxy-*|--preproxy|--socks*|--connect-to|--resolve|--unix-socket|\
        --abstract-unix-socket|--config|--next|--doh-url|--interface|--haproxy-protocol|\
        --expand-*|--variable|--location-trusted|--proto|--proto-redir|--path-as-is|\
        --max-redirs|--insecure|--cert|--key|--cacert|--capath|--trace|--trace-ascii|\
        --stderr|--netrc*|--aws-sigv4|--libcurl) return 0 ;;
    esac
    return 1
}

is_refused_short() {
    case "$1" in
        x|U|K|:|n|k) return 0 ;;
    esac
    return 1
}

# ── URL candidate → vault URL ─────────────────────────────────
# Sets REWRITTEN. Path and query bytes are kept exactly (no re-encoding).
rewrite_url() {
    local url="$1" scheme rest authority tail path seg lseg
    scheme=$(lower "${url:0:8}")
    if [ "$scheme" != "https://" ]; then
        if [ "$(lower "${url:0:7}")" = "http://" ]; then
            die 2 "only https:// URLs can use saved secrets"
        fi
        die 2 "$url is not an https:// URL"
    fi
    rest="${url:8}"

    # Authority ends at the first / ? or #.
    authority="${rest%%[/?#]*}"
    tail="${rest:${#authority}}"

    case "$authority" in
        *@*) die 2 "credentials in the URL are not supported; put credentials in -u or a header" ;;
    esac
    authority=$(lower "$authority")
    authority="${authority%:443}"
    case "$authority" in
        '') die 2 "$url has no host" ;;
        *:*) die 2 "only the default https port (443) is supported" ;;
    esac

    # Drop any #fragment.
    tail="${tail%%#*}"

    # No . or .. path segments (curl runs with --path-as-is, and the proxy
    # refuses them too). Percent-encoded dots count: URL parsers decode them.
    path="${tail%%\?*}"
    while [ -n "$path" ]; do
        path="${path#/}"
        seg="${path%%/*}"
        path="${path:${#seg}}"
        lseg=$(lower "$seg")
        case "$lseg" in
            .|..|%2e|.%2e|%2e.|%2e%2e) die 2 "./.. path segments are not supported" ;;
        esac
    done

    [ -n "$tail" ] || tail="/"
    REWRITTEN="${VAULT_BASE}/vault/fwd/${authority}${tail}"
}

# ── Argument walk ─────────────────────────────────────────────
OUT=()
URLS=0
positional=0
while [ "$#" -gt 0 ]; do
    arg="$1"
    shift

    if [ "$positional" -eq 1 ]; then
        rewrite_url "$arg"; OUT+=("$REWRITTEN"); URLS=$((URLS + 1))
        continue
    fi

    case "$arg" in
        --)
            positional=1
            ;;
        --*)
            name="${arg%%=*}"
            if [ "$name" != "$arg" ]; then has_eq=1; value="${arg#*=}"; else has_eq=0; value=""; fi
            if is_refused_long "$name"; then refuse "$name"; fi
            if [ "$name" = "--url" ]; then
                if [ "$has_eq" -eq 0 ]; then
                    [ "$#" -gt 0 ] || die 2 "--url needs a value"
                    value="$1"; shift
                fi
                rewrite_url "$value"; OUT+=("--url" "$REWRITTEN"); URLS=$((URLS + 1))
            elif in_list "$LONG_FLAGS" "$name"; then
                [ "$has_eq" -eq 0 ] || die 2 "$name does not take a value"
                OUT+=("$arg")
            elif in_list "$LONG_VALUE" "$name"; then
                if [ "$has_eq" -eq 1 ]; then
                    OUT+=("$arg")
                else
                    [ "$#" -gt 0 ] || die 2 "$name needs a value"
                    OUT+=("$arg" "$1"); shift
                fi
            else
                die 2 "$name is not supported"
            fi
            ;;
        -?*)
            # Short bundle, one letter at a time. The first value letter takes
            # the rest of the bundle (or the next argument) as its value.
            i=1
            took_next=0
            while [ "$i" -lt "${#arg}" ]; do
                c="${arg:$i:1}"
                if is_refused_short "$c"; then refuse "-$c"; fi
                case "$SHORT_FLAGS" in
                    *"$c"*) i=$((i + 1)); continue ;;
                esac
                case "$SHORT_VALUE" in
                    *"$c"*)
                        if [ $((i + 1)) -ge "${#arg}" ]; then
                            [ "$#" -gt 0 ] || die 2 "-$c needs a value"
                            took_next=1
                        fi
                        break
                        ;;
                esac
                die 2 "-$c is not supported"
            done
            if [ "$took_next" -eq 1 ]; then
                OUT+=("$arg" "$1"); shift
            else
                OUT+=("$arg")
            fi
            ;;
        *)
            rewrite_url "$arg"; OUT+=("$REWRITTEN"); URLS=$((URLS + 1))
            ;;
    esac
done

[ "$URLS" -gt 0 ] || die 2 "no https:// URL given"

resolve_key

# -q must be curl's first argument (it disables ~/.curlrc). --proto =http is
# right: every URL curl fetches is a rewritten plain-http vault URL on 6PN.
run_curl ${OUT[@]+"${OUT[@]}"}