alex0ptr/as soon as: Run a command as soon as, reuse its output for some time. · GitHub

Run a command as soon as, reuse its output for some time.
as soon as executes a command in your present listing, prints its stdout and
retains it in reminiscence in a small per-user background daemon. Repeated calls with
the identical command, listing and tenant key are answered from reminiscence till the
entry expires. The daemon stops by itself as soon as its final entry has expired.
The typical use case: studying secrets and techniques from 1Password with out approving each
single learn along with your fingerprint.
# as soon as, e.g. in ~/.zshrc: a tenant key for this terminal session
export ONCE_TENANT="${ONCE_TENANT:-$(uuidgen)}"
# later whilst you work in your scripts / direnv / mise.toml
export GITHUB_TOKEN="$(as soon as --ttl 8h --no-dir -- op learn op://Private/GitHub/token)"
The first name asks in your fingerprint, each additional name inside 8 hours
with the identical tenant key doesn’t.
With mise in any listing hierarchy or a particular venture utilizing a mise.native.toml:
[env]
ONCE_TENANT = "5D0F399A-5091-43F1-9C43-7C5A2377D90D" # particular to this context
TOKEN = "{{ exec(command='as soon as --ttl 12h --no-dir -- op learn op://Private/one thing/token') }}"
go set up github.com/alex0ptr/as soon as@newest # Go 1.27+, no dependencies apart from the usual library
macOS and Linux solely.
This is the place to begin: it lists each possibility and command
(as soon as assist and -h work too).
For extra verbose documentation maintain studying.
as soon as --ttl DURATION [--until TIME] [--tenant KEY] [--refresh] [--no-dir] -- COMMAND [ARGS...]
as soon as standing
as soon as clear
as soon as assist
| Option | Meaning |
|---|---|
--ttl |
How lengthy to cache the end result, e.g. 30m, 1h, 24h (required). |
--until |
Absolute higher sure for the expiry (see under). |
--tenant |
Key that separates cache contexts (see under). Defaults to $ONCE_TENANT; considered one of each is required. |
--refresh |
Ignore the cache, run the command and overwrite the cached worth. |
--no-dir |
Leave the working listing out of the cache key (see under). |
as soon as standing reveals the daemon, its entry depend and when it would cease.
as soon as clear drops each cached worth and stops the daemon.
Everything after -- is executed instantly (no shell). Environment variables,
working listing and terminal are inherited out of your shell. If you want
pipes, globs or different shell options, wrap them your self:
as soon as --ttl 1h -- sh -c 'op merchandise get "AWS" --format json | jq -r .fields[0].worth'
Only stdout is cached. stdin and stderr are handed by way of, so interactive
prompts maintain working. Results of instructions that exit non-zero are by no means cached,
and the exit code is handed on.
By default the working listing is a part of the cache key, as a result of the identical
command can produce totally different output in several directories (git rev-parse HEAD, cat .model, …). For instructions whose output doesn’t rely upon the
listing, reminiscent of op learn, cross --no-dir so one cached worth serves each
listing. On a miss the command nonetheless runs in your present listing.
Entries created with and with out --no-dir are separate.
--until takes an absolute time; the entry expires at whichever comes first,
--ttl or --until. as soon as itself by no means computes relative dates; let your
shell try this.
Accepted codecs: 2026-10-09T06:00:00+02:00, 2026-10-09T06:00:00+0200,
2026-10-09T06:00, 2026-10-09 (kinds with out a zone are native time).
Cache for as much as a day, however by no means previous 10 pm at this time:
as soon as --ttl 24h --until "$(date +%F)T22:00" --no-dir -- op learn op://Work/DB/password
Never previous tomorrow morning:
# GNU date (Linux)
as soon as --ttl 24h --until "$(date -d tomorrow +%F)T06:00" -- op learn …
# BSD date (macOS)
as soon as --ttl 24h --until "$(date -v+1d +%F)T06:00" -- op learn …
If --until already lies prior to now, the command runs usually and its
end result just isn’t cached.
The cache secret’s HMAC-SHA256(tenant, working listing ‖ command ‖ args)
(with out the listing when --no-dir is ready).
The tenant secret’s a namespace, not a safety boundary, and it’s not meant
to be stored secret; it might properly reside in a config file. It does two issues:
- It separates cache contexts (shells, initiatives, scripts): a context with a
totally different tenant key will get its personal entries and has to fetch its secrets and techniques
itself. - It makes cache keys virtually unguessable.
It doesn’t cease different processes of your personal consumer from utilizing the cache; see
Security model.
The ${ONCE_TENANT:-$(uuidgen)} sample above provides each new terminal its
personal tenant whereas subshells and scripts began from it share the cache.
Use a hard and fast worth to share the cache throughout terminals.
(Side be aware: a key handed through --tenant reveals up within the course of checklist.)
as soon ascomputes the important thing and asks the daemon over a Unix socket.- On successful, it prints the cached output and exits.
- On a miss, it runs the command itself, in your listing, along with your
surroundings. If the command succeeds,as soon asbegins the daemon if wanted
and palms it the output along with the expiry time. - The daemon retains entries in reminiscence solely. It tracks the newest expiry and
shuts down when no entry is left.
The socket lives in $XDG_RUNTIME_DIR/once- (or the temp dir) in a
listing with mode 0700, so different customers can not join. That listing additionally
holds daemon.log. Set ONCE_RUNTIME_DIR to make use of a special location.
The consumer computes the cache key itself, so the tenant secret’s by no means despatched to
the daemon.
- Protection towards different customers comes from the Unix socket and file
permissions: the runtime listing has mode0700, the socket0600. - Processes of the identical consumer are usually not stored out. They can discuss to the socket
and may learn the tenant key (from the surroundings or a config file), so
they will use the cache similar to you. This is accepted by design. - Values are stored unencrypted within the daemon’s reminiscence. They are overwritten
earlier than they’re dropped (finest effort; reminiscence just isn’t locked towards
swapping). - Core dumps of the daemon are disabled (
RLIMIT_CORE0, plus
PR_SET_DUMPABLE0 on Linux), so cached values don’t find yourself in a core
file.
- Outputs bigger than 64 MiB are handed by way of however not cached.
- Expiry is checked towards the wall clock on each learn, so entries additionally
expire appropriately after a laptop computer has been asleep.
