Skip to content

Add Homebrew install for the macOS app - #91

Merged
leoisadev1 merged 4 commits into
mainfrom
cursor/homebrew-cask-3756
Oct 8, 2026
Merged

leoisadev1 merged 4 commits into
mainfrom
cursor/homebrew-cask-3756

Conversation

@leoisadev1

@leoisadev1 leoisadev1 commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

One-time steps for Leo

I could not create opencoredev/homebrew-tap from here (gh is read-only). Until that repo exists, the working install is:

brew tap opencoredev/convt https://github.com/opencoredev/convt
brew install --cask convt

To get the shorter brew install --cask opencoredev/tap/convt command:

  1. Create a public GitHub repo named opencoredev/homebrew-tap (empty is fine; the first successful release job will add Casks/convt.rb and a short README).
  2. Add a repo secret on opencoredev/convt named HOMEBREW_TAP_TOKEN: a fine-grained PAT (or classic repo token) that can push to opencoredev/homebrew-tap.
  3. If main is protected against GITHUB_TOKEN pushes, either allow GitHub Actions to push cask-only commits, or rely on the tap repo only and ignore the in-repo Casks/convt.rb bump.

No other secrets are required. The new Release job never fails the publication itself (continue-on-error: true).

What this does

  • Adds Casks/convt.rb for the signed, notarized Apple silicon DMG from the latest GitHub release (v0.2.0, convt-macos-arm64.dmg). There is no Intel or universal Mac build today, so the cask is depends_on arch: :arm64.
  • The cask has livecheck (:github_latest), zap, and links the bundled convt CLI onto PATH.
  • After gh release create succeeds, a new Update Homebrew cask job reads release-manifest.json, rewrites version + sha256, commits to this repo, and (if HOMEBREW_TAP_TOKEN is set) pushes the same file to opencoredev/homebrew-tap. A missing tap, a 404, or a blocked push cannot fail the Release workflow.
  • README and /download now show the brew tap / brew install --cask lines. The download-page change is a small add so it should not fight PR Redesign /download as a get-the-app install guide #20 or docs: describe the shipped desktop app and cloud API in README and AGENTS.md #77.

Skipped

No Linux Homebrew formula. An AppImage is a poor Homebrew formula, and Linux already has .deb, .rpm, and the AppImage. Revisit if we later ship a small relocatable tarball.

Release artifact names are unchanged. The Release workflow change is one additive job after publish, so it should merge cleanly next to PR #73.

Checks

  • python3 -m unittest discover -s scripts/release -p 'test_*.py' — 32 tests, OK
  • brew style --cask — clean (via a throwaway local tap; Homebrew rejects a raw path)
  • brew audit --cask — clean
  • brew audit --cask --online — downloaded the v0.2.0 DMG and verified the sha256; then failed only because this Linux box has no hdiutil to mount the image. macOS CI runs style + offline audit.
  • bun run check and bun run check-types — OK
  • bun test apps/web/test/unit/download.test.tsx — OK

Do not merge; a reviewer should.

Open in Web Open in Cursor 

Devin Review

Users can tap this repo and `brew install --cask convt` for the signed
Apple silicon app. A non-blocking Release job rewrites the cask from
release-manifest.json so a tap failure cannot fail publication.

Co-authored-by: Leo <leoisadev1@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

Devin Review

Comment thread scripts/release/templates.py Outdated
homebrew_spec=spec_from_file_location('homebrew_cask',Path(__file__).with_name('homebrew_cask.py'))
homebrew=module_from_spec(homebrew_spec); homebrew_spec.loader.exec_module(homebrew)
source=Path(__file__).resolve().parents[2]/'Casks'/'convt.rb'
text(Path('homebrew/convt.rb'),homebrew.bump_cask(source.read_text(),version=version,sha256=mac['sha256']))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Staged cask loses its artifact URL

For a manifest hosted at downloads.convt.app, templates.py emits a GitHub DMG URL instead of the artifact URL. Releases published only to the manifest's host leave the staged cask unable to install.

Learn more

The package-manager renderer takes an artifact record from a release manifest. The release script generates that manifest with https://downloads.convt.app as its default base URL. The cask template hard-codes a GitHub release URL, and bump_cask changes only its version and digest. Rendering therefore changes the download host even when the manifest points to a working artifact elsewhere.

Example: A 0.3.0 manifest lists https://downloads.convt.app/0.3.0/convt-macos-arm64.dmg. The generated cask points to https://github.com/opencoredev/convt/releases/download/v0.3.0/convt-macos-arm64.dmg; if the DMG was uploaded only to the download host, Homebrew fails to install it.

Recommended fix: Render the staged cask from mac['url'], preserving the exact manifest artifact URL. If the in-repository GitHub cask must remain the source template, give bump_cask an explicit URL override and use it when templates.py renders a manifest-backed cask.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread scripts/release/update-homebrew-cask.py Outdated
Comment on lines +66 to +69
if changed and args.commit:
message = f"chore(homebrew): bump convt to {version}"
if homebrew.commit_if_changed(repo, cask, message):
print(f"committed {cask.relative_to(repo)}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Failed cask commit blocks tap update

If commit_if_changed fails, main exits before publish_tap runs. The valid tap stays on the previous release despite its independent token and write access.

Learn more

The updater first writes a new cask to its local checkout, then optionally commits it to the source repository and publishes it to a separate Homebrew tap. Only errors from push_head are caught inside the source-repository branch. An error from commit_if_changed instead reaches the outer handler, skipping the independent tap publication. This matters when the repository commit fails but the tap credentials and repository work.

Example: The new release is 0.3.0 and HOMEBREW_TAP_TOKEN can update the tap. If git commit fails in the source checkout, the script returns 1 without cloning or updating the tap, so tap users remain on 0.2.0.

Recommended fix: Handle commit and push errors within the optional source-repository operation, record them, and continue to publish_tap using the already-written cask. Report both operation results afterward.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

</div>
<DownloadButton artifact={slot?.artifact ?? null} large />
{slot?.artifact && <Sha value={slot.artifact.sha256} />}
{os === "macos" && <HomebrewInstall />}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Homebrew instructions remain visible without downloads

When the macOS download says “Shipping today,” HomebrewInstall still offers an install command. Confirm whether that fallback is intended before the first published release or during manifest outages.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread apps/web/test/unit/download.test.tsx Outdated
Comment on lines +42 to +43
expect(page).toContain("brew install --cask convt");
expect(page).toContain("brew tap opencoredev/convt");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Download tests miss Homebrew visibility states

The new assertions search source text, not rendered output. They cannot catch changes to when the Homebrew instructions appear for different platforms or release states.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@greptile-apps

greptile-apps Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 1/5

[High risk] Adds Homebrew distribution and release automation.

This PR needs fixes before merging because release updates can break CI and publish an uninstallable cask.

Fix All in Claude CodeFindings

  1. P1 Later releases break CI ▶
  2. P1 Manual updates keep old URLs ▶
  3. P1 Versioned Mac filenames fail ▶
  4. P1 Old retries replace new releases ▶
  5. P2 Missing instructions escape tests ▶
  6. P2 Cask names the wrong source ▶

Summary

Adds an Apple silicon Homebrew cask, install commands on the download page, and a post-release job that updates this repository and an optional separate tap.

  • The latest changes preserve the manifest’s download URL and keep unrelated staged files out of cask commits.
  • Two new problems remain: later cask versions break fixed-version tests, and manual updates can keep an old download URL.
  • Checks were based on source inspection; no runtime tests or browser checks were run.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Release[GitHub release published] --> Manifest[Read release manifest]
  Manifest --> Bump[Update cask version, checksum and URL]
  Bump --> Main[Commit and push to main]
  Bump --> Token{Tap token configured?}
  Token -->|Yes| Tap[Copy cask and push separate tap]
  Token -->|No| Skip[Skip separate tap]
Loading

Reviews (3) · Last reviewed commit: "Keep Homebrew cask updates from writing ..." · Reviewed by Greptile

Comment thread scripts/release/test_homebrew.py Outdated
Comment thread scripts/release/templates.py Outdated
Comment thread scripts/release/test_homebrew.py Outdated
Comment thread scripts/release/update-homebrew-cask.py Outdated
Comment thread apps/web/test/unit/download.test.tsx Outdated
Comment on lines +42 to +43
expect(page).toContain("brew install --cask convt");
expect(page).toContain("brew tap opencoredev/convt");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing instructions escape tests

The new assertions only search the route’s source text. They still pass if both HomebrewInstall calls are removed or the commands stop appearing on the page. Add a rendered-output test for the Mac install block so the test protects the behavior users see.

Confidence: 5/5.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

cursoragent and others added 2 commits October 7, 2026 23:21
Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
Derive the committed cask version and digest in tests so a later
release bump does not fail CI. Always honor --push even when the
cask file already matches, and keep tap publication independent of
the source-repo commit. Mock the missing-tap clone.

Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
"depends_on macos:",
'app "convt.app"',
"zap trash:",
"convt-macos-arm64.dmg",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Versioned Mac filenames fail

templates.py now rejects valid Mac download URLs whose filenames include the version, such as the existing fixture convt-0.1.0-macos-arm64.dmg. After bump_cask copies that URL, this required string makes validate_cask(bumped) raise an error. The script stops before writing the cask or later package indexes.

Check the cask’s shape without requiring the GitHub-only filename. Confidence: 5/5.

Suggested change
"convt-macos-arm64.dmg",

Fix in Claude Code

repo = work / "tap"
dest = repo / "Casks" / "convt.rb"
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(cask.read_text())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Old retries replace new releases

Retrying just Update Homebrew cask for an older release can undo a newer cask update. The job keeps its original RELEASE_VERSION, but publish_tap clones the current tap and overwrites its cask without comparing versions. The updater also overwrites the current cask on main. New Homebrew installs can then use the older app.

Skip older incoming versions in both repositories before writing or pushing them. Confidence: 4/5.

Fix in Claude Code

Comment on lines +81 to +84
if url is not None:
bumped, count = re.compile(r'^(\s*url\s+")([^"]+)(")', re.M).subn(
rf"\g<1>{url}\g<3>", bumped, count=1
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cask names the wrong source

bump_cask changes url to the manifest’s download location but leaves verified: "github.com/opencoredev/convt/" behind, including for downloads.convt.app. The generated cask names the wrong verified download source, so release managers must correct the file.

Update or remove verified when replacing the URL. Confidence: 5/5.

Fix in Claude Code

Comment thread scripts/release/homebrew_cask.py Outdated
Pass the manifest DMG URL through to bump_cask so a retry cannot
leave the cask pointing at an old host. Refuse a version older than
the checked-in cask, and commit only the cask file so leftover staged
paths stay out of the source-repo commit.

Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
Comment on lines +80 to +82
current, _ = cask_fields(text)
if version_key(version) < version_key(current):
raise ValueError(f"refusing to downgrade cask from {current} to {version}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Later releases break CI

Once Casks/convt.rb advances past 0.3.0, this new check breaks several release tests. They read the current cask and call bump_cask with the fixed version 0.3.0, so they raise before reaching their assertions. CI runs these tests on pull requests.

Use a fixed cask fixture for tests that need specific versions. Confidence: 5/5.

Fix in Claude Code

Comment on lines +60 to +61
version, sha256, url = release_fields(args)
bumped = homebrew.bump_cask(text, version=version, sha256=sha256, url=url)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Manual updates keep old URLs

Manifest updates now replace the cask’s interpolated URL with a literal release URL. After that, the documented --version --sha256 command returns url=None. It changes the version and checksum but keeps the previous release’s URL. Publishing that cask makes Homebrew download the old app and reject its checksum.

Preserve URL interpolation where possible, or require a matching URL for manual updates. Confidence: 5/5.

Fix in Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants