The LUCY family · repos

GitHub, reduced to what a person would say about it.

Repositories, pull requests, issues, CI, files and commits as one narrow HTTP contract. It holds no GitHub credential of its own: each call runs under the caller's, resolved from keyring for the profile the request names.

Port 8011 Audience github-api No raw GitHub payloads MIT licensed
$ curl -sH "Authorization: Bearer $TOKEN" \
       -H "X-Keyring-Profile: personal" \
       http://127.0.0.1:8011/v1/me

{ "login": "octo",
  "kind": "app",
  "selection": "selected",
  "repositories": 4 }
No credential at restThe person's GitHub token comes from keyring per request and is never stored.
A fixed projectionEvery answer is a small public model; no raw GitHub payload leaves the service.
Hidden means missingA repository the connection cannot see answers 404, exactly as one that does not exist.
Every cap is confessedLists answer with a total; logs, files and patches say what was cut.

How it works

One call, under the caller's own GitHub connection.

Every /v1 route needs a keyring token minted for audience github-api, and reads X-Keyring-Profile to choose which of the person's GitHub connections to use.

01

The token is verified

A keyring JWT with the exact audience github-api. Anything else is a 401 before GitHub is touched.

02

The credential is resolved

Keyring answers with that person's GitHub credential for the profile: the Lucy GitHub App, or a fine-grained personal access token. It is cached only until shortly before it expires, with one forced refresh on a GitHub 401.

03

GitHub is called, and the answer narrowed

The raw payload is mapped to a public model. Failures are application/problem+json whose detail names the fix; a GitHub rate limit is a 429 carrying GitHub's own Retry-After.

The API

What each group of routes is for.

{repo} is {owner}/{name}. Lists take limit (1 to 100, default 20). The full table, the models and every error code are in docs/api.md.

GET /v1/me

Identity

Who the connection is, whether it is the app or a token, and whether it reaches all repositories or selected ones.

/v1/repos

Repositories

Find, read, create, change visibility or description, archive and delete.

/v1/repos/{repo}/pulls

Pull requests

List, read with reviews, threads and checks, read the changed files patch by patch, open, update, merge and review.

/v1/repos/{repo}/issues

Issues

List, open, close and comment. Comments work on pull requests too.

/v1/repos/{repo}/checks

CI

The checks on a pull request, branch or commit, a window of a job's log, re-run, cancel and workflow dispatch.

contents · tree · commits · branches

Files and commits

Read a file or a tree, commit several files through the Git Data API (never forced), and create or delete a branch.

/v1/subscriptions

Subscriptions

Watch for checks settling, a pull request merging, a review landing or a run completing. Each ends with one signed signal.

GET /healthy · GET /ready

Probes

Liveness, and readiness that is 503 when keyring's signing keys, GitHub or the database cannot be reached.

Run it

Three commands, then call it.

It needs keyring running and this service listed in keyring's service tokens as github-api. The tests need neither: they run against a fake keyring and an in-memory GitHub.

  1. Install the dependencies.
    make install
  2. Copy the example environment, then set GHAPI_KEYRING_SERVICE_TOKEN in .env.
    cp .env.example .env
  3. Run it. The OpenAPI page is at http://127.0.0.1:8011/docs.
    make run

The README has the checklist for registering the GitHub App, and docs/operations.md lists every GHAPI_ variable.

What it will not do

Limits that are part of the design.

Does it keep my GitHub token?

No. Keyring is the vault. The credential is resolved per request, cached in memory only until shortly before it expires, and never written to disk. A subscription holds it in memory for a bounded time and stores nothing; after a restart the hub's sweep drives the subscription instead.

Can it see more of GitHub than I allowed?

No. How much it reaches is chosen on GitHub: the app is installed on all repositories or on selected ones, and a fine-grained token carries its own limits. GET /v1/me reports which.

Does it receive webhooks?

No. It polls. A subscription is looked at when it is opened, in the background, and on every read, and every subscription expires on its own, at most a week out.

Can a caller make it POST anywhere?

Not in production: GHAPI_SIGNAL_URL_PREFIXES limits where a signal may be sent. A signal says that something happened, never the result, and is signed with the subscription's own secret.

Docs

Written for the person who has to run it.