Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/cli-upgrade-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"sideshow": minor
---

Add `sideshow upgrade` to self-update the CLI to the latest npm release.

- `sideshow upgrade` fetches the latest published version and, when newer,
runs `npm install -g sideshow@<latest>` for you.
- `--check` reports whether an update is available without installing;
`--dry-run` prints the install command instead of running it.
- It refuses on a development checkout (a published package ships no `.git`)
with a `git pull && npm install` hint, so it never clobbers a source tree.
- `sideshow version` now points users at `sideshow upgrade` instead of the
manual `npm install -g sideshow`, and both share one cached registry check.
92 changes: 70 additions & 22 deletions bin/sideshow.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ usage:
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 upgrade [--check] [--dry-run] update the CLI to the latest npm release
--check report whether an update is available, don't install
--dry-run print the install command instead of running it
sideshow mcp run the stdio MCP server (for agent configs)

flags:
Expand Down Expand Up @@ -603,6 +606,34 @@ function writeUpdateCache(version) {
} catch {}
}

// Resolve the latest published version from the npm registry. Uses the 24h disk
// cache unless `force` is set (the `upgrade` command wants a fresh answer before
// installing). Returns null when offline / timed out / the registry errors, so
// callers stay best-effort.
async function fetchLatestVersion({ force = false } = {}) {
if (!force) {
const cached = readUpdateCache();
if (cached) return cached;
}
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 3000);
try {
const res = await fetch("https://registry.npmjs.org/sideshow/latest", { signal: ctrl.signal });
if (res.ok) {
const pkg = await res.json();
if (typeof pkg.version === "string") {
writeUpdateCache(pkg.version);
return pkg.version;
}
}
} catch {
// Offline / timed out / registry error — caller treats null as "unknown".
} finally {
clearTimeout(timer);
}
return null;
}

// One comment → one line (one monitor notification). Newlines are collapsed so
// a multi-line comment stays a single notification.
function watchLine(c) {
Expand Down Expand Up @@ -1602,35 +1633,52 @@ const commands = {
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.
}
}
const latest = await fetchLatestVersion();
if (latest && versionGt(latest, PKG_VERSION)) {
console.log(`\nUpdate available: ${PKG_VERSION} → ${latest}`);
console.log(`Run: npm install -g sideshow`);
console.log(`Run: sideshow upgrade`);
}
} catch {
// Never let the update check fail the command.
}
},

// Self-update the globally installed CLI to the latest npm release. Refuses on
// a development checkout (a published package ships no .git) — there you
// upgrade with git. --check reports without installing; --dry-run prints the
// exact install command instead of running it.
async upgrade() {
const { values: flags } = parse({
options: { check: { type: "boolean" }, "dry-run": { type: "boolean" } },
});
const latest = await fetchLatestVersion({ force: true });
if (!latest) fail("could not reach the npm registry — check your connection and retry");
if (!versionGt(latest, PKG_VERSION)) {
console.log(`sideshow ${PKG_VERSION} is already up to date.`);
return;
}
if (flags.check) {
console.log(`Update available: ${PKG_VERSION} → ${latest}`);
console.log(`Run: sideshow upgrade`);
return;
}
if (existsSync(join(ROOT, ".git"))) {
fail(`development checkout at ${ROOT} — upgrade with: git pull && npm install`);
}
const spec = `sideshow@${latest}`;
if (flags["dry-run"]) {
console.log(`npm install -g ${spec}`);
return;
}
console.log(`Upgrading sideshow ${PKG_VERSION} → ${latest}…`);
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
try {
execFileSync(npm, ["install", "-g", spec], { stdio: "inherit" });
} catch {
fail(`upgrade failed — try manually: npm install -g ${spec}`);
}
console.log(`\nUpgraded to sideshow ${latest}.`);
},
};

async function fetchTextWithFallback(path, localFile) {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const app = createApp({
// SIDESHOW_VERSION fakes the running version (manual testing of the
// notice); set it to the empty string to disable the update check
version: process.env.SIDESHOW_VERSION ?? (JSON.parse(pkgJson) as { version: string }).version,
upgradeCommand: "npm install -g sideshow",
upgradeCommand: "sideshow upgrade",
});

const port = Number(process.env.PORT ?? 8228);
Expand Down
Loading