npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@better-age/cli

v0.0.2

Published

Release-facing `bage` CLI for Better Age.

Readme

@better-age/cli

Release-facing bage CLI for Better Age.

Responsibilities:

  • exact and guided command execution
  • interactive/headless terminal policy
  • hidden passphrase prompts
  • editor, secure viewer, and picker flows
  • human stderr presentation
  • clean machine stdout for load and identity export
  • age interop transparency for local key paths

Install / Build

Install workspace dependencies from the repository root:

pnpm install

Build the standalone CLI artifact:

pnpm -F @better-age/cli build

Build output:

packages/cli/dist/bage

The package bin points to that standalone bundled file.

Command List

bage create [payload]
bage edit [payload]
bage grant [payload] [identity-ref]
bage inspect [payload]
bage load [payload] --protocol-version=1
bage revoke [payload] [identity-ref]
bage update [payload]
bage view [payload]

bage identity export
bage identity forget [identity-ref]
bage identity import [identity-string] [--alias <alias>] [--trust-key-update]
bage identity keys [--current | --retired] [--path]
bage identity list
bage identity passphrase
bage identity pass
bage identity pw
bage identity rotate

bage setup [--name <display-name>]
bage interactive
bage i

Promptable operands are optional in the grammar. In an interactive terminal, missing payloads, identities, and setup names can be requested by the command flow. Protocol inputs stay strict; load requires --protocol-version=1.

Command grammar, help, and parser errors are owned by @effect/cli. Normal interactive prompts are backed by @inquirer/prompts; custom terminal handling is limited to the secure viewer and final stdout/stderr writing.

Examples

bage setup --name Isaac
bage create .env.prod.enc
bage edit .env.prod.enc
bage identity import 'better-age://identity/v1/...' --alias ops
bage identity import 'better-age://identity/v1/...' --trust-key-update
bage identity keys --current --path
bage grant .env.prod.enc ops
bage inspect .env.prod.enc
bage view .env.prod.enc
bage load .env.prod.enc --protocol-version=1

Interactive launcher:

bage interactive

Machine Output Policy

  • bage load --protocol-version=1 <payload> writes raw .env text to stdout.
  • bage identity export writes the public identity string to stdout.
  • bage identity keys --path writes local key file paths to stdout, one per line.
  • Prompts, warnings, errors, success messages, viewer UI, and editor UI are not written to stdout.
  • Human output goes to stderr and may use ANSI color when stderr is a TTY.
  • NO_COLOR disables ANSI color.

Runtime Behavior

  • Passphrases are prompted only in interactive terminals. Prompt input is hidden without echoing mask characters, so passphrase length is not displayed.
  • New passphrases must be at least 8 characters.
  • Headless payload reads fail fast because MVP has no passphrase injection mechanism.
  • Missing existing payload paths discover .env.enc and .env.*.enc in the current directory.
  • Payload creation suggests .env.enc; collisions offer Override, Change Name, and Cancel in interactive mode.
  • Grant merges payload recipients, known identities, aliases, and self into one picker. Self and already granted identities are visible but disabled.
  • Grant can import a pasted identity string before granting it.
  • Revoke lists only actual payload recipients and never offers arbitrary identity string entry.
  • identity forget lists known identities only and never changes payload files.
  • Guided identity import retries invalid strings and duplicate aliases.
  • Reimporting a known owner with a changed public key requires explicit trust. Interactive mode asks for confirmation with old/new fingerprints; exact mode requires --trust-key-update.
  • Recoverable prompt-loop feedback, such as wrong passphrases or invalid edited .env content, is printed immediately in interactive sessions instead of being buffered until the command returns to the menu.
  • edit resolves $VISUAL, then $EDITOR, then remembered editor preference, then interactive editor picker.
  • External editor mode necessarily writes plaintext to a private temp file while the editor runs. Better Age uses a private temp directory, a random temp file name, 0600 file permissions, and deletes the file afterward, but editor swap files, backups, crash recovery, plugins, or shell tooling can still leave residual plaintext outside Better Age's control.
  • Invalid edited .env content logs the validation failure, then offers Reopen Editor or Cancel while preserving the edited text for retry.
  • view uses an in-process secure viewer with keyboard scrolling and quit. Control characters are rendered visibly, not interpreted by the terminal.
  • interactive opens a setup-aware menu loop. It excludes load and interactive from menus.
  • identity keys lists local current and retired key files. --path switches to path-only stdout for shell interop.

Local Security And Durability

  • Home state and encrypted private key files are written under private filesystem permissions where supported.
  • Loose home/key permissions are repaired before use and surfaced as notices.
  • Private key refs are constrained to keys/<safe-name>.age.
  • Local private key files decrypt to an age-compatible identity-file plaintext: a Better Age metadata comment followed by one age identity line.
  • Current and retired key files stay flat under keys/<fingerprint>.age; Home State is the authority for current vs retired status.
  • Passphrase changes prepare and verify all replacement key blobs before committing. A transaction marker lets the next read recover if a crash interrupts the replace.
  • Payload writes encrypt and verify in memory, then write the encrypted wrapper to <payload>.tmp in the same directory and rename it over the target. Cleanup removes the temp file on failure when possible.
  • Human-rendered identity/display text is sanitized before terminal output.

Payload File Format

Payload files are readable wrapper files around untouched age armor:

# better-age encrypted env payload
# Docs: https://github.com/PaulSenon/better-age
# This file is safe to commit only if your policy allows encrypted secrets.
# Do not edit the armored block manually.

-----BEGIN BETTER AGE PAYLOAD-----
-----BEGIN AGE ENCRYPTED FILE-----
...
-----END AGE ENCRYPTED FILE-----
-----END BETTER AGE PAYLOAD-----

load, view, inspect, and write commands extract the inner age armor before decrypting. Missing, duplicated, malformed, or non-age-armored Better Age blocks fail clearly.

Age CLI Interop

For transparency, Better Age local key files are passphrase-encrypted age identity files. To find the current key path:

bage identity keys --current --path

Payload files intentionally keep the Better Age wrapper, so direct age -d .env.enc is not expected to work. Extract the inner age armor first:

sed -n '/^-----BEGIN AGE ENCRYPTED FILE-----$/,/^-----END AGE ENCRYPTED FILE-----$/p' .env.enc \
  | age -d -i "$(bage identity keys --current --path)"

Portable-ish awk variant:

awk '
  /^-----BEGIN AGE ENCRYPTED FILE-----$/ { on=1 }
  on { print }
  /^-----END AGE ENCRYPTED FILE-----$/ { on=0 }
' .env.enc | age -d -i "$(bage identity keys --current --path)"

[!NOTE] This is just for transparency purpose. Do not rely on this other than really specific adhoc needs. This will decrypt the raw payload with the better-age metadata. For real life scenario, use bage load --protocol-version=1 <payload> when raw .env stdout is needed.

Known Limitations

  • Docker and pseudo-TTY E2E are deferred.
  • Interactive terminal behavior is covered by unit/contract tests plus the repository manual QA checklist.
  • The MVP targets Unix-like terminals.
  • Headless secret injection is out of scope.

Development

pnpm -F @better-age/cli test
pnpm -F @better-age/cli check
pnpm -F @better-age/cli build

[!NOTE] @better-age/cli package is meant to be a standalone bin. So do not install any npm deps. Only devDependencies.

Manual QA:

Implementation plan source: