loopy auth github

One-command GitHub App onboarding — the manifest flow

Bring-your-own GitHub App · no central app · no persistent server

~2 clicks serverless private key never enters sandbox status: draft
1

Run the command & build a manifest

CLI · local

The CLI assembles a GitHub App manifest (name, minimal permissions, no webhook) and starts a one-shot 127.0.0.1 callback listener — the gh auth login pattern.

zsh — loopy
$ loopy auth github --org acme-labs building app manifest (contents:write, pull_requests:write, metadata:read) listening on http://127.0.0.1:8765/callback (one-shot) opening browser to create the app…
2

Browser auto-submits the manifest to GitHub

browser · local page

A tiny locally-served HTML page auto-submits a form, POSTing the manifest to GitHub's create-app URL. The user doesn't fill anything in.

127.0.0.1:8765/start → github.com/organizations/acme-labs/settings/apps/new
⟳ Redirecting you to GitHub to create the app…
(form auto-submits the manifest)
3

GitHub shows the create-app confirmation

github.com

GitHub renders a pre-filled confirmation from the manifest. The user clicks once to create the App under their own account/org — loopy owns nothing.

https://github.com/organizations/acme-labs/settings/apps/new?...

Create GitHub App: loopy-acme-labs

Owned by acme-labs · private · no webhook

Repository contentsRead & write
Pull requestsRead & write
MetadataRead-only
4

Redirect back & exchange the temporary code

CLI · GitHub API

GitHub redirects to the local callback with a temporary ?code=. The CLI exchanges it for the App's permanent credentials.

?code=abc123 ──▶ POST /app-manifests/{code}/conversions ──▶ { id, pem, client_id }
5

Persist credentials locally

control-plane · local

The app id and private key are merged into the existing loopy.env. The key is stored inline (newlines escaped) rather than referenced by a file path, so credential loading never depends on which --root a later command uses; loopy.env is added to .gitignore since it now holds the key.

written to disk
loopy.env (gitignored — now holds the key) GITHUB_APP_ID=1234567 GITHUB_APP_PRIVATE_KEY=-----BEGIN RSA PRIVATE KEY-----\n…\n-----END…\n .gitignore (+ loopy.env)
Note: the App still exists but isn't installed on any repos yet — the manifest creates, it doesn't install.
6

Guide installation — pick the repos

github.com

The CLI prints (and optionally opens) the install URL so the user chooses exactly which repos the App can touch.

https://github.com/apps/loopy-acme-labs/installations/new

Install loopy-acme-labs

Where do you want to install this app?

○ All repositories
● Only select repositoriesacme-labs/api, acme-labs/web
7

Verify — mint a token, get a green check

CLI · GitHub API

Best-effort: the CLI mints a scoped installation token from the stored key and reports what it can see.

loopy auth status
$ loopy auth github # final verify step minted installation token (repo-scoped, ~1h) installation #4815 can access 2 repos: acme-labs/api acme-labs/web ✓ GitHub App ready. loopy can now mint short-lived tokens for agents.

Why an App, not a PAT — the trust boundary

🔐 Control-plane (your machine)

long-lived secret lives here, only here

  • GITHUB_APP_PRIVATE_KEY (in gitignored loopy.env) — the App private key
  • Mints JWTs (RS256, ~9 min)
  • Exchanges JWT → installation token

📦 Sandbox (where agents run)

only ephemeral, repo-scoped creds enter

  • installation token — short-lived, ~1h
  • Scoped to selected repos only
  • Used for clone / push / open PR
private key never crosses into the sandbox  ·  only the disposable token does