openvibers/openvibe.directory Commits

AGENTS.md: the context an agent starts from (#14)

OP OpenVibers committed 1 days ago · ea058799e5 · 2 files, +146 −0

Changes

ddiff --git a/AGENTS.md b/AGENTS.md
nnew file mode 100644
iindex 0000000..bf672c6
---- /dev/null
++++ b/AGENTS.md
@@@ -0,0 +1,145 @@
++# AGENTS.md — OpenVibe.Directory
++
++## Project overview
++
++Source-linked directories for the OpenVibe network: OpenVibe sites, tools, AI models and providers,
++open-source projects, local businesses from OpenStreetMap, and opt-in people listings. Every entry
++carries a source and a last-checked date. Node.js/Express, server-rendered pages, one small
++PostgreSQL table for people listings.
++
++- Origin `https://openvibe.directory`, port `5281`, unit `openvibe-directory.service`,
++ release root `/opt/openvibe.directory`.
++- Public sources refresh at startup and every six hours; a failed source keeps its last successful
++ in-memory snapshot and the failure is visible in `/api/v1/kinds`.
++
++## What it owns
++
++- `GET /`, `/kinds/:kind`, `/search`, `/entries/:kind/:slug` — HTML, with `Accept: text/markdown` on
++ entry pages.
++- `GET /api/v1/kinds`, `/api/v1/entries`, `/api/v1/entries/:kind/:slug` — the public read API.
++- `/api/v1/people/me` and `/people/me` — a signed-in person's own listing (get, put, delete).
++- `robots.txt`, `sitemap.xml`, `llms.txt`, JSON-LD, `/release.json`, `/shared/*`, `/api/health`,
++ `/api/ready`.
++- The `directory_people` table and its validation.
++
++It does not own the sources: it reads Network's frame, Tools, Exchange models, GitHub's public search
++and OpenStreetMap. It does not publish anything outside this site.
++
++## Where the data comes from
++
++[server/sources.js](server/sources.js) `URLS`/`kinds`:
++
++- `https://openvibe.network/api/frame?host=openvibe.network` — OpenVibe sites.
++- `https://openvibe.tools/api/v1/tools` — tools.
++- `https://openvibe.exchange/api/v1/models` — AI models and providers.
++- `https://api.github.com/search/repositories?q=org%3AOpenVibers+archived%3Afalse` — projects.
++- `https://api.openstreetmap.org/api/0.6/map?bbox=…` — a **bounded San Francisco pilot** under
++ ODbL 1.0.
++- People come from PostgreSQL, not a feed.
++
++Every entry is normalised (`entry()`, `slug()`, `safeUrl()`): a missing id/name/source or a non-https
++source is dropped rather than guessed.
++
++## Repository map
++
++- **Entry:** [server/index.js](server/index.js) — builds the source store, opens the people store when
++ `DATABASE_URL` is set, listens on `PORT || 5281`.
++- **App/routes:** [server/app.js](server/app.js) — security headers, static `/public`, `/shared`,
++ `/auth`, health/ready, release, the v1 API, pages, people form, 404.
++- **Sources:** [server/sources.js](server/sources.js) — fetch/refresh, normalisation, search.
++- **People:** [server/people.js](server/people.js) — `createPeopleStore` (migration + CRUD), `validate`,
++ `publicEntry`, `writeGuard`, `createAuth`.
++- **Views:** [server/views.js](server/views.js) — HTML/markdown, escaping, JSON-LD.
++- **Browser:** [public/people.js](public/people.js).
++- **Schema:** [migrations/0001_people.sql](migrations/0001_people.sql).
++- **Deploy:** [deploy/systemd/openvibe-directory.service](deploy/systemd/openvibe-directory.service),
++ [deploy/nginx/openvibe.directory.conf](deploy/nginx/openvibe.directory.conf).
++
++## Commands
++
++```bash
++npm ci
++node server/index.js # http://127.0.0.1:5281; start script is `npm start`
++npm test # node --test test/*.test.js
++ov test # the harness's sharded run of what the change reaches
++node --check <file.js>
++```
++
++There is no `dev` script and no build step. Without `DATABASE_URL` the directories still serve from
++the feeds; the people routes answer `503 people.unavailable`.
++
++## Database and migrations
++
++One table, `directory_people`, created from [migrations/0001_people.sql](migrations/0001_people.sql)
++**at startup** by [server/people.js](server/people.js), not by a boot migration runner: the file is
++read and applied through the **owner** connection (`DATABASE_DIRECT_URL`), while reads and writes use
++the runtime connection (`DATABASE_URL`). The statement is `CREATE TABLE IF NOT EXISTS`, so startup is
++idempotent. Keep that split: the test asserts exactly one owner call (the DDL) and then a runtime
++`SELECT`. Listing text is capped at 500 characters, names at 100, websites must be `https:` with no
++credentials, and `visible` defaults to **false**.
++
++## Auth and the people listing
++
++- Sign-in is OpenVibe.Network SSO via `openvibe-sdk/sso` ([server/people.js](server/people.js)
++ `createAuth`); without `OV_OAUTH_CLIENT_ID`/`SECRET` the auth object is absent and the people routes
++ are disabled.
++- Only the person's own row: the subject comes from the session (`usr_…`); never take an id from the
++ query or body.
++- `writeGuard` requires the same-origin `Origin` (or no Origin plus a Bearer token) and
++ `application/json` (DELETE excepted); invalid requests answer 403/415.
++- An entry is private until the person explicitly publishes it (`visible: true`).
++
++## Frontend loading model
++
++Server-rendered HTML from [server/views.js](server/views.js) with `openvibe-shared/kit`,
++`/thread`, `/shell` and `/serve`; **no SPA, no bundler**. Static files come from `public/`;
++`/shared/*` is served from the pinned `openvibe-shared` node_modules by `openvibe-shared/serve`.
++The only page script is [public/people.js](public/people.js). The home page redirects a signed-out
++browser to silent SSO unless `?sso=none` or the guest hint cookie is set — keep that for crawlers
++(`bot|crawl|spider|slurp` must not be redirected).
++
++## Deploy
++
++Unit [deploy/systemd/openvibe-directory.service](deploy/systemd/openvibe-directory.service) runs
++`/opt/openvibe.directory/server/index.js` on `127.0.0.1:5281`, env `/etc/openvibe/directory.env`;
++vhost [deploy/nginx/openvibe.directory.conf](deploy/nginx/openvibe.directory.conf). The repository does
++not document an `ovhost deploy` command — change the unit/vhost with the host's own installer rather
++than inventing one. No secret belongs in a file or a log.
++
++## Tests
++
++`npm test` runs Node's built-in runner over [test/directory.test.js](test/directory.test.js) and
++[test/people.test.js](test/people.test.js). They pin: OSM phone-tag omission and no age/city/phone
++fields; unlisted by default; the owner-connection DDL / runtime-read split; entry validation; the
++public pages, markdown negotiation, sitemap and llms output. `ov test` runs the same suite.
++
++## Conventions
++
++- CommonJS; **compact single-line JS** is the style in `server/app.js`, `server/sources.js` and
++ `server/people.js`; the newer view helpers use 4 spaces. Match the file you edit.
++- Escape every interpolated value (`views.esc`); embed JSON with the `<`-escaping `json()`.
++- Errors are small JSON bodies (`{ error: 'entry.not_found' }`) or problem-style statuses; do not leak
++ upstream text.
++- Source URLs are `https:` only; never invent a source, a checked date or a phone number.
++
++## Pitfalls
++
++- **Unverified OSM phone tags are omitted.** A business number needs a business-owned source before
++ publication; do not re-add phone fields.
++- **No private-person searches or reverse lookups.** People listings are claim/edit/unpublish/delete
++ of one's own row only.
++- **The SF business pilot is bounded and ODbL 1.0.** Do not widen the bounding box or change the
++ licence without the owner.
++- **A failed source keeps its last snapshot** and reports the failure; it never empties the directory
++ or silently serves stale data as fresh.
++- **The startup migration uses the owner connection**; using `DATABASE_URL` for DDL will fail in
++ production and breaks the test.
++- **`visible` defaults false.** Publishing is an explicit act by the person.
++- **Crawler behavior:** home may redirect signed-out browsers to silent SSO; bots must not be
++ redirected and must still see the page.
++
++## Documentation
++
++- [README.md](README.md) — the product brief, sources, licence and the database split.
++- [migrations/0001_people.sql](migrations/0001_people.sql) — the only table.
++- OpenVibe.Contracts, if a directory manifest is added, is the place for the public schema.
ddiff --git a/CLAUDE.md b/CLAUDE.md
nnew file mode 100644
iindex 0000000..43c994c
---- /dev/null
++++ b/CLAUDE.md
@@@ -0,0 +1 @@