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

@rajbos/ai-engineering-fluency-sharing-server

v0.2.0

Published

Self-hosted sharing server for AI Engineering Fluency — accepts daily usage uploads from VS Code via GitHub OAuth tokens

Readme

AI Engineering Fluency — Sharing Server

A self-hosted API server + web dashboard that makes sharing AI Engineering Fluency data across a team dramatically easier. No Azure account required — anyone with Docker can host it in minutes.

Development, testing and downstream customization must follow the data separation contract (sharing-server/AGENTS.md in this repository, also shipped as AGENTS.md in the published npm package).

How it works

[VS Code Extension]  ──Bearer token──►  POST /api/upload
                         (GitHub session)       │
                                        [Sharing Server]
                                               │
                                         SQLite DB (./data/)
                                               │
[Web Browser]  ──OAuth login──►  GET /dashboard

The extension already holds a GitHub OAuth session (the same one used by Copilot and GitHub PR statistics). When you configure a sharing server endpoint URL, the extension automatically uses that token for uploads — no API keys, no copy-paste, no new consent required.

Quick start with Docker Compose

1. Create a GitHub OAuth App

  1. Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App
  2. Fill in:
    • Application name: AI Engineering Fluency
    • Homepage URL: https://your-server.example.com
    • Authorization callback URL: https://your-server.example.com/auth/github/callback
  3. Copy the Client ID and generate a Client Secret

2. Create the compose file

services:
  sharing-server:
    image: ghcr.io/rajbos/copilot-sharing-server:latest
    ports:
      - "3000:3000"
    environment:
      - GITHUB_CLIENT_ID=your_client_id
      - GITHUB_CLIENT_SECRET=your_client_secret
      - SESSION_SECRET=a_long_random_string_min_32_chars
      - BASE_URL=https://your-server.example.com
      # Optional: restrict uploads to members of a specific GitHub org
      # - ALLOWED_GITHUB_ORG=your-org-name
    volumes:
      - sharing_data:/data
    restart: unless-stopped

volumes:
  sharing_data:

Tip: Generate SESSION_SECRET with openssl rand -hex 32 in bash, or in PowerShell:

$bytes = New-Object byte[] 32
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
($bytes | ForEach-Object { $_.ToString("x2") }) -join '' | Set-Content -Path .session_secret.txt
Get-Content .session_secret.txt

3. Start and verify

docker compose up -d
curl https://your-server.example.com/health
# → {"status":"ok","timestamp":"..."}

4. Configure the VS Code extension

In VS Code settings (JSON):

{
  "aiEngineeringFluency.backend.enabled": true,
  "aiEngineeringFluency.backend.backend": "sharingServer",
  "aiEngineeringFluency.backend.sharingServer.endpointUrl": "https://your-server.example.com"
}

Or search for AI Engineering Fluency: Backend in the Settings UI and fill in the fields.

That's it. The extension will start uploading data automatically. No authentication prompt — it reuses your existing GitHub session.

Building from source

For code changes, also run the server validation checks and satisfy the data separation contract's HTTP/HTML privacy tests. The root .\build.ps1 -Project sharing -Target test runs the server's npm test; run the type/build/headless interaction checks separately as described there.

# From the repo root:
./build.ps1 -Project sharing

# Or from the sharing-server/ directory:
cd sharing-server
npm ci
npm run build            # development build
npm run build:production # minified build

Running locally (without Docker)

1. Install dependencies

cd sharing-server
npm ci

2. Create a .env file

Copy the example and fill in your values:

cp .env.example .env

Minimum required in .env:

GITHUB_CLIENT_ID=your_github_oauth_app_client_id
GITHUB_CLIENT_SECRET=your_github_oauth_app_client_secret
SESSION_SECRET=any_long_random_string_at_least_32_chars
BASE_URL=http://localhost:3000
PORT=3000
DB_PATH=./data/sharing.db

GitHub OAuth App callback URL for local dev: http://localhost:3000/auth/github/callback

3. Build and start

npm run build   # compile TypeScript → dist/server.js
npm start       # start the server (automatically loads .env via Node --env-file)

Or for watch mode (auto-restarts dist/server.js whenever it changes):

npm run dev

Run npm run build in a separate terminal to rebuild after editing src/. The dev script uses node --watch which restarts automatically when dist/server.js changes.

4. Verify

curl http://localhost:3000/health
# → {"status":"ok","timestamp":"..."}

For manual verification, open http://localhost:3000/dashboard in your browser to test the OAuth login flow. Automated tests use isolated SQLite fixtures and stubbed GitHub access, never production data or live GitHub; see the required validation contract.

Environment variables

| Variable | Required | Description | |---|---|---| | GITHUB_CLIENT_ID | ✅ | GitHub OAuth App client ID (for dashboard login) | | GITHUB_CLIENT_SECRET | ✅ | GitHub OAuth App client secret (for dashboard login) | | SESSION_SECRET | ✅ | Random secret for signing session cookies (≥32 chars) | | BASE_URL | ✅ | Public base URL of the server (no trailing slash) | | PORT | ❌ | HTTP port (default: 3000) | | DB_PATH | ❌ | SQLite database path (default: /data/sharing.db) | | ALLOWED_GITHUB_ORG | ❌ | If set, only members of this GitHub org can upload data | | ADMIN_GITHUB_LOGINS | ❌ | Comma-separated GitHub logins to auto-grant admin access (e.g. alice,bob). When set, this list is authoritative: listed users get admin, all others do not. Leave unset to manage admins manually via SQLite. |

REST API

Upload daily rollups (used by the VS Code extension)

POST /api/upload
Authorization: Bearer <github-token>
Content-Type: application/json

[
  {
    "day": "2026-04-21",
    "model": "gpt-4o",
    "workspaceId": "my-project",
    "workspaceName": "My Project",
    "machineId": "laptop-abc123",
    "machineName": "My Laptop",
    "editor": "Copilot CLI (App)",
    "inputTokens": 15000,
    "outputTokens": 8000,
    "interactions": 42,
    "datasetId": "default"
  }
]

Accepts up to 500 entries per request. The server upserts by (user_id, dataset_id, day, model, workspace_id, machine_id, editor) so repeated uploads are safe and idempotent.

Other endpoints

| Method | Path | Auth | Description | |---|---|---|---| | GET | /health | Public | Health check | | GET | /api/me | Bearer token | Current user info | | GET | /api/data?days=30 | Bearer token | Own usage data (last N days) | | GET | /auth/github | Public | Dashboard login (OAuth redirect) | | GET | /auth/github/callback | Public | OAuth callback | | GET | /auth/logout | Session | Clear session | | GET | /dashboard | Session cookie | Web dashboard | | GET | /team?days=30 | Session cookie | Identity-free Team Insights page | | GET | /team/export?days=30&format=csv | Session cookie | Team Insights download (csv or json), using the same safe projection | | GET | /api/team-insights?days=30 | Bearer token | Identity-free team totals and own comparison |

Team Insights

/team and /api/team-insights accept days=7, days=30 or days=90. Omitted, malformed or unsupported values default to 30. Each window covers exactly N UTC calendar dates including today, beginning N−1 dates ago; future-dated uploads are excluded.

Team Insights shows exact numeric period totals for anonymous active members, marks your own row, and compares your input + output tokens with active uploaders: rank, share of team tokens, percentile and a Light / Medium / Heavy / Very heavy usage cohort. Registered users without positive tokens or interactions in the window are not active members. If you are inactive, your self row has zero numeric totals/activity/share and rank, percentile and cohort are null; you are excluded from members. Only aggregate team and your own daily trends are exposed, never peer daily series.

Period filters are full-page /team?days=N links and work without JavaScript. The page includes cohort counts/thresholds, an exact numeric member table and expandable exact daily team/own totals. Trend modes compare your own line with either team totals or the team average per daily active uploader, not all registered users or the entire period's active-member count.

Download the selected period with cookie-authenticated /team/export?days=N&format=csv or format=json (N is 7, 30 or 90). Both formats use the same safe Team Insights projection; exports do not add peer identities, metadata or per-peer daily data.

Tied token totals have the same rank and cohort. Cohorts use interpolated p25/p50/p75 thresholds with <= boundaries; percentile counts only other active uploaders with strictly lower totals, and is undefined when there are no peers. These are relative usage groups, not productivity ratings. See the authoritative comparison definitions. If team tokens total zero, sharePercent is 0 and the UI displays 0.0% by convention, rather than claiming a mathematically defined share.

Anonymous labels remove direct identifiers, not all re-identification risk. Exact totals and small-team comparisons can reveal identity; there is no minimum team size or suppression. Peer responses are an explicit numeric allowlist plus self flags and calculated comparisons/cohorts, not uploaded rows. The data separation contract applies to API JSON, HTML, embedded scripts, chart data and any new exports. Personal uploaded data remains owner-only; named member detail remains server-authorized admin-only. Admins using member surfaces receive the same identity-free team shape. Personalized and team responses use Cache-Control: private, no-store.

Team Insights JSON response

The API returns the following explicit projection (the server keeps grouping IDs internal):

| Field | Contents | |---|---| | days, startDay, endDay | Selected duration and inclusive UTC date bounds | | summary | activeUsers, inputTokens, outputTokens, totalTokens, interactions, averageTokens, medianTokens | | members | Active member rows, without identifiers | | self | Your member row, including the zero-valued inactive case | | cohorts | Rows with label and members (count) | | daily | Rows with day, inputTokens, outputTokens, totalTokens, interactions, ownTokens, activeUsers | | quartiles | Interpolated token thresholds q1, q2, q3 |

Each members/self row contains only isSelf, inputTokens, outputTokens, totalTokens, interactions, daysActive, tokensPerActiveDay, sharePercent, rank, percentile and cohort. daily.activeUsers is that date's active uploader count; daily.ownTokens belongs only to the authenticated viewer. There are no stable member aliases or peer-specific daily rows.

Rate limits

| Scope | Limit | |---|---| | Per IP (all requests) | 200 requests / minute | | Per user (uploads) | 100 upload requests / hour |

Extending this server

This server is also published as a library so you can build a customised server on top of it without forking. Import createApp and startServer, mount your own routes and register your own tables:

import {
	createApp,
	startServer,
	registerSchemaExtension,
	requireBearerAuth,
	getDb,
} from '@rajbos/ai-engineering-fluency-sharing-server';
import { Hono } from 'hono';

// Runs before the first getDb(), so the table exists at startup.
registerSchemaExtension('my-metrics', (db) => {
	db.exec(`CREATE TABLE IF NOT EXISTS my_metrics (
		id           INTEGER PRIMARY KEY,
		user_id      INTEGER NOT NULL REFERENCES users(id),
		dataset_id   TEXT NOT NULL DEFAULT 'default',
		day          TEXT NOT NULL,
		workspace_id TEXT NOT NULL,
		machine_id   TEXT NOT NULL,
		metrics_json TEXT NOT NULL,
		UNIQUE(user_id, dataset_id, day, workspace_id, machine_id)
	)`);
});

const routes = new Hono();
routes.post('/upload', requireBearerAuth, async (c) => {
	const user = c.get('user');
	// … store rows for user.id …
	return c.json({ ok: true });
});

const app = createApp({
	healthExtra: () => ({ edition: 'my-company' }),
	extend: (a) => a.route('/api/mine', routes),
});

await startServer(app);

Extension points

| Export | Purpose | |---|---| | createApp({ extend }) | Register routes before the built-in ones. Because Hono matches in registration order, this also lets you override a built-in path. | | createApp({ healthExtra }) | Merge extra fields into /health. | | createApp({ mountApi, mountDashboard }) | Opt out of the built-in route groups. | | registerSchemaExtension(name, fn) | Add tables/indexes/migrations. Call before the first getDb(). | | requireBearerAuth | Authenticate with the same GitHub token as /api/upload, so your rows share the same user_id. | | getTeamInsights(viewerId, days), parseTeamDays(raw) | Reuse the safe Team Insights projection and supported-period parsing. Derive viewerId only from server-authenticated identity, never request/query input. | | TeamInsights, TeamMember, UsageCohort | Public TypeScript types for the identity-free projection. | | startServer(app, opts) | Backup/restore, DB init with retry, periodic backup, graceful shutdown. |

Guidance

  • Preserve the data separation contract. Overrides registered before built-in routes can bypass their protection: retain owner/admin authorization and the same allowlisted member projection, including caching, charts and exports. Add the contract's HTTP/HTML privacy regression tests for custom routes; importing the library alone does not enforce these boundaries on your extensions.
  • Add tables, don't widen usage_uploads. Keeping downstream tables separate means core migrations and your migrations never conflict.
  • Join on (user_id, dataset_id, day, workspace_id, machine_id) — the natural rollup key — and use a LEFT JOIN, since upload ordering between clients is not guaranteed.
  • Upsert on that key so a re-upload of the same day replaces rather than duplicates.
  • Reuse requireBearerAuth. A separate auth scheme would produce a different user_id and make the two datasets impossible to join.

Security

  • Bearer token auth — the extension sends your GitHub OAuth token. The server validates it against GET https://api.github.com/user and caches the result for 10 minutes. Bad tokens are cached for 1 minute to prevent API spam.
  • CSRF protection — the dashboard OAuth flow uses a short-lived state cookie.
  • Signed session cookies — dashboard sessions use HMAC-SHA256-signed cookies storing only {sub, iat, exp}. User data is re-read from SQLite on each request.
  • XSS prevention — all user-supplied strings are HTML-escaped before rendering.
  • No API keys — authentication is fully managed by GitHub OAuth; there are no API keys to issue, rotate, or leak.

Privacy note

Unlike the Azure Storage backend which supports anonymized and pseudonymous modes, the sharing server is identified mode only — every upload is linked to a GitHub user ID. Workspace and machine names are included or excluded based on the extension's shareWorkspaceMachineNames setting (off by default).

Identified storage does not grant peers access to those fields. The personal dashboard, identity-free Team Insights and named admin views have separate server-enforced access boundaries; see the data separation contract.

Data schema

users (
  id, github_id, github_login, github_name, avatar_url,
  created_at, last_seen_at, is_admin
)

usage_uploads (
  id, user_id, dataset_id, day, model,
  workspace_id, workspace_name, machine_id, machine_name, editor,
  input_tokens, output_tokens, interactions, schema_version,
  uploaded_at
)
-- UNIQUE(user_id, dataset_id, day, model, workspace_id, machine_id, editor)

Backup

The entire state is one SQLite file at /data/sharing.db (or the path in DB_PATH). Back it up with any tool that can copy files, or use sqlite3 /data/sharing.db .dump.