From f2896ec475e89d8d7d5a7fefe84b2051ca805b28 Mon Sep 17 00:00:00 2001 From: Justin Giancola Date: Thu, 25 Jun 2026 22:13:07 -0400 Subject: [PATCH 1/2] feat(cli): add --version / -V / version with update check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sideshow --version`, `sideshow -V`, and `sideshow version` all print the current version and check the npm registry for a newer release. The check is best-effort: 3 s timeout, 24 h disk cache, failures silently swallowed. When a newer version exists the output shows the available update and the install command. Reuses the same registry lookup the server already does for the viewer update notice, but runs it directly from the CLI — no running server required. --- bin/sideshow.js | 81 +++++++++++++++++++++++++++++++++++++++++++++++- test/cli.test.ts | 16 ++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/bin/sideshow.js b/bin/sideshow.js index db4dc95..413feaf 100755 --- a/bin/sideshow.js +++ b/bin/sideshow.js @@ -10,6 +10,7 @@ import { parseArgs } from "node:util"; const BASE = (process.env.SIDESHOW_URL ?? "http://localhost:8228").replace(/\/$/, ""); const TOKEN = process.env.SIDESHOW_TOKEN; const ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); +const PKG_VERSION = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).version; // This script's own path — used to register the Stop hook so it works whether // or not `sideshow` is on PATH (a fresh clone, an npx run, a global install). const SELF = fileURLToPath(import.meta.url); @@ -115,8 +116,12 @@ usage: sideshow guide print the design contract for posts sideshow setup print the AGENTS.md integration block sideshow agent-howto print current agent how-to + sideshow version show version and check for updates sideshow mcp run the stdio MCP server (for agent configs) +flags: + --version, -V print version and exit + environment: SIDESHOW_URL server base URL (default http://localhost:8228; set to a deployed instance, e.g. https://sideshow.you.workers.dev) @@ -471,6 +476,43 @@ async function publishSurface(parts, flags) { const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); +// Semver greater-than for plain x.y.z (mirrors server/app.ts versionGt). +function versionGt(a, b) { + const pa = a.split("-")[0].split(".").map(Number); + const pb = b.split("-")[0].split(".").map(Number); + for (let i = 0; i < 3; i++) { + const d = (pa[i] ?? 0) - (pb[i] ?? 0); + if (d !== 0) return d > 0; + } + return false; +} + +// Disk-cached update check so `sideshow version` doesn't hit the registry every +// time. TTL = 24 hours; stale/missing/corrupt cache is silently ignored. +const UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1000; + +function updateCachePath() { + const dir = join(tmpdir(), `sideshow-${userInfo().username}`); + mkdirSync(dir, { recursive: true, mode: 0o700 }); + return join(dir, "update-check.json"); +} + +function readUpdateCache() { + try { + const data = JSON.parse(readFileSync(updateCachePath(), "utf8")); + if (Date.now() - data.at < UPDATE_CACHE_TTL_MS && typeof data.version === "string") { + return data.version; + } + } catch {} + return null; +} + +function writeUpdateCache(version) { + try { + writeFileSync(updateCachePath(), JSON.stringify({ at: Date.now(), version })); + } catch {} +} + // One comment → one line (one monitor notification). Newlines are collapsed so // a multi-line comment stays a single notification. function watchLine(c) { @@ -1345,6 +1387,41 @@ const commands = { parse(); console.log(await fetchTextWithFallback("/agent-howto", join(ROOT, "guide", "AGENT_HOWTO.md"))); }, + + // Print the running version and check for updates (non-blocking, best-effort). + async version() { + parse(); + console.log(`sideshow ${PKG_VERSION}`); + try { + const cached = readUpdateCache(); + let latest = cached; + if (!latest) { + const ctrl = new AbortController(); + const timer = setTimeout(() => ctrl.abort(), 3000); + try { + const res = await fetch("https://registry.npmjs.org/sideshow/latest", { + signal: ctrl.signal, + }); + clearTimeout(timer); + if (res.ok) { + const pkg = await res.json(); + if (typeof pkg.version === "string") { + latest = pkg.version; + writeUpdateCache(latest); + } + } + } catch { + // Offline / timed out — skip silently. + } + } + if (latest && versionGt(latest, PKG_VERSION)) { + console.log(`\nUpdate available: ${PKG_VERSION} → ${latest}`); + console.log(`Run: npm install -g sideshow`); + } + } catch { + // Never let the update check fail the command. + } + }, }; async function fetchTextWithFallback(path, localFile) { @@ -1355,7 +1432,9 @@ async function fetchTextWithFallback(path, localFile) { return readFileSync(localFile, "utf8"); } -if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") { +if (cmd === "--version" || cmd === "-V") { + await commands.version(); +} else if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") { console.log(HELP); } else if (commands[cmd]) { await commands[cmd](); diff --git a/test/cli.test.ts b/test/cli.test.ts index 159d20d..f383ecd 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -63,6 +63,22 @@ const post = (url: string, body: unknown) => body: JSON.stringify(body), }).then((r) => r.json() as Promise); +// --- version --- + +for (const flag of ["--version", "-V", "version"]) { + test(`${flag} prints the version`, async () => { + const { code, stdout } = await run(...(flag.startsWith("-") ? [flag] : [flag])); + assert.equal(code, 0); + assert.match(stdout, /^sideshow \d+\.\d+\.\d+/); + }); +} + +test("version runs end-to-end (update check is best-effort)", async () => { + const { code, stdout } = await run("version"); + assert.equal(code, 0); + assert.match(stdout, /^sideshow \d+\.\d+\.\d+/); +}); + // None of these reach the network: --help and option errors resolve in // parsing, before any request (no server needs to be running). From 681a4606262a68f8d2724f3e384b1f0af85d54ee Mon Sep 17 00:00:00 2001 From: Justin Giancola Date: Thu, 25 Jun 2026 22:25:07 -0400 Subject: [PATCH 2/2] chore: add changeset for --version --- .changeset/cli-version-flag.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cli-version-flag.md diff --git a/.changeset/cli-version-flag.md b/.changeset/cli-version-flag.md new file mode 100644 index 0000000..e6ee21d --- /dev/null +++ b/.changeset/cli-version-flag.md @@ -0,0 +1,5 @@ +--- +"sideshow": patch +--- + +Add `sideshow --version`, `-V`, and `version` subcommand. Prints the installed version and checks the npm registry for updates (best-effort, 3 s timeout, 24 h disk cache).