Part 5: CLI Tool (packr-cli)
Previous: docs/04-registry-api.md
See also: docs/08-cli-auth.md (credential storage & login flows)
Quick Reference
packr-cli # Print full usage
packr-cli <command> --help # Per-command help (intercepts before dispatch)
All subcommands accept --help / -h at any position; the CLI prints the usage
block and exits 0 without dispatching to the handler.
packr-cli version # packr-cli v0.30.0 (731ce13, built 2026-09-13T12:00:00Z)
packr-cli --version # same; only as the first argument
An unstamped build (go build without -ldflags) reports dev (unknown, built unknown). make build-cli stamps it from VERSION, git rev-parse and the
clock via -X main.version=… -X main.commit=… -X main.date=…; the binary in the
registry image picks up the identity the Dockerfile stamps into
internal/version. packr-cli doctor prints the CLI's version next to the
server's, so a stale binary is visible.
One-shot usage
# Login once (creates ~/.packr/credentials.json)
packr-cli login # OAuth device flow (prompts in browser)
packr-cli login --password -- blueforge-ci "$PACKR_CI_PASSWORD" # CI-friendly, no browser
packr-cli login --scope @learnquest # Restrict to one npm scope
# Inspect who's logged in
packr-cli whoami
# Create a publish token (works against remote registry via stored JWT)
packr-cli token create --name dev-publish --role admin --scope @learnquest --expires 90
# Publish
PACKR_TOKEN=$(packr-cli token create --name dev --role admin --scope @learnquest --expires 90) \
pnpm publish --no-git-checks
# Is it published?
packr-cli view @learnquest/utils@1.2.0 >/dev/null && echo published
Overview
packr-cli is the official command-line client for the Packr registry. It can run in two modes:
| Mode | Trigger | Used when |
|---|---|---|
| Local DB | No PACKR_REGISTRY env var AND no --registry flag | Running server-side (inside the Fly.io container) — reads/writes the on-disk DB directly |
| Remote API | PACKR_REGISTRY env, --registry flag, OR single credential in ~/.packr/credentials.json | Running on a developer laptop — talks to the registry over HTTPS via stored JWT |
The remote-API mode is what 99% of users touch. It reuses the JWT stored by packr-cli login and routes calls through the admin API endpoints below.
Default registry: When
PACKR_REGISTRYis unset and no--registryis passed, the CLI defaults tohttp://localhost:4873. To target the live instance, setexport PACKR_REGISTRY=https://api.packr.blueforge.studioor pass--registry https://api.packr.blueforge.studioexplicitly.
Full Command Tree
packr-cli
├─ license License key management
│ ├─ activate <key> Activate license key (offline HMAC check)
│ └─ status Show current license status
│
├─ token Personal access tokens
│ ├─ create <name> Create a token (remote: POST /api/v1/admin/users/me/tokens)
│ │ --output-file <path> writes it instead of printing it
│ │ --to-forge-git <owner>/<repo>:<SECRET> puts it in a forge-git secret
│ ├─ list List your tokens, with expiry and last use (remote: GET .../tokens)
│ └─ revoke <id> Revoke a token (remote: DELETE .../tokens/:id)
│ falls back to the operator-secret route on 401/403
│
├─ user User accounts (admin operation)
│ └─ create <name> [--password <p>] Create a user (local DB only)
│
├─ login Authenticate and store credentials
│ ├─ (default) OAuth device flow (GitHub, Google, GitLab, OIDC)
│ └─ --password <p> Username/password fallback (CI-friendly)
│
├─ logout [--scope @x] Remove stored credential + clean .npmrc
├─ whoami Print authenticated user, role, scopes, expiry
├─ doctor Reachability, CLI + server versions, auth + credential expiry,
│ ecosystem endpoints, a real tarball download, local configs
├─ version | --version CLI version, build commit and build date
│
├─ view <pkg>[@<version>] Packument: latest, versions, publish times, dist-tags (GET /<pkg>)
│ ├─ versions | dist-tags | time Just that field
│ └─ --json The field(s) as JSON
│
├─ init [--scope @x] Create .packr.json and update .npmrc for this project
│
├─ config Local CLI config
│ └─ set credential-store {file|keychain|secret-service}
│
├─ rotate [--name N] Rotate stored token (creates new one with same role/scopes)
│ [--expires D] ... valid for D days instead of the server's 30-day default
│ [--to-forge-git <owner>/<repo>:<SECRET>] ... and put it in a forge-git secret
│
├─ owner Package ownership (remote API)
│ ├─ list <scope>/<pkg> GET .../owners
│ ├─ add <scope>/<pkg> <user> POST .../owners
│ ├─ remove <scope>/<pkg> <user> DELETE .../owners/:user_id
│ └─ transfer <scope>/<pkg> <user> POST .../transfer
│
├─ visibility Set package visibility
│ └─ <ecosystem>/[scope/]<name> <public|private> POST .../packages/visibility
│
├─ admin Super-admin operations
│ └─ reset-owners <scope>/<pkg> <owners...> POST .../owners/reset
│ (requires PACKR_SUPER_ADMIN_TOKEN
│ or --super-admin-token)
│
└─ agent-* AI-agent helpers
├─ agent-info <@scope/pkg> GET /agent/<pkg>
├─ agent-search <capability> GET /agent/search?q=...
└─ agent-deps <@scope/pkg> GET /agent/<pkg>/deps
Authentication
packr-cli login — OAuth device flow (default)
packr-cli login # uses default provider
packr-cli login --provider github
packr-cli login --provider google
packr-cli login --provider gitlab
packr-cli login --provider oidc # custom OIDC (Keycloak, Okta, Azure AD)
packr-cli login --scope @learnquest # restrict to one scope
packr-cli login --registry https://api.packr.blueforge.studio
What happens:
- POST
/-/v1/device/authorizewith chosen provider → registry returnsdevice_code,user_code,verification_uri. - CLI prints the URL + code, opens your default browser.
- CLI polls
/-/v1/device/tokenevery 5 s until approved. - JWT is stored in
~/.packr/credentials.json(mode 0600) — or in the OS keychain ifcredential-storeis set tokeychain/secret-service.
⚠️ Heads up: the device flow's
verification_urion the live BlueForge Studio instance points atpackr.blueforge.studio/cli/auth— the dashboard Next.js app deployed on Vercel as projectpackr-site. That project is currently errored (404), so the browser handoff fails withERR_SSL_PROTOCOL_ERRORon real registry. Workaround: use the password login below.
packr-cli login --password <pwd> — CI-friendly fallback
For registries in standalone or hybrid auth mode, you can log in with username + password — same call as the legacy login <name> <password> form, but with explicit flags. Works against POST /-/v1/login.
# Interactive: prompts for username + password
packr-cli login --password
# Non-interactive (CI):
packr-cli login --password "$PACKR_CI_PASSWORD" -- blueforge-ci
# Note the `--` separator: positional username goes AFTER flags.
# Restrict to a scope and update .npmrc:
packr-cli login --password "$PACKR_CI_PASSWORD" --scope @learnquest -- blueforge-ci
Caveat: Username parsing takes the first non-flag positional arg. Use
--to clearly separate flags from positional, since flags after the username would otherwise be interpreted as part of the password.
packr-cli logout [--scope @x]
Removes stored credential and scrubs ~/.npmrc lines for that scope/registry.
packr-cli whoami
$ packr-cli whoami
Logged in as kristianmandrup (admin) via password — expires 2026-10-08
Returns non-zero exit code if not logged in (script-friendly).
Project Setup: packr-cli init
Writes .packr.json (registry URL + scope) and updates the project-level .npmrc to point at the registry with //host/:_authToken=${PACKR_TOKEN} (NOT the actual JWT — resolves from env at install/publish time).
packr-cli init --scope @learnquest
Generated .packr.json:
{
"registry": "https://api.packr.blueforge.studio",
"scope": "@learnquest"
}
Generated .npmrc:
@learnquest:registry=https://api.packr.blueforge.studio
//api.packr.blueforge.studio/:_authToken=${PACKR_TOKEN}
Notes:
- Only lines containing
<scope>:registry=or//<host>/:_authToken=get replaced; other.npmrcentries are preserved. - The CLI writes
${PACKR_TOKEN}into the project.npmrc(NOT the literal token) so the file can be committed safely. The token value comes fromPACKR_TOKENenv var at install/publish time. ~/.npmrc(user-level, written onlogin --scope) DOES contain the literal token — keep that file mode 0600 and out of git.
Credential Store
The CLI saves tokens in one of three places:
packr-cli config set credential-store file # ~/.packr/credentials.json (default)
packr-cli config set credential-store keychain # macOS Keychain / Windows Credential Manager
packr-cli config set credential-store secret-service # Linux libsecret (GNOME Keyring, KWallet)
The active backend is stored in ~/.packr/config.json. Setting re-applies on next login / rotate. See docs/08-cli-auth.md for full credential-file schema and keychain instructions.
Token Management
packr-cli token create <name>
Creates a personal access token bound to the currently authenticated user.
Flags:
| Flag | Description |
|---|---|
--role | Preset role: ci-readonly, ci-publish, maintainer, admin. Defaults to admin if omitted. |
--scope | Restrict to one npm scope (repeatable for multiple). Empty = all scopes. |
--expires | Override default 30-day expiry in days. |
--registry | Target registry URL (alternative: PACKR_REGISTRY env). |
--output-file | Write the token to this path (mode 0600) instead of stdout. |
--to-forge-git | <owner>/<repo>:<SECRET> — write the token into that forge-git Actions secret instead of stdout. Cannot be combined with --output-file. See below. |
When the registry URL resolves to the live server, the call goes through POST /api/v1/admin/users/me/tokens. The CLI prints the raw token to stdout (so it can be piped into gh secret set or PACKR_TOKEN=$(...)) and a one-line summary to stderr.
Keeping the token out of logs and agent context
--output-file writes the token to disk and prints nothing to stdout, so the
credential never lands in a terminal transcript, a CI log, or an AI agent's
context window. An agent can mint a credential and hand the path to whatever
consumes it without ever holding the value itself:
$ packr-cli token create ci-publish --role ci-publish --scope @blueforge-studio \
--output-file ~/.packr/ci-token
Token written to /Users/me/.packr/ci-token (mode 0600)
$ gh secret set PACKR_TOKEN < ~/.packr/ci-token && rm ~/.packr/ci-token
If the file cannot be written the command fails rather than falling back to printing — a fallback would defeat the flag exactly when secrecy was asked for. Because the token already exists server-side at that point, the error names its id so it can be revoked rather than left orphaned.
Straight into a forge-git secret
--to-forge-git removes the file too. The token goes from the registry's
response into a forge-git (Gitea) Actions secret inside one process: it is
never printed and never written to disk.
export FORGE_GIT_URL=https://forge-git.blueforge.studio
export FORGE_GIT_TOKEN=... # a forge-git access token with write access to the repo
$ packr-cli token create ci-publish --role ci-publish --scope @blueforge-studio --expires 90 \
--to-forge-git blueforge/blueforge-org:PACKR_TOKEN_PUBLISH
secret PACKR_TOKEN_PUBLISH set on blueforge/blueforge-org (token id 145, expires 2026-12-12)
It calls PUT {FORGE_GIT_URL}/api/v1/repos/{owner}/{repo}/actions/secrets/{SECRET}
with Authorization: token $FORGE_GIT_TOKEN. The credentials come from the
environment only — never a flag, which would land in shell history and ps.
Everything that can be checked is checked before a token is minted, and
fails with exit 2 (or 1 for missing FORGE_GIT_* variables): the
owner/repo:SECRET shape, owner and repo characters, and the secret name
(A–Z, 0–9, _, not starting with a digit or with the reserved GITEA_ /
GITHUB_ prefixes).
If the secret PUT fails after the token was minted, the CLI revokes that token
at once and says so, and exits 1 — an unused live credential is not left
behind. If the revoke fails as well, it prints the id and the exact
packr-cli token revoke <id> to run.
packr-cli rotatenever prints a token at all: it writes the new value straight into~/.packr/credentials.jsonand~/.npmrc. Prefer it overtoken createwhen replacing a credential that is already in use.
Rotating a credential without ever seeing it
rotate keeps the token off stdout by storing it, which is what makes it safe
to run in CI or under an AI agent. But a rotation has a second half -- pushing
the new value into CI secrets -- and that used to mean reading the token back
out, putting it straight into the log or context the storage was protecting it
from. rotate --output-file closes that gap.
Authentication is the other half, and normally there is no password to
supply at all. packr-cli login runs the OAuth device flow: it prints a URL
and a short user code, a human approves in a browser, and the CLI writes
credentials.json itself. Nothing secret is ever displayed, so this is safe to
run under an agent -- the code on screen is not a credential.
packr-cli login --provider github --scope @acme # approve in the browser
Reach for PACKR_CREDENTIALS_FILE only where no human can approve a browser
prompt -- an unattended CI job. It points at a 0600 file holding the username
and password on two lines, so the caller passes only the path; an env var
would hand the password to every child process, and on the command line it is
visible in ps.
# only for headless use; written once, by a human, in their own terminal
umask 077 && printf '%s\n%s\n' "$USERNAME" "$PASSWORD" > ~/.packr/creds
export PACKR_CREDENTIALS_FILE=~/.packr/creds
Either way, the rotation itself is the same and displays no secret:
f=$(mktemp); trap 'rm -f "$f"' EXIT
packr-cli rotate --scope @acme --name rotated-2026-09 --output-file "$f"
gh secret set PACKR_TOKEN --repo acme/some-repo < "$f"
packr-cli token list # find the OLD id
packr-cli token revoke <old> # only after the new one is in place
The credentials file is rejected if it is readable by group or others -- a
world-readable password file is barely better than an env var. Revoke last:
rotate deliberately leaves the old token active so there is no window where
publishing is broken.
$ packr-cli token create --name dev-publish --role admin --scope @learnquest --expires 90
eyJhbGciOiJIUzI1NiIs...
Created token id=42 name="dev-publish" role=admin scopes=[@learnquest]
Response shape (from internal/npm/admin_tokens.go):
{
"id": 42,
"token": "eyJhbGciOiJIUzI1NiIs...",
"name": "dev-publish",
"role": "admin",
"permissions": ["read","publish","unpublish","admin"],
"scopes": ["@learnquest"]
}
⚠️ First-publish gotcha: the API returns
E403onPUT /@scope/pkgwhen publishing a brand-new package name unless the token has theadminpermission.ci-publishandmaintainertokens can update existing packages but not create new ones. For local CLI publish of new namespaces, mint with--role admin. For CI pipelines (where the package already exists in the registry),ci-publishis fine.
packr-cli token list
ID Name Role Scopes Created Expires Last used
42 dev-publish admin [@learnquest] 2026-07-10 unknown 2026-09-12
38 rotated-password admin [@learnquest] 2026-06-30 unknown never
Expires shows EXPIRED for a token past its date. It reads expires_at from
the list response, which the server does not send today — the mint path never
persists an expiry, it lives only inside the JWT — so the column says unknown
rather than guessing from the creation date (a --expires 90 token would look
dead at day 30). To see a token's real expiry, run packr-cli doctor with it as
PACKR_TOKEN; it reads the JWT's exp claim.
packr-cli token revoke <id>
Deletes the token. Idempotent — re-revoking a deleted token returns 404.
Flags:
| Flag | Description |
|---|---|
--super-admin-token | Operator secret, for revoking a token you cannot authenticate as (also read from PACKR_SUPER_ADMIN_TOKEN). |
--reason | Recorded in the registry's audit log. |
A 401 or 403 from the ordinary route usually does not mean you lack authority —
it means your own credential can no longer authenticate. A token issued before the
last JWT_SECRET rotation still publishes (that path is a hash lookup) but fails
every admin route (those verify the JWT signature), so it cannot revoke itself. The
CLI recognises this and says so:
Your own credential could not authenticate against the admin API.
If it predates the last signing-key rotation it will still publish but
cannot revoke. Retry with the operator secret:
packr-cli token revoke 19 --super-admin-token <token>
With the secret supplied, it posts {"id": N} to /api/v1/admin/tokens/revoke,
which deletes the row. That is the only form that retires a token predating jti
claims: there is no jti to denylist, and deleting the row is what stops the hash
lookup it authenticates on. See Part 7 — Token Lifecycle.
packr-cli rotate
Rotates the stored token: POST to /api/v1/admin/users/me/tokens with the same role/scopes, swaps it in ~/.packr/credentials.json and writes the new value into ~/.npmrc if a scope is set. The old token remains active until you revoke it explicitly (run packr-cli token list → token revoke <old-id>).
If the POST fails with 401/403 (e.g., the stored token is no longer authorized for the admin endpoint), rotate falls back to password-based re-auth — but only if PACKR_USERNAME and PACKR_PASSWORD are set in the env. Otherwise it errors out with instructions to run packr-cli login.
packr-cli rotate --name "rotated-2026-07" # optional token name
packr-cli rotate --scope @learnquest # restrict + update .npmrc
packr-cli rotate --expires 90 # valid for 90 days, not the default 30
packr-cli rotate --expires 90 --to-forge-git blueforge/blueforge-org:PACKR_TOKEN_PUBLISH
--expires takes a positive whole number of days and is validated like
token create's. The expiry recorded in credentials.json is read from the new
token's exp claim, so the "expires in N days" warning fires at the right time.
(The password re-login fallback does not honour --expires and says so.)
--to-forge-git works as it does for token create, with one ordering rule:
the secret is set before the new token replaces the stored login. If the
PUT fails, the new token is revoked and nothing on the machine changes — the old
login stays stored and working. On success the new token is stored as every
rotation stores it (credential store and ~/.npmrc) and put in the secret; it
is never printed and never written to any other file.
Unknown flags and flags missing their value are refused with exit 2; they
used to be skipped silently.
Is it published? packr-cli view
packr-cli view <pkg>[@<version>] [versions|dist-tags|time] [--json] [--registry <r>]
Reads the npm packument — GET {registry}/{pkg}, a scoped name escaped as one
segment (@scope%2fname) the way npm sends it — from the registry you are
logged in to (--registry, PACKR_REGISTRY, or the single stored credential),
with your credential when there is one, so private packages answer too.
$ packr-cli view @blueforge-studio/service-payments
@blueforge-studio/service-payments
latest 0.4.0
modified 2026-07-23T21:36:07Z
versions (4):
0.1.0
0.2.0
0.3.0
0.4.0 [latest]
(publish times: not recorded per version by this registry)
dist-tags:
latest: 0.4.0
$ packr-cli view @blueforge-studio/service-payments@0.5.0
@blueforge-studio/service-payments@0.5.0 is not published on https://api.packr.blueforge.studio — latest is 0.4.0
published versions (4): 0.1.0, 0.2.0, 0.3.0, 0.4.0
$ echo $?
1
- Default: name,
latest,created/modified, every version in semver order with its publish time and tags, and the dist-tags. Per-version times come from the packument'stimemap; packr records onlymodifiedthere today, so the listing says so instead of printing a column of blanks. <pkg>@<version>: that version's publish time, tags, tarball and integrity, and its deprecation message if any. A dist-tag works in place of a version (@latest). Exits 1 when the version is not published, naminglatestand the published versions — so a release script can gate onpackr-cli view pkg@1.2.3 >/dev/null.versions/dist-tags/time: just that field, one entry per line.--json:versionsis an array in semver order;dist-tagsandtimeare the raw objects; with no field, an object ofname,dist-tags,versionsandtime; with@<version>, that version's raw manifest.
Exit codes: 0 found, 1 package or version absent (404), private to you
(401/403), or any other failed request, 2 usage.
Package Ownership (packr-cli owner)
All owner commands hit the admin API and require an authenticated session.
# List owners of @blueforge-studio/service-factory
packr-cli owner list @blueforge-studio/service-factory
# Add a co-owner (by username or numeric user_id)
packr-cli owner add @blueforge-studio/service-factory alice
# Remove a co-owner (resolves username → user_id via list)
packr-cli owner remove @blueforge-studio/service-factory alice
# Transfer ownership (you are removed, target becomes sole owner)
packr-cli owner transfer @blueforge-studio/service-factory bob
Notes:
- Removing the last owner returns
400 cannot_remove_last_owner— add the new owner first. - All mutations are audit-logged (
GET /api/v1/admin/audit).
packr-cli visibility <ecosystem>/[scope/]<name> <public|private>
Set a package's visibility. Requires a stored login JWT (run packr-cli login).
# npm scoped package
packr-cli visibility npm/@acme/private-pkg public
# NuGet (no scope — global id)
packr-cli visibility nuget/GameVault.SDK private
Supply chain: packr-cli verify
packr-cli verify <ecosystem>/[scope/]<name>@<version> [--artifact <path>] [--key <keyring>]
Checks what the registry recorded about a version against the bytes you hold.
packr-cli verify cargo/widget@1.0.0 --artifact ./widget-1.0.0.crate
cargo/widget@1.0.0
integrity ok sha256 matches what the registry stored at publish
signature ok gpg accepted the pgp signature
provenance ok published by alice
Three checks, reported separately because they answer different questions:
| what it means | |
|---|---|
integrity | the bytes hash to the digest the registry computed at publish |
signature | your gpg or cosign accepted the stored signature over those bytes |
provenance | who the registry observed publishing it — plus, labelled as unverified, whatever the client claimed about its build |
The trust root is yours. The registry stores signatures and never asserts
they are valid, because it has no way to know which keys may sign for which
package. This command runs the verifier on your machine against the keys you
already trust; --key points gpg at a specific keyring rather than your
default one.
Exit codes are meant for CI:
| code | meaning |
|---|---|
0 | something was checked and nothing contradicted the record |
1 | a check failed — the bytes or the signature do not match |
2 | nothing could be checked at all |
2 exists so a pipeline step cannot pass by verifying a version that has
nothing to verify. For the same reason, a verifier that is not installed
reports as skipped rather than as a bad signature, and an unsigned version
reports as absent rather than failed — most versions are unsigned, and an
error there would train you to ignore the command.
Without --artifact it reports what material exists and stops, rather than
implying a check it did not run.
Super-Admin Operations (packr-cli admin)
packr-cli admin reset-owners <scope>/<pkg> <owner1[,owner2,...]>
Force-replaces the entire owner list. Used for emergency recovery when the existing owners are unreachable.
export PACKR_SUPER_ADMIN_TOKEN=<your-token>
packr-cli admin reset-owners @blueforge-studio/abandoned-pkg kmandrup,bob
# Or pass via flag:
packr-cli admin reset-owners @blueforge-studio/abandoned-pkg kmandrup \
--super-admin-token "$PACKR_SUPER_ADMIN_TOKEN"
Underlying endpoint: POST /api/v1/admin/packages/:scope/:name/owners/reset
Auth: X-Super-Admin-Token header (set on the server as SUPER_ADMIN_TOKEN).
The CLI also attaches the user JWT (if any) so audit logs capture who triggered the reset.
User Management
packr-cli user create <name> [--password <pwd>]
Creates a user. Local-DB only — does NOT route through the admin API. Use this for bootstrapping a fresh registry or for self-hosted instances. The dashboard's "create user" flow uses POST /api/v1/admin/users instead.
packr-cli user create blueforge-ci --password "long-random-string"
Password is bcrypt-hashed at default cost and stored in the metadata DB.
License
packr-cli license activate <key> — verifies an offline HMAC signature.
packr-cli license status — shows current tier, package limit, expiry.
Not required for normal use; only relevant to the hosted service.
Agent Endpoints
Three convenience commands that hit the AI-agent endpoints:
packr-cli agent-info @learnquest/utils # Package metadata + quality score
packr-cli agent-search "form-validation" # Capability search
packr-cli agent-deps @learnquest/utils # Dependency graph
Equivalent direct URLs:
GET /agent/<pkg>
GET /agent/search?q=<capability>
GET /agent/<pkg>/deps
Useful for code-search agents that need an at-a-glance summary of a package.
Examples
Publish a brand-new package (laptop, brand-new namespace)
# 1. Login once — use password if the dashboard is unreachable
packr-cli login --password "$PACKR_CI_PASSWORD" --scope @learnquest -- blueforge-ci
# 2. Mint an admin token (admin role bypasses first-publish E403)
TOKEN=$(packr-cli token create --name dev-publish \
--role admin --scope @learnquest --expires 90)
# 3. Build + publish
cd libs/screens/household
pnpm build
PACKR_TOKEN=$TOKEN pnpm publish --no-git-checks --tag latest
# 4. Verify
curl -s https://api.packr.blueforge.studio/@learnquest/screen-household | jq '.["dist-tags"]'
Rotate an expired CI token
# 1. Have credentials in ~/.packr/credentials.json (run 'packr-cli login' if not)
export PACKR_REGISTRY=https://api.packr.blueforge.studio
# 2. Rotate
packr-cli rotate --name "ci-publish-2026-q3" --scope @learnquest
# 3. Upload to GitHub
gh secret set PACKR_TOKEN --org learn-quest --body "$PACKR_TOKEN"
# 4. Revoke old
packr-cli token list
packr-cli token revoke <old-id>
Recover an abandoned package's ownership
export PACKR_SUPER_ADMIN_TOKEN=$(cat ~/.packr-super-admin)
packr-cli admin reset-owners @blueforge-studio/abandoned-pkg new-maintainer
# Verify
packr-cli owner list @blueforge-studio/abandoned-pkg
Environment Variables
| Var | Default | Purpose |
|---|---|---|
PACKR_REGISTRY | http://localhost:4873 | Target registry URL (overridden by --registry) |
PACKR_CREDENTIAL_STORE | file | One of file, keychain, secret-service |
PACKR_SUPER_ADMIN_TOKEN | — | Used by admin reset-owners when --super-admin-token not passed |
PACKR_USERNAME / PACKR_PASSWORD | — | Used by rotate fallback re-auth |
PACKR_TOKEN | — | Token to use instead of the stored login; doctor warns when its JWT exp is within 7 days |
FORGE_GIT_URL | — | forge-git base URL for --to-forge-git (e.g. https://forge-git.blueforge.studio) |
FORGE_GIT_TOKEN | — | forge-git access token with write access to the target repo, for --to-forge-git |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Error: not logged in to <url> | No JWT in ~/.packr/credentials.json | Run packr-cli login |
Error: not logged in to <url> — run 'packr-cli login' from token create | Same | Same |
packr-cli --help output differs from these docs | Old binary | packr-cli version; doctor shows it next to the server's version |
Token expires warning after cli startup, or ! credential expiry in doctor | <7 days left | Run packr-cli rotate --expires <days> (add --to-forge-git for a CI secret) |
E403 Forbidden on publish of NEW package | Token role lacks admin permission | packr-cli token create --role admin --scope @scope |
E403 on existing package | Wrong scope on token | Re-create with matching --scope |
Device flow hangs at Opening browser... | Dashboard at packr.blueforge.studio is down (Vercel project errored) | Use packr-cli login --password fallback |
keychain get: <err> on macOS | Non-interactive SSH session | packr-cli config set credential-store file |
Cannot autolaunch D-Bus without X11 on Linux | Headless / no desktop session | packr-cli config set credential-store file |
Error: maximum of 20 tokens per user | Per-user cap reached | Revoke unused tokens first |
Next: docs/history/06-dockerfile.md — Dockerfile