feat: add CLI update check with npm registry indicator

On app start, check the npm registry for the latest
@anthropic-ai/claude-code version and compare against the installed
version. If behind, the CLI badge turns amber with a pulsing up-arrow
and a tooltip advising how to update.

Also bumps SUPPORTED_CLI_VERSION to 2.1.72.
This commit is contained in:
2026-03-10 11:48:19 -07:00
committed by Naomi Carrigan
parent 292bf50f50
commit 45c1caa133
4 changed files with 176 additions and 5 deletions
+51 -3
View File
@@ -19,7 +19,7 @@
import { describe, it, expect } from "vitest";
const SUPPORTED_CLI_VERSION = "2.1.53";
const SUPPORTED_CLI_VERSION = "2.1.72";
function compareVersions(a: string, b: string): number {
const aParts = a.split(".").map(Number);
@@ -41,7 +41,7 @@ describe("SUPPORTED_CLI_VERSION", () => {
});
it("matches the expected audited version", () => {
expect(SUPPORTED_CLI_VERSION).toBe("2.1.53");
expect(SUPPORTED_CLI_VERSION).toBe("2.1.72");
});
});
@@ -128,7 +128,55 @@ describe("compareVersions", () => {
});
it("returns 0 for exactly the supported version", () => {
expect(compareVersions("2.1.53", SUPPORTED_CLI_VERSION)).toBe(0);
expect(compareVersions("2.1.72", SUPPORTED_CLI_VERSION)).toBe(0);
});
});
});
// Mirrors the updateAvailable derived logic in CliVersion.svelte
function isUpdateAvailable(installedVersion: string, latestNpmVersion: string | null): boolean {
if (!latestNpmVersion || installedVersion === "Loading..." || installedVersion === "Unknown") {
return false;
}
const semverMatch = /(\d+\.\d+\.\d+)/.exec(installedVersion);
if (!semverMatch) return false;
return compareVersions(semverMatch[1], latestNpmVersion) < 0;
}
describe("updateAvailable", () => {
it("returns false when latestNpmVersion is null", () => {
expect(isUpdateAvailable("2.1.70", null)).toBe(false);
});
it("returns false when installed is Loading...", () => {
expect(isUpdateAvailable("Loading...", "2.1.72")).toBe(false);
});
it("returns false when installed is Unknown", () => {
expect(isUpdateAvailable("Unknown", "2.1.72")).toBe(false);
});
it("returns false when installed equals latest", () => {
expect(isUpdateAvailable("2.1.72", "2.1.72")).toBe(false);
});
it("returns false when installed is ahead of latest", () => {
expect(isUpdateAvailable("2.1.73", "2.1.72")).toBe(false);
});
it("returns true when installed is behind latest", () => {
expect(isUpdateAvailable("2.1.70", "2.1.72")).toBe(true);
});
it("returns true when installed has a lower minor version", () => {
expect(isUpdateAvailable("2.0.99", "2.1.72")).toBe(true);
});
it("handles version strings with extra info like '2.1.70 (build 123)'", () => {
expect(isUpdateAvailable("2.1.70 (build 123)", "2.1.72")).toBe(true);
});
it("returns false for unparseable installed version", () => {
expect(isUpdateAvailable("not-a-version", "2.1.72")).toBe(false);
});
});