Skip to content

Help · CLI · Credentials

CLI Credentials Guide

Where the jsm CLI stores credentials, how to silence the passphrase prompt, and how to wire up the platform-native secret store on macOS, Windows, and Linux.

CLI Credentials: Keyring, Passphrase, and Headless Setups

When you sign in with jsm login, the CLI completes Google OAuth in your browser and then needs somewhere durable to put the resulting access and refresh tokens. This page covers where those tokens land, what to do when the CLI prompts you for a passphrase, and how to wire up the platform-native secret store on each OS so you stop getting prompted at all.

Why the CLI cares about credential storage

The refresh token the CLI receives after jsm login is a bearer credential. Anyone who reads it off your disk can use it to download premium content as you until it expires. The CLI deliberately avoids ever writing it in plaintext: the default path is to hand it to the operating system's secret store (Keychain on macOS, Credential Manager on Windows, GNOME Keyring or KWallet on Linux), and the fallback path is an AES-256-GCM-encrypted file, credentials.enc, with a passphrase you set on first save. The file sits in jsm's config directory: ~/Library/Application Support/jsm/ on macOS, ~/.config/jsm/ on Linux, %APPDATA%\jsm\ on Windows (jsm config show prints it).

You can tell which path you're on by running jsm doctor. The Credential Storage check reports one of:

  • OS keyring — you're on the recommended default. No prompts.
  • Encrypted file (interactive passphrase) — the keyring wasn't usable, so the CLI fell back to the file and prompts for a passphrase on each load. The details line explains why the keyring isn't available (volatile backend, not installed, daemon not running, etc.).
  • Encrypted file (env passphrase) — same file fallback, but you've opted into reading the passphrase from environment variables so the CLI doesn't prompt.
  • No credentials stored; jsm login will save them in the OS keyring (or in the encrypted file) — fresh machine; run jsm login or jsm auth set-key. The line under it names the keyring that will be used, or says why it can't be (since jsm 0.3.29).

macOS — Keychain

Keychain works out of the box in a normal desktop terminal. The first time the CLI writes credentials, macOS may prompt once to grant jsm access. After that, no prompts.

An SSH session cannot display or approve that macOS authorization UI. If a first-time jsm login --remote receives Keychain's exact "User interaction is not allowed" status, jsm uses the AES-256-GCM encrypted credential file for that login instead and asks you to choose a local passphrase. This fallback is deliberately narrow: other Keychain errors still stop the login, and jsm will not create file credentials when it cannot prove that the Keychain and fallback file were both empty.

For a headless SSH invocation with no TTY, opt into an environment-provided passphrase before logging in:

export JSM_ALLOW_ENV_PASSPHRASE=1
export JSM_CREDENTIALS_PASSPHRASE='choose-a-long-unique-secret'
jsm login --remote

If you ever see a passphrase prompt on macOS, that means Keychain access was unavailable for that session, or was denied or revoked. For the SSH case, the prompt is expected and protects the encrypted fallback. For a desktop session, open Keychain Access, search for jsm, and restore access before logging in again.

Windows — Credential Manager

Credential Manager works out of the box. Stored credentials live under Control Panel → User Accounts → Credential Manager → Windows Credentials with the target name jsm. Deleting that entry and re-running jsm login re-creates it.

Linux — GNOME Keyring (Fedora, Ubuntu, Debian, Arch)

GNOME Keyring is the most common Linux backend. It's a daemon (gnome-keyring-daemon) that exposes the Secret Service D-Bus API the CLI talks to via the secret-service Rust binding (pure-Rust zbus, so the static release binary needs no libdbus). If you're in a normal desktop session, the daemon starts automatically when you log in and the CLI uses it without you doing anything.

This needs jsm 0.3.29 or newer. Releases up to 0.3.28 were built without the Secret Service backend, so on Linux they always used the encrypted file, even with GNOME Keyring running and unlocked (jsm doctor blamed a "volatile (UntilReboot) keyring backend"). Run jsm update, then jsm logout && jsm login to move existing file credentials into the keyring.

Before it reads or writes credentials, the CLI checks that a Secret Service is on the session bus and that its default keyring exists. An unlocked keyring is used directly. A locked one is used only from a desktop session, where GNOME asks you to unlock it; elsewhere (SSH, cron, the scheduled jsm auto-update run) the CLI uses the encrypted file rather than pop up an unlock prompt.

If jsm doctor reports the encrypted-file fallback on Linux, install GNOME Keyring and confirm the daemon is running:

# Fedora / RHEL
sudo dnf install gnome-keyring libsecret

# Ubuntu / Debian
sudo apt install gnome-keyring libsecret-1-0

# Arch / Manjaro
sudo pacman -S gnome-keyring libsecret

In a desktop session, the daemon should already be running. To verify:

pgrep -af gnome-keyring-daemon
busctl --user list | grep -i secret

For a non-GNOME desktop or an SSH session that needs the keyring (rare but sometimes useful), start the daemon manually and export the variables it prints:

eval "$(gnome-keyring-daemon --start --components=secrets)"
export GNOME_KEYRING_CONTROL SSH_AUTH_SOCK

Once the daemon is reachable, run jsm logout && jsm login so the CLI writes fresh credentials into the keyring instead of the file fallback. The passphrase prompts should disappear.

Linux — KWallet (KDE)

KWallet exposes the same Secret Service API used by GNOME Keyring, so the CLI talks to it through the same Rust binding without any extra configuration. Install with:

# Fedora / KDE Plasma
sudo dnf install kwalletmanager5
# Ubuntu / Kubuntu
sudo apt install kwalletmanager

After install, the wallet daemon (kwalletd5) starts with your session. The wallet itself is unlocked with your KDE login password.

Linux — no Secret Service (servers, SSH sessions)

When no Secret Service is reachable, the CLI uses the encrypted file and jsm doctor says why:

Credential Storage:    Encrypted file (interactive passphrase)
  OS keyring not used: no Secret Service keyring (GNOME Keyring, KWallet, KeePassXC) is reachable on the D-Bus session bus...

The kernel keyutils keyring is never used for new credentials: it is volatile (entries die at reboot). jsm 0.3.29 and newer only read it to move credentials an older jsm left there into the encrypted file.

Headless servers — env-passphrase opt-in

On a server with no desktop session (no GNOME Keyring, no KWallet), the keyring path isn't available at all. You have two options:

Option A — Use an API key instead of OAuth

API keys are stored under the same encryption, but they're per-user identity tokens you mint from the website and revoke from the website, which is a better fit for unattended use:

# Generate a key at /settings/api-keys, then paste it at the hidden prompt
jsm auth set-key
# or pipe it in (scripts):
printf '%s\n' "$JSM_KEY" | jsm auth set-key

Both keep the key out of shell history and the process list. Releases before these forms (v0.3.35 and older) need the key as an argument: jsm auth set-key jsm_xxxxxxxxxxxxxxxxxxxxxxxx.

This still triggers a passphrase prompt on first save, but you only set it once on the machine.

Option B — Read the passphrase from environment variables

Add both of these to your shell rc (~/.bashrc, ~/.zshrc, systemd unit Environment=, etc.) and the CLI will stop prompting:

export JSM_ALLOW_ENV_PASSPHRASE=1
export JSM_CREDENTIALS_PASSPHRASE='<your passphrase>'

Both variables are required. JSM_CREDENTIALS_PASSPHRASE alone is intentionally ignored — we want the env-var path to be an explicit opt-in, not a default that erodes the on-disk encryption guarantee silently. With both set, the CLI prints a one-line notice to stderr on first use so you can tell the env-var path is active:

jsm: using credential passphrase from JSM_CREDENTIALS_PASSPHRASE (JSM_ALLOW_ENV_PASSPHRASE is set).

To suppress that line in fully unattended scripts, also set JSM_QUIET=1.

If you genuinely want no keyring path at all (e.g. you're testing the file fallback or your distro ships a broken keyring backend), set JSM_DISABLE_KEYRING=1 so the CLI doesn't even try.

Troubleshooting

"Enter passphrase for CLI credentials:" on every command

Run jsm doctor. If Credential Storage shows "Encrypted file (interactive passphrase)", the keyring isn't being used. The details line tells you why — that's your starting point. Most often it's one of:

  • GNOME Keyring not installed → install it (commands above).
  • GNOME Keyring installed but daemon not running → check pgrep -af gnome-keyring-daemon; if empty, you're not in a desktop session.
  • JSM_DISABLE_KEYRING=1 was set → unset it and re-run.
  • jsm 0.3.28 or older on Linux → those releases could not use the Secret Service at all; run jsm update.
  • "the Secret Service default keyring is locked and no desktop session is available to unlock it" → unlock the keyring (log in to your desktop) or use the env-passphrase setup below.

"Cannot prompt for passphrase in non-interactive environment"

You're running jsm over SSH or under a process that has no TTY, and the keyring is unavailable. Set JSM_ALLOW_ENV_PASSPHRASE=1 and JSM_CREDENTIALS_PASSPHRASE as above, or switch to API-key auth.

Wiped Keychain / Credential Manager and now things are weird

jsm logout then jsm login from a regular terminal session. The CLI will re-acquire credentials and store them into whichever backend is healthy.

I want to start over

jsm logout
rm -f ~/.config/jsm/credentials.enc                       # Linux
rm -f ~/Library/Application\ Support/jsm/credentials.enc  # macOS
jsm login

jsm logout clears the keyring entries; the rm clears the encrypted file. jsm login then starts a fresh OAuth flow.

See also

  • jsm doctor — top-line health check, including Credential Storage
  • /help/cli — CLI installation guide
  • /settings/api-keys — generate and revoke API keys