feat(NO-TASK): Add version detection and self-update, and document the CLI as a whole - #64
Merged
Merged
Conversation
Two commands and a notifier, so a user finds out a new version exists without asking and without paying for the check. `linchpin version` reports what is installed, whether a newer version is published, and how this copy was installed. `linchpin update` runs the install command for *that* install method — derived from the path the process is running from, because handing a pnpm or bun install an `npm install -g` leaves two copies on the machine and which one answers depends on PATH order. The notice costs no latency: it is read from a 24-hour cache file, and a stale cache is refreshed by a detached process that outlives the command. It goes to stderr only, so `cd "$(linchpin wt switch)"` and `eval "$(linchpin shell-init)"` keep working, and it is suppressed in --json, --quiet, CI, and for agents — an agent asks `linchpin version --check --json` instead of paying tokens for a line it did not request. Two ways to ask, deliberately: `version --check` always exits 0 so it is safe in a prompt or status line, while `update --check` exits 3 when an update is pending so it can gate a job with nothing to parse. Also fixes a pre-existing gap the new commands made visible: the mode flags were only accepted *before* a subcommand, so `linchpin version --json` was an unknown-option error while `linchpin --json version` worked. They are now accepted at any depth, except on the `wt` passthrough which forwards them to the legacy dispatcher. Commands receive the Output renderer and the package manifest through CommandContext, so mode is still decided once per process. The suite never reaches the network — the checker is exercised against a local registry stub, and the shared fixture disables the notifier so no test can be perturbed by a real release. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every release from v1.1.0 to v1.1.3 failed at `npm publish` with
npm error 404 Not Found - PUT https://registry.npmjs.org/@linchpinagency%2fcli
which reads as if the package did not exist. It does exist: npm answers an
*unauthorized* write with 404 rather than 403, so the message names the
wrong problem. 1.1.1 reached npm only because it was published by hand
from a workstation — it carries no provenance attestation, and no CI
publish has ever succeeded.
The cause is the credential: NPM_TOKEN was minted 2026-02-17, before this
package first existed on npm (2026-08-08), and a granular npm token only
ever covers the packages that existed when it was created.
Prove the credential before spending a build on it, and if the publish
still fails, say what a 404 after a passing preflight can only mean. Also
write repository.url in the form npm was auto-correcting on every publish.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The README opened as if `wt` were the product, so a reader could not tell that this is one tool carrying every repeatable piece of the WordPress workflow, with worktrees as one command group among several. It now leads with the command surface and each command's effect classification, and carries the three lifecycle walkthroughs it was missing: install (per package manager, plus the shell wrapper and a source checkout), update, and uninstall — including what deliberately *stays* behind, since `.linchpin.json`, hooks, worktrees and symlinks all outlive the CLI and a teammate still needs them. docs/updating.md covers the mechanism rather than the walkthrough: how the check is cached, who is told and why an agent is not, how the install method is detected, and why `version --check` and `update --check` exit differently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
What this does
Three things, in three reviewable commits.
feat: version detection and self-update.linchpin versionreports what is installed, whether a newer version is published, and how this copy was installed.linchpin updateruns the install command for that install method. When a newer version exists, a notice appears on stderr after the command completes.ci: verify npm credentials before publishing. Diagnostics for the failure below.docs: rewrite the README around the whole CLI. It opened as ifwtwere the product. It now leads with the command surface and each command's effect classification, and carries the install / update / uninstall walkthroughs it was missing.docs/updating.mdcovers the mechanism.Why the release job kept failing
Every release from v1.1.0 to v1.1.3 failed at
npm publish:That reads as if the package does not exist. It does — npm answers an unauthorized write with 404 rather than 403, so the message names the wrong problem. Evidence that this was always the credential:
attestations: null— by hand from a workstation, since CI pins Node 22.12 and passes--provenance.NPM_TOKENwas minted 2026-02-17, before this package first existed on npm (2026-08-08), and a granular npm token only ever covers packages that existed when it was created.The token has since been rotated. This PR makes the next failure name itself: a preflight step fails on an empty or non-authenticating token before a build is spent on it, and a publish failure after a passing preflight says that a 404 can then only be a permissions problem. It also writes
repository.urlin the form npm was auto-correcting on every publish.v1.1.3 is tagged and released on GitHub but absent from npm. Re-running the
publish-npmjob on run 32974737883 publishes it from the existing tag — no new release needed. Worth doing before this PR merges, or npm skips from 1.1.1 to 1.2.0.Design notes worth a reviewer's attention
The notice costs no latency. It is read from a 24-hour cache file, never the network. A stale cache is refreshed by a detached,
unref()ed process that outlives the command, marked with an env flag so it cannot spawn a refresh of its own.stderr only, and never for machine readers.
cd "$(linchpin wt switch)"andeval "$(linchpin shell-init)"both consume stdout, so a notice there would be executed. It is suppressed in--jsonand--quiet, in CI, and when an agent is driving — an agent askslinchpin version --check --jsoninstead of paying tokens for a line it did not request.LINCHPIN_NO_UPDATE_NOTIFIER(orNO_UPDATE_NOTIFIER) turns it off entirely.Install method is read from the path, resolved through
realpathSync, not fromnpm_config_user_agent— which is only set while npm itself is the parent process, true duringnpm installand never when a user runslinchpin. Getting this wrong is not cosmetic: handing a pnpm or bun install annpm install -gleaves two copies on the machine and which one answers depends onPATHorder.Two ways to ask, deliberately.
version --checkalways exits 0, so it is safe in a shell prompt or status line.update --checkexits 3 when an update is pending, so it can gate a job with nothing to parse.An unparseable version never reads as newer, or a registry answering with something odd would nag on every invocation with no version that could satisfy it.
Incidental fix
The mode flags were only accepted before a subcommand:
linchpin version --jsonwas an unknown-option error whilelinchpin --json versionworked. They are now accepted at any depth — except on thewtpassthrough, which must keep forwarding--jsonto the legacy dispatcher that reads it out of its own argv.Verification
npm run typecheckclean,npm test113/113 pass (13 new).🤖 Generated with Claude Code