Skip to content

Releases: enthus-appdev/gh-attach

v0.6.0

11 Apr 11:59
270f538

Choose a tag to compare

Highlights

📥 Downloading upload refs with get (#22)

gh attach get [NUMBER|--key KEY] walks an upload ref back to the local disk — the exact inverse of the upload flow. Completes the round-trip story: you can now upload an image, delete the local copy, and pull the bytes back identically from a fresh clone.

# Pull every file for PR/issue #42 into the current directory
gh attach get 42

# Pull into a specific directory (created if missing, including parents)
gh attach get 42 --output ./restored

# Pull an ad-hoc upload
gh attach get --key design-v2 --output ./mockups

# Overwrite existing files
gh attach get 42 --force

# Auto-detect PR from the current branch
gh attach get

# JSON result for scripting
gh attach get 42 --json | jq -r '.files[].path'

Pre-flight atomicity: if any target file already exists without --force, gh attach get fails before writing any files and lists every conflict. You never end up with a half-populated output directory from a failed run.

Output contract — text mode writes file paths to stdout (one per line, pipeable to xargs) and the narrative + humanized sizes to stderr, so interactive users see the progress without polluting pipe consumers:

Downloading from #42 in owner/repo...
  shot.png → ./shot.png (850 B)
  note.md → ./note.md (1.2 KiB)
Downloaded 2 file(s) to .

JSON mode suppresses stderr entirely and emits a single downloadResult object on stdout with repo, target, namespace, number/key, ref, sha, output_dir, and files: [{name, path, size, sha}]. Use cases: round-trip attachments across a fresh clone, scripted cleanup-with-backup, review workflows, cross-repo migration.

Under the hood, gh.GetAttachments is the 4-step inverse of PushAttachments: ref → commit → tree → blobs via the Git Data API. Non-blob tree entries (subtrees, submodules) are silently skipped — current uploads never produce them, but a forward-looking reader should not crash on a mixed tree.

A latent bug fix came along with the first GetAttachments test: the private c.get helper returned fmt.Errorf("not found") on HTTP 404 instead of the package-level errNotFound sentinel, silently breaking errors.Is(err, ErrNotFound) for every caller. Fixed — c.get now returns the sentinel directly so matching works.


🔗 Non-image attachments render as plain links (#25)

Before v0.6.0, gh attach 42 doc.pdf emitted markdown with ![doc.pdf](url) — the image-embed syntax wrapped around a URL that doesn't return image bytes. GitHub, Slack, and email clients all rendered it as a broken-image icon. The --comment path was worst-affected because gh-attach itself posted the broken markdown without the caller ever seeing it first.

New behavior: an isImage(name) helper matches a fixed set of image extensions (case-insensitive), and FormatSection branches to emit ![name](url) for images and [name](url) for everything else.

Before (v0.5.0) — gh attach 42 shot.png report.pdf:

| shot.png | report.pdf |
|---|---|
| ![shot.png](...) | ![report.pdf](...) |    ← broken image icon

After (v0.6.0) — same command:

| shot.png |
|---|
| ![shot.png](...) |

| report.pdf |
|---|
| [report.pdf](...) |                        ← clickable link

Pure image-only uploads are byte-identical to v0.5.0 — the existing 1- or 2-column image table still wraps ![...] cells, so before/after screenshot workflows keep their side-by-side preview. Mixed uploads render the image table first, followed by the plain-link table for non-images (so images stay grouped for visual comparison and non-images don't get wedged into image-embed syntax).

Recognized image extensions (case-insensitive): png, jpg, jpeg, gif, webp, svg, bmp, ico, apng, avif, heic, heif. Covers what GitHub natively renders inline plus the newer mobile formats (HEIC from iPhones, AVIF from newer cameras).

Why extension-based (and not MIME sniffing)? FormatSection only has the basename at render time — the caller reads bytes in PushAttachments, not here. Extension matching is predictable, doesn't depend on reading 512 bytes per file, and matches the caller's intent: if they named it .pdf, they want a link.


🧪 Quality

  • Coverage: internal/cli now at 98.2% (sustained from v0.5.0). gh.GetAttachments and cli.runGet at 100% and 98.1% respectively; FormatSection and isImage at 100%. Remaining uncovered statements are defensive json.Encode error paths (symmetric in runUpload and runGet) and the production-only defaultDeps lambdas.
  • Test count: 18 new runGet cases + 8 new gh.GetAttachments httptest cases + 7 new FormatSection / isImage cases + 1 new TestStripWhitespace + 1 new TestHumanizeBytes (11 boundary cases incl. int64 max → "8.0 EiB").
  • fakeGitClient extended with a savePushContent snapshot flag (already useful for stdin tests) and new getAttachments / getSHA / getErr fields plus a GetAttachments method.
  • Neutral test fixtures (#23) — internal test fixtures now use demo-repo naming instead of previous project-specific references, so future reviewers don't have to squint at unfamiliar repo names.

Migration from v0.5.x

No breaking changes. Every v0.5.x invocation keeps working identically:

  • get is a new subcommand — additive only.
  • Non-image rendering is purely a fix: any file that used to render correctly (images) still renders the same bytes. Any file that used to render as a broken image embed (PDFs, logs, archives) now renders as a clickable link.
  • --comment with non-images: v0.5.0 would post a broken image embed on your PR/issue; v0.6.0 posts a clickable link.
gh extension upgrade gh-attach

All PRs in this release

PR Type Summary
#22 feat Add get subcommand for downloading upload refs
#23 chore Use demo-repo as neutral test fixture name
#25 feat Render non-image attachments as plain links

Install

gh extension install enthus-appdev/gh-attach

Already installed? Upgrade with:

gh extension upgrade gh-attach

Full commit log

v0.5.0...v0.6.0

v0.5.0

11 Apr 10:24
db371b8

Choose a tag to compare

Highlights

🗃️ Managing upload refs with list + delete (#16)

Two new subcommands for inspecting and removing existing upload refs — the ergonomic equivalents of raw gh api calls against refs/uploads/*. Unblocks the ad-hoc (--key) workflow from v0.4.0: since misc uploads have no close event to hook, you need a way to see what's there and clean it up.

# See every upload ref in the current repo
gh attach list

# TARGET           SHA        NAMESPACE
# #42              abc1234    issue
# #123             def5678    issue
# misc/design-v2   9876abc    misc
# misc/docs/arch   5555000    misc
#
# 4 upload ref(s) in owner/repo

# Filter by namespace
gh attach list --issues    # only refs/uploads/issues/*
gh attach list --misc      # only refs/uploads/misc/*

# JSON for scripting
gh attach list --json

# Delete an ad-hoc upload (prompts for confirmation)
gh attach delete --key design-v2
gh attach delete --yes --key design-v2    # scripts skip the prompt

# Delete an issue/PR upload (rare — the cleanup workflow handles these)
gh attach delete 42

Confirmation prompt: gh attach delete reads y/n from stdin by default. Running it in a non-interactive context without --yes produces a clear "pass --yes to skip" error instead of silently hanging. Answering n, empty line, or anything else exits 0 with Aborted — aborting is a no-op, not an error.

Safety: deleting a ref that doesn't exist returns exit 1 with error: refs/... not found in OWNER/NAME. No silent no-ops.


📦 --json structured upload output (#18)

gh attach --json emits a machine-readable uploadResult object on stdout instead of the markdown table, so the tool composes cleanly with jq and shell pipelines. Stderr is suppressed in JSON mode (no progress line, no Uploaded: list) so the output is pipe-safe.

$ gh attach --json 123 screenshot.png | jq
{
  "repo": "owner/repo",
  "target": "#123",
  "namespace": "issue",
  "number": 123,
  "ref": "refs/uploads/issues/123",
  "sha": "abc1234def5678cafe",
  "files": [
    {
      "name": "screenshot.png",
      "url": "https://github.com/owner/repo/blob/abc1234def5678cafe/screenshot.png?raw=true"
    }
  ],
  "markdown": "| screenshot.png |\n|---|\n| ![screenshot.png](...) |"
}

Key-mode uploads populate key + namespace: "misc" instead of number + "issue". With --comment, a comment_url field appears with the URL of the upserted comment. Both number/key and comment_url use omitempty so consumers see exactly the relevant fields and nothing else.

# Extract just the URL
URL=$(gh attach --json 123 file.png | jq -r '.files[0].url')

# Capture the commit SHA for later reference
SHA=$(gh attach --json --key design-v2 mockup.png | jq -r '.sha')

# Use the rendered markdown verbatim
MARKDOWN=$(gh attach --json 123 file.png | jq -r '.markdown')

Failure contract: JSON on stdout means success, check exit code before parsing. Errors still write plain text to stderr and exit 1. If --comment fails after a successful upload, the whole operation exits 1 with no JSON — split into two steps (gh attach ... then gh pr comment) if partial-success handling matters.


📥 Stdin input with --name BASENAME - (#20)

Pass - as the single file argument (with --name BASENAME) to read file bytes from stdin instead of disk. Tools that emit images to a pipe — screen capture, image processing, clipboard readers — can now feed gh attach directly without writing a temp file first:

# macOS: interactive screen capture straight into an upload
screencapture -i -t png - | gh attach --name shot.png 123 -

# Linux / Wayland: grim + slurp region capture
grim -g "$(slurp)" - | gh attach --name region.png 123 -

# ImageMagick: resize on the fly before uploading
magick input.png -resize 50% - | gh attach --name small.png --key docs/diagram -

# macOS: upload the current clipboard image
pbpaste | gh attach --name clipboard.png 42 -

Works with --key, --comment, --json, and --repo exactly like a disk-backed upload. Under the hood, stdin is drained into a fresh temp directory under --name and passed to gh.PushAttachments as a normal filesystem path — the gh package doesn't even learn about stdin.

Strict validation, all rejected at flag time before any network work:

  • --name is required when - is the file argument, rejected when it isn't.
  • - must be the only file argument — mixing stdin with disk files is a user error with a clear message (rather than an opaque "no files matched").
  • --name must be a safe basename: non-empty, ≤255 bytes, no / or \, not . or .., no NUL bytes, and none of the Windows-reserved characters (<, >, :, ", |, ?, *) so uploads round-trip safely across filesystems.
  • Empty stdin is allowed (produces a 0-byte upload) so upstream tools that pipe nothing fail loud at the subsequent embed instead of silently no-op'ing.

🧪 Quality

  • Coverage: internal/cli now at 98.1% (up from 96.6% in v0.4.0). Every function that does meaningful work is at 100%; the remaining uncovered statements are defensive error branches (json.Encode error path, the two production-only defaultDeps lambdas).
  • Tests: 20+ new cases for stdin alone — issue mode, key mode, comment mode, JSON mode, empty input, read error, plus a 6-case arg-conflict table and a 4-case materializeStdinWith error-branch suite covering MkdirTemp / Create / Close failures via an injected stdinFS seam. validateName has 15 table cases (7 valid, 8 rejected).
  • runUpload refactored (post-review on #20) to take a labeled uploadOptions struct instead of 11 positional parameters. No semantic change — pure readability win that makes future flag additions mechanical.
  • fakeGitClient gained a savePushContent flag that snapshots file bytes at PushAttachments call time, so stdin tests can verify byte fidelity across the temp-file boundary (runUpload's deferred cleanup removes the file before the test body runs assertions).

Migration from v0.4.x

No breaking changes. Every v0.4.x invocation keeps working identically. list, delete, --json, --name, and - are all additive.

gh extension upgrade gh-attach

All PRs in this release

PR Type Summary
#16 feat Add list + delete subcommands for upload ref management
#18 feat Add --json flag for structured upload output
#20 feat Add --name flag for reading file bytes from stdin

Install

gh extension install enthus-appdev/gh-attach

Already installed? Upgrade with:

gh extension upgrade gh-attach

Full commit log

v0.4.0...v0.5.0

v0.4.0

11 Apr 08:16
c0cc975

Choose a tag to compare

Highlights

🔑 Ad-hoc uploads with --key (#12)

gh attach --key KEY FILE... stores files under refs/uploads/misc/<key> instead of refs/uploads/issues/<N>, letting you upload without a PR or issue. Unlocks use cases that were structurally impossible before:

  • Embedding a screenshot in a README without a tracking PR
  • Preparing an image for a not-yet-created issue (chicken-and-egg: you need the markdown in the issue body before the issue exists)
  • Repo-scoped image hosting for docs sites, release notes, etc.
# README banner — no tracking PR needed
gh attach --key readme-banner banner.png

# Prepare an image for an issue you haven't created yet
MARKDOWN=$(gh attach --key feature-mockup screenshot.png)
gh issue create --title "New feature" --body "## Design

$MARKDOWN"

# Hierarchical keys for organization
gh attach --key docs/arch-diagram diagram.png
gh attach --key releases/v1.0/hero hero.png

Key validation is strict — charset [a-zA-Z0-9._/-], length 1–100, cannot be purely numeric (confusable with PR/issue numbers), and every /-separated segment must satisfy git's ref name rules (no leading . / -, no .lock suffix, no ..).

Argument conflict guards: NUMBER + --key errors; --key + --comment errors (comments need a PR/issue); --repo still requires one of NUMBER or --key to be passed explicitly.

Storage and cleanup: Ad-hoc uploads live in refs/uploads/misc/<key> as a sibling to the existing refs/uploads/issues/<N> namespace. The cleanup workflow from v0.2.0 only auto-cleans refs/uploads/issues/* — ad-hoc refs are user-managed via a gh api one-liner documented in the README:

gh api -X DELETE repos/OWNER/NAME/git/refs/uploads/misc/KEY

An in-tool --list / --delete is planned for a future release.


🧪 Test coverage + CI (#13, #14, #15)

The repo now has a full Go CI pipeline running on every push and PR:

  • Go Test with coverage tracking (#13) — vladopajic/go-test-coverage writes a live coverage badge to the badges orphan branch on every main run. fgrosse/go-coverage-report posts a per-PR coverage diff comment showing exactly how coverage shifts per file.
  • Go Lint (#14) — golangci-lint with the stock default linter set. No .golangci.yml, no rule overrides — started clean and fixed the 23 existing issues (22 errcheck + 1 staticcheck QF1012) so the baseline ships green.
  • Standard Go project layout (#15) — moved from a flat single-package root to cmd/gh-attach/main.go + internal/cli/ + internal/gh/. Introduced a small dependency-injection layer (runDeps) so cli.Run can be tested end-to-end with in-process fakes instead of real shell-outs. gh.ResolveRepo / gh.ResolvePR / ghAuthToken now go through a package-level execCommand indirection so unit tests can stub the git / gh CLI.

Coverage went from 55.6% → 96.6% on the internal packages. Every function that does meaningful work is at 100%; the remaining gap is defensive code against impossible-in-practice inputs (http.NewRequest invalid-URL branches) and the two defaultDeps lambdas whose bodies require cross-package test hooks to reach.

Coverage


🐞 Bug fixes bundled in

Two real issues surfaced via the PR #15 review and got fixed inside that PR:

  • URL-encode filenames in embed URLs — filenames containing spaces, #, ?, or non-ASCII characters (like Screen Shot 2026-04-10.png) were reaching the blob/<sha>/<file>?raw=true URLs unencoded, producing broken markdown images. Now wrapped in url.PathEscape in both the stdout markdown and the stderr Uploaded: list. Alt text stays raw so users see the original filename.
  • Accept ssh:// prefix in --repo overridessh://git@github.com/owner/repo.git was silently parsing as Repo{Owner: "ssh:", Name: "..."} (garbage). Now routes through parseRepoFromRemote alongside git@, http://, https://, and github.com/.

Migration from v0.3.x

No breaking changes. Every existing invocation keeps working identically. --key is purely additive; the CI + restructure work is internal and doesn't affect the CLI surface.

gh extension upgrade gh-attach

All PRs in this release

PR Type Summary
#12 feat Add --key flag for ad-hoc uploads to refs/uploads/misc/<key>
#13 ci Add Go test + coverage workflow with PR diff reports
#14 ci Add golangci-lint job with default rules + fix existing issues
#15 refactor cmd/ + internal/ layout, test injection, 56% → 97% coverage

Install

gh extension install enthus-appdev/gh-attach

Already installed? Upgrade with:

gh extension upgrade gh-attach

Full commit log

v0.3.0...v0.4.0

v0.3.0

10 Apr 19:53
9c4e6fc

Choose a tag to compare

⚠️ Breaking change

Markdown goes to stdout by default, comment-posting is now opt-in

gh attach <N> FILE... no longer posts a PR/issue comment by default (#9). The rendered markdown is written to stdout instead, making the tool composable with shell pipelines, PR body edits, clipboards, and any other caller. The pre-v0.3.0 comment-posting behavior is still available via the new --comment flag.

Before (v0.2.x) After (v0.3.0)
gh attach 123 file.png → uploads + posts comment gh attach 123 file.png → uploads + prints markdown
(no way to just get the markdown) gh attach --comment 123 file.png → uploads + posts comment (old behavior)

Why the inversion

The tool was always two independent things welded together: an upload step and a comment-posting step. Until now, the caller had no way to get the markdown out without also posting a comment — which meant embedding uploads in a PR body, a Slack message, an issue template, or anywhere else required either parsing the Done: URL back out or not using the tool at all.

After this change, the caller owns the output. gh attach produces markdown; what you do with it is your call.


Highlights

🪄 Composable stdout contract (#9)

The output contract is now stable and pipeline-friendly:

Stream Content
stdout The bare rendered markdown — no <!-- gh-attach --> marker, no ### Attachments heading. Just the pasteable table.
stderr Progress line (Uploading ...) + one directly-embeddable URL per file under an Uploaded: header. With --comment, also a Commented: <url> line.

--comment is additive, not modal: it adds a comment-post side-effect without changing what goes to stdout. gh attach --comment 123 file.png | tee backup.md works exactly as you'd expect.

🌍 New --repo flag — run from anywhere (#11)

gh attach can now target an arbitrary repo from any working directory via --repo OWNER/NAME (or a full SSH/HTTPS GitHub URL). No more "must be inside a specific clone" constraint for CI runners, cross-repo scripts, or one-off invocations.

# Target any repo from anywhere
gh attach --repo enthus-appdev/gh-attach 123 screenshot.png
gh attach --repo https://github.com/enthus-appdev/gh-attach 123 screenshot.png
gh attach --repo git@github.com:enthus-appdev/gh-attach.git 123 screenshot.png

When --repo is used, NUMBER must be passed explicitly — PR auto-detection via gh pr view only makes sense inside a clone of the target repo, so the tool short-circuits with a clear error if you forget.

🛠 New use cases unlocked

# Embed uploads directly in a PR body (impossible before)
MARKDOWN=$(gh attach 123 dist/report.png)
gh pr edit 123 --body "Build passed.

$MARKDOWN"

# Copy to clipboard (Wayland / X11 / macOS)
gh attach 123 screenshot.png | wl-copy
gh attach 123 screenshot.png | xclip -selection clipboard
gh attach 123 screenshot.png | pbcopy

# Pipe into gh-cli's own commenter instead of the built-in upsert
gh attach 123 file.png | gh pr comment 123 --body-file -

# Save for later
gh attach 123 file.png > upload.md

# Run from a CI job with no git clone at all
gh attach --repo enthus-appdev/myapp 456 ./screenshots/*.png

Migration from v0.2.x

If you were relying on the automatic comment post, add --comment:

# Before
gh attach 123 file.png

# After
gh attach --comment 123 file.png

If you want the new stdout behavior, no changes needed — gh attach 123 file.png now does exactly what you'd expect for a composable CLI.


All PRs in this release

PR Type Summary
#9 feat! Print markdown to stdout by default, add --comment flag
#11 feat Add --repo flag to target an arbitrary repo

Install

gh extension install enthus-appdev/gh-attach

Already installed? Upgrade with:

gh extension upgrade gh-attach

Full commit log

v0.2.1...v0.3.0

v0.2.1

10 Apr 16:50
4239546

Choose a tag to compare

🎉 First installable release

v0.2.1 is the first release of this tool that actually installs via gh extension install. Both v0.1.x and v0.2.0 had a latent .goreleaser.yml asset-naming bug that made them incompatible with gh-cli's extension manager — v0.2.1 fixes the release pipeline so gh extension install enthus-appdev/gh-attach works end-to-end for the first time.

Install

gh extension install enthus-appdev/gh-attach

Verified end-to-end on linux-amd64:

$ gh extension install enthus-appdev/gh-attach
$ gh extension list
gh attach    enthus-appdev/gh-attach    v0.2.1
$ gh attach --help
Upload images to a GitHub PR or issue.

Usage:
  gh attach [flags] [NUMBER] FILE...

If NUMBER is omitted, it is auto-detected as a PR from the current branch.

What's actually different from v0.2.0

Code: identical. The gh-attach binary and everything it does is byte-for-byte the same as v0.2.0.

Release pipeline: .goreleaser.yml now produces raw binaries named darwin-amd64, darwin-arm64, linux-amd64, linux-arm64 instead of gh-attach_linux_amd64.tar.gz etc. See #7 for the full diagnosis — the short version is that gh extension install uses strings.HasSuffix(name, "{os}-{arch}") on raw binaries with no archive-extraction logic, so our underscore-separated tar.gz archives were silently ignored.

For the actual feature changes

All of the substantive changes in this release came from v0.2.0 — the rename from gh-pr-screenshot to gh-attach, the invisible refs/uploads/issues/<N> storage, the cleanup workflow template, basename collision detection, and the latent flag-order bug fix in the help text.

👉 See the v0.2.0 release notes for the complete changelog. Everything documented there also applies to v0.2.1.

Upgrade from v0.1.x

# If you were somehow running v0.1.x (most likely built from source):
gh extension uninstall enthus-appdev/gh-pr-screenshot  # if you installed under the old name
gh extension install enthus-appdev/gh-attach

Then swap gh pr-screenshot for gh attach in any scripts or muscle memory. Note the new flag ordering: flags must come before the number (gh attach --title "..." 123 file.png), not after.

Full commit log

v0.2.0...v0.2.1

v0.2.0

10 Apr 16:07
b47c093

Choose a tag to compare

⚠️ This release is not installable via gh extension install due to a latent bug in .goreleaser.yml that produced asset names incompatible with gh-cli's extension manager. The actual code and functionality are fine — use v0.2.1 for a working install. The binary and all behavior are identical; only the release asset naming differs. See #7 for the diagnosis and fix.


⚠️ Breaking changes

Project renamed from gh-pr-screenshot to gh-attach

The binary, CLI invocation, and install command all changed (#6):

Before After
gh extension install enthus-appdev/gh-pr-screenshot gh extension install enthus-appdev/gh-attach
gh pr-screenshot 123 file.png gh attach 123 file.png
Repo: github.com/enthus-appdev/gh-pr-screenshot github.com/enthus-appdev/gh-attach (old URL redirects)

The tool is no longer screenshot-specific — it stores files by git blob, so any content type that renders as a markdown image works (diagrams, mockups, photos, GIFs, before/after composites). The rename acknowledges that reality and aligns with GitHub's own "attachments" terminology.

Comment marker changed

The HTML comment marker used to find-and-update existing PR comments changed from <!-- pr-screenshots --> to <!-- gh-attach --> (#6). PR comments posted by v0.1.x will be invisible to v0.2.0's upsert logic — new uploads post a fresh comment alongside any old one instead of appending. Old comments stay readable in PR history forever, they just stop being updated.

The in-comment heading also changed from ### Screenshots to ### Attachments for the same reason.


Highlights

🗂 Invisible-ref storage (#2)

v0.1.x pushed blobs to a visible claude/_screenshots branch that showed up in every repo's branches list, triggered push workflows, and had to be exempted from branch protection rulesets.

v0.2.0 stores them under refs/uploads/issues/<N> — a per-PR custom-namespace ref that:

  • Invisible in the Branches UI (lives outside refs/heads/* so it doesn't appear in git branch -a, the GitHub Branches page, or branch dropdowns)
  • Bypasses branch protection rulesets (rules with target: branch apply only to refs/heads/*, so naming/review/status-check rules are automatically skipped)
  • Doesn't trigger push workflows
  • Aligned with GitHub's unified issue/PR number space — the same ref scheme already works for issues (the CLI will gain explicit issue support in a future release)

Embed URLs now reference the commit SHA directly (blob/<sha>/<file>?raw=true) instead of the branch name, so URLs from prior uploads continue to work as the ref fast-forwards with new uploads.

🧹 Cleanup workflow template (#5)

v0.2.0 ships .github/workflows/cleanup-gh-attach.yml as a canonical, copy-pasteable template. Install it in any repo that uses gh-attach and upload refs auto-delete when PRs or issues close — no more manual cleanup, no growing blob storage over time.

Key properties:

  • Listens to both pull_request: closed and issues: closed events
  • Explicit "does the ref exist?" check before the DELETE, so the "no upload ref for this PR" case is handled silently while real auth/network failures still fail loudly
  • set -euo pipefail for safety
  • Zero customization required — github.repository and the event payload provide everything

Copy from main whenever you want to pick up improvements — it's a file snapshot, not a live link.

🔒 Basename collision detection (#2)

v0.1.x used timestamped tree paths (pr-<N>/<timestamp>-<basename>) which made collisions impossible but polluted the tree structure. v0.2.0 uses clean basenames, and adds a pre-flight check that rejects duplicate basenames before any API calls with a clear error:

duplicate basename "img.png": /tmp/a/img.png and /tmp/b/img.png would
collide in the same upload — rename one of the files

No silent data loss if you pass two files with the same basename from different directories.

🐛 Latent help-text bug fixed (#6)

The v0.1.x help text advertised gh pr-screenshot [PR_NUMBER] [flags] FILE... but Go's flag.Parse() stops at the first non-flag argument, so gh pr-screenshot 123 --title "foo" file.png silently dropped --title. The README had a workaround note ("flags must come before PR number") but the program's own --help output still showed the wrong ordering.

Fixed in v0.2.0: gh attach [flags] [NUMBER] FILE... matches actual parser behavior. The help text also generalizes PR_NUMBERNUMBER because explicit numbers work for both PRs and issues today (only the auto-detect path via gh pr view is still PR-specific).


All PRs in this release

PR Type Summary
#2 feat Store uploads under refs/uploads/issues/<N> (Phase 1)
#3 docs Remove deprecation notice
#4 chore Gitignore the build artifact and untrack the stale copy
#5 chore Ship cleanup workflow as a real file + install in this repo (Phase 2)
#6 chore! Rename project to gh-attach
#1 chore(deps) Bump goreleaser/goreleaser-action from 6 to 7

Upgrade from v0.1.x

gh extension uninstall enthus-appdev/gh-pr-screenshot
gh extension install enthus-appdev/gh-attach

Then swap gh pr-screenshot for gh attach in any scripts or muscle memory. Existing v0.1.x installs continue working indefinitely if you don't want to upgrade (they have the old binary baked in).

Install (fresh)

gh extension install enthus-appdev/gh-attach

Full commit log

v0.1.2...v0.2.0

v0.1.2

01 Apr 03:46

Choose a tag to compare

Changelog

  • cbf0d32 docs: fix flag ordering in README usage examples

v0.1.1

01 Apr 03:25

Choose a tag to compare

Changelog

  • ed43010 fix: rename branch from _screenshots to claude/_screenshots

v0.1.0

01 Apr 03:18

Choose a tag to compare

Changelog

  • 9ab5c3a Initial commit
  • 3a13f27 ci: add goreleaser config and release workflow
  • 47ca314 docs: add README with install and usage instructions
  • dc20afd feat: Git Data API for pushing screenshots to _screenshots branch
  • e824e59 feat: PR comment formatting and upsert
  • e90e1aa feat: context resolution for repo and PR detection
  • 4039a2d feat: project scaffolding with CLI parsing