GitHub Copilot Premium Requests Counter (counter.pepega.app)
Cloudflare Worker service for counting GitHub Copilot premium requests with a GitHub-authenticated dashboard and OBS widget output.
- Signs users in with GitHub OAuth (GitHub App user tokens).
- Stores encrypted GitHub access/refresh tokens in D1.
- Lets users configure monthly GitHub Copilot premium request quota and optional OBS widget title.
- Calculates “premium requests available today” from GitHub Copilot premium request usage.
- Serves an OBS-friendly JSON endpoint and a simple dashboard UI.
- Cloudflare Workers + Hono
- Cloudflare D1
- Plain HTML/CSS/JS frontend (served as static assets from Worker)
- TypeScript
valibotfor validation
- Node.js 20+
pnpm- Cloudflare account + D1 database
- GitHub App with user permission
Plan: read
- Install dependencies:
pnpm install-
Create local secrets file (optional) or use Wrangler secrets/local env.
-
Apply migrations locally:
pnpm d1:migrate:local- Start dev server:
pnpm dev- Open
http://localhost:8787
Required vars:
APP_BASE_URLGITHUB_APP_CLIENT_ID
Required secrets:
GITHUB_APP_CLIENT_SECRETSESSION_SECRETSECRETS_ENCRYPTION_KEY_B64
APP_BASE_URL=http://localhost:8787
GITHUB_APP_CLIENT_ID=replace_with_github_app_client_id
GITHUB_APP_CLIENT_SECRET=replace_with_github_app_client_secret
SESSION_SECRET=replace_with_session_secret
SECRETS_ENCRYPTION_KEY_B64=replace_with_base64_32_byte_keySECRETS_ENCRYPTION_KEY_B64 must be a base64-encoded 32-byte key.
Migrations live in migrations/ and are applied in order.
Key auth-related migrations:
migrations/003_github_app_auth_hard_cutover.sqlmigrations/004_github_only_auth_hard_cutover.sql(destructive reset to GitHub-only auth)
004_github_only_auth_hard_cutover.sql drops and recreates:
userssessionsusage_cache
This is intentional and will remove existing users/sessions/cache data.
Apply migrations:
pnpm d1:migrate:local
pnpm d1:migrate:remote- All API routes live under
/api/* - Mutating routes (
POST,PUT,DELETE,PATCH) require a validOriginheader matchingAPP_BASE_URL - Auth/session cookies are
HttpOnly,Secure,SameSite=Lax - API responses are JSON unless redirecting during OAuth flow
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable message"
}
}Starts GitHub OAuth flow.
Behavior:
- Creates OAuth
state - Sets
rc_oauth_state_githubcookie (10 min) - Redirects (
302) to GitHub authorize URL
Auth required: No
Completes GitHub OAuth flow and creates a session.
Query params:
codestateerrorerror_description
Behavior:
- Verifies
stateagainstrc_oauth_state_github - Exchanges
codefor expiring GitHub App user tokens (access + refresh) - Fetches GitHub user profile (
/user) - Creates/updates user by
github_user_id - Stores encrypted tokens + GitHub login metadata
- Clears cached OBS payload for the user
- Creates session and sets
rc_session - Redirects to
/?auth=connectedon success - Redirects to
/?authError=cancelledonaccess_denied - Redirects to
/?authError=stateon state mismatch - Redirects to
/?authError=failedon other errors
Auth required: No (this endpoint creates auth session)
Destroys current session and clears rc_session cookie.
Response:
{
"ok": true
}Auth required: No (safe to call even without active session)
Deletes the current user and related data (via foreign keys / cascades).
Response:
{
"ok": true
}Also clears rc_session cookie.
Auth required: Yes
Returns dashboard/profile state for the current signed-in user.
Response example:
{
"cacheUpdatedAt": "2026-02-23T10:00:00.000Z",
"dashboardData": {
"dailyTarget": 120,
"daysRemaining": 7,
"display": "137/120",
"monthRemaining": 960,
"todayAvailable": 137
},
"githubAuthStatus": "connected",
"monthlyQuota": 3000,
"obsTitle": "Copilot premium requests available today",
"obsUrl": "https://counter.pepega.app/obs?uuid=...",
"user": {
"githubLogin": "octocat",
"githubUserId": "12345678"
}
}Notes:
githubAuthStatusis one ofmissing,connected,reconnect_requireddashboardDatamay benullif quota or GitHub auth is not ready / temporarily faileddashboardData.displayformat is<todayAvailable>/<dailyTarget>;todayAvailablemay be negative when today's spend exceeds the targetcacheUpdatedAtmay benull
Auth required: Yes
Updates user settings.
Request body (at least one field required):
{
"monthlyQuota": 3000,
"obsTitle": "Copilot premium requests available today"
}Rules:
monthlyQuota: positive integer, max1_000_000_000obsTitle: string, max 120 chars; empty string resets to default title
Response:
{
"ok": true
}Auth required: Yes
Regenerates the authenticated user's OBS UUID.
Response:
{
"obsUrl": "https://counter.pepega.app/obs?uuid=..."
}Auth required: Yes
Returns OBS widget payload for a public OBS UUID.
Requirements:
- valid
uuid - user exists
- user has
monthly_quota - user has stored GitHub auth tokens
Behavior:
- uses cached data when fresh
- fetches GitHub and refreshes cache when needed
- returns
404if widget is not configured yet - may return
503on upstream GitHub/API issues
Response example:
{
"dailyTarget": 120,
"daysRemaining": 7,
"display": "-15/120",
"monthRemaining": 960,
"title": "Copilot premium requests available today",
"todayAvailable": -15,
"updatedAt": "2026-02-23T10:00:00.000Z"
}Auth required: No
The Worker validates Origin on mutating routes (POST, PUT, DELETE, PATCH).
If you call APIs manually (curl/Postman/browser extension), use:
Origin: <APP_BASE_URL origin>
Otherwise the API returns 403.
- UI page:
/ - OBS page:
/obs?uuid=<uuid> - OBS data API:
/api/obs-data?uuid=<uuid>
The OBS page loads widget data from /api/obs-data and can be added as a Browser Source in OBS.
- GitHub access/refresh tokens are never returned by API responses
- GitHub access/refresh tokens are encrypted with AES-GCM before storing in D1
- Session tokens are hashed before storing in D1
- OAuth state cookie (
rc_oauth_state_github) is short-lived andHttpOnly/Secure - Session cookie (
rc_session) isHttpOnly/Secure
Deploy production Worker:
pnpm deploySet required secrets first (example):
pnpx wrangler secret put SESSION_SECRET --env production
pnpx wrangler secret put GITHUB_APP_CLIENT_SECRET --env production
pnpx wrangler secret put SECRETS_ENCRYPTION_KEY_B64 --env productionConfigure your GitHub App with:
- User authorization callback URL:
https://<your-domain>/api/auth/github/callback - User permissions:
Plan: Read
The app uses GitHub App user-to-server OAuth tokens (expiring access + refresh tokens).
.
├── migrations/
│ ├── 001_init.sql
│ ├── 002_add_obs_title.sql
│ ├── 003_github_app_auth_hard_cutover.sql
│ └── 004_github_only_auth_hard_cutover.sql
├── public/
│ ├── index.html
│ ├── index.js
│ ├── obs.html
│ └── obs.js
├── src/
│ ├── lib/
│ │ ├── auth.ts
│ │ ├── cache.ts
│ │ ├── crypto.ts
│ │ ├── data-loader.ts
│ │ ├── env.ts
│ │ ├── errors.ts
│ │ ├── github-auth.ts
│ │ ├── github.ts
│ │ └── schemas.ts
│ └── worker.ts
├── wrangler.jsonc
└── README.md
Use a base64-encoded 32-byte key.
Example (Node.js):
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"If you rotate SECRETS_ENCRYPTION_KEY_B64, old encrypted GitHub tokens become undecryptable.
Users must sign in with GitHub again so new tokens are stored with the new key.
Make sure the request includes a valid Origin header equal to APP_BASE_URL origin.
Reset local migrations and re-apply:
rm -rf .wrangler/state
pnpm d1:migrate:local