openvibers/openvibe.tools Commits
AGENTS.md: the context an agent starts from (#315)
Changes
nnew file mode 100644@@@ -0,0 +1,123 @@++# AGENTS.md — OpenVibe.Tools++++## Project overview++++OpenVibe.Tools is the network's tools suite: hundreds of single-purpose tools (images, text, audio, docs,++maps, video) at `openvibe.tools` and `*.openvibe.tools`, each open by API. It is a multi-app monorepo —++a gateway plus seven satellites — that share a registry, a run API, a job runtime and an anti-abuse++guard.++++```++apps/gateway openvibe.tools, net./dev./pastes. and the tool aliases :4001++apps/maps maps.openvibe.tools :4010++apps/food food.openvibe.tools (the page moved to OpenVibe.Food) :4011++apps/img img.openvibe.tools + format aliases :4012++apps/yt yt.openvibe.tools :4013++apps/audio audio.openvibe.tools + effect aliases :4014++apps/text text./logo.openvibe.tools + aliases :4015++apps/docs docs./pdf.openvibe.tools :4016++apps/_shared openvibe-tools-shared: host roles, internal auth, jobs, guard, egress++```++++- **Registry:** `apps/gateway/server/registry` is the one list of tools/families; each has a canonical++ host, an optional short host and aliases that 301. Descriptors are `tools.tool@1`.++- **Run API:** `POST /api/v1/tools/:id/run` on the gateway and on img/audio/docs for their tools++ (`tools.run-request@1`, JSON or multipart, `wait_ms`, `idempotency_key`); SDK `openvibe-sdk/tools`.++- **Jobs:** `apps/_shared/jobs/` plus the gateway jobs facade and each satellite's job type; job++ records, abuse logs, revocations, analytics and the outbox are in PostgreSQL.++- **Guard:** `apps/_shared/guard/` (callers, quotas, sniffing, ffmpeg hardening, egress throttles,++ abuse log) and `apps/_shared/egress.js` (SSRF-safe fetch; public addresses only, checked after DNS,++ redirect hops re-checked).++- **Pages:** all server-rendered (the gateway index/family/tool pages, catalogue, sitemap/llms);++ browser tools ship their own JS, no bundler at the service level. `openvibe-shared` is pinned per app.++++## Owns, does not own++++Owns the tool registry (families, tools, descriptors, canonical/short/alias hosts), the catalog and++every tool page on `openvibe.tools` and `*.openvibe.tools`, the run API and jobs runtime (files, SSE++progress), the guard, the `tools.*` events, Tools' Search documents and the `tools.usage` user module.++++Does not own: identity/sign-in (Network), pastes (Community; `pastes.openvibe.tools` hands over),++result storage (Media keeps job results), custom-domain records (Network's domains admin — the gateway++merges the owner's host overrides every minute).++++## Who calls it, what it calls++++People use the pages and the API; services and developers use `openvibe-sdk/tools` (`list`, `get`,++`schema`, `run`, `jobs`). The run API is the public capability (`tools.tool.run`; `tools.net.probe` for++network probes; `tools.tool.read` for the registry), with per-tier quotas and per-actor limits. Tools++calls Network (SSO, JWKS, registry/domains admin, notification/usage), Media (job results), Community++(pastes hand-over), Events (outbox and `host.release.published`) and the outside hosts a tool needs++through the SSRF guard. Billing receives Tools' job usage readings.++++## Auth model++++The gateway is the OAuth client (`client_id: tools`): `/auth/login` → Network authorize, `/auth/callback`++→ server-side code exchange → sets `ov_token` (JS-readable, `Domain=.openvibe.tools`, SameSite=Lax,++Secure) and `ov_refresh` (httpOnly, `Path=/auth`). A satellite never talks OAuth: it reads `ov_token`++and verifies offline with the Network JWKS. `next` accepts only a relative path, `https://*.openvibe.tools`++or `https://openvibe.network/...`. Every caller is identified (service principal, user, session or++address) and held to quotas/limits; every tool reaching a visitor-chosen host or URL goes through the++SSRF guard. `trust proxy` is one loopback hop.++++## Database and migrations++++One PostgreSQL database for all eight apps through `openvibe-sdk/db`; `DATABASE_DIRECT_URL` applies++the immutable `migrations/` at boot, `DATABASE_URL` serves. Development/tests use PGlite through the++same API. Transient guard throttle counters and the salt live in Valkey (`VALKEY_URL`), with++process-local state when unset. Migrations only add — never edit one that has run, and nothing may++block a rollback.++++## Deploy++++`sudo ovhost deploy tools` via `deploy/scripts/deploy.sh` (strategy `multi-app`; flags `--wait-idle`,++`--restart`, `--rollback`, `DRY_RUN=1`). The wrapper pulls as the checkout owner, installs in changed++apps (`apps/_*` are packages, not apps), checks every dependency resolves and the job runtime/guard++load, restarts every `openvibe-tools*` unit, waits for the gateway `/api/ready` and `/release.json` to++name the new sha with every unit active, rolls back otherwise, then announces the release. Production++path `/opt/openvibe.tools`; env `/etc/openvibe/tools.env` (0600, shared by all units; per-app `PORT` is++in each systemd unit, so tools.env must NOT define PORT). Units: `openvibe-tools.service` and++`openvibe-tools-<name>.service` (`apps/<name>/deploy/systemd/`); nginx satellites have specific++`server_name` blocks and the gateway's wildcard `*.openvibe.tools` catches the rest. New custom domain:++`sudo deploy/scripts/add-custom-domain.sh <domain>`.++++## Commands and tests++++```bash++npm ci && npm run install:all++npm run start:gateway # http://localhost:4001++npm run start:img # any satellite, e.g. :4012++npm test # syntax-check every server file + every apps/*/test/*.test.js++npm run test:pg # the suite against PostgreSQL + Valkey++npm run test:discovery # catalog/hosts/discovery/run-api/perf subset++npm run test:worker # the worker runtime tests++```++++`npm test` runs each file in its own process and covers the job runtime end to end (restart, reattach,++cancel, idempotency, owner scoping, SSE resume, pruning, Media results), real img/audio/docs processes++killed mid-job, canonical hosts, the registry-driven catalog, `registry-consistency.test.js` (every++catalogue tool resolves to a real operation/page or is marked unavailable), descriptors against the++contracts and code, every browser tool's engine against its output schema, the run API on gateway/img/++docs, the jobs facade and the pinned SDK client. Audio tests skip without `ffmpeg`; docs/img check the++503 path and run real qpdf/poppler/libheif work when installed. `sdk-client.test.js` needs an++OpenVibe.SDK checkout (`OV_SDK_DIR`) and says it skipped without one.++++## Conventions and pitfalls++++- **The registry is the source of truth:** adding a tool means a descriptor, a registry entry, a route/++ operation and a page that resolve to each other — `registry-consistency.test.js` fails otherwise.++ Never hand-write a catalog page that the registry does not know.++- **Descriptor first:** `execution` (`client`/`sync`/`job`), `api`, `run`, input schema, files, output,++ limits, auth, `quotaClass`, `cost`, `egress`, hosts and docs are one contract; the code must match it.++- **Every outbound fetch of a visitor-chosen URL uses `apps/_shared/egress.js`.** Uploads are sniffed++ against the descriptor before a job sees them; ffmpeg runs hardened. A new tool must not call `fetch`++ directly.++- **Idempotency:** a run with the same key replays; a different request under the key is a conflict.++ Jobs are owner-scoped and survive reattach after a restart.++- **One shared env file:** a `PORT` in `tools.env` will fight the per-unit ports. Secrets++ (`OV_OAUTH_CLIENT_SECRET`, `TOOLS_EVENTS_SECRET`, NET_IPINFO_TOKEN, RIDB/NPS keys) live only there.++- **YouTube refusal is infrastructure, not code:** `/api/health` reports `youtube: ok|blocked|unknown`++ and the page shows a banner; a proxy is set in `tools.env` and applied by restarting++ `openvibe-tools-yt`. Do not add a bypass that ignores the block.++- **`apps/_shared` is a versioned package:** every app declares the range it needs and refuses to boot++ when the checkout does not satisfy it.nnew file mode 100644@@@ -0,0 +1 @@
new file mode 100644
new file mode 100644
@@ -0,0 +1,123 @@
@@ -0,0 +1,123 @@
+# AGENTS.md — OpenVibe.Tools
+
+## Project overview
+
+OpenVibe.Tools is the network's tools suite: hundreds of single-purpose tools (images, text, audio, docs,
+maps, video) at `openvibe.tools` and `*.openvibe.tools`, each open by API. It is a multi-app monorepo —
+a gateway plus seven satellites — that share a registry, a run API, a job runtime and an anti-abuse
+guard.
+
+```
+apps/gateway openvibe.tools, net./dev./pastes. and the tool aliases :4001
+apps/maps maps.openvibe.tools :4010
+apps/food food.openvibe.tools (the page moved to OpenVibe.Food) :4011
+apps/img img.openvibe.tools + format aliases :4012
+apps/yt yt.openvibe.tools :4013
+apps/audio audio.openvibe.tools + effect aliases :4014
+apps/text text./logo.openvibe.tools + aliases :4015
+apps/docs docs./pdf.openvibe.tools :4016
+apps/_shared openvibe-tools-shared: host roles, internal auth, jobs, guard, egress
+```
+
+- **Registry:** `apps/gateway/server/registry` is the one list of tools/families; each has a canonical
+ host, an optional short host and aliases that 301. Descriptors are `tools.tool@1`.
+- **Run API:** `POST /api/v1/tools/:id/run` on the gateway and on img/audio/docs for their tools
+ (`tools.run-request@1`, JSON or multipart, `wait_ms`, `idempotency_key`); SDK `openvibe-sdk/tools`.
+- **Jobs:** `apps/_shared/jobs/` plus the gateway jobs facade and each satellite's job type; job
+ records, abuse logs, revocations, analytics and the outbox are in PostgreSQL.
+- **Guard:** `apps/_shared/guard/` (callers, quotas, sniffing, ffmpeg hardening, egress throttles,
+ abuse log) and `apps/_shared/egress.js` (SSRF-safe fetch; public addresses only, checked after DNS,
+ redirect hops re-checked).
+- **Pages:** all server-rendered (the gateway index/family/tool pages, catalogue, sitemap/llms);
+ browser tools ship their own JS, no bundler at the service level. `openvibe-shared` is pinned per app.
+
+## Owns, does not own
+
+Owns the tool registry (families, tools, descriptors, canonical/short/alias hosts), the catalog and
+every tool page on `openvibe.tools` and `*.openvibe.tools`, the run API and jobs runtime (files, SSE
+progress), the guard, the `tools.*` events, Tools' Search documents and the `tools.usage` user module.
+
+Does not own: identity/sign-in (Network), pastes (Community; `pastes.openvibe.tools` hands over),
+result storage (Media keeps job results), custom-domain records (Network's domains admin — the gateway
+merges the owner's host overrides every minute).
+
+## Who calls it, what it calls
+
+People use the pages and the API; services and developers use `openvibe-sdk/tools` (`list`, `get`,
+`schema`, `run`, `jobs`). The run API is the public capability (`tools.tool.run`; `tools.net.probe` for
+network probes; `tools.tool.read` for the registry), with per-tier quotas and per-actor limits. Tools
+calls Network (SSO, JWKS, registry/domains admin, notification/usage), Media (job results), Community
+(pastes hand-over), Events (outbox and `host.release.published`) and the outside hosts a tool needs
+through the SSRF guard. Billing receives Tools' job usage readings.
+
+## Auth model
+
+The gateway is the OAuth client (`client_id: tools`): `/auth/login` → Network authorize, `/auth/callback`
+→ server-side code exchange → sets `ov_token` (JS-readable, `Domain=.openvibe.tools`, SameSite=Lax,
+Secure) and `ov_refresh` (httpOnly, `Path=/auth`). A satellite never talks OAuth: it reads `ov_token`
+and verifies offline with the Network JWKS. `next` accepts only a relative path, `https://*.openvibe.tools`
+or `https://openvibe.network/...`. Every caller is identified (service principal, user, session or
+address) and held to quotas/limits; every tool reaching a visitor-chosen host or URL goes through the
+SSRF guard. `trust proxy` is one loopback hop.
+
+## Database and migrations
+
+One PostgreSQL database for all eight apps through `openvibe-sdk/db`; `DATABASE_DIRECT_URL` applies
+the immutable `migrations/` at boot, `DATABASE_URL` serves. Development/tests use PGlite through the
+same API. Transient guard throttle counters and the salt live in Valkey (`VALKEY_URL`), with
+process-local state when unset. Migrations only add — never edit one that has run, and nothing may
+block a rollback.
+
+## Deploy
+
+`sudo ovhost deploy tools` via `deploy/scripts/deploy.sh` (strategy `multi-app`; flags `--wait-idle`,
+`--restart`, `--rollback`, `DRY_RUN=1`). The wrapper pulls as the checkout owner, installs in changed
+apps (`apps/_*` are packages, not apps), checks every dependency resolves and the job runtime/guard
+load, restarts every `openvibe-tools*` unit, waits for the gateway `/api/ready` and `/release.json` to
+name the new sha with every unit active, rolls back otherwise, then announces the release. Production
+path `/opt/openvibe.tools`; env `/etc/openvibe/tools.env` (0600, shared by all units; per-app `PORT` is
+in each systemd unit, so tools.env must NOT define PORT). Units: `openvibe-tools.service` and
+`openvibe-tools-<name>.service` (`apps/<name>/deploy/systemd/`); nginx satellites have specific
+`server_name` blocks and the gateway's wildcard `*.openvibe.tools` catches the rest. New custom domain:
+`sudo deploy/scripts/add-custom-domain.sh <domain>`.
+
+## Commands and tests
+
+```bash
+npm ci && npm run install:all
+npm run start:gateway # http://localhost:4001
+npm run start:img # any satellite, e.g. :4012
+npm test # syntax-check every server file + every apps/*/test/*.test.js
+npm run test:pg # the suite against PostgreSQL + Valkey
+npm run test:discovery # catalog/hosts/discovery/run-api/perf subset
+npm run test:worker # the worker runtime tests
+```
+
+`npm test` runs each file in its own process and covers the job runtime end to end (restart, reattach,
+cancel, idempotency, owner scoping, SSE resume, pruning, Media results), real img/audio/docs processes
+killed mid-job, canonical hosts, the registry-driven catalog, `registry-consistency.test.js` (every
+catalogue tool resolves to a real operation/page or is marked unavailable), descriptors against the
+contracts and code, every browser tool's engine against its output schema, the run API on gateway/img/
+docs, the jobs facade and the pinned SDK client. Audio tests skip without `ffmpeg`; docs/img check the
+503 path and run real qpdf/poppler/libheif work when installed. `sdk-client.test.js` needs an
+OpenVibe.SDK checkout (`OV_SDK_DIR`) and says it skipped without one.
+
+## Conventions and pitfalls
+
+- **The registry is the source of truth:** adding a tool means a descriptor, a registry entry, a route/
+ operation and a page that resolve to each other — `registry-consistency.test.js` fails otherwise.
+ Never hand-write a catalog page that the registry does not know.
+- **Descriptor first:** `execution` (`client`/`sync`/`job`), `api`, `run`, input schema, files, output,
+ limits, auth, `quotaClass`, `cost`, `egress`, hosts and docs are one contract; the code must match it.
+- **Every outbound fetch of a visitor-chosen URL uses `apps/_shared/egress.js`.** Uploads are sniffed
+ against the descriptor before a job sees them; ffmpeg runs hardened. A new tool must not call `fetch`
+ directly.
+- **Idempotency:** a run with the same key replays; a different request under the key is a conflict.
+ Jobs are owner-scoped and survive reattach after a restart.
+- **One shared env file:** a `PORT` in `tools.env` will fight the per-unit ports. Secrets
+ (`OV_OAUTH_CLIENT_SECRET`, `TOOLS_EVENTS_SECRET`, NET_IPINFO_TOKEN, RIDB/NPS keys) live only there.
+- **YouTube refusal is infrastructure, not code:** `/api/health` reports `youtube: ok|blocked|unknown`
+ and the page shows a banner; a proxy is set in `tools.env` and applied by restarting
+ `openvibe-tools-yt`. Do not add a bypass that ignores the block.
+- **`apps/_shared` is a versioned package:** every app declares the range it needs and refuses to boot
+ when the checkout does not satisfy it.
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@