feat(hive-forge): add artifact-get verb to download CI run artifacts

Forgejo 15 has no REST endpoint to download an Actions artifact — the
only path is the web UI download route, which is keyed by the run's
global id rather than the per-repo run number shown in run-page URLs.
The REST artifacts list route keys off the run number instead, so the
two can't be chained directly.

artifact-get takes the run number (what pr-status surfaces as a CI
context target_url), translates it to the global run id via the REST
runs list by matching each run's html_url tail, then GETs the web
download route with the agent's forge token. Saves the artifact zip to
a path (default /tmp/forge-artifact-<name>.zip) or streams to stdout
with -o -.

The artifact name is percent-encoded into the path. The encoder that
list already used for query-string filters is promoted to a shared
verbs::pct_encode helper so both call sites stay in sync.

Lets an agent pull a CI-built artifact (e.g. a paper PDF) into /shared
without host access.
This commit is contained in:
atlas 2026-06-15 17:29:07 +02:00 committed by mara
commit 6490fc422e
6 changed files with 184 additions and 41 deletions

View file

@ -273,6 +273,16 @@ impl Client {
format!("{}/attachments/{uuid}", self.base)
}
/// Build a full URL for a base-relative *web* path (i.e. NOT under
/// `/api/v1/`). Used for endpoints Forgejo only serves through its
/// web UI rather than the REST API --- e.g. Actions artifact
/// downloads at `<base>/<owner>/<repo>/actions/runs/<n>/artifacts/<name>`.
/// `path` should start with `/`.
#[must_use]
pub fn web_url(&self, path: &str) -> String {
format!("{}{path}", self.base)
}
/// GET a raw (non-API) URL and return the response body as bytes.
/// The client's auth headers are still sent — Forgejo requires them
/// for private attachment downloads. Uses the full URL as-is; the