Skip to content

Add a foreground freebuff update command - #1554

Open
yablokolabs wants to merge 2 commits into
CodebuffAI:mainfrom
yablokolabs:feat/freebuff-update-command
Open

yablokolabs wants to merge 2 commits into
CodebuffAI:mainfrom
yablokolabs:feat/freebuff-update-command

Conversation

@yablokolabs

@yablokolabs yablokolabs commented Oct 25, 2026 •

Copy link
Copy Markdown

What

Adds freebuff update, which installs the latest release right away instead of waiting for the launcher's background check or the exit-time install a deferred update uses.

Why

Today a new release only lands after a restart: the npm wrapper stages it while the session runs and moves it into place on exit. Users who leave the CLI open (or never see the background check finish) have no way to ask for the update. The wrapper already implements fetch → sha256 verify → extract → atomic install, so the command reuses that path rather than adding a second one.

How

  • cli/release-core/launcher.js
    • isUpdateCommand() reads argv and finds the first positional argument, skipping flags and --cwd's value, so a directory named update is not mistaken for the command.
    • runUpdateCommand() prints the installed version, resolves latest from the npm registry, exits 0 with "already up to date" when nothing is newer, and otherwise runs the existing stageBinary() + installStagedBinary() with the registry's checksums. Failures print through printDownloadFailure() and exit 1, leaving the installed binary untouched.
    • main() dispatches to it before the binary is spawned, so no running process holds the files being replaced.
  • freebuff/cli/release/index.js sets the new supportsUpdateCommand wrapper flag. isUpdateCommand() returns false for every wrapper that doesn't set it (see Scoping below).
  • cli/src/cli-args.ts accepts update as a command, so the freebuff binary no longer rejects it.
  • cli/src/index.tsx explains the case where the binary was started without the npm wrapper: for freebuff it points at npm install -g freebuff@latest instead of failing on an unknown command.
  • README.md documents the command.

Scoping

Only the freebuff binary defines an update command. The launcher and the binary's command check are both shared with the codebuff and codecane wrappers, where the first positional argument is a prompt: codebuff update the readme parses as { command: 'update', initialPrompt: 'update the readme' }. Intercepting it there would install a release and drop what the user typed, so the command is gated behind supportsUpdateCommand (set only by the freebuff wrapper) in the launcher and IS_FREEBUFF in index.tsx.

Tests

bun test cli/src/__tests__/release/update-command.test.ts (8 passing) covers command detection (including --cwd update, --, and a codebuff config that must not detect it), the up-to-date no-op, a full download/verify/install against a local release server, unknown latest version, checksum mismatch (fails closed), and a first install with no cached binary. cli-args.test.ts covers the new command, and wrapper-safety.test.ts pins the freebuff wrapper config alongside the codebuff and codecane ones that must not set the flag.

Users could only pick up a new release by waiting for the launcher's
background check or restarting the CLI. The npm wrapper already knows how to
fetch, verify and install a release, so `freebuff update` now runs that same
install in the foreground and reports the result. A binary started without the
wrapper says so and points at npm instead of crashing on an unknown command.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@codebuff-team

Copy link
Copy Markdown
Contributor

Good work here. The core idea — reusing stageBinary/installStagedBinary rather than inventing a second install path — is the right call, and the test suite (update-command.test.ts) is unusually thorough for a CLI launcher PR: it spins up a real HTTP server, builds a tarball, and exercises the checksum-mismatch and already-up-to-date paths explicitly. That's the kind of coverage this repo should expect for anything touching binary installation.

A few things worth double-checking before this gets ported:

  1. isUpdateCommand() in launcher.js only special-cases --cwd as a flag that consumes a value (FLAGS_WITH_VALUES). If there are other flags upstream that take values (anything parsed later in cli-args.ts besides --cwd), this heuristic will misread the value as the command. Worth confirming --cwd really is the only value-taking flag that can precede a positional command, or generalizing the set.

  2. installStagedBinary(stagedBinary, { quiet: true }) passes a new options object — please confirm the existing signature actually honors quiet; if it doesn't, this is harmless (extra arg ignored) but the intended quieting of its own logging won't happen, which would produce double messaging with your own console.log calls around it.

  3. No consideration of a background update already staged concurrently with a user-invoked freebuff update — probably a rare race, but worth a one-line comment on expected behavior (overwrite vs. no-op) if the maintainers care.

  4. The index.tsx fallback message is a nice touch for binaries invoked without the npm wrapper — correctly scoped and low-risk.

Overall this is in-scope, correct in the common case, and tested to a standard the maintainers will appreciate. Flag the argv-parsing edge case above when it's ported.

@codebuff-team codebuff-team added bot:triaged Classified by the community triage bot pr:port-candidate Worth porting into the private source tree labels Oct 25, 2026
The launcher and the binary's command check are shared with codebuff and
codecane, where the first positional argument is the prompt. Gating on a
wrapper config flag keeps `codebuff update the readme` from installing a
release and dropping the prompt.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@yablokolabs

yablokolabs commented Oct 25, 2026 •

Copy link
Copy Markdown
Author

Thanks @codebuff-team — went through all three, plus one thing the review only hinted at.

Value-taking flags. --cwd <directory> is the only required-value flag in cli-args.ts, so the heuristic holds where it matters, and --cwd update / --cwd=update are both correctly rejected. One caveat: --continue [conversation-id] takes an optional value that commander still consumes, so freebuff --continue update parses as continueId: 'update' while the launcher reads update as the command. Left as is — a conversation id is never going to be update, and generalizing the set to model optional values buys nothing here.

quiet. Confirmed honored: installStagedBinary({...}, { quiet = false, keepStagingOnFailure = false } = {}) uses it to suppress the "Download complete!" line, so there's no double messaging. No change needed.

Concurrent staged update. Already safe, so no comment needed. installPendingUpdate re-reads the installed version and returns early — deleting the staging dir — when currentVersion >= staged.version, with a comment about exactly this race, and adoptOrphanedStagedUpdates applies the same comparison. An older stage left by a concurrently running session is a no-op, never a downgrade.

What did need fixing: the command wasn't scoped to freebuff. Both isUpdateCommand and the index.tsx check are shared with codebuff and codecane, where the first positional argument is a prompt. codebuff update the readme parsed as { command: 'update', initialPrompt: 'update the readme' }, and the binary printed the "Freebuff updates through its npm launcher" message and exited 1 instead of sending the prompt; at the wrapper level it would have installed a release and dropped what the user typed. Both are now gated — a supportsUpdateCommand wrapper flag that only the freebuff wrapper sets, and IS_FREEBUFF in index.tsx — with a test that a codebuff config returns false. Pushed as 6342763.

Tests: bun test cli/src/__tests__/release/update-command.test.ts cli/src/__tests__/release/wrapper-safety.test.ts cli/src/__tests__/cli-args.test.ts — 43 pass, 0 fail.

This branch has not been deployed

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

Labels

bot:triaged Classified by the community triage bot pr:port-candidate Worth porting into the private source tree

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants