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

axvault

v1.13.1

Published

Remote credential storage server for axkit

Downloads

835

Readme

axvault

Remote credential storage server for a╳kit.

Prerequisites

  • Node.js 22.19+
  • PostgreSQL 14+ (local install or via Docker: docker run -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:17-alpine)
  • pnpm (for pnpm dlx axvault) or npx (for npx -y axvault)
  • jq for scripting against JSON API responses

If axvault is not installed globally, prefix commands with npx -y axvault (or pnpm dlx axvault).

Quick start

# Start PostgreSQL (if not already running)
docker run -d --name axvault-db -p 5432:5432 \
  -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=axvault \
  postgres:17-alpine

# Generate encryption key
umask 077
printf 'AXVAULT_ENCRYPTION_KEY=' > .env
openssl rand -base64 32 >> .env
printf '\nAXVAULT_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/axvault\n' >> .env
chmod 600 .env
set -a
. ./.env
set +a

# Start server (runs migrations and creates bootstrap admin key on first startup)
npx -y axvault serve

On first startup, axvault runs database migrations and creates a bootstrap admin API key with full access. The secret is printed to stderr — save it immediately, it cannot be retrieved later.

Keep the .env file and reuse the same encryption key between restarts to avoid losing access to existing credentials.

Architecture

axvault is a server-only tool. The CLI has a single command (serve) that starts the HTTP server. All key and credential management is done through the HTTP API.

On startup, the server:

  1. Runs database migrations (idempotent, so they are safe to re-run on every startup)
  2. Creates a bootstrap admin API key if no keys exist (serialized with an advisory lock, prints secret to stderr)
  3. Starts listening for HTTP requests

Migration files must remain replay-safe because axvault re-runs them on every startup instead of tracking applied versions. Use guarded SQL such as CREATE ... IF NOT EXISTS or ALTER ... ADD COLUMN IF NOT EXISTS. If a future schema change cannot be written safely that way, switch back to tracked migrations rather than adding a one-shot file to this runner.

Agent Rule

Add to your CLAUDE.md or AGENTS.md:

# Rule: `axvault` Usage

Run `npx -y axvault --help` to learn available options.

Use `axvault serve` to start the credential vault server. All key and credential
management is done via the HTTP API. On first startup, a bootstrap admin API key
is created and printed to stderr.

Configuration

Environment Variables

| Variable | Description | Default | | ---------------------------- | -------------------------------------------------------------------- | ------------------------------------- | | AXVAULT_PORT | Port to listen on | 3847 | | AXVAULT_HOST | Host to bind to | 127.0.0.1 | | AXVAULT_DATABASE_URL | PostgreSQL connection URL | postgresql://localhost:5432/axvault | | AXVAULT_ENCRYPTION_KEY | Encryption key (min 32 chars, required) | — | | AXVAULT_REFRESH_THRESHOLD | Refresh credentials expiring within this many seconds (0 to disable) | 3600 | | AXVAULT_REFRESH_TIMEOUT_MS | Timeout for refresh operations in milliseconds | 30000 | | AXVAULT_LOG_LEVEL | Log level (trace, debug, info, warn, error, fatal, silent) | info |

CLI Flags

The serve command accepts flags that override environment variables:

npx -y axvault serve \
  --port 8080 \
  --host 0.0.0.0 \
  --database-url postgresql://localhost:5432/axvault \
  --refresh-threshold 7200 \
  --refresh-timeout 60000 \
  --log-level debug

Setting --refresh-threshold 0 disables automatic credential refresh.

API Keys

API keys control access to the credential API. Each key has configurable permissions:

  • Read: retrieve credentials
  • Write: store and delete credentials
  • Grant: manage other API keys

Bootstrap Key

On first startup (when no keys exist), axvault creates a "Bootstrap Admin" key with full access (* for read, write, and grant). The secret is printed to stderr.

Managing Keys via API

All key management is done through the HTTP API:

  • POST /api/v1/keys accepts either a bootstrap/admin key or a scoped grant key, as long as every requested read/write/grant entry stays within the caller's grantAccess list.
  • PATCH /api/v1/keys/:id only works when the target key's current and resulting permissions both stay within the caller's grantAccess list. In practice, scoped grant keys can only update keys that are already fully inside their scope.
  • GET /api/v1/keys, GET /api/v1/keys/:id, and DELETE /api/v1/keys/:id require full grant access (grantAccess: ["*"]).
API_KEY="axv_sk_..."  # Bootstrap/admin key with grantAccess ["*"]

# Create a new key
curl -X POST https://vault.example.com/api/v1/keys \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "CI Pipeline", "readAccess": ["*"], "writeAccess": ["*"], "grantAccess": []}'

# List all keys
curl https://vault.example.com/api/v1/keys \
  -H "Authorization: Bearer $API_KEY"

# Get a single key
curl https://vault.example.com/api/v1/keys/k_a1b2c3d4e5f6 \
  -H "Authorization: Bearer $API_KEY"

# Update key permissions
curl -X PATCH https://vault.example.com/api/v1/keys/k_a1b2c3d4e5f6 \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"readAccess": ["claude.work", "codex.ci"]}'

# Revoke a key
curl -X DELETE https://vault.example.com/api/v1/keys/k_a1b2c3d4e5f6 \
  -H "Authorization: Bearer $API_KEY"

Container Deployments

Container images are published automatically to registry.j4k.dev/axvault on every release (multi-arch: amd64 + arm64). To rebuild manually, run workflow_dispatch on publish-image and provide the required version input (for example 1.7.0).

Running the Container

The image uses an external UID pattern—no user is baked into the image. Always specify a non-root user with -u/--user to limit container privileges:

Security note: Without -u/--user, the container runs as root. For Kubernetes, set runAsUser: 1000 and runAsNonRoot: true in your SecurityContext.

# Docker (requires a PostgreSQL instance accessible from the container)
docker run -d \
  --name axvault \
  -p 3847:3847 \
  -u 1000:1000 \
  -e AXVAULT_ENCRYPTION_KEY="your-secret-key-minimum-32-chars!" \
  -e AXVAULT_DATABASE_URL="postgresql://user:pass@db-host:5432/axvault" \
  registry.j4k.dev/axvault:latest

# Podman
podman run -d \
  --name axvault \
  -p 3847:3847 \
  --user 1000:1000 \
  -e AXVAULT_ENCRYPTION_KEY="your-secret-key-minimum-32-chars!" \
  -e AXVAULT_DATABASE_URL="postgresql://user:pass@db-host:5432/axvault" \
  registry.j4k.dev/axvault:latest

Note: AXVAULT_DATABASE_URL must point to an accessible PostgreSQL instance. The Containerfile default (postgresql://localhost:5432/axvault) refers to the container itself, so standalone docker run users must provide this variable. For a batteries-included setup, use docker-compose.yml which includes a PostgreSQL service.

The bootstrap admin key is created on first startup and printed to the container logs. Retrieve it with docker logs axvault or podman logs axvault.

Quadlet (systemd)

This example references axvault-db.service, which is a PostgreSQL container you must provide separately as a companion Quadlet (axvault-db.container). Alternatively, point AXVAULT_DATABASE_URL at an existing PostgreSQL instance and remove the Requires/After lines.

Create /etc/containers/systemd/axvault.container:

[Unit]
Description=axvault credential server
Requires=axvault-db.service
After=axvault-db.service

[Container]
Image=registry.j4k.dev/axvault:latest
PublishPort=3847:3847
User=1000
Group=1000
Environment=AXVAULT_ENCRYPTION_KEY=your-secret-key-minimum-32-chars!
Environment=AXVAULT_DATABASE_URL=postgresql://axvault:axvault@axvault-db:5432/axvault

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

Then reload and start:

sudo systemctl daemon-reload
sudo systemctl start axvault

Credentials API

Store a Credential

curl -X PUT https://vault.example.com/api/v1/credentials/claude.prod \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "oauth-credentials",
    "provider": "anthropic",
    "data": {"access_token": "...", "refresh_token": "..."},
    "expiresAt": "2025-12-31T23:59:59Z"
  }'

Returns 201 with the stored credential's metadata:

{
  "name": "claude.prod",
  "createdAt": "2026-01-15T10:30:00.000Z",
  "updatedAt": "2026-01-15T10:30:00.000Z"
}

The type field is required and must be one of:

  • "oauth-credentials" — Full OAuth with refresh_token (eligible for auto-refresh)
  • "oauth-token" — Long-lived OAuth token like CLAUDE_CODE_OAUTH_TOKEN (static)
  • "api-key" — API key (static)

The provider field is optional for single-provider agents. For OpenCode (multi-provider), provider is required (e.g., "anthropic", "openai", "gemini").

Optional metadata fields:

  • "displayName" — Human-readable label (e.g., "Claude (Work)")
  • "notes" — Free-form notes about the credential

These fields are stored as plaintext metadata (not encrypted) and returned in list and get responses.

List Credentials

curl https://vault.example.com/api/v1/credentials \
  -H "Authorization: Bearer <api_key>"

Returns metadata for all accessible credentials. Supports opt-in cursor-based pagination:

| Parameter | Description | Default | | --------- | -------------------------------------------------------- | ----------- | | limit | Results per page (1–1000; values above 1000 are clamped) | All results | | cursor | Name from previous page's nextCursor | — |

Without limit, all accessible credentials are returned (backward-compatible). cursor requires limit — providing cursor without limit returns 400.

# First page
curl 'https://vault.example.com/api/v1/credentials?limit=50' \
  -H "Authorization: Bearer <api_key>"

# Next page (cursor is the last name from the previous response)
curl 'https://vault.example.com/api/v1/credentials?limit=50&cursor=claude.prod' \
  -H "Authorization: Bearer <api_key>"

Response includes nextCursor when limit is set and more results are available:

{
  "credentials": [
    {
      "name": "claude.staging",
      "displayName": "Claude (Staging)",
      "createdAt": "...",
      "updatedAt": "..."
    },
    { "name": "gemini.ci", "createdAt": "...", "updatedAt": "..." }
  ],
  "nextCursor": "gemini.ci"
}

Optional fields (agent, provider, displayName, notes) are included in responses only when set.

Retrieve a Credential

curl https://vault.example.com/api/v1/credentials/claude.prod \
  -H "Authorization: Bearer <api_key>"

Delete a Credential

Returns 204 No Content on success.

curl -X DELETE https://vault.example.com/api/v1/credentials/claude.prod \
  -H "Authorization: Bearer <api_key>"

Auto-Refresh

axvault automatically refreshes oauth-credentials type credentials that are near expiration when they are retrieved. This behavior is controlled by the refresh threshold setting. Only credentials containing a refresh token field are eligible for auto-refresh. Supported field names: refresh_token (standard OAuth), refreshToken (Claude), refresh (OpenCode).

Access Control Note

Auto-refresh is a server-side maintenance operation that occurs transparently during credential retrieval. Read-only API keys can trigger refresh because:

  • The refresh uses the credential's own refresh token (already authorized by the token owner)
  • The credential's identity and ownership remain unchanged
  • Only token values and expiry timestamps are updated
  • This prevents wasteful repeated refreshes and rate limit issues

This follows the pattern used by credential vaults like HashiCorp Vault, where credential maintenance is handled transparently on reads.

Response Headers

When retrieving credentials, the response may include these headers:

| Header | Value | Description | | -------------------------- | ------ | ------------------------------------------------------------ | | X-Axvault-Refreshed | true | Credential was successfully refreshed during this request | | X-Axvault-Refresh-Failed | true | Refresh was attempted but failed; stale credentials returned |

When X-Axvault-Refresh-Failed is present, the response still returns HTTP 200 with the existing (potentially expired) credentials. Error details are logged to the audit log.

CI/CD Integration

With axrun (Recommended)

Use the --vault-credential flag to fetch credentials directly. Match the credential name to the agent (for example ci-claude-oauth-token for Claude and ci-codex-oauth-credentials for Codex):

- name: Run Claude Review
  env:
    AXVAULT: ${{ secrets.AXVAULT }}
  run: |
    axrun --agent claude --vault-credential ci-claude-oauth-token \
      --prompt "Review this PR"

With axauth (Explicit Fetch)

For more control, fetch credentials separately:

- name: Fetch credentials
  run: |
    axauth vault fetch --agent claude --name ci --env \
      | sed 's/^export //' >> $GITHUB_ENV
  env:
    AXVAULT: ${{ secrets.AXVAULT }}

- name: Run Claude
  run: axrun --agent claude --prompt "Hello"

Required Secret

Store AXVAULT as a JSON object in your repository secrets:

{ "url": "https://vault.example.com", "apiKey": "axv_sk_..." }

Alternatively, use separate environment variables:

| Variable | Description | | ----------------- | ------------------------ | | AXVAULT_URL | Vault server URL | | AXVAULT_API_KEY | API key with read access |

Troubleshooting

Common Errors

| Error | Cause | Solution | | ---------------- | ------------------------------------------ | ------------------------------------------------------------------------- | | not-configured | Missing AXVAULT_URL or AXVAULT_API_KEY | Set both environment variables or use AXVAULT JSON | | unauthorized | Invalid API key | Check the key via the keys API, create a new one if needed | | forbidden | No access to credential | Update key permissions via PATCH /api/v1/keys/:id | | not-found | Credential doesn't exist | Store credential first: axauth vault push --agent <agent> --name <name> | | unreachable | Network issue or server down | Check vault URL, verify server is running |

Debugging

  1. Test vault connectivity:

    curl -I $AXVAULT_URL/api/v1/health
  2. List API keys (requires grant access):

    curl -H "Authorization: Bearer $AXVAULT_API_KEY" \
      $AXVAULT_URL/api/v1/keys
  3. Check credential exists:

    curl -H "Authorization: Bearer $AXVAULT_API_KEY" \
      $AXVAULT_URL/api/v1/credentials

License

MIT