Documentation

Local setup

Get a registry running and point your toolchains at it.

Source: api.packr.blueforge.studio/-/docs/quickstart

Local Setup & Token Management

Overview

The Packr registry uses a single shared service account (blueforge-ci) for all internal BlueForge projects. This avoids proliferating user accounts and tokens — one token handles both publishing and installing @blueforge-studio packages across all repos.

Prerequisites

  • Node.js 22+ with pnpm
  • gh CLI authenticated (gh auth login)
  • Access to the Packr registry at https://api.packr.blueforge.studio

How It Works

ContextAuth MethodWhere Token Lives
Local development~/.npmrcToken stored directly in file
CI/CD (GitHub Actions)PACKR_TOKEN secretGitHub repo secret per repo
blueforge-org monorepo.npmrc in repo rootReferences ${PACKR_TOKEN} env var

All contexts use the same blueforge-ci JWT token. The token is a standard npm auth token — pnpm, npm, and yarn all read it from .npmrc.

Local Development Setup

1. Global ~/.npmrc

Your ~/.npmrc should contain these lines (alongside any existing config):

# BlueForge Packr Registry
@blueforge-studio:registry=https://api.packr.blueforge.studio
//api.packr.blueforge.studio/:_authToken=<your-token>

This routes all @blueforge-studio/* package installs and publishes to Packr. All other packages continue to resolve from npmjs.org.

2. Per-Repo .npmrc (for CI)

Each repo that publishes or consumes @blueforge-studio packages should have a .npmrc in its root:

@blueforge-studio:registry=https://api.packr.blueforge.studio
//api.packr.blueforge.studio/:_authToken=${PACKR_TOKEN}

The ${PACKR_TOKEN} variable is resolved from the environment — set as a GitHub secret for CI, or from your shell for local use.

3. Store CI Credentials

The blueforge-ci password is stored locally for token rotation:

echo 'your-password-here' > ~/.packr-credentials
chmod 600 ~/.packr-credentials

This file is only read by the rotation script. It is never committed to any repo.

Publishing a Package

From any repo with a @blueforge-studio scoped package:

# Ensure package.json has publishConfig
# "publishConfig": { "registry": "https://api.packr.blueforge.studio" }

pnpm publish --access public

The ~/.npmrc token handles authentication automatically.

Publishing from CI (GitHub Actions)

The blueforge-org monorepo uses tag-based publishing. To publish from other repos, add a workflow like:

- uses: actions/setup-node@v4
  with:
    node-version: 22
    registry-url: https://api.packr.blueforge.studio
    scope: '@blueforge-studio'

- run: pnpm publish --access public
  env:
    NODE_AUTH_TOKEN: ${{ secrets.PACKR_TOKEN }}

Installing Packages

From any project with the .npmrc config above:

pnpm add @blueforge-studio/core-agent

No additional setup needed — the registry routing in .npmrc handles it.

Token Rotation

Tokens expire every 30 days. The rotation script regenerates the token and updates all configured locations.

Running Rotation

cd /path/to/packr-registry
./scripts/rotate-token.sh

What It Updates

TargetHow
~/.npmrcReplaces the _authToken value in-place
GitHub repo secretsUpdates PACKR_TOKEN via gh secret set

Schedule

ItemValue
Token lifetime30 days
Recommended rotationEvery 25 days (5-day buffer)
Script locationscripts/rotate-token.sh
Credentials file~/.packr-credentials (mode 600)

Central Configuration: .packr.json

All registry configuration lives in .packr.json at the repo root — the single source of truth for token rotation, repo list, and registry settings:

{
  "registry": "https://api.packr.blueforge.studio",
  "scope": "@blueforge-studio",
  "ci_user": "blueforge-ci",
  "token_expiry_days": 30,
  "rotate_before_days": 25,
  "repos": [
    "blueforge-studio/blueforge-org",
    "blueforge-studio/restaurant-os",
    "blueforge-studio/investor-os"
  ]
}
FieldDescription
registryPackr registry URL
scopenpm scope routed to this registry
ci_userShared service account username
token_expiry_daysJWT token lifetime (set on server)
rotate_before_daysWhen to rotate (buffer before expiry)
reposGitHub repos that receive PACKR_TOKEN secret on rotation

Adding Repos to Rotation

Add the repo to the repos array in .packr.json:

{
  "repos": [
    "blueforge-studio/blueforge-org",
    "blueforge-studio/restaurant-os",
    "blueforge-studio/new-project"
  ]
}

Then run ./scripts/rotate-token.sh — it reads from .packr.json automatically.

Manual Rotation

If the script is unavailable, rotate manually:

# 1. Get new token
curl -s -X POST https://api.packr.blueforge.studio/-/v1/login \
  -H 'Content-Type: application/json' \
  -d '{"name":"blueforge-ci","password":"<password>"}' | jq -r .token

# 2. Update ~/.npmrc with new token

# 3. Update each GitHub repo
gh secret set PACKR_TOKEN --body "<new-token>" --repo blueforge-studio/<repo>

Troubleshooting

ProblemCauseFix
401 Unauthorized on publishToken expiredRun ./scripts/rotate-token.sh
401 Unauthorized on installMissing .npmrc configAdd @blueforge-studio:registry line to ~/.npmrc
403 Forbidden on publishNot a package ownerFirst publish creates ownership; subsequent publishes require it
registration is disabledALLOW_REGISTRATION=falseExpected — use existing blueforge-ci account
tarball exceeds maximum sizePackage > 5MBSet MAX_TARBALL_SIZE env var on registry, or reduce package size

Architecture

Developer Machine                    GitHub Actions
┌─────────────┐                     ┌──────────────────┐
│ ~/.npmrc     │                     │ PACKR_TOKEN      │
│ (auth token) │                     │ (GitHub secret)  │
└──────┬──────┘                     └────────┬─────────┘
       │                                      │
       ▼                                      ▼
┌─────────────────────────────────────────────────────┐
│         https://api.packr.blueforge.studio          │
│                  (Fly.io — ams)                      │
│                                                      │
│  Auth: JWT Bearer token (blueforge-ci)              │
│  Rate: 30 publish/min, 10 login/min per IP          │
│  Limits: 5MB tarball, 20 tokens/user                │
└─────────────────────────────────────────────────────┘

DNS Configuration

The packr dashboard site (packr-site Vercel project) serves at packr.blueforge.studio.

Important: The packr.blueforge.studio subdomain must be assigned to the packr-site Vercel project (not the main site or forge-platform project). If packr-cli login shows a 404 on /cli/auth, the domain is assigned to the wrong project.

Fix via Vercel API:

# Remove from wrong project
curl -X DELETE "https://api.vercel.com/v9/projects/{wrong-project-id}/domains/packr.blueforge.studio?teamId={team-id}" \
  -H "Authorization: Bearer $VERCEL_TOKEN"

# Add to packr-site project
curl -X POST "https://api.vercel.com/v10/projects/{packr-site-project-id}/domains?teamId={team-id}" \
  -H "Authorization: Bearer $VERCEL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "packr.blueforge.studio"}'

Cloudflare DNS: packr.blueforge.studio → CNAME → cname.vercel-dns.com (already configured in zone b2a7319881e0600969600b0572793515).