Skip to content

Show a remote banner fetched from banner.json on main - #973

Open
u9g wants to merge 9 commits into
mainfrom
jason/banner
Open

Show a remote banner fetched from banner.json on main#973
u9g wants to merge 9 commits into
mainfrom
jason/banner

Conversation

@u9g

@u9g u9g commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Why

We want to tell people running an installed lk things like "a new version is out, run brew upgrade livekit-cli" or "livekit.toml layout changed" without shipping an auto-updater (decided against) and without waiting for them to update to see the notice.

What

  • banner.json at the repo root, fetched from main on every run. It is a list of notices, each with its own version selector:
    [
      {"message": "lk agent simulate can now export runs. Upgrade: brew upgrade livekit-cli", "versions": "< 2.18.0"},
      {"message": "lk 3.0 is out and moves the agent id to [cloud] in livekit.toml.", "versions": "< 3.0.0"}
    ]
    versions is a semver constraint; empty matches every build. Every entry that matches prints, in file order, so a new-feature announcement for old builds can sit next to an upgrade notice. The committed file is []. Editing the file on main is the whole publishing process.
  • Shown at the top, before the command's output, inside a fence on stderr via out.Status, so it honors --quiet and never mixes with stdout. Only shown on an interactive terminal, so scripts and pipes are unaffected.
  • Printing first means the fetch is awaited: 1s timeout, and the file is cached at ~/.livekit/banner.json for an hour, so only the first run each hour pays the round trip (~70-170ms to raw.githubusercontent.com). A failed fetch falls back to the stale cache. Non-interactive runs skip the fetch entirely.

Verification

go build ./cmd/lk, go vet ./cmd/lk, go test ./cmd/lk pass. Demo video in a comment below.

The CLI fetches banner.json from the repo's main branch on every run and, when
the message is non-empty and the semver constraint in "versions" matches the
running build, prints it to stderr after the command finishes. The fetch is
never awaited: whatever has not arrived by then is dropped, so no command gets
slower. Non-interactive runs never see it.

Editing banner.json on main is the whole publishing process, so notices such as
"upgrade via brew" reach installed CLIs without a release or an auto-updater.
@u9g

u9g commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Demo: banner.json as a list of two notices (served locally for the recording). A 2.17.0 build prints both notices fenced at the top of lk room list on stderr, on the very first run. 2.18.6 matches only the < 3.0.0 one, --quiet silences it, --json | head keeps stdout clean, and a 3.0.0 build runs clean.

banner-sync.mp4

u9g added 8 commits September 11, 2026 16:51
One entry per notice, each with its own versions constraint, so a new-feature
announcement for old builds can sit next to an upgrade notice without either
having to be removed first.
Every --json flag binds to a shared jsonOutput destination so the banner,
which prints after the command returns, can tell a --json run apart from
an interactive one and keep stderr clean for downstream parsers.
Status puts it on stderr and honors --quiet; the border sets it apart from
the command's own output.
Printing before the command means the notice cannot wait on the network,
so each run shows the banner.json cached at ~/.livekit/banner.json by the
run before it and refreshes that cache in the background. A notice lands
one run after it is published; no command gets slower.
The fetch is awaited with a 1s timeout and the file is cached for an hour,
so only the first run each hour pays the round trip. Non-interactive runs
skip the fetch entirely.
The cache is {data, downloadedAt}; a refetch happens once downloadedAt is
over an hour old. Sync tools and backups that rewrite mtime no longer force
a refetch or hide a stale file.

@rektdeckard rektdeckard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, small nits mainly. My one question is whether there will ever be other conditions besides just your lk version that might want to trigger a banner? A specific command for example might want to provide usage tips that aren't dependent on version, but aren't appropriate to show at all times.

Comment thread cmd/lk/banner.go
// The banner prints before the command, so the fetch is awaited. To keep that off
// most runs, the fetched file is cached and reused for bannerTTL.
const (
bannerTimeout = time.Second

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this enough time for remote locations / mobile hotspot / etc?

Comment thread cmd/lk/banner.go
BorderForeground(util.Warning()).
Padding(0, 1).
Width(76)
out.Statusf("%s\n", fence.Render(strings.Join(msgs, "\n\n")))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe put a separator between message?

Comment thread cmd/lk/banner.go
continue
}
if b.Versions != "" {
c, err := semver.NewConstraint(b.Versions)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I was gonna check you were using this since we had it already

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants