{"_id":"@khaentertainment/ofa","_rev":"9-1b01ff883aa74ea2188a4c673b0f9e4f","name":"@khaentertainment/ofa","dist-tags":{"latest":"0.4.0"},"versions":{"0.2.0":{"name":"@khaentertainment/ofa","version":"0.2.0","keywords":["ofa","harness","update","cli","agent","claude","codex","opencode","kimi","copilot","devin"],"license":"MIT","_id":"@khaentertainment/ofa@0.2.0","maintainers":[{"name":"bbrenner2217","email":"billy@khaentertainment.com"}],"homepage":"https://github.com/KHAEntertainment/ofa","bugs":{"url":"https://github.com/KHAEntertainment/ofa/issues"},"os":["darwin","linux"],"bin":{"ofa":"bin/ofa"},"cpu":["arm64","x64"],"dist":{"shasum":"a603eeeb42ff867f5abf805876b0c8e4d9d2ebdb","tarball":"https://registry.npmjs.org/@khaentertainment/ofa/-/ofa-0.2.0.tgz","fileCount":10,"integrity":"sha512-8YrGBAqhZhp6wn4t5aouSSKe6BlJYmCL4OKogdhBO32hAI6yPTgg/9FGSyxDw6M9FhmXAVAAcwSl2CH/toEZbA==","signatures":[{"sig":"MEQCICxh3SIapPiUDfUeJqMwGfwWMYGSafinvONvDyeh5LI7AiBPbdZ0GkJK9zgv/p3a47dfa8ltupzksEUj7L3h2E6TAA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCO4O8cJDrvnqafhKuAEkOx3XEH+265W5Hr4qwbsQvfpAIgJuxDV1XQyjSsNx30UyG1bcj8GA9BkHtx4xNmKQ2d5+0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35772937},"engines":{"node":">=18"},"scripts":{"postinstall":"node ./scripts/postinstall.js"},"_npmUser":{"name":"bbrenner2217","email":"billy@khaentertainment.com"},"repository":{"url":"git+https://github.com/KHAEntertainment/ofa.git","type":"git"},"_npmVersion":"11.20.0","description":"One For All: detect and update every coding harness from a single CLI. https://github.com/KHAEntertainment/ofa","directories":{},"_nodeVersion":"26.12.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ofa_0.2.0_1792534041764_0.6618781489868149","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@khaentertainment/ofa","version":"0.2.1","keywords":["ofa","harness","update","cli","agent","claude","codex","opencode","kimi","copilot","devin"],"license":"MIT","_id":"@khaentertainment/ofa@0.2.1","maintainers":[{"name":"bbrenner2217","email":"billy@khaentertainment.com"}],"homepage":"https://github.com/KHAEntertainment/ofa","bugs":{"url":"https://github.com/KHAEntertainment/ofa/issues"},"os":["darwin","linux"],"bin":{"ofa":"bin/ofa"},"cpu":["arm64","x64"],"dist":{"shasum":"7b45fcf87551032c2ce2c417d31375ed6a55b951","tarball":"https://registry.npmjs.org/@khaentertainment/ofa/-/ofa-0.2.1.tgz","fileCount":9,"integrity":"sha512-mV+KNqR3R8BDrref+H/0x0Lkjj5FNHFpaswLXvO9HPVF4Bh46L1yVTPGu5Wys+d7rgmFA7fdVhrgnc0WSxTLCA==","signatures":[{"sig":"MEYCIQD47pFjBkGbr1Pg0wqVo2kf7uPVD9HEZoY1mWPNtDfCUQIhANSmu8TrHZTL8+GAxqhWzEH4VCX4iElUoDmE7btOUQsI","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@khaentertainment%2fofa@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":28910007},"engines":{"node":">=18"},"scripts":{"postinstall":"node ./scripts/postinstall.js"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","approver":{"name":"bbrenner2217","email":"billy@khaentertainment.com"},"trustedPublisher":{"id":"github","oidcConfigId":"7f26a3a9-d2ff-4091-a2b3-ea031f4b1554"}},"repository":{"url":"git+https://github.com/KHAEntertainment/ofa.git","type":"git"},"_npmVersion":"11.21.1","description":"One For All: detect and update every coding harness from a single CLI. https://github.com/KHAEntertainment/ofa","directories":{},"_nodeVersion":"24.21.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ofa_0.2.1_1792882838608_0.2673303048000337","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@khaentertainment/ofa","version":"0.2.2","keywords":["ofa","harness","update","cli","agent","claude","codex","opencode","kimi","copilot","devin"],"license":"MIT","_id":"@khaentertainment/ofa@0.2.2","maintainers":[{"name":"bbrenner2217","email":"billy@khaentertainment.com"}],"homepage":"https://github.com/KHAEntertainment/ofa","bugs":{"url":"https://github.com/KHAEntertainment/ofa/issues"},"os":["darwin","linux"],"bin":{"ofa":"bin/ofa"},"cpu":["arm64","x64"],"dist":{"shasum":"7cd1e79e2cd9a510b1e5146a26f18c8c08deaf4a","tarball":"https://registry.npmjs.org/@khaentertainment/ofa/-/ofa-0.2.2.tgz","fileCount":9,"integrity":"sha512-cUC8Ehgk47Ng76myyZtf2KHcvIKn+mvfTttc6bTS8WGTY6frP3+nBagv6d1qK62RW1nYiOaA4/h/ALEqGldAXQ==","signatures":[{"sig":"MEUCIBKc8I0s9idCyD2aFrUEaO2M/Um4obf9xJtJiUzOWcv7AiEA1HEWI2u/eezvBN+/2PVqHF3wTepw66/y8Lli8d4DWuY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@khaentertainment%2fofa@0.2.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":28928609},"engines":{"node":">=18"},"scripts":{"postinstall":"node ./scripts/postinstall.js"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","approver":{"name":"bbrenner2217","email":"billy@khaentertainment.com"},"trustedPublisher":{"id":"github","oidcConfigId":"7f26a3a9-d2ff-4091-a2b3-ea031f4b1554"}},"repository":{"url":"git+https://github.com/KHAEntertainment/ofa.git","type":"git"},"_npmVersion":"11.21.1","description":"One For All: detect and update every coding harness from a single CLI. https://github.com/KHAEntertainment/ofa","directories":{},"_nodeVersion":"24.21.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ofa_0.2.2_1792887346156_0.9548505693753586","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@khaentertainment/ofa","version":"0.2.3","keywords":["ofa","harness","update","cli","agent","claude","codex","opencode","kimi","copilot","devin"],"license":"MIT","_id":"@khaentertainment/ofa@0.2.3","maintainers":[{"name":"bbrenner2217","email":"billy@khaentertainment.com"}],"homepage":"https://github.com/KHAEntertainment/ofa","bugs":{"url":"https://github.com/KHAEntertainment/ofa/issues"},"os":["darwin","linux"],"bin":{"ofa":"bin/ofa"},"cpu":["arm64","x64"],"dist":{"shasum":"98349e73618371a2e9bd5d68e9043a26f617807f","tarball":"https://registry.npmjs.org/@khaentertainment/ofa/-/ofa-0.2.3.tgz","fileCount":9,"integrity":"sha512-pYANq2dvhlFr7PunzH3vGfvWvxhSAjx9PludcHz6NrRNc2axoRZlcUCnt/RyUiyuhOwT179kuKcRhDi2PIoR8g==","signatures":[{"sig":"MEQCIE0nSNwtmAwFqIKMd+waJzxw3hGM1VqVHMqdzbBgmwMDAiAzOGgjOcOwXYnLuVHb+9N20rBw17RfBVD7zMGB7HbqBA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@khaentertainment%2fofa@0.2.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":28944993},"engines":{"node":">=18"},"scripts":{"postinstall":"node ./scripts/postinstall.js"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","approver":{"name":"bbrenner2217","email":"billy@khaentertainment.com"},"trustedPublisher":{"id":"github","oidcConfigId":"7f26a3a9-d2ff-4091-a2b3-ea031f4b1554"}},"repository":{"url":"git+https://github.com/KHAEntertainment/ofa.git","type":"git"},"_npmVersion":"11.21.1","description":"One For All: detect and update every coding harness from a single CLI. https://github.com/KHAEntertainment/ofa","directories":{},"_nodeVersion":"24.21.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ofa_0.2.3_1792892895134_0.5690678609102497","host":"s3://npm-registry-packages-npm-production"}},"0.2.4":{"name":"@khaentertainment/ofa","version":"0.2.4","keywords":["ofa","harness","update","cli","agent","claude","codex","opencode","kimi","copilot","devin"],"license":"MIT","_id":"@khaentertainment/ofa@0.2.4","maintainers":[{"name":"bbrenner2217","email":"billy@khaentertainment.com"}],"homepage":"https://github.com/KHAEntertainment/ofa","bugs":{"url":"https://github.com/KHAEntertainment/ofa/issues"},"os":["darwin","linux"],"bin":{"ofa":"bin/ofa"},"cpu":["arm64","x64"],"dist":{"shasum":"69ddaa8d0f797e2420130d288d4bb41b81e0e46d","tarball":"https://registry.npmjs.org/@khaentertainment/ofa/-/ofa-0.2.4.tgz","fileCount":9,"integrity":"sha512-iog2dDMCeYfhZrM8ZsVnsxTo3XtoEnfSZKGHIywD56CCBhzyxq7+wKJXaJOgcRjATd1SZY+DixWYk2ICkLLgZg==","signatures":[{"sig":"MEQCIBb1cj1l21HP7ATIZIqpdJUYMH98sIiB7oC5Wij8aksaAiBau7rC0gXzCBFQodGUH7S3san4AGThKEhd1VED5uYkuA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@khaentertainment%2fofa@0.2.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":28945293},"engines":{"node":">=18"},"scripts":{"postinstall":"node ./scripts/postinstall.js"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","approver":{"name":"bbrenner2217","email":"billy@khaentertainment.com"},"trustedPublisher":{"id":"github","oidcConfigId":"7f26a3a9-d2ff-4091-a2b3-ea031f4b1554"}},"repository":{"url":"git+https://github.com/KHAEntertainment/ofa.git","type":"git"},"_npmVersion":"11.21.1","description":"One For All: detect and update every coding harness from a single CLI. https://github.com/KHAEntertainment/ofa","directories":{},"_nodeVersion":"24.21.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ofa_0.2.4_1792894025081_0.6885866188194996","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@khaentertainment/ofa","version":"0.3.0","keywords":["ofa","harness","update","cli","agent","claude","codex","opencode","kimi","copilot","devin"],"license":"MIT","_id":"@khaentertainment/ofa@0.3.0","maintainers":[{"name":"bbrenner2217","email":"billy@khaentertainment.com"}],"homepage":"https://github.com/KHAEntertainment/ofa","bugs":{"url":"https://github.com/KHAEntertainment/ofa/issues"},"os":["darwin","linux"],"bin":{"ofa":"bin/ofa"},"cpu":["arm64","x64"],"dist":{"shasum":"6da042463bd556d6957573d818dbb716dacbbde7","tarball":"https://registry.npmjs.org/@khaentertainment/ofa/-/ofa-0.3.0.tgz","fileCount":9,"integrity":"sha512-tOwS+MOZ+ZcWpsgyHRgrnlKw3P3gY/ZUujDi8Beiy/JnjgfLPnvntfgIcSg+Cw0AKiSo/bahWNnria9S9SsXfQ==","signatures":[{"sig":"MEQCICLeNVrCEk+gF1fYTuTY1r4Y1Xc7YtgckBzaortl8FpOAiBqWqGeG+oE4kaXaVBEEAllXbDL90w+r+WXLr9IuCXdHg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@khaentertainment%2fofa@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":32402810},"engines":{"node":">=18"},"scripts":{"postinstall":"node ./scripts/postinstall.js"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","approver":{"name":"bbrenner2217","email":"billy@khaentertainment.com"},"trustedPublisher":{"id":"github","oidcConfigId":"7f26a3a9-d2ff-4091-a2b3-ea031f4b1554"}},"repository":{"url":"git+https://github.com/KHAEntertainment/ofa.git","type":"git"},"_npmVersion":"11.21.1","description":"One For All: detect and update every coding harness from a single CLI. https://github.com/KHAEntertainment/ofa","directories":{},"_nodeVersion":"24.21.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ofa_0.3.0_1792909209938_0.8443929497729199","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"os":["darwin","linux"],"_id":"@khaentertainment/ofa@0.4.0","bin":{"ofa":"bin/ofa"},"cpu":["arm64","x64"],"bugs":{"url":"https://github.com/KHAEntertainment/ofa/issues"},"dist":{"shasum":"ac2a5660132d7d94e4b80eaee7a339087334b28d","tarball":"https://registry.npmjs.org/@khaentertainment/ofa/-/ofa-0.4.0.tgz","integrity":"sha512-V9Fo0Vus3f+zo7QPli7oPXmLQOeGANxo/wZsZxD/+/k8ntH+Esg8orPryMdq2ECvs27KNRYYOtcq84bmWGg9lA==","fileCount":9,"unpackedSize":32471012,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@khaentertainment%2fofa@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQClWm+I5/LOW+KLACmbqaH2TCerqD1ZNXU0RQ2ObREY5wIhAK/7W9CUaudEPTN/6lpTvOHnK0Xw4ruSI/2nnA/WQYPs"}]},"name":"@khaentertainment/ofa","engines":{"node":">=18"},"license":"MIT","scripts":{"postinstall":"node ./scripts/postinstall.js"},"version":"0.4.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"7f26a3a9-d2ff-4091-a2b3-ea031f4b1554"},"approver":{"name":"bbrenner2217","email":"billy@khaentertainment.com"}},"homepage":"https://github.com/KHAEntertainment/ofa","keywords":["ofa","harness","update","cli","agent","claude","codex","opencode","kimi","copilot","devin"],"repository":{"url":"git+https://github.com/KHAEntertainment/ofa.git","type":"git"},"_npmVersion":"11.21.1","description":"One For All: detect and update every coding harness from a single CLI. https://github.com/KHAEntertainment/ofa","directories":{},"maintainers":[{"name":"bbrenner2217","email":"billy@khaentertainment.com"}],"_nodeVersion":"24.21.0","_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ofa_0.4.0_1792988487552_0.2918424007080871"},"_hasShrinkwrap":false}},"time":{"created":"2026-10-20T22:05:39.251Z","modified":"2026-10-26T04:21:28.125Z","0.0.0-stage":"2026-10-20T22:05:39.251Z","0.2.0":"2026-10-20T22:07:22.042Z","0.2.1":"2026-10-24T23:00:38.852Z","0.2.2":"2026-10-25T00:15:46.468Z","0.2.3":"2026-10-25T01:48:15.438Z","0.2.4":"2026-10-25T02:07:05.307Z","0.3.0":"2026-10-25T06:20:10.277Z","0.4.0":"2026-10-26T04:21:27.801Z"},"bugs":{"url":"https://github.com/KHAEntertainment/ofa/issues"},"license":"MIT","homepage":"https://github.com/KHAEntertainment/ofa","keywords":["ofa","harness","update","cli","agent","claude","codex","opencode","kimi","copilot","devin"],"repository":{"url":"git+https://github.com/KHAEntertainment/ofa.git","type":"git"},"description":"One For All: detect and update every coding harness from a single CLI. https://github.com/KHAEntertainment/ofa","maintainers":[{"name":"bbrenner2217","email":"billy@khaentertainment.com"}],"readme":"# OFA — One For All\n\nA single command to detect, version-check, and update every coding harness on your machine.\n\n`ofa` knows about Claude Code, Codex, opencode, Kimi, Cursor's agent, GitHub Copilot, Antigravity (`agy`), Devin, CodeRabbit, and the rest of the popular CLI harnesses. It scans what's installed, asks each package manager / self-updater whether anything's available, and runs the update — interactive picker or one-shot.\n\n```\n$ ofa check\nscan: 13 harnesses, 0 with updates, 8 updaters available\n  agy          1.3.4          [self]    agy → updater available\n  claude       2.1.300        [self]    claude → updater available\n  coderabbit   0.7.6          [self]    coderabbit → updater available\n  copilot      1.0.80         [self]    copilot → updater available\n  cursor-agent 2026.10.14-... [self]    cursor-agent → updater available\n  devin        3000.11.3      [self]    devin → updater available\n  kimi         2.2.0          [self]    kimi → updater available\n  mmx          1.0.25         [self]    mmx → updater available\n```\n\nExit code is `1` if any install is **known to be outdated** (and not [pinned](#freezing-a-harness)), `0` otherwise — pipe it into your shell scripts. An available updater (a self-tier harness with no version to compare) is reported separately and deliberately does **not** make the exit code `1`, so `ofa check && …` fires when nothing is actually pending.\n\n## Why\n\nEvery coding harness has its own update story. Claude Code has `claude update`. Codex is `brew upgrade --cask codex`. opencode lives in three places simultaneously — npm, a brew tap, and a beta npm package — and only one is on your PATH. Tracking which is current, and which is shadowed, is friction OFA absorbs.\n\nOFA never reimplements an updater. It runs the native one (`brew upgrade`, `npm i -g <pkg>@latest`, `claude update`, `agy update`, etc.) and reports the outcome.\n\n## Install\n\n### macOS (Homebrew)\n\n```sh\n$ brew install KHAEntertainment/tap/ofa\n```\n\n### npm (any platform)\n\n```sh\n$ npm install -g @khaentertainment/ofa\n```\n\n### From source\n\n```sh\n$ git clone https://github.com/KHAEntertainment/ofa\n$ cd ofa && go build -o ~/.local/bin/ofa ./cmd/ofa\n```\n\nGo 1.26+ required.\n\n### Verifying what you installed\n\nReleases since `0.2.1` are built and published entirely by GitHub Actions,\nusing npm trusted publishing — the workflow authenticates over OIDC, so no\nlong-lived publish token exists anywhere in the pipeline. Each published\ntarball carries a SLSA provenance statement naming the repository and workflow\nthat built it.\n\nConfirm an attestation exists (no install needed — this follows whatever\n`latest` points at):\n\n```sh\n$ npm view @khaentertainment/ofa dist.attestations\n{\n  url: 'https://registry.npmjs.org/-/npm/v1/attestations/@khaentertainment%2fofa@0.2.1',\n  provenance: { predicateType: 'https://slsa.dev/provenance/v1' }\n}\n```\n\nTo actually verify the signatures, install it as a dependency of a project —\n`npm audit signatures` deliberately refuses global installs (`EAUDITGLOBAL`):\n\n```sh\n$ mkdir verify && cd verify && npm init -y\n$ npm install @khaentertainment/ofa\n$ npm audit signatures\n\n1 package has a verified registry signature\n\n1 package has a verified attestation\n```\n\nThe same attestation is browsable as **View provenance** on the\n[package page](https://www.npmjs.com/package/@khaentertainment/ofa).\n\n### One PATH caveat\n\n`ofa` resolves to the **first** match on your `PATH`. If you built from source\ninto `~/.local/bin` and later installed via npm or Homebrew, the older local\nbinary keeps winning — and will keep reporting its own version:\n\n```sh\n$ which -a ofa            # every copy, in PATH order\n$ ofa --version           # what you are actually running\n```\n\nRemove or update the stale copy to let the packaged binary take over. This is\nthe same shadowing OFA reports for other harnesses.\n\n## Usage\n\n### Interactive picker (default)\n\n```\n$ ofa\n  ▓▓▓▓▓▓╗   ▓▓▓▓▓▓▓▓▓▓╗   ▓▓▓▓▓▓╗\n▓▓╔═════▓▓╗ ▓▓╔═══════╝ ▓▓╔═════▓▓╗\n▓▓║     ▓▓║ ▓▓║         ▓▓║     ▓▓║\n▓▓║     ▓▓║ ▓▓▓▓▓▓▓▓╗   ▓▓▓▓▓▓▓▓▓▓║\n▓▓║     ▓▓║ ▓▓╔═════╝   ▓▓╔═════▓▓║\n▓▓║     ▓▓║ ▓▓║         ▓▓║     ▓▓║\n╚═▓▓▓▓▓▓╔═╝ ▓▓║         ▓▓║     ▓▓║\n  ╚═════╝   ╚═╝         ╚═╝     ╚═╝\n\n> [ ] agy            1.3.4           agy → updater available\n  [ ] claude         2.1.300         claude → updater available\n  [ ] coderabbit     0.7.6           coderabbit → updater available\n  ...\n\n↑/↓ navigate · space toggle · a all · enter apply · q quit\n```\n\nThe \"OFA\" logomark above is rendered in 24-bit color in a real terminal —\nthe letters fade from `#e67a4c` (orange) on the left to `#4282d7` (blue)\non the right. The banner is generated at build time by\n[`scripts/gen-banner/`](./scripts/gen-banner) from\n[ascii.rest](https://ascii.rest)'s `tall` font and baked into the binary\nas a constant string, so the CLI has no ascii.rest runtime and no network\ndependency. `NO_COLOR=1` strips the escapes for plain logs.\n\nFirst run on a machine with no `~/.config/ofa/state.json` auto-scans first, so the picker has data to work with.\n\n### Non-interactive\n\n```sh\n$ ofa check             # what's outdated? exit 1 if anything pending\n$ ofa check --json      # jq-friendly\n$ ofa check --refresh   # bypass cached latest versions (--refresh also on apply and `ofa`)\n$ ofa apply --dry-run   # show the plan without running\n$ ofa apply --yes       # run all available updates\n$ ofa apply --only pi   # narrow to one harness\n$ ofa apply --verbose   # stream each installer's full output (default: progress lines)\n$ ofa scan              # detect + write state, report drift since last scan\n```\n\n`ofa check --json` is the scriptable variant:\n\n```json\n{\n  \"scanned_at\": \"2026-10-15T20:31:42Z\",\n  \"harness_count\": 13,\n  \"update_count\": 0,\n  \"updater_available_count\": 8,\n  \"harnesses\": {\n    \"claude\": {\n      \"name\": \"Claude Code\",\n      \"installs\": [\n        {\"version\": \"2.1.300\", \"tier\": \"self\", \"outdated\": false, \"updater_available\": true}\n      ]\n    }\n  }\n}\n```\n\n### Freezing a harness\n\nPin a harness in `~/.config/ofa/config.toml` (create it yourself; OFA never\nwrites it) to hold it at its installed version:\n\n```toml\n# Harness ids to freeze. Updates are never offered or applied for these.\npins = [\"opencode\"]\n```\n\nA pinned harness still shows up in `ofa check` and the picker, marked\n`(pinned)`, but it no longer counts toward the exit code — so scripts keep\nseeing `0` while it is held back. `ofa apply --only <pinned-id>` also skips it:\nthe pin is authoritative. Remove the id to update again.\n\nTwo limits worth knowing:\n\n- **Freezing, not version targeting.** OFA delegates every update to the\n  harness's own updater (`brew upgrade`, `npm i -g …@latest`), which cannot\n  install an arbitrary version. A pin means \"do not update\", not \"stay on\n  exactly 1.2.3\".\n- **Typos are visible, not silent.** An unknown key fails the command, and a pin\n  id that is not in the Catalog produces a warning — so a misspelled pin cannot\n  look like it worked while OFA updates the harness anyway.\n\n## How it works\n\n```\ncatalog.Embedded()  →  detect.Scan()  →  resolve.Resolve()  →  picker / CLI\n                                              ↓\n                                       update.Apply()  →  DefaultExecutor\n```\n\nTwo-layer registry:\n\n1. **Catalog** — a directory of TOML files, one per harness, embedded into the binary at build time. Adding a new harness is adding a file.\n2. **State** — `~/.config/ofa/state.json`, the result of the last scan on this machine, used to compute drift (added / removed / changed).\n\nDetection is deterministic. Each install method (`brew-formula`, `brew-cask`, `npm`, `pipx`, `uv`, `native`) is a labeled branch in `internal/detect/methods.go` that shells out and parses the package manager's own output, plus a catalog `method` value on the entries that use it. Adding a method means a parser, a `latest` tier, and catalog rows — no change to the resolver.\n\nResolution is per-tier and runs concurrently: the single `brew outdated --json` call overlaps the per-package lookups instead of queueing behind them. `npm` queries the public registry's JSON API directly — one HTTP GET per package, no `npm view` process spawn — and falls back to `npm view <pkg> version` only when a custom registry is configured in `.npmrc`/env, where npm itself must apply the config. `pypi` resolves via the PyPI JSON API; `self` reports \"updater available\" without fabricating a version diff; `none` is report-only.\n\nResolved latest versions are cached in `state.json` for 12 hours, so a warm `ofa check` re-answers from cache instead of re-querying six registries. `--refresh` (accepted by `ofa`, `ofa check`, `ofa apply`) bypasses fresh entries, and when a refresh fails offline the stale cached answer is served with a warning on stderr — never a hard failure.\n\nUpdates are delegated to native updaters. `ofa apply` never reimplements an update — it runs `brew upgrade <name>`, `npm install -g <pkg>@latest`, or `<bin> update` and reports the outcome.\n\n## The shadow problem\n\n`opencode`, `qwen`, and `kimi` can each be installed multiple ways on the same machine (e.g., opencode via npm *and* via the `anomalyco/tap` brew formula, both writing to `/opt/homebrew/bin`). `which opencode` answers one of them — the wrong one is the one you didn't update. OFA enumerates every install and marks the active one (the `which`-winner) versus the shadowed ones:\n\n```\n$ ofa check\n  opencode       1.18.35                          [npm]   opencode → 1.19.1\n  opencode       1.19.0                           [brew]  opencode (at latest) (shadowed)\n```\n\nThe shadowed row is marked in the table, and `check --json` carries\n`\"status\": \"shadowed\"` for it, so scripts can see which copy is live.\n\n## Supported harnesses\n\n| Harness                | Install methods                              | Update path |\n|------------------------|----------------------------------------------|-------------|\n| Claude Code            | native, npm                                  | `claude update`, `npm i -g @anthropic-ai/claude-code@latest` |\n| Codex                  | brew cask, npm                               | `brew upgrade --cask codex`, `npm i -g @openai/codex@latest` |\n| Cursor Agent           | native                                       | `cursor-agent update` |\n| Devin                  | native                                       | `devin update` |\n| GitHub Copilot CLI     | npm                                          | `copilot update` |\n| Kimi Code              | uv, native                                    | `uv tool upgrade kimi-cli`, `kimi upgrade --yes` |\n| Kilo Code              | npm                                          | `npm i -g @kilocode/cli@latest` |\n| MiniMax (`mmx`)        | npm                                          | `mmx update` |\n| opencode               | npm, brew formula, npm (beta), native        | `npm i -g ...`, `brew upgrade opencode`, `opencode upgrade` |\n| Pi Coding Agent        | npm                                          | `npm i -g @earendil-works/pi-coding-agent@latest` |\n| Qwen Code              | npm, brew formula                            | `npm i -g ...`, `brew upgrade qwen-code` |\n| Antigravity (`agy`)    | native                                       | `agy update` |\n| CodeRabbit CLI         | native                                       | `coderabbit update` |\n\nAdding a harness is a PR with a TOML file. See [`internal/catalog/data/agy.toml`](./internal/catalog/data/agy.toml) for the simplest example.\n\n## Platform\n\nmacOS and Linux. Every install method OFA implements works on both: npm, pipx,\nuv, Homebrew (including Linuxbrew), curl-installer natives, and the per-harness\nself-updaters. The binary is a single cross-compiled static release; CI tests on\nLinux.\n\nLinux users are covered through the same catalog entries as macOS. The install\nmethods that matter there — npm for Claude Code and Codex, the opencode curl\ninstaller, native paths under `~/.local/bin` — are detected and updated the same\nway. Distro package backends (`apt`, `dnf`, `snap`) are not implemented yet\nbecause no harness in the catalog ships through a distro repository; adding one\nis a parser branch in `internal/detect/methods.go` plus a `latest` tier when a\ncatalog entry needs it.\n\n`update.cmd` is data, so a new install path is a new field on the same catalog\nentry rather than new code.\n\n## Roadmap\n\nDistribution is done: npm, the Homebrew tap, and tag-triggered releases all ship\n(see [Install](#install)). What follows is tentative, roughly in priority order,\nand uncommitted to any date.\n\n### Medium-term: platform reach\n\n- **Distro package backends.** `apt`, `dnf`, and `snap` detection plus their\n  latest-version tiers. Deferred until a catalog entry actually ships through a\n  distro repository — today none do, so these parsers would detect nothing.\n  Everything else OFA supports already runs on Linux (see [Platform](#platform)).\n- **Windows (WSL) and native Windows.** WSL reuses the existing Linux-capable\n  methods; native Windows adds `scoop`, `winget`, and Windows registry probing\n  for native installs.\n\n### Long-term: agent-assisted Catalog (`ofa doctor`)\n\nWhen a scan finds a binary on PATH that doesn't match any Catalog entry, or a Catalog entry fails to detect a known install, OFA needs help from a human-shaped intelligence to write the TOML. The design avoids any API key or rate-limit dependency on OFA's side:\n\n1. OFA writes the facts it knows into a shared context folder under its config directory: `~/.config/ofa/doctor/CLAUDE.md` (always loaded by Claude Code), `~/.config/ofa/doctor/context.md` (per-invocation facts: which binary, its `--version` output, the gaps it fills).\n2. OFA spawns a headless session of an installed agent — e.g. `claude -p \"Read ~/.config/ofa/doctor/CLAUDE.md and ~/.config/ofa/doctor/context.md. Draft a Catalog entry.\"` — whichever harness the user has installed. No model API, no vendor lock-in; OFA is the orchestrator, the agent does the thinking.\n3. The proposed TOML lands at `~/.config/ofa/doctor/history/<timestamp>/proposed-catalog.toml`. The user reviews it; if approved, copies it into the user Catalog (`~/.config/ofa/catalog/<id>.toml`) or upstream via PR for shared coverage.\n\nDoctor is opt-in (`ofa doctor` only) and never runs in the default `ofa` path. Its directory is created on first use, not at install.\n\n### Open ideas (not scheduled)\n\n- **Update hooks.** Run a shell command before/after each update (`~/.config/ofa/hooks.toml`).\n- **Schedule.** Optional cron-style background scan + report (no auto-apply — OFA never applies without explicit user action).\n\n## Contributing\n\nThe Catalog is the contribution surface. Adding a harness:\n\n```sh\n$ cp internal/catalog/data/agy.toml internal/catalog/data/your-harness.toml\n$ # edit id, name, installs (method/bin/package/version/latest/update)\n$ go test ./...\n$ # open a PR\n```\n\nIf `ofa doctor` (see [Roadmap](#roadmap)) finds a binary on your machine that doesn't match a catalog entry, it drafts a TOML for you to review and contribute upstream. Doctor is an opt-in command — not part of the default `ofa` path.\n\nCutting a release (version bump, tag, staged npm publish, Homebrew tap bump) is\ndocumented in [`docs/RELEASING.md`](https://github.com/KHAEntertainment/ofa/blob/main/docs/RELEASING.md).\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n\n## Acknowledgments\n\n- The [bubbletea](https://github.com/charmbracelet/bubbletea) TUI framework (v2, imported as `charm.land/bubbletea/v2`) powers the interactive picker.\n- Each catalog entry is a thin pointer at the actual upstream tool that does the work. OFA is glue, not a reimplementation.","readmeFilename":"README.md"}