{"id":48232868,"url":"https://github.com/drogers0/gh-image","last_synced_at":"2026-05-27T02:01:18.568Z","repository":{"id":347218813,"uuid":"1193240669","full_name":"drogers0/gh-image","owner":"drogers0","description":"A gh CLI extension that uploads images to GitHub from the command line","archived":false,"fork":false,"pushed_at":"2026-05-27T00:23:31.000Z","size":41,"stargazers_count":57,"open_issues_count":3,"forks_count":9,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-27T01:20:42.374Z","etag":null,"topics":["gh-cli","gh-issue","gh-s3-artifact","github","github-images","github-issues","github-prs"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/drogers0.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-27T02:37:32.000Z","updated_at":"2026-05-27T00:23:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/drogers0/gh-image","commit_stats":null,"previous_names":["drogers0/gh-image"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/drogers0/gh-image","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drogers0%2Fgh-image","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drogers0%2Fgh-image/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drogers0%2Fgh-image/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drogers0%2Fgh-image/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/drogers0","download_url":"https://codeload.github.com/drogers0/gh-image/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drogers0%2Fgh-image/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33546836,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gh-cli","gh-issue","gh-s3-artifact","github","github-images","github-issues","github-prs"],"created_at":"2026-04-04T19:46:24.451Z","updated_at":"2026-05-27T02:01:18.556Z","avatar_url":"https://github.com/drogers0.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/user-attachments/assets/92463e67-b897-4212-91b4-a4f9b80ec4d4\" alt=\"gh-image banner\" width=\"640\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cem\u003eDrop images into GitHub issues, PRs, and READMEs, straight from the command line.\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/drogers0/gh-image/releases/latest\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/drogers0/gh-image?color=blue\" alt=\"Latest release\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/drogers0/gh-image/stargazers\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/drogers0/gh-image?style=flat\u0026color=yellow\" alt=\"GitHub stars\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/drogers0/gh-image/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/downloads/drogers0/gh-image/total?color=green\" alt=\"Total downloads\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/drogers0/gh-image?color=lightgrey\" alt=\"License: MIT\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://goreportcard.com/report/github.com/drogers0/gh-image\"\u003e\u003cimg src=\"https://goreportcard.com/badge/github.com/drogers0/gh-image\" alt=\"Go Report Card\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nGitHub 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.\n\n```console\n$ gh image screenshot.png\n![screenshot.png](https://github.com/user-attachments/assets/88f4599a-…-bc24)\n```\n\n## Installation\n\n```bash\ngh extension install drogers0/gh-image\n```\n\nThat'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).\n\n\u003cdetails\u003e\n\u003csummary\u003eBuild from source\u003c/summary\u003e\n\n```bash\ngit clone https://github.com/drogers0/gh-image\ncd gh-image\ngo build -o gh-image\ngh extension install .\n```\n\nRequires Go 1.26+.\n\n\u003c/details\u003e\n\n## Usage\n\n```bash\n# Upload an image (infers repo from the current git workspace)\ngh image screenshot.png\n\n# Upload multiple images at once\ngh image hero.png diagram.png chart.png\n\n# Target a specific repository\ngh image screenshot.png --repo owner/repo\n```\n\nEach successful upload prints a ready-to-paste markdown reference on its own line:\n\n```\n![hero.png](https://github.com/user-attachments/assets/…)\n![diagram.png](https://github.com/user-attachments/assets/…)\n```\n\nIf any upload fails, the error is printed to stderr and the process exits non-zero — other images in the batch still upload.\n\n### Pipe directly into an issue, PR, or comment\n\nFrom inside the repo's working directory, both `gh image` and `gh issue create` infer the target repository automatically:\n\n```bash\ngh issue create \\\n  --title \"Login button stuck in loading state\" \\\n  --body \"Repro on staging:\n\n$(gh image bug.png)\n\nHappens consistently after the third click.\"\n```\n\n## Authentication\n\n`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.\n\n**Supported browsers:** Chrome · Brave · Chromium · Edge · Firefox · Opera · Safari\n\n**Supported platforms:** macOS · Linux · Windows\n\nOn 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.\n\n\u003e [!NOTE]\n\u003e **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).\n\n### Session token override\n\nFor CI, headless environments, or shared machines, you can supply the session token explicitly. Resolution order (first match wins):\n\n| Priority | Source | When to use |\n|---|---|---|\n| 1 | `--token \u003cvalue\u003e` flag | One-off invocations |\n| 2 | `GH_SESSION_TOKEN` env var | CI/CD, shared machines |\n| 3 | Browser cookie store | Local interactive use (default) |\n\n```bash\n# Flag (visible in process listings like `ps aux` — avoid on shared machines)\ngh image --token \"$MY_TOKEN\" screenshot.png --repo owner/repo\n\n# Environment variable (preferred — not visible to `ps aux`)\nGH_SESSION_TOKEN=\"$MY_TOKEN\" gh image screenshot.png --repo owner/repo\n```\n\n\u003e [!WARNING]\n\u003e `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).\n\n\n## CI / CD\n\n`gh-image` runs unattended in GitHub Actions when given a session token via `GH_SESSION_TOKEN`.\n\n\u003e [!CAUTION]\n\u003e **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.\n\n**Setup**\n\n1. Run `gh image extract-token` locally to capture the token (token → stdout, status → stderr), then run `gh image check-token --token \u003ctoken\u003e` to confirm it authenticates as the intended user (username → stdout on success, exit code `0` = valid).\n2. Create a GitHub environment (Settings → Environments → New environment), e.g. `gh-image`, and restrict deployment branches to a trusted set (e.g. `main` only).\n3. Add the token as an **environment secret** named `GH_SESSION_TOKEN` on that environment.\n\n```yaml\njobs:\n  upload:\n    runs-on: ubuntu-latest\n    environment: gh-image                                # binds this job to the scoped environment\n    steps:\n      - name: Upload screenshots\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}              # for gh CLI auth\n          GH_SESSION_TOKEN: ${{ secrets.GH_SESSION_TOKEN }}  # for the upload itself\n        run: |\n          gh extension install drogers0/gh-image\n          gh image check-token                                # optional: fail fast if the session expired\n          gh image screenshot.png --repo ${{ github.repository }}\n```\n\n\u003e [!NOTE]\n\u003e `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.\n\n## How it works\n\n1. Resolves a `user_session` cookie from the configured source (flag → env → browser).\n2. Fetches the target repository's page to obtain an `uploadToken` from the embedded JS payload.\n3. Requests an S3 upload policy from `/upload/policies/assets`.\n4. Uploads the file directly to S3 using the presigned form fields.\n5. Calls back to GitHub to finalize the asset.\n6. Prints `![name](url)` to stdout.\n\nThe final URL is the standard `https://github.com/user-attachments/assets/\u003cuuid\u003e` format — visibility inherits from the target repository, so a private-repo upload requires authentication to view.\n\nFor 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)**.\n\n## Requirements\n\n- A supported browser with an active GitHub session — or a `GH_SESSION_TOKEN` for CI.\n- Write access to the target repository (uploads require it).\n- A target repository — pass `--repo owner/repo`, or run from a git workspace whose `origin` remote is on GitHub.\n- The `gh` CLI must be installed and authenticated (used for repository ID lookup).\n\n## Limitations\n\n- Uses an **undocumented** internal GitHub API that may change without notice.\n- `uploadToken` is only issued to users with write access on the target repository.\n- Session cookies are not scoped credentials; they expire when GitHub invalidates the session.\n\n## Contributing\n\nIssues and pull requests are welcome. For bug reports, please include:\n\n- Your OS and browser\n- The exact `gh image` invocation\n- The error output (with any session token values redacted)\n\nBefore opening a PR, run `go test ./...` and `go vet ./...`.\n\n## Support\n\nIf `gh-image` saves you a few drag-and-drops, a ⭐ helps others find it:\n\n```bash\ngh api --method PUT user/starred/drogers0/gh-image\n```\n\n(or just click the star at the [top of this page](https://github.com/drogers0/gh-image))\n\n## License\n\n[MIT](LICENSE) © 2025-2026 drogers0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrogers0%2Fgh-image","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrogers0%2Fgh-image","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrogers0%2Fgh-image/lists"}