Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

53 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

governor

governor logo

CI Go Reference Latest release License: MIT

A command cache: governor runs a command, caches its stdout/stderr/exit code for a TTL, and replays that result on repeat invocations instead of running the command again. Useful for anything slow, rate-limited, or just wasteful to re-run on every single invocation — a self-update check, a shell prompt segment, a network call you don't want to repeat on every script run.

Examples

Throttle a self-update check

Wrap a frequently self-updating CLI so the update check only actually happens once every few hours instead of on every invocation:

thatcmd() { governor run --ttl 4h --silent -- brew upgrade thatcmd; command thatcmd "$@"; }

--silent suppresses the upgrade check's own output (you don't want to see "Already up-to-date" on every invocation), and the real thatcmd "$@" call after it is what actually runs and receives your real arguments — governor only ever wraps and throttles the upgrade check, never the tool invocation itself.

Speed up a shell prompt

Prompt segments that shell out over the network (current AWS account, cloud project, etc.) add real, noticeable latency to every single prompt render. Cache the check with a short TTL so the prompt stays fast and only refreshes periodically:

governor run --ttl 30s --silent -- aws sts get-caller-identity --query Account --output text

Avoid re-hitting a slow or rate-limited API

$ governor run --ttl 1h -- curl -s https://api.ipify.org
203.0.113.42
$ governor run --ttl 1h -- curl -s https://api.ipify.org   # cached, no network round trip
203.0.113.42

Handy in scripts you run repeatedly during development, or against APIs with rate limits you don't want to burn through on every run.

Install

brew install calini/tap/governor

Or, with Go 1.25+:

go install github.com/calini/governor/cmd/governor@latest

Usage

governor run --ttl 4h -- some-command --with --args
governor peek -- some-command --with --args   # replay the last result, stale or not
governor forget -- some-command --with --args # evict the cached entry
governor list                                  # show what's cached, and whether it's still fresh
governor purge                                 # sweep the whole store for expired entries
governor --help                                # overview of all commands
governor run --help                            # flags for a specific command

Cache keys

By default the cache key is derived from the wrapped command's argv. Use --vary-cwd, --vary-path, --vary-env=VAR (repeatable), or --vary-modtime=FILE (repeatable) to fold additional inputs into the key — named after the HTTP Vary header, which does the same thing for response caching. A changed input simply produces a different key; there's no separate invalidation step. --key overrides the derived key entirely (bypassing all of the above as key-folding inputs — --scope still tags the stored record and remains filterable via governor list --scope / governor purge --scope even when --key is set).

Scopes

--scope STRING takes on two related roles depending on the subcommand. On run/peek/forget it's a key-folding input, same as a --vary-* flag: it folds an arbitrary string into the cache key and tags the stored entry with it. On list/purge it's instead a filter over already-stored entries: governor list --scope / governor purge --scope narrow to just the entries tagged with that scope, letting you namespace entries you want to inspect or sweep together later. What a scope represents (an environment, a project, a tenant) is entirely up to you; governor attaches no meaning to the string itself. Entries with different scopes never collide, and omitting --scope behaves exactly as before — fully additive, opt-in.

governor run --ttl 1h --scope prod -- curl -s https://api.prod.example.com/status
governor list --scope prod   # only prod-scoped entries
governor purge --scope prod  # only sweep expired prod-scoped entries

Flags

  • --ttl DURATION (required for run) — accepts stdlib time.ParseDuration units plus d (day) and w (week), e.g. 4h, 1d, 2w, 1d12h.
  • --force — skip the freshness check and execute unconditionally.
  • --scope STRING — fold a scope into the cache key and tag the entry with it; see Scopes.
  • --discard-failures — on non-zero exit, leave the cache untouched instead of caching the failure (the default caches failures too, overwriting any previously cached success). A prior good result keeps being served until the command succeeds again; if there wasn't one, it just stays a miss.
  • --silent — capture output without streaming it to the terminal.
  • --cache-dir DIR — override the cache directory (also settable via the GOVERNOR_CACHE_DIR env var; default os.UserCacheDir()/governor).

Shell completion

governor completion <shell> generates a completion script for bash, zsh, fish, or PowerShell — e.g. source <(governor completion zsh). Run governor completion --help or governor completion <shell> --help for shell-specific setup instructions.

Exit codes

  • governor run and a governor peek cache-hit both pass through the wrapped/cached command's own exit code — including when the command ran successfully but the cache write afterward failed; a caching problem never overrides the command's real exit code.
  • 3 is used for internal governor errors that happen before the command ever runs (bad flags, cache open failure, etc.). This can collide with a wrapped command that itself legitimately exits 3 — a known, accepted limitation of exit-code passthrough, not something governor tries to solve.
  • governor peek / governor forget exit 1 when nothing is cached for the derived key / nothing was found to forget.
  • A command killed by a signal is cached with exit code -1 (Go's os/exec convention for signal death), which the OS then reports as exit 255 when governor itself exits with it.

Library

import "github.com/calini/governor"

cmd := governor.Command(exec.Command("some-command"))
result, err := cmd.Run(ctx, 4*time.Hour)

governor.Command wraps an *exec.Cmd, mirroring exec.Command's own naming — every exec.Cmd field and method (Dir, Env, Stdout, Start, Wait, ...) is available directly on it. Set Key/Scope/VaryCwd/VaryPath/VaryEnv/VaryModtime/Force/DiscardFailures/CacheDir before calling Run, Peek, or Forget. List(cacheDir, scope string) ([]ListEntry, error) and Purge(cacheDir, scope string) (int, error) operate on the whole store rather than a single Cmd. See the package docs for details.

Design

See docs/design.md.

About

Command cache

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages