Lcr.Keys.Cli 0.1.0

dotnet tool install --global Lcr.Keys.Cli --version 0.1.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Lcr.Keys.Cli --version 0.1.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Lcr.Keys.Cli&version=0.1.0
                    
nuke :add-package Lcr.Keys.Cli --version 0.1.0
                    

lcr-keys

A command-line tool to manage LLM provider API keys stored in the LLM Cost Router. Wraps the /v1/credentials HTTP API so customers can add, list, inspect, and revoke their OpenAI / Anthropic / DeepSeek keys without leaving the terminal.

  • No plaintext key ever leaves the tool in stdout, logs, or files. The router stores keys under envelope encryption; this CLI only passes them through once during add and never displays them again.
  • Single-binary install via dotnet tool install --global.
  • Scriptable: every command supports --json for clean machine output, and exit codes follow Unix conventions.

Table of contents


Requirements

  • .NET 8 runtime (the SDK is not required; dotnet --info should show at least one 8.0.x runtime).
  • Network access to your LCR deployment (LCR_API_URL).
  • A valid LCR bearer token (LCR_API_KEY) issued to your customer account.

Install

dotnet tool install --global Lcr.Keys.Cli

The command name installed on your PATH is lcr-keys (not Lcr.Keys.Cli). Confirm:

lcr-keys --version
lcr-keys --help

If lcr-keys is not found, add the dotnet tools directory to your PATH:

OS Add to PATH
macOS / Linux export PATH="$PATH:$HOME/.dotnet/tools" (add to ~/.zshrc / ~/.bashrc)
Windows (PowerShell) $env:Path += ";$env:USERPROFILE\.dotnet\tools"

Quick start

# 1. Tell the CLI where your router lives and who you are.
export LCR_API_URL=https://api.your-router.com
export LCR_API_KEY=lcr_live_xxxxxxxxxxxxxxxxxxxx

# 2. Store an OpenAI key (you'll be prompted; input is hidden).
lcr-keys add --provider openai --label prod-2026Q2

# 3. Verify it landed.
lcr-keys list

Expected output of list:

ID                                  PROVIDER    LABEL                     PREFIX     LAST4   STATUS    CREATED
------------------------------------------------------------------------------------------------------------------------
8f3c1e2a9b6d4f0e8c7a2b5d1f9e3c4a    openai      prod-2026Q2               sk-proj    a9f1    active    2026-05-20 18:00:00Z

Configuration

The CLI reads two values per invocation. Set them once via environment variables for the common case; flags override per-call.

Variable Purpose Flag override
LCR_API_URL Base URL of your LCR deployment, e.g. https://api.your-router.com --url
LCR_API_KEY LCR bearer token (issued when your account was provisioned) --token

Resolution order (highest priority first):

  1. --url / --token flag on the command line
  2. LCR_API_URL / LCR_API_KEY environment variables
  3. Error — the tool exits with a clear message naming the missing piece

The bearer token authenticates you to LCR. It is not the same thing as an OpenAI / Anthropic / DeepSeek provider key — those go in as the --key argument to add.

Commands

All commands share the global flags. Use lcr-keys <command> --help for the full per-command reference.

add — store a new key

Registers a provider API key with the router. The plaintext key never leaves your machine in cleartext after this call: the router encrypts it under your account's data key (envelope encryption, AES-256-GCM) before persisting.

lcr-keys add --provider <openai|anthropic|deepseek> \
             --label <human-friendly-name> \
             [--key <plaintext-key> | (prompted)] \
             [--expires-at <YYYY-MM-DD>]
Hidden-input prompt

Omit --key and the tool will prompt without echoing:

$ lcr-keys add --provider anthropic --label staging
Provider API key (input hidden):
Explicit key (CI / scripting)

Suitable when the key already lives in a CI secret store and you have a way to inject it:

lcr-keys add --provider openai --label ci-runner \
             --key "$OPENAI_API_KEY"

Beware of shell history: passing --key sk-... literally records it in ~/.bash_history. Prefer the prompt form or pull the key from an env var as shown above.

Output

Human-readable (default):

id:               8f3c1e2a9b6d4f0e8c7a2b5d1f9e3c4a
provider:         openai
label:            prod-2026Q2
prefix...last4:   sk-proj...a9f1
fingerprint:      e5e280f3c1cc…
created:          2026-05-20 18:00:00Z
last used:        (never)
last validated:   (never)

JSON (--json):

{
  "id": "8f3c1e2a9b6d4f0e8c7a2b5d1f9e3c4a",
  "provider": "openai",
  "label": "prod-2026Q2",
  "prefix": "sk-proj",
  "last4": "a9f1",
  "fingerprint_hex": "e5e280f3c1cc5539940bfde9e7fd9edb…",
  "last_used_at": null,
  "last_validated_at": null,
  "last_validation_error": null,
  "created_at": "2026-05-20T18:00:00Z",
  "revoked_at": null
}
Conflict on duplicate

Re-submitting the same plaintext key (same SHA-256 fingerprint) returns HTTP 409 and the CLI exits with a non-zero status:

HTTP 409 Conflict: {"message":"credential already exists","id":"8f3c1e2a..."}

This is intentional — the router never accepts duplicates so revocation remains unambiguous.

list — show your stored keys

Returns every credential owned by your customer account (cross-tenant isolation is enforced server-side).

lcr-keys list                         # all active credentials
lcr-keys list --provider openai       # filter by provider
lcr-keys list --include-revoked       # include soft-deleted rows
lcr-keys ls                           # alias for list
Output

Default table form:

ID                                  PROVIDER    LABEL                     PREFIX     LAST4   STATUS    CREATED
------------------------------------------------------------------------------------------------------------------------
8f3c1e2a9b6d4f0e8c7a2b5d1f9e3c4a    openai      prod-2026Q2               sk-proj    a9f1    active    2026-05-20 18:00:00Z
1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a    anthropic   staging                   sk-ant     8b2c    active    2026-05-20 17:42:11Z

JSON for scripting:

lcr-keys list --json | jq '.[] | select(.provider=="openai") | .id'

get — inspect one key

lcr-keys get 8f3c1e2a9b6d4f0e8c7a2b5d1f9e3c4a
lcr-keys get 8f3c1e2a9b6d4f0e8c7a2b5d1f9e3c4a --json

Returns the same per-credential view as add. If the id is not yours (or does not exist), the server returns 404 to avoid leaking the existence of other customers' credentials.

delete — revoke a key

Soft-revokes a credential. The row is preserved (audit trail), but excluded from routing lookups going forward, so the next LLM call will fail to find a key for that provider unless another active credential exists.

lcr-keys delete 8f3c1e2a9b6d4f0e8c7a2b5d1f9e3c4a
# Soft-revoke credential 8f3c1e2a9b6d4f0e8c7a2b5d1f9e3c4a? [y/N] y
# Revoked credential 8f3c1e2a9b6d4f0e8c7a2b5d1f9e3c4a.

lcr-keys delete 8f3c1e2a... --yes      # skip the confirmation prompt
lcr-keys delete 8f3c1e2a... --yes --json
# {"deleted": "8f3c1e2a..."}

lcr-keys rm 8f3c1e2a...                # alias for delete

To undo a soft-revoke, contact your LCR operator — there's no client-side restore in v1.

Global flags

All commands accept these in addition to their own options.

Flag Description
--url <URL> Override LCR_API_URL for this invocation.
--token <TOKEN> Override LCR_API_KEY for this invocation.
--json Emit JSON instead of the human-readable table.
-? / -h / --help Show help for the current command.
--version Show the installed CLI version.

Per-command flags:

Command Flag Notes
add --provider, -p One of openai, anthropic, deepseek. Required.
add --label, -l Required. Used to recognize keys in list.
add --key, -k Optional. Hidden-input prompt if omitted.
add --expires-at ISO-8601 date reminder. Not server-enforced in v1.
list --provider, -p Filter to one provider.
list --include-revoked Show soft-deleted rows too.
delete --yes, -y Skip the interactive [y/N] confirmation.

JSON output shape

--json returns the raw router response. Useful fields:

{
  "id":                    "<32-hex-char uuid>",
  "provider":              "openai | anthropic | deepseek",
  "label":                 "string | null",
  "prefix":                "first 7 chars of the plaintext key, e.g. sk-proj",
  "last4":                 "last 4 chars of the plaintext key",
  "fingerprint_hex":       "SHA-256(plaintext) as hex; deterministic; not reversible",
  "last_used_at":          "ISO-8601 timestamp | null",
  "last_validated_at":     "ISO-8601 timestamp | null",
  "last_validation_error": "string | null",
  "created_at":            "ISO-8601 timestamp",
  "revoked_at":            "ISO-8601 timestamp | null (null = active)"
}

list returns a JSON array of the same shape.

Exit codes

Code Meaning
0 Success.
1 Generic failure — bad arguments, HTTP error, network failure, server validation error (4xx / 5xx). The message on stderr names the cause.
2 User aborted an interactive confirmation (e.g. answered n to delete).

Suitable for shell scripts:

if lcr-keys get "$id" --json > /tmp/cred.json; then
  echo "found"
else
  echo "missing or unauthorized" >&2
fi

Security notes

  • Plaintext keys never appear in tool output. Only prefix (first 7 chars), last4 (last 4), and a SHA-256 fingerprint_hex (one-way) are ever returned by the router.
  • No on-disk cache. This CLI does not write any state to disk — every invocation is a one-shot HTTP request.
  • Bearer token in env, not stored. LCR_API_KEY lives in your shell's environment for the session. Use your OS keychain or a password manager to inject it into your shell on demand if you're concerned about shell history.
  • HTTPS enforced by the LCR deployment. Connecting over plain HTTP will fail unless your operator explicitly allows it.
  • Cross-tenant safety. The server resolves customer_id from the bearer token only — you cannot pivot to view or modify another customer's keys, and unknown ids return 404 (not 403) to avoid leaking existence.

Troubleshooting

Symptom Likely cause Fix
LCR_API_URL not configured… Neither env var nor --url set export LCR_API_URL=https://… or pass --url.
LCR_API_KEY not configured… Neither env var nor --token set export LCR_API_KEY=lcr_live_… or pass --token.
HTTP 401 Unauthorized Bearer token is wrong or expired Verify with your operator; rotate via the LCR dashboard.
HTTP 403 Forbidden: Provider 'X' is not enabled for this customer Account is not yet authorized for provider X Have your operator add X to customers.enabled_providers.
HTTP 404 on get / delete Either id is wrong, or it belongs to another customer (return is identical for both) Re-list and copy the id verbatim.
HTTP 409 Conflict on add Same plaintext key already registered Use list to find the existing entry; revoke the old one if rotating.
lcr-keys: command not found ~/.dotnet/tools not on PATH See Install.
Hangs on add with no prompt The terminal is non-interactive (e.g. CI without a TTY) Provide the key with --key "$ENV_VAR" instead.

For verbose diagnostic output, set DOTNET_CLI_TELEMETRY_OPTOUT=1 and re-run with --help to ensure the parser sees your arguments.

Upgrade and uninstall

# Latest stable
dotnet tool update --global Lcr.Keys.Cli

# Pinned version
dotnet tool update --global Lcr.Keys.Cli --version 0.1.1

# List all installed dotnet tools
dotnet tool list --global

# Remove
dotnet tool uninstall --global Lcr.Keys.Cli

License and issues

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.1.0 91 5/20/2026