# 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

| Context | Auth Method | Where Token Lives |
|---|---|---|
| Local development | `~/.npmrc` | Token stored directly in file |
| CI/CD (GitHub Actions) | `PACKR_TOKEN` secret | GitHub repo secret per repo |
| blueforge-org monorepo | `.npmrc` in repo root | References `${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:

```bash
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:

```bash
# 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:

```yaml
- 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:

```bash
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

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

### What It Updates

| Target | How |
|---|---|
| `~/.npmrc` | Replaces the `_authToken` value in-place |
| GitHub repo secrets | Updates `PACKR_TOKEN` via `gh secret set` |

### Schedule

| Item | Value |
|---|---|
| Token lifetime | 30 days |
| Recommended rotation | Every 25 days (5-day buffer) |
| Script location | `scripts/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:

```json
{
  "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"
  ]
}
```

| Field | Description |
|---|---|
| `registry` | Packr registry URL |
| `scope` | npm scope routed to this registry |
| `ci_user` | Shared service account username |
| `token_expiry_days` | JWT token lifetime (set on server) |
| `rotate_before_days` | When to rotate (buffer before expiry) |
| `repos` | GitHub repos that receive `PACKR_TOKEN` secret on rotation |

### Adding Repos to Rotation

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

```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:

```bash
# 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

| Problem | Cause | Fix |
|---|---|---|
| `401 Unauthorized` on publish | Token expired | Run `./scripts/rotate-token.sh` |
| `401 Unauthorized` on install | Missing `.npmrc` config | Add `@blueforge-studio:registry` line to `~/.npmrc` |
| `403 Forbidden` on publish | Not a package owner | First publish creates ownership; subsequent publishes require it |
| `registration is disabled` | `ALLOW_REGISTRATION=false` | Expected — use existing `blueforge-ci` account |
| `tarball exceeds maximum size` | Package > 5MB | Set `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:**
```bash
# 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`).
