Agent Resources is a web application for managing Enclave characters, finding games, and more!
This project uses Bun as its runtime and package manager. Install Bun first if you don't already have it:
curl -fsSL https://bun.sh/install | bashThe first step differs depending on where your database will live. Pick the path that matches your setup and follow it through the remaining steps.
The easiest path: a free-tier project on supabase.com, no Docker required,
and bun run setup handles the full DB bootstrap.
-
Sign up at https://supabase.com and create a new project. Wait for it to finish provisioning (~2 min).
-
Create a
.envfrom the template and fill in your project's credentials from Project Settings → API and Project Settings → Database:cp .env.example .env $EDITOR .envRequired keys:
SUPABASE_URL— Project URL, e.g.https://abcdefgh.supabase.coSUPABASE_PUBLISHABLE_KEY— theanon/ publishable keySUPABASE_SECRET_KEY— theservice_role/ secret keySUPABASE_DB_PASS— the database password you set at project creationSUPABASE_DB_REGION— pooler region inaws-0-<region>form, from Project Settings → General. Only needed if your project is not inus-east-1.bun run scripts/probe-region.mjsdetects it.
Optional:
OPENAI_API_KEY,SITE_URL,GITHUB_TOKEN,GITHUB_ISSUE_REPO, and anySYSTEM_MESSAGE_*settings.SITE_URLis the canonical public origin (e.g.https://agent-resources.vip) used for the absolute URLs in/sitemap.xml; leave it unset in development and the sitemap uses the request's own scheme and host.GITHUB_TOKENenables the in-app bug reporter (see Reporting bugs from the app); without it the widget is not rendered. -
Run the setup script. It installs dependencies, applies all migrations in
supabase/migrations/, and seeds the navigation table — safe to re-run:bun run setup
-
Start the app:
bun run dev
Runs the entire Supabase stack in Docker on your machine — useful for offline development, frequent DB resets, or working against a throwaway environment.
Prerequisites: Docker and the
Supabase CLI (brew install supabase/tap/supabase, or npm i -D supabase, or the install
script).
-
Start the local stack (Postgres, GoTrue, PostgREST, etc.). The repository includes
supabase/config.toml, migrations, and seed data, so no separate initialization step is needed:supabase start
The first run pulls Docker images and may take a few minutes. When it finishes, copy the printed
API URL,anon key, andservice_role key— you'll need them in a moment. -
Create a
.envfrom the template. The local stack uses fixed defaults — the DB password is alwayspostgres:cp .env.example .env $EDITOR .envRequired keys for local dev:
SUPABASE_URL=http://127.0.0.1:54321SUPABASE_PUBLISHABLE_KEY=<anon key fromsupabase status>SUPABASE_SECRET_KEY=<service_role key fromsupabase status>SUPABASE_DB_PASS=postgres
-
Apply migrations and seeds via the Supabase CLI. (The bundled
bun run setupscript targets the Supabase cloud pooler and won't work against a local stack —supabase db resetis the local equivalent: it runs every file insupabase/migrations/plussupabase/seed.sql):supabase db reset
-
Seed the local app data (admin user, classes, and badges).
db resetonly loadsnav_items; this one idempotent command fills in the rest in the right order and is safe to re-run (each step is skipped when its table is already populated):bun run seed:local
It seeds a
dummy@testing.com/dummypasswordadmin, the class definitions, and the badge catalog. Badge art is pulled from the public prod storage bucket on first run (no credentials needed); override the source withBADGE_ART_SOURCE_URL. -
Start the app:
bun run dev
Useful flags on bun run setup (Path A only):
| Flag | Effect |
|---|---|
--with-admin |
Also seed a dummy@testing.com admin user (interactive confirmation by default; auto-confirmed with --yes). |
--with-classes |
Also seed the class definitions. |
--skip-install |
Skip bun install even if node_modules is missing. |
--skip-seed |
Skip applying supabase/seed.sql. |
--yes |
Non-interactive mode (auto-confirm prompts, assume "yes"). |
--dry-run |
Print what would happen without making any changes. |
If you don't have a real Supabase project and Docker is unavailable, sign up for the free tier on supabase.com — it's the simplest path with no infrastructure to manage.
If you prefer to do each step by hand, see Manual installation below.
-
Clone the repository:
git clone https://github.com/david-torres/agent-resources.git cd agent-resources -
Install the dependencies:
bun install
-
Make a copy of the
.env.examplefile and fill in the values. Database Setup will help you fill in the Supabase values.cp .env.example .env
-
Set up the database — see Database Setup below.
This project uses Supabase (hosted Postgres) for
storage and auth. The full schema is reconstructed by applying every file
in supabase/migrations/ in filename order. bun run setup does this
automatically; you can also use the Supabase CLI directly once your
project is linked.
supabase/
├── migrations/
│ ├── 20240101000000_baseline_schema.sql ← full schema for a fresh DB
│ ├── 20241213_collaborative_missions.sql
│ ├── …
│ └── 20260609000100_nav_items.sql ← dynamic-navigation table
└── seed.sql ← default nav_items rows
New schema changes go in a new timestamped file under supabase/migrations/
using the standard Supabase CLI convention (<14-digit-timestamp>_<name>.sql).
The migrations are the canonical schema.
An alternative to bun run setup for Path A, if you have the
Supabase CLI installed.
-
Link the local checkout to your cloud project. Your project ref is the subdomain of
SUPABASE_URL— forhttps://abcdefgh.supabase.coit isabcdefgh. You only need to do this once per checkout:supabase link --project-ref=<project-ref>
-
Apply the migrations:
supabase db push
db push applies every file in supabase/migrations/ and tracks them in
the supabase_migrations.schema_migrations table, just like the bundled
script. It does not apply supabase/seed.sql (the CLI reserves seed
files for supabase db reset); for nav items seeding either run bun run setup once or apply supabase/seed.sql manually.
For a local stack, bun run seed:local seeds everything supabase db reset
doesn't — an admin user, class definitions, and the badge catalog — in
dependency order and idempotently. It's the recommended one-command path, and it
runs load:aspirant-v1 for you, after seed:classes, whose rows that step forks.
The individual seeds can also be run directly:
bun run seed:admin # dummy@testing.com / dummypassword admin (dev only)
bun run seed:classes # class definitions — requires an admin profile first
bun run fetch:badges # download badge art from the public prod bucket
bun run seed:badges # upload art to the badges bucket + upsert catalog rows
bun run load:aspirant-v1 # the twelve ENCLAVE: Aspirant V1 classesseed:classes sets each class's created_by to the admin profile, so
seed:admin (or an existing admin) must run first. seed:badges reads art
from public/img/badges/, which fetch:badges populates.
load:aspirant-v1 is a required step, not an optional extra: seed:classes
does not create the twelve Aspirant V1 classes, and they hold 12 of the 24 class
ids in CORE_CLASS_UNLOCKS (util/starter-content.js), so
util/core-roster.integration.test.js fails until it has run. seed:local runs
it in the right place; the standalone command is here for an environment seeded
step by step. It loads the extracted book-content artifact and forks each class
off the row CORE_CLASS_UNLOCKS names for it (util/starter-content.js) -- the
id seed:classes gives that row -- which must therefore already exist, so it
follows seed:classes. It is idempotent: a second run resolves the same twelve
rows as updates and, finding nothing changed, issues no statement.
Extracted book content (the JSON artifacts load:aspirant-v1 and the
pre-release loader read) is never committed. It lives in the gitignored
private-data/ folder at the repo root -- override the location with the
CLASS_DATA_DIR env var -- and scripts/lib/books.mjs resolves each book's
artifact path from there. Tests that read an artifact skip (rather than fail)
when it's absent, which is the normal state for CI and a fresh clone.
You can see a visual representation of the database schema on the Supabase dashboard for your project under Database > Schema Visualizser.
You can check the rows of your table from the Supabase dashboard for your
project under Table Editor. If you ran seed:admin and seed:classes above,
you should see them in your database.
scripts/db-backup.sh runs pg_dump against your project's Supabase pooler
and writes a compressed dump to backups/. It derives the host and user from
SUPABASE_URL, SUPABASE_DB_REGION, and SUPABASE_DB_PASS in .env:
bun run db:backupTo start the application in development mode (auto-reload on file changes):
bun run devTo start the application in production mode:
bun run startThis project uses Bun for its test runner and module-mocking API, and Playwright for the browser tier. There are four tiers:
| Command | Tier | Requires |
|---|---|---|
bun run test |
Unit (jsdom, no DB) | nothing |
bun run test:http |
HTTP (Express + mocked models) | nothing |
bun run test:integration |
Integration (real Supabase, no browser) | supabase start |
bun run test:e2e |
End-to-end (Chromium) | supabase start + bun run seed:local |
bun run test is the default isolated, database-free suite. Route HTTP tests are
a separate tier because they bind a local ephemeral port.
The two database integration suites are intentionally excluded from the default
run. To run them, start local Supabase and reset it with the repository
migrations, configure your .env with the local credentials from
supabase status, then run:
supabase start
supabase db reset
bun run test:integrationtest:integration rejects a non-local SUPABASE_URL, so it cannot write to a
cloud project by accident.
The E2E tier drives a real Chromium against a real server and a real database. It covers behavior the other three tiers structurally cannot reach: htmx swaps, Alpine's settle phase, boosted navigation, and the back button.
supabase start
bun run seed:local
bun run test:e2eIt boots its own server on port 3100, so it runs alongside bun run dev. It
applies the same non-local SUPABASE_URL guard as the integration tier, seeds
and deletes its own rows under an e2e- prefix, and never resets your
database.
Open the report for a failed run with:
bunx playwright show-report e2e/report/htmlThe suite currently fails by design. Nine tests are deliberate characterizations of defects found on this branch and are expected to be red;
bun run test:e2etherefore exits non-zero even when nothing is wrong. Seedocs/superpowers/reports/2026-08-03-e2e-findings.mdfor what each one records and which are refactor regressions rather than pre-existing bugs.
Signed-in users get a small bug button in the bottom-right corner of every
page. It opens a reporter that files the report as an issue on the repository
named by GITHUB_ISSUE_REPO (default david-torres/agent-resources).
- A bug report can optionally attach a screenshot of the current page, the browser and screen details, and the recent browser console output. Each is a separate opt-in checkbox and all three start off; the screenshot is shown back as a preview before it can be sent.
- Screenshots are stored in the public
bug-screenshotsSupabase bucket so GitHub can render them in the issue, and the reporter is told as much. - The feature needs
GITHUB_TOKEN— a fine-grained token with Issues: read and write on that repository, and nothing else. Without it the button is not rendered andPOST /feedbackanswers503. POST /feedbackrequires a signed-in session and is rate limited to five reports per profile per ten minutes.
Long-lived personal access tokens can be created per user for agent integrations.
POST /profile/agent-tokenswith{ "name": "My agent" }creates a token and returns the raw token once.GET /profile/agent-tokenslists active tokens for the signed-in user.DELETE /profile/agent-tokens/:idrevokes a token.GET /api/agent/meverifies a token sent viaX-Agent-TokenorAuthorization: Bearer ....GET /api/agent/classesreturns the class list visible to that user.GET /api/agent/classes/:idreturns full details or teaser-only details based on the same access rules as the web app. Rows markedfree_play_accessexpose their plaintext/play content without granting any stored class PDF; direct and book entitlements continue to grant product access.
Server-side agent routes should use SUPABASE_SECRET_KEY so token-authenticated requests can evaluate ownership and unlock state without a Supabase browser session.
The same agent read API is exposed as an MCP server at /api/mcp (production: https://agent-resources.vip/api/mcp).
- Transport: Streamable HTTP, stateless, JSON responses. Only
POSTis supported;GETandDELETEanswer405. - Authentication: every request needs credentials; without them the server answers
401withWWW-Authenticate: Bearer resource_metadata="https://agent-resources.vip/.well-known/oauth-protected-resource/api/mcp". Two kinds are accepted:- OAuth (ChatGPT, Claude and other MCP clients): Supabase Auth's OAuth 2.1 server is the authorization server. Clients discover it from the protected-resource metadata, register dynamically, and send the user to
/oauth/consentto approve. Access tokens are Supabase JWTs and must carry aclient_idclaim — ordinary website session tokens are refused. - Agent tokens: the same
ar_pat_…personal tokens as/api/agent, sent asAuthorization: Bearer ar_pat_…orX-Agent-Token.
- OAuth (ChatGPT, Claude and other MCP clients): Supabase Auth's OAuth 2.1 server is the authorization server. Clients discover it from the protected-resource metadata, register dynamically, and send the user to
- Clients must send
Accept: application/json, text/event-stream.
Tools (all read-only):
getMe— the authenticated user, profile, and token.listClasses(rules_edition?, rules_version?, status?, is_player_created?)— class summaries visible to the user.getClass(id)— one class by UUID. Locked classes return teaser-only data, exactly asGET /api/agent/classes/:iddoes.searchCharacters(q?)— characters visible to the user, optionally matched by name.getCharacter(id)— one character by UUID.
Tool failures come back with isError: true and a text content of {"error":{"code":"…","message":"…"}}, where code is one of invalid_argument (e.g. a non-UUID id), not_found, or internal.
Locally this is on in supabase/config.toml ([auth.oauth_server]). On the hosted project, in the dashboard under Authentication → OAuth Server: enable the OAuth server, allow dynamic OAuth apps, set the authorization path to /oauth/consent, and check the Site URL is https://agent-resources.vip. Then confirm https://<project-ref>.supabase.co/.well-known/oauth-authorization-server/auth/v1 lists a registration_endpoint. The project must also sign tokens with asymmetric JWT signing keys (the legacy HS256 shared secret never appears in the JWKS), and SUPABASE_URL must be the same origin as the token issuer rather than a separate custom auth domain -- otherwise every OAuth token this endpoint receives is rejected.
To connect ChatGPT, add a connector in developer mode with the URL https://agent-resources.vip/api/mcp and OAuth authentication; ChatGPT registers itself and walks the user through the consent page.
An ar_pat_ token is the quickest way to try the endpoint from a terminal:
curl -s https://agent-resources.vip/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'Authorization: Bearer ar_pat_your_token_here' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"getMe","arguments":{}}}'const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
const { StreamableHTTPClientTransport } = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
const transport = new StreamableHTTPClientTransport(new URL('https://agent-resources.vip/api/mcp'), {
requestInit: { headers: { Authorization: 'Bearer ar_pat_your_token_here' } }
});
const client = new Client({ name: 'my-agent', version: '1.0.0' });
await client.connect(transport);
const { tools } = await client.listTools();
const result = await client.callTool({ name: 'listClasses', arguments: { is_player_created: false } });
await client.close();This project is built using:
New to the Enclave? Watch the video:
This project is licensed under the MIT License.
