Fetch extensions from Forge's own releases - #29
Merged
Merged
Conversation
A tool Forge does not ship arrives one of two ways: Homebrew, which is the user's own package manager, or a build Forge hosts and pins. This adds the second. A JSON manifest names the version, the licence, the project and the exact bytes; the install streams the archive to a scratch folder inside the managed directory, checks its SHA-256 against the manifest, unpacks it, takes the quarantine flag off it and moves the finished folder into place in one rename. An extension is a folder, not a file: a binary that travels with its dylibs or its data is the ordinary case. Nothing is bundled in the .app, and nothing runs that did not hash to what the manifest promised. Developer ID verification has a hook and no implementation: the hosted builds are ad-hoc signed and vouched for by their hash. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018H9CZxWumc3ryumgEHd578
Three things stood between installing a tool and using it. `locate` looked only at PATH, so a fetched extension was invisible to every part of the app that decides whether to offer a conversion. It now looks in the managed directory first: that build is the one the manifest named and the hash was checked against, which is the only copy Forge can say anything true about. What pandoc reads and writes was asked once and kept forever. A pandoc that was not installed when it was asked answered nothing, and keeping that answer hid every format it had just added. And a tool's standard output went to a pipe nobody read, which stops a chatty one dead at 64KB. It goes to /dev/null now, and its complaint is read as it comes with only the last 64KB kept. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018H9CZxWumc3ryumgEHd578
Fixtures are built in the test's own scratch directory and served from a file: URL, so the suite needs no network. The store is repointed for the duration, so a run cannot see or write what the person using Forge has installed. The pilot converts a real Markdown file to DOCX through a pandoc that was fetched, checked and unpacked by the manager, and skips itself where pandoc is not installed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018H9CZxWumc3ryumgEHd578
The card now shows which copy of a tool is here — one Forge fetched, at its version, or one the user's own package manager put there — and offers the download when Forge hosts a build. Consent first, as with the Homebrew command: version, licence, project, size and where it lands, before anything is downloaded. The README claimed Forge downloads nobody's binaries. It downloads the ones you ask it for now, from its own releases, checksum checked, and says so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018H9CZxWumc3ryumgEHd578
The same pipeline as the app's card, from a script and without one. A download is printed in full before it happens and needs --yes: it writes to the user's machine, so it is not something a command should do on the way past. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018H9CZxWumc3ryumgEHd578
# Conflicts: # Sources/Forge/Services/ExternalTools.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A tool Forge does not ship arrives one of two ways: Homebrew, which is the user's own package manager, or a build Forge hosts and pins. This adds the second.
A JSON manifest names the version, the licence, the project and the exact bytes. The install resolves the build for this processor, streams the archive into a scratch folder inside the managed directory, checks its SHA-256 against the manifest, unpacks it, takes macOS's quarantine flag off it, and moves the finished folder into place in one rename — a rename, so the scratch lives on the same volume. Nothing is bundled in the
.app, and nothing runs that did not hash to what the manifest promised.An extension is a folder, not a file: a binary that travels with its dylibs or its data is the ordinary case. Developer ID verification has a hook and no implementation, because the hosted builds are ad-hoc signed and vouched for by their hash.
The leverage point is
ExternalTools.locate, which now looks in the managed directory first — soExternalProcessor,ConvertKindgating andFormatCatalogneeded no changes at all. Two things stood between installing a tool and using it and are fixed here: what pandoc reads and writes was asked once and kept forever, and a tool's standard output went to a pipe nobody read, which stops a chatty one dead at 64KB.Surfaces: the Capabilities card (version installed, update available, download with progress, remove) with consent first — version, licence, project, size, and where it lands — and
forge extensions list | add | remove, where a download needs--yes.Not done, needs your call: publishing
extensions.jsonand the built tarballs to the Releases page. That is release configuration, so I stopped there.Test plan
swift test— green--yes, installed, converted, tampered checksum aborted, removed🤖 Generated with Claude Code
https://claude.ai/code/session_018H9CZxWumc3ryumgEHd578