An open API service indexing awesome lists of open source software.

https://github.com/drogers0/gh-image

A gh CLI extension that uploads images to GitHub from the command line
https://github.com/drogers0/gh-image

gh-cli gh-issue gh-s3-artifact github github-images github-issues github-prs

Last synced: 22 days ago
JSON representation

A gh CLI extension that uploads images to GitHub from the command line

Awesome Lists containing this project

README

          


gh-image banner


Drop images into GitHub issues, PRs, and READMEs, straight from the command line.


Latest release
GitHub stars
Total downloads
License: MIT
Go Report Card

---

GitHub has no public API for image uploads. The web UI uses an internal endpoint that produces `user-attachments` URLs whose visibility is scoped to the repository they were uploaded to. `gh-image` replicates that flow as a `gh` CLI extension, so you can drop a screenshot into a bug report, README, or Slack thread without leaving the terminal — and images on private repos stay private.

```console
$ gh image screenshot.png
![screenshot.png](https://github.com/user-attachments/assets/88f4599a-…-bc24)
```

## Installation

```bash
gh extension install drogers0/gh-image
```

That's it. The [`gh` CLI](https://cli.github.com) auto-detects your platform and downloads the prebuilt binary. Pre-built releases ship for **macOS** (arm64, amd64), **Linux** (amd64, arm64), and **Windows** (amd64).

Build from source

```bash
git clone https://github.com/drogers0/gh-image
cd gh-image
go build -o gh-image
gh extension install .
```

Requires Go 1.26+.

## Usage

```bash
# Upload an image (infers repo from the current git workspace)
gh image screenshot.png

# Upload multiple images at once
gh image hero.png diagram.png chart.png

# Target a specific repository
gh image screenshot.png --repo owner/repo
```

Each successful upload prints a ready-to-paste markdown reference on its own line:

```
![hero.png](https://github.com/user-attachments/assets/…)
![diagram.png](https://github.com/user-attachments/assets/…)
```

If any upload fails, the error is printed to stderr and the process exits non-zero — other images in the batch still upload.

### Pipe directly into an issue, PR, or comment

From inside the repo's working directory, both `gh image` and `gh issue create` infer the target repository automatically:

```bash
gh issue create \
--title "Login button stuck in loading state" \
--body "Repro on staging:

$(gh image bug.png)

Happens consistently after the third click."
```

## Authentication

`gh-image` authenticates with your existing GitHub session — **no tokens to provision, no OAuth scopes to configure** for everyday local use. The tool reads the `user_session` cookie from your browser's encrypted cookie store.

**Supported browsers:** Chrome · Brave · Chromium · Edge · Firefox · Opera · Safari

**Supported platforms:** macOS · Linux · Windows

On macOS, a Keychain prompt may appear on first use to authorize access to your browser's cookie encryption key. Click **Always Allow** to skip future prompts.

> [!NOTE]
> **Windows + Chrome 127+:** Some versions of Chrome on Windows are not yet supported by the underlying cookie library. Use another browser or [investigate potential workarounds](https://github.com/drogers0/gh-image/issues/4).

### Session token override

For CI, headless environments, or shared machines, you can supply the session token explicitly. Resolution order (first match wins):

| Priority | Source | When to use |
|---|---|---|
| 1 | `--token ` flag | One-off invocations |
| 2 | `GH_SESSION_TOKEN` env var | CI/CD, shared machines |
| 3 | Browser cookie store | Local interactive use (default) |

```bash
# Flag (visible in process listings like `ps aux` — avoid on shared machines)
gh image --token "$MY_TOKEN" screenshot.png --repo owner/repo

# Environment variable (preferred — not visible to `ps aux`)
GH_SESSION_TOKEN="$MY_TOKEN" gh image screenshot.png --repo owner/repo
```

> [!WARNING]
> `user_session` cookies grant **full account access** — they are not scoped like personal access tokens. Treat them with the same care as a password. If leaked, **[sign out of GitHub](https://github.com/logout)** on the machine that holds the session; if you are not on that machine, revoke it through [Settings → Sessions](https://github.com/settings/sessions), or [change your password](https://github.com/settings/security) (which kills every session in one action).

## CI / CD

`gh-image` runs unattended in GitHub Actions when given a session token via `GH_SESSION_TOKEN`.

> [!CAUTION]
> **Use a dedicated bot account for CI/CD on shared repos.** GitHub hides secret values in the UI and masks log emissions, but a determined collaborator with write access can craft a workflow that exfiltrates the value through channels masking doesn't cover. Storing your *personal* `user_session` means such a leak compromises your account; a bot account scopes the blast radius to that bot. Decide whose token to extract in step 1 below accordingly.

**Setup**

1. Run `gh image extract-token` locally to capture the token (token → stdout, status → stderr), then run `gh image check-token --token ` to confirm it authenticates as the intended user (username → stdout on success, exit code `0` = valid).
2. Create a GitHub environment (Settings → Environments → New environment), e.g. `gh-image`, and restrict deployment branches to a trusted set (e.g. `main` only).
3. Add the token as an **environment secret** named `GH_SESSION_TOKEN` on that environment.

```yaml
jobs:
upload:
runs-on: ubuntu-latest
environment: gh-image # binds this job to the scoped environment
steps:
- name: Upload screenshots
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # for gh CLI auth
GH_SESSION_TOKEN: ${{ secrets.GH_SESSION_TOKEN }} # for the upload itself
run: |
gh extension install drogers0/gh-image
gh image check-token # optional: fail fast if the session expired
gh image screenshot.png --repo ${{ github.repository }}
```

> [!NOTE]
> `user_session` cookies expire when GitHub invalidates the session. A scheduled `check-token` job is the cleanest way to detect expiry before it breaks a real run.

## How it works

1. Resolves a `user_session` cookie from the configured source (flag → env → browser).
2. Fetches the target repository's page to obtain an `uploadToken` from the embedded JS payload.
3. Requests an S3 upload policy from `/upload/policies/assets`.
4. Uploads the file directly to S3 using the presigned form fields.
5. Calls back to GitHub to finalize the asset.
6. Prints `![name](url)` to stdout.

The final URL is the standard `https://github.com/user-attachments/assets/` format — visibility inherits from the target repository, so a private-repo upload requires authentication to view.

For the full architecture, see **[documentation/architecture.md](documentation/architecture.md)**. For the reverse-engineered upload protocol, see **[documentation/github-image-upload-flow.md](documentation/github-image-upload-flow.md)**.

## Requirements

- A supported browser with an active GitHub session — or a `GH_SESSION_TOKEN` for CI.
- Write access to the target repository (uploads require it).
- A target repository — pass `--repo owner/repo`, or run from a git workspace whose `origin` remote is on GitHub.
- The `gh` CLI must be installed and authenticated (used for repository ID lookup).

## Limitations

- Uses an **undocumented** internal GitHub API that may change without notice.
- `uploadToken` is only issued to users with write access on the target repository.
- Session cookies are not scoped credentials; they expire when GitHub invalidates the session.

## Contributing

Issues and pull requests are welcome. For bug reports, please include:

- Your OS and browser
- The exact `gh image` invocation
- The error output (with any session token values redacted)

Before opening a PR, run `go test ./...` and `go vet ./...`.

## Support

If `gh-image` saves you a few drag-and-drops, a ⭐ helps others find it:

```bash
gh api --method PUT user/starred/drogers0/gh-image
```

(or just click the star at the [top of this page](https://github.com/drogers0/gh-image))

## License

[MIT](LICENSE) © 2025-2026 drogers0