Token Manager
Store API tokens in named profiles and print them as shell exports.
Each profile can keep tokens as plaintext JSON, or encrypt them with
gpg or openssl.
The PyPI name is lupaxa-token-manager. The import path is
lupaxa.token_manager. The console scripts are tokenctl and
token-manager.
Install
pip install lupaxa-token-manager
Requires Python 3.10 or newer. Encryption modes need the gpg or
openssl binary on PATH. Plaintext profiles do not.
Quick Start
tokenctl profile init
tokenctl add --type github --name main --value 'ghp_xxx' --env-var GITHUB_TOKEN
tokenctl list
source <(tokenctl set --type github --name main --format export)
A child process cannot change the parent shell, so source the export
line. Status text is coloured on a terminal: green for success, red for
errors, yellow for a plaintext profile, and cyan for information.
tokenctl help and tokenctl help list colour the command and option
names. export, dotenv, value, JSON, and name lists stay plain so they can be
sourced or parsed. Set NO_COLOR to turn colour off. CLICOLOR_FORCE
turns colour on when the stream is a pipe. --profile goes before the
subcommand and defaults to default:
source <(tokenctl --profile ci set --type github --name main --format export)
Names are unique within a type, so main can exist for both github
and aws. Selecting by --name also needs --type. --id selects a
token on its own. --env-var defaults to TYPE_TOKEN (for example
GITHUB_TOKEN).
Command Reference
| Command | Purpose |
|---|---|
list |
Print tokens as a table, JSON, or names |
types |
Print the token types in the profile |
add |
Store a new token |
update |
Change type, name, value, or env var |
delete |
Remove a token |
show |
Print metadata, or the value if asked |
set |
Print export, dotenv, or the raw value |
profile init |
Create a profile and encryption mode |
profile update |
Write config.json for a legacy profile |
profile list |
List every profile directory |
profile show |
Print the profile name and encryption |
profile set-encryption |
Rewrite tokens in a new encryption mode |
migrate |
Copy or move tokens between profiles |
help |
Show help for every command, or one |
List and Show
tokenctl list
tokenctl list --type github --format names
tokenctl list --format json
tokenctl list --format json --reveal
tokenctl list --columns name,env_var,value --max-width 100
tokenctl types
tokenctl show --type github --name main
tokenctl show --type github --name main --reveal
The default list is a grouped text table. The table and JSON both hide
secret values unless you pass --reveal. show does the same.
--columns picks and orders columns. The names are name, env_var,
id, updated, created, value, and type. --max-width wraps the
table to that many characters.
Add, Update, and Delete
tokenctl add --type pypi --name publish
printf '%s' 'pypi-secret' | tokenctl add --type pypi --name publish --value -
tokenctl update --type github --name main --new-name prod --value 'ghp_new'
tokenctl delete --type pypi --name publish
Omit --value on add and the tool prompts without echoing. --value -
reads the secret from stdin, which keeps it out of shell history and the
process list. On update, omit --value to leave the secret unchanged,
or pass --value - to replace it from stdin. update --env-var '' clears
a stored variable name.
Set
tokenctl set --type github --name prod --format export
tokenctl set --type pypi --name publish --format dotenv
tokenctl set --type github --name prod --format value
export is the default. export and dotenv both single-quote the value.
The variable name must be a shell identifier.
Profiles
tokenctl profile list
tokenctl profile init
tokenctl profile update
tokenctl --profile ci profile init --encryption openssl
tokenctl --profile ci profile show
tokenctl --profile ci profile set-encryption gpg
profile list prints every profile directory. The columns are name,
whether it is initialised, and encryption. A directory with no config and
no token file is listed as not initialised, with a blank encryption. Do
not pass --profile to it. --format names prints one name per line.
--format json prints profile, initialised, and encryption.
profile init creates a profile. A profile is also available when it
already has a token file and no config.json: that is a legacy profile.
Commands do not create a directory for a name that has neither. A second
init stops and leaves an existing profile unchanged. profile update
writes config.json for a legacy profile and does not rewrite its token
file. tokens.json is recorded as none, tokens.json.gpg as gpg, and
tokens.json.enc as openssl. A profile that already has config.json
is left as it is. If more than one token file is present and
config.json is missing, the command stops rather than guessing a mode.
set-encryption writes and checks the new token file before it updates
config.json, then removes the previous file. Changing between gpg and
openssl asks for the current passphrase and the new one. If that write
fails, the existing profile is left as it is.
Migrate
tokenctl migrate --from-profile default --to-profile ci --dry-run
tokenctl migrate --from-profile default --to-profile ci --type github --name prod --move --overwrite
--dry-run prints the change and writes nothing. It still reads both
profiles, so an encrypted profile asks for its passphrase. The destination
must already exist. Each migrated token is stored with the destination
profile's encryption. A plaintext destination stores plaintext. A gpg or
openssl destination stores that mode. Without --move, the source
profile is left as it is. --move changes the source only after the
destination reads back every migrated token. --overwrite replaces a
destination token that already uses the same type and name, or the same id
with different contents. A conflict without --overwrite is still an
error during a dry run. Copying the same token again is allowed when the
destination copy is unchanged. A migrate asks once when one profile is
encrypted, and twice when both are. TOKENCTL_PASSPHRASE is used only
when one passphrase is required.
Storage and Encryption
Tokens live under $XDG_CONFIG_HOME/tokenctl (or ~/.config/tokenctl).
Each profile is profiles/<profile>/ with a config.json encryption
mode of none, gpg, or openssl.
| Mode | File | Tool |
|---|---|---|
none |
tokens.json |
Plaintext JSON, mode 0600 |
gpg |
tokens.json.gpg |
gpg --symmetric AES256 |
openssl |
tokens.json.enc |
openssl enc -aes-256-cbc -pbkdf2 |
Profile names use letters, digits, ., _, and -. Profile directories
are mode 0700. Set TOKENCTL_PASSPHRASE for a non-interactive shell
when the command needs one passphrase. A migrate between two encrypted
profiles ignores it and asks for each passphrase on a terminal. That
passphrase is passed to gpg or openssl on its own pipe. It is not
written into the environment. Each write uses a private temporary file,
is read back, and then replaces the live file. A profile lock is held for
the whole command. A token file that is missing, unreadable, or not a list
of tokens stops
the command and is left in place. config.json must name none, gpg,
or openssl; any other value stops the command.
Each token stores id, type, name, value, env_var, created_at,
and updated_at. Timestamps are UTC in YYYY-MM-DDTHH:MM:SSZ form.
Exit 0 is success. Exit 1 is a runtime failure (missing token,
conflict, or crypto error). Exit 2 is a usage error, such as --name
without --type.
Library
from lupaxa.token_manager import Store
store = Store(profile="default")
for token in store.load():
print(token.name, token.type)
Store(profile, config_dir=...) overrides the config root. Hold
store.exclusive() across a load, change, and save so another process
cannot write the profile in between.
Documentation
Site pages live in mkdocs/ and publish to
https://token-manager.thelupaxaproject.org/.
make init
make python-install-dev
make mkdocs-serve
Metadata
Release files for lupaxa-token-manager 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lupaxa_token_manager-0.2.0.tar.gz | 23.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lupaxa_token_manager-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 51.0 kB
Release files / lupaxa_token_manager-0.2.0.tar.gz
| Download URL | lupaxa_token_manager-0.2.0.tar.gz |
|---|---|
| Size | 23.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4be8b42ec058b6a344728a3a77a71e59b6509d7f71636695c8029d4a216c3123
|
|
BLAKE2b-256 checksum How to use checksums |
c1ae71b7b15ac4a5891ba63a018fa2d21e4d997c002a6703f5ac3dfd2d3d3afa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / lupaxa_token_manager-0.2.0-py3-none-any.whl
| Download URL | lupaxa_token_manager-0.2.0-py3-none-any.whl |
|---|---|
| Size | 28.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a43e8f549e7d65e55b6e309e79d8135b2d0c4f5695fad10bdf4dcb28d1d76a99
|
|
BLAKE2b-256 checksum How to use checksums |
80fd8b6eb1002b8d58013d073fdc48f7568572b3667bfcd07e22afd430407064
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency log