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

@tryhand/ai-gateway

v0.1.11

Published

Internal AI Gateway CLI, local collector, and agent hook bridge

Readme

AI Tracking Gateway

Core Mode implementation for docs/plans/proposal.md: local endpoint-side collection for Codex, Claude Code, and Cursor usage without requiring Team/Enterprise plans or routing model traffic through a provider proxy.

This is an internal company gateway, not a public product. It intentionally avoids product-style signup, billing, tenant self-service, and release/go-live ceremony. User identity and device credentials are managed locally by company tooling.

What Is Implemented

  • Local Collector with:
    • GET /health
    • POST /hooks/:agent/:event
    • POST /policy/evaluate
    • POST /v1/logs for OTLP HTTP JSON logs
    • offline queue and POST /sync
  • Gateway API with:
    • POST /api/v1/devices/register
    • POST /api/v1/events
    • POST /api/v1/telemetry/health
    • device-scoped dashboard routes under /api/v1/dashboard/device/*
    • temporary admin dashboard and device management routes under /api/v1/admin/*
  • PostgreSQL schema in migrations/*.sql
  • Local prompt redaction and policy decisions before upload
  • Explicit usage confidence through usage.quality

Quick Start

For an employee workstation after this package is published to the private npm registry:

npm install -g @tryhand/ai-gateway
ai-gateway setup --gateway-url https://<gateway-host> --enrollment-token <company-enrollment-token>
ai-gateway ping

setup does the local onboarding work in one command:

  • registers this machine to an owner email and writes a per-device token to ~/.ai-gateway/device.json
  • installs machine-level Codex/Cursor hooks
  • installs Codex OTel config for account-email verification
  • creates Windows collector autostart, using a login Scheduled Task plus a 5-minute watchdog task that health-checks the local collector and only starts it when it is not already running; if Scheduled Task creation is denied, setup falls back to the current user's HKCU\...\Run entry
  • starts the local collector in the background
  • runs the same verification checks as ai-gateway ping

ping is the operator smoke test. It exits non-zero when device registration, collector health, gateway health, hooks, OTel, or collector autostart is not working. During setup, the first Codex identity check may be reported as pending until Codex Desktop emits its first OTel identity event; run ai-gateway ping after opening or restarting Codex Desktop for the strict identity check.

For source checkout development:

npm install
Copy-Item .env.example .env
# Edit .env with DATABASE_URL and internal service settings.
npm run build

Onboard this workstation from source:

npm run ai-gateway -- setup --gateway-url http://127.0.0.1:8080 --enrollment-token <company-enrollment-token> --owner-email <[email protected]>
npm run ai-gateway -- ping

setup and register-device call the Gateway API and write a per-device token to ~/.ai-gateway/device.json. Device registration requires an owner email from --owner-email or AI_GATEWAY_OWNER_EMAIL. The owner is the employee responsible for the machine. The observed AI account is verified from each agent runtime: Cursor hook payloads provide user_email, and Codex uses the identity-only OTel channel to provide user.email.

The machine owner and observed AI account are stored separately. If an employee switches AI accounts, uses a borrowed account, or the observed account differs from the registered machine owner, the gateway keeps the event and exposes the mismatch in the admin dashboard instead of rewriting ownership history.

For local-only development, register-device --token <token> still writes a legacy local token, but shared DEVICE_TOKEN is not the recommended company rollout path.

Apply the database migration:

npm run db:migrate

Data access convention:

  • Use Kysely for typed application queries in src/gateway-api/db.ts, especially straightforward lookups, inserts, updates, and transactional repository code.
  • Keep migrations as explicit SQL files under migrations/.
  • Keep complex dashboard analytics as raw SQL when CTEs, window functions, token-source selection, or bigint aggregate behavior are clearer and easier to audit in PostgreSQL syntax. Preserve token accounting semantics when changing those queries.

Start Gateway API:

npm run ai-gateway -- serve-api

By default the Gateway API binds to 127.0.0.1, so it is reachable only from the local machine. Keep this default for source checkout development. For an intentional LAN or central deployment, set GATEWAY_HOST to the deployment bind address and set GATEWAY_CORS_ALLOWED_ORIGINS to the exact dashboard origins that may call the API. Do not use a LAN bind as an implicit CORS allowlist.

For a central deployment, set:

ENROLLMENT_TOKEN=<secret used only for first-time device registration>
ADMIN_TOKEN=<secret used to view dashboard data until SSO/admin auth exists>

Token roles:

  • ENROLLMENT_TOKEN is setup-only. It can register a device, but it cannot ingest events or view dashboard data.
  • The per-device token issued by /api/v1/devices/register is machine auth. It authorizes ingest from that registered device and device-scoped dashboard routes for that device only.
  • DEVICE_TOKEN is a legacy/dev fallback for local testing. Do not use one shared DEVICE_TOKEN as the company rollout credential.
  • ADMIN_TOKEN is a temporary global admin fallback until real admin auth exists. Admin endpoints fail closed when ADMIN_TOKEN is not configured.

Dashboard UI:

  • /dashboard redirects to /dashboard/device.
  • /dashboard/device is the device-scoped dashboard. Its login is an in-page form that accepts the token field from ~/.ai-gateway/device.json. The id field starts with dev_ and identifies the device; it is not a login token. Pasting a dev_* value shows a targeted correction in the form.
  • /dashboard/admin is the admin dashboard. Its separate login form accepts ADMIN_TOKEN from the Gateway API environment.
  • Device and admin tokens are route-specific. A device token cannot authorize the admin dashboard, and an admin token is not the device dashboard token.

The React/Vite dashboard is built into dist/dashboard. Fastify serves the built app for the dashboard UI paths and serves its generated static assets under /dashboard/assets/.

Dashboard API routes:

  • Device-scoped routes use the registered device token: /api/v1/dashboard/device/summary and /api/v1/dashboard/device/sessions.
  • Admin routes use ADMIN_TOKEN: /api/v1/admin/dashboard/summary, /api/v1/admin/dashboard/sessions, /api/v1/admin/dashboard/token-status, /api/v1/admin/devices, and /api/v1/admin/dashboard/mismatches.
  • Legacy /api/v1/dashboard/summary, /api/v1/dashboard/sessions, and /api/v1/dashboard/token-status remain admin-authenticated compatibility routes.

Dashboard development commands:

# Start the Vite frontend development server (frontend-only development).
npm run dashboard:dev

# Build only the dashboard into dist/dashboard.
npm run dashboard:build

# Preview the built dashboard with Vite.
npm run dashboard:preview

For the implemented same-origin dashboard and API behavior, build the dashboard and run Fastify, then open /dashboard/device or /dashboard/admin on the configured Gateway API port:

npm run dashboard:build
npm run dev:api

The full package build runs the backend TypeScript compiler and then the Vite dashboard build:

npm run build

Start Local Collector:

npm run ai-gateway -- serve-collector

The collector uses a generated local shared secret to protect local control endpoints. The token is stored in ~/.ai-gateway/config.json and can be overridden with AI_GATEWAY_COLLECTOR_TOKEN when an operator needs to inject a known non-empty value. The generated hook bridge and collector launcher read the current token at runtime instead of storing a token copy in generated scripts. Protected collector endpoints are /hooks/:agent/:event, /policy/evaluate, /sync, and /telemetry/health. /health stays open for local health checks, and /v1/logs stays unauthenticated for OTLP HTTP compatibility.

Check local setup:

npm run ai-gateway -- doctor

Install machine-level hooks for Codex and Cursor so every project can send hook events without per-repo .codex/config.toml or .cursor/hooks.json setup:

npm run ai-gateway -- init-machine --agents codex,cursor
npm run ai-gateway -- machine-hook-status

The generated bridge preserves the original project working directory before entering this gateway runtime, so hook events from other repositories can still be attributed to the correct project. For Codex, init-machine also installs an OpenTelemetry exporter used only to verify the signed-in Codex account email against ALLOWED_EMAIL_DOMAINS. Codex OTel payloads are not stored as usage events.

Clean up machine-level gateway config and remove the global package:

ai-gateway uninstall-package

Current npm versions do not run package uninstall lifecycle scripts during npm uninstall, so raw npm uninstall -g @tryhand/ai-gateway only removes the package files. Use ai-gateway uninstall-package when you want gateway cleanup and npm package removal in one step.

Project identity prefers the normalized Git remote when it can be detected. HTTPS, SSH, and git-protocol clones of the same repository normalize to the same project identity, for example https://github.com/org/repo.git and [email protected]:org/repo.git. If no usable remote is available, the collector falls back to workspace path and project name identity.

Send an end-to-end smoke event through the collector:

npm run ai-gateway -- test-event

Sync Codex session JSONL usage into the gateway:

npm run ai-gateway -- sync-codex-jsonl

This reads $CODEX_HOME/sessions, $CODEX_HOME/archived_sessions, or the default ~/.codex paths. Parser state is stored at ~/.ai-gateway/codex-jsonl-state.json. The sync only emits events when token usage fields are present; metadata-only session lines are skipped so the dashboard does not show fake token usage.

If an earlier parser run created unknown Codex JSONL rows, clean them up with:

npm run ai-gateway -- cleanup-codex-jsonl-unknown

The .env file should not contain per-user identity or access rules. Configure company AI access policy in config/access-policy.json:

{
  "version": 1,
  "allowedEmailDomains": ["tryhand.tech"],
  "defaultPolicy": "allow_domain"
}

The checked-in default policy allows verified tryhand.tech accounts with defaultPolicy: "allow_domain". Copy config/access-policy.example.json to config/access-policy.json for local changes, and set ACCESS_POLICY_PATH in .env only when the policy file lives outside the default path. With defaultPolicy: "allow_domain", any verified account from allowedEmailDomains is allowed. Use defaultPolicy: "block" to fail closed until the domain policy is explicitly changed.

Hook Bridge

Agent hooks should call the bridge command and pass their hook JSON on stdin:

npm run ai-gateway-hook-bridge -- codex user-prompt

The bridge posts to:

http://127.0.0.1:17654/hooks/<agent>/<event>

Exit behavior:

  • 0: collector allowed/warned and accepted the event.
  • 2: collector blocked the event or could not accept it.

Optional hook debug logging is off by default. When enabled, it only records user-prompt and session-stop hook flows, not tool/edit/shell events. This keeps debug output focused on the retained lifecycle events. AI_GATEWAY_HOOK_DEBUG=1 writes redacted records with a payload summary by default. AI_GATEWAY_HOOK_DEBUG_RAW=1 is a sensitive opt-in for raw payload capture and should only be used during tightly scoped local troubleshooting. Prefer writing per-agent debug files with AI_GATEWAY_HOOK_DEBUG_DIR; if only AI_GATEWAY_HOOK_DEBUG_PATH is configured, all debug records are written to the legacy single JSONL file.

AI_GATEWAY_HOOK_DEBUG=1
AI_GATEWAY_HOOK_DEBUG_RAW=0
AI_GATEWAY_HOOK_DEBUG_DIR=.data/hook-debug
AI_GATEWAY_HOOK_DEBUG_PATH=.data/hook-debug.jsonl

With AI_GATEWAY_HOOK_DEBUG_DIR=.data/hook-debug, records are written to .data/hook-debug/codex.jsonl, .data/hook-debug/cursor.jsonl, or .data/hook-debug/claude.jsonl.

Do not write debug logs to stdout; agent hook stdout is reserved for the hook protocol response.

If the offline collector queue contains invalid records, they are moved to the quarantine file next to the queue. With the default COLLECTOR_QUEUE_PATH, that location is .data/collector-queue.jsonl.quarantine.jsonl; in general it is <collectorQueuePath>.quarantine.jsonl.

Cursor Quick Start

Cursor Core Mode tracks lifecycle, prompt, tool, and edit events through hooks. It does not proxy Cursor model traffic. Policy handling for Cursor Core Mode is audit/reporting oriented in this MVP. The collector records policy decisions from Cursor hook events, but Cursor runtime fail-closed blocking is not claimed unless Cursor-specific hook output and exit-code behavior is verified against the Cursor runtime.

Install Cursor hooks on this workstation:

npm run ai-gateway -- install-hooks --agents cursor

Check installed hook state:

npm run ai-gateway -- hook-status

Run Cursor-specific preflight checks:

npm run ai-gateway -- preflight --agent cursor

Send a Cursor smoke event to the local collector:

npm run ai-gateway -- test-cursor-event

Cursor token usage is not billing-grade in the MVP. Cursor sessions remain estimated or unknown unless the hook payload includes token fields that can be normalized.

Codex OTel

Install project-local Codex OTel config:

npm run ai-gateway -- install-otel --agent codex

Check OTel config and latest ingested OTel event:

npm run ai-gateway -- otel-status

Send a sample OTLP HTTP JSON log through the local collector:

npm run ai-gateway -- test-otel

Successful setup should make /api/v1/dashboard/summary show usage_quality = otel_reported with non-zero tokens.

Example Hook Event

Invoke-RestMethod `
  -Method Post `
  -Uri http://127.0.0.1:17654/hooks/codex/user-prompt `
  -ContentType application/json `
  -Body '{"prompt":"Review this repository","session_id":"ses_demo","model":"gpt-5","cwd":"D:\\tryhand-project\\ai-tracking-gateway"}'

Architecture

The request path to AI providers stays unchanged. The collector receives telemetry and hooks locally, redacts sensitive content, evaluates local policy, queues events if offline, then syncs to the Gateway API.

Cursor MITM/protobuf decoding is intentionally not part of the MVP. It should remain a future opt-in experimental adapter after Core Mode has stable telemetry health and policy reporting.