nsc-tui is a Go + bubbletea /
huh front-end to
nsc, the NATS tool that manages operators,
accounts and users. It shows the current operator as a tree, opens every entity
in a dialog-style editor, and drives nsc for every change: each action becomes
a plan of nsc commands that is previewed, confirmed, run and followed by a
reload. Nothing is ever written except through nsc itself, so the preview is
exactly what you could type in a shell.
go install github.com/olgeni/nsc-tui@latest # or: go build -o nsc-tui .
nsc-tui # nsc's current store, operator and account
nsc-tui -H /path/to/env # nsc's own directory flags are accepted and passed on
nsc-tui -mouse # with mouse support (m toggles it at runtime)
nsc-tui -json | jq '.accounts[].users[].name' # non-interactive dump; -tree for a text listing
go test ./... # unit tests + nsc-backed tests (skipped without nsc)
man ./nsc-tui.1 # manual page
| Key | Action |
|---|---|
↑/↓ j/k pgup/pgdn home/end |
move |
→ / ← / space / * |
expand / collapse an account (*: all) |
/ |
filter by name, public key or tag (esc clears) |
enter |
details of the entity; J toggles the raw JSON of its JWT |
e |
edit the operator, account or user |
a / A |
add a user to the account (an account on the operator row) / add an account |
d / del |
delete the account or user (with revoke / creds / key options), or the operator — nsc has no command for that one, so the name has to be typed and the rm lines are previewed before the files go |
T |
JetStream tiers of the account: R1/R3/R5 limits, added, edited and removed (an account has either these or the global limits) |
n |
rename an account (nsc rename account): the JWT is reissued under the new name with the same identity key, so its users, their creds and other accounts' imports stay valid — or a user, which nsc cannot rename, so its JWT is deleted and added again with -k |
x i M R |
exports, imports, subject mappings, revocations of the account |
s |
signing keys of the account (or of the operator): add, remove, and give one a role to make it scoped |
C |
authorization callout of the account |
K |
every key of the operator context (nsc list keys -A); a generates an nkey (nsc generate nkey), E exports them to a directory, I imports a directory of nkeys |
I |
import / export: write the selected entity's JWT to a file (nsc describe --raw), export or import keys, import an account or a user from a JWT (a user also from a .creds file), describe a JWT without importing it (nsc describe jwt) |
c |
credentials of a user: show, save to a file, or make a nats CLI context |
v |
revoke a user (or lift the revocation) |
g |
generate: a server configuration (operator), an activation token (account export) |
p / P |
push / pull the account — on the operator row, all accounts |
V / X |
validate the store / expirations report |
S |
make the entity nsc's current operator / account (nsc select) |
O |
switch operator, add one, or initialize an environment |
m |
toggle the mouse (or start with -mouse): click a row to select it, click it again to open it, wheel scrolls; in editors a click focuses a field, a second click edits or toggles it, OK/Cancel are buttons; hold shift to select text |
r |
reload from disk |
h / ? |
key map / help, q quit |
Rows are coloured by state: expired or revoked in red, expiring within 30 days or unverified in yellow; the header counts what is expiring.
enter opens the details of the selected entity: the decoded claims, whether
the signature verifies, where the JWT and the private key are, and everything
the editors set. J shows the raw JWT payload as JSON.
Every entity is edited in the same kind of dialog: labelled fields moved with
↑/↓/tab, enter to edit a text field in place (enter keeps, esc
reverts), space for checkboxes, ←/→ for choices, ctrl+s for OK. Lists
(subjects, tags, networks, connection types) are comma-separated; a subscribe
subject with a queue group is written subject queue. Limits are -1 for
unlimited; sizes take k/m/g/t. Expiry and start are entered the way
nsc takes them (0, yyyy-mm-dd, 30d, 2w, 6M, 1y) and left blank to
keep the current value.
Only what changed is passed to nsc, and the preview shows the commands:
[1/2] remove permissions
nsc edit user -a billing -n alice --rm 'orders.>'
[2/2] apply the changes
nsc edit user -a billing -n alice --allow-sub 'orders.>' --bearer=false --payload 1000000
The plans know a few things about nsc that are easy to get wrong by hand
(all found by the nsc-backed tests):
--rmstrips a subject from every permission list, so a subject moved from one list to another is removed first and re-added by a second command.--rm-response-permsmakesnsc edit userstop right there, so it always runs alone.nsc edit accountresets--wildcard-exportsand--disallow-bearerto their defaults on every edit unless they are restated; every generated command restates them.--js-enableis refused as soon as any JetStream field was ever set, while--js-mem-storage/--js-disk-storageenable JetStream in every case; the plans use the latter, and--js-disable(alone) to turn it off.- Every
nsc edit accountwrites the global JetStream stream, consumer, ack and max-stream limits as unlimited, even on an account that never asked for JetStream, and nsc then refuses to add a tier to it; the test for "this account has global limits" is therefore nsc's own (any field set), not "it has storage". - An account has either the global JetStream limits or the replication tiers,
and nsc refuses any command that would leave both, so adding the first tier
removes the global one first (
--rm-js-tier 0) and the account editor drops its JetStream fields once tiers exist. --js-max-bytes-requiredis only honoured while the limits of a tier are being created (loadLimitstestsjs-max-ack-required, a flag that does not exist, and restores the stored value otherwise), so it is set when a tier is added and reported as dropped on a later edit rather than run for nothing.- There is no
delete operator, norename operatorand norename user;nsc rename accountexists but is hidden and needs--OK. A user is renamed by deleting its JWT and adding it again with-k, which needs every setting restated from the old claims — the times included, and nsc parses2006-01-02 15:04:05 UTCback exactly, so the expiry survives to the second. - An export is identified by its subject and there is no
edit import, so changing an export's subject or type, or editing an import, is a delete plus an add — the preview says so.
-
Exports (
x): add, edit, delete;tgenerates an activation token for a private export (for one of the store's accounts or a typed key);Rmanages activation revocations. -
Imports (
i): add from another account of the store (picked by name) or from an activation token file/URL; edit is delete + add. -
JetStream tiers (
T): the global tier and the R1/R3/R5 ones, added with their limits, edited and removed. -
Mappings (
M): weighted subject mappings, add / change weight / delete. -
Revocations (
R): revoked user keys of the account, revoke another user or a typed key, lift a revocation. -
Signing keys (
s): the keys that may sign users of the account (or accounts, on the operator row), with their role, keystore status and template summary.enteredits a scope: giving a role to a plain key promotes it, and every user that key signs then gets the template's permissions, limits, source networks, times and connection types instead of its own. nsc cannot turn a scoped key back into a plain one (remove and re-add), and removing a key invalidates the users it signed — the preview says both. -
Auth callout (
C): hands authorization to an external service. The auth users bypass the callout (the service's own credentials), the allowed accounts are those it may issue users for (or*, which cannot be mixed with keys), and the curve key is the X key requests are encrypted to (generatemakes one). Both key fields are pickable and free-form:enteropens the store's users (this account's first) or accounts plus*, ticked where the field already holds them — enter adds or removes one, the list stays open, and keys from outside the store are listed too so they can be removed the same way.eedits the field as plain text. nsc wants public keys there, not names, and a key that lives outside this store is equally valid. The names of the keys a field holds are shown under it. Turning it off isnsc edit authcallout --disable, which nsc accepts only on its own and which clears the whole configuration. -
Keys (
K): identity and signing keys with their keystore status;agenerates an nkey of any kind — the key an account or user is later added with (--public-key), or a curve key for the authorization callout — and either stores the private half or only prints the seed;Eexports them all to a directory. -
Expirations (
X):nsc expirationsas a table;wchanges the window,shides what is not expiring. -
Credentials (
c), validate (V), push/pull (p/P) and the generated server configuration (gon the operator) shownsc's output.
nsc-tui takes nsc's own directory flags — -H, --config-dir,
--data-dir, --keystore-dir — plus -K for a signing key and -nsc for
the binary, and passes them to every command. Without them nsc's defaults
and environment ($XDG_CONFIG_HOME, $XDG_DATA_HOME, $NSC_HOME,
$NKEYS_PATH) apply, exactly as nsc env shows.
The store is read directly: the store directory, current operator and account
come from nsc env, the JWT files under <store>/<operator>/ are decoded
(and their signatures verified) with nats-io/jwt, and one nsc list keys -A -J tells which private keys are in the keystore. An entity whose JWT does
not decode or verify is still listed, marked, so a damaged store is visible.
To try it without touching your real environment:
D=/tmp/nsc-play
nsc --config-dir $D/config --data-dir $D/stores --keystore-dir $D/keys init --dir $D/stores --name demo
nsc-tui --config-dir $D/config --data-dir $D/stores --keystore-dir $D/keys(-H $D alone points --data-dir at $D, so nsc init would put the
operator under ~/.local/share/nats/nsc/stores instead; init --dir and the
three explicit flags keep everything under $D.)
nsc-tui -json prints one JSON document with the store directory, the current
operator and account, the operator names, and the operator JWT plus every
account and user: name, kind, public_key, issuer (and issuer_name),
issued_at, expires, not_before, expired, expires_soon (within 30
days), verified, error, key_stored, key_path, creds (users),
revoked (users), system_account (accounts), tags, jwt_path and the
decoded claims. -tree prints the same as an indented text listing, one
entity per line with its key, expiry and flags. Both exit 1 only when the store
cannot be loaded at all; nsc expirations --json remains the tool for expiry
reports.
go test ./... # everything, a few seconds
go test -v ./nsc # the nsc-backed tests, verbose (SKIP without nsc)
go vet ./...The "nsc-backed" tests build a throwaway store under t.TempDir() with the
real nsc, run the plans the editors would produce, reload and compare the
resulting JWTs with what the specs said — the same idea as facl's
kernel-backed tests. They skip themselves when nsc is not installed.




