openvibers/openvibe.tips Commits
openvibe-shared v4.108.0 (was v4.107.4) (#186)
Changes
@@@ -473,5 +473,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [<!-- versions:start -->- openvibe-contracts: v0.222.0- openvibe-sdk: v0.82.0--- openvibe-shared: v4.107.4++- openvibe-shared: v4.108.0<!-- versions:end -->@@@ -11,7 +11,7 @@"database": "PostgreSQL 18 through PgBouncer (transaction mode) as the system of record, Valkey 9 for shared state (per-actor limits, overlay fan-out, overlay stream slots), ADR-035: database ov_tips from OpenVibe.Host's data role, schema in migrations/ (0001_initial expand, 0002_drop_legacy_import contract), async on openvibe-sdk/db v0.28.0, money paths serializable with row locks, PostgreSQL outbox and inbox.","contracts": "openvibe-contracts v0.222.0","sdk": "openvibe-sdk v0.82.0",-- "shared": "openvibe-shared v4.107.4",++ "shared": "openvibe-shared v4.108.0","notes": "Runtime, API, SSR pages, overlays, the Billing settlement consumer and the events outbox are built, tested and deployed, together with /metrics (loopback only), truthful /api/ready (Billing and Events checks), supporter privacy (anonymous, hidden amount, private message), goal and supporter-page settings, export and erasure (tips.interaction.erased), moderation (word filter, hide/show, invited moderators, tips.interaction.moderated) and per-actor limits. Capabilities (18 tips.*) and payload schemas are in openvibe-contracts. Billing is the money ledger since the cutover (2026-10-17). A written internal threat review (docs/threat-review.md) with its fixes and test/security.test.js. Paid sounds (creators upload up to 12, supporters pay to play one on the overlay) since 2026-10-17. Not done: an independent security review.","features": ["tips, paid messages, TTS and media requests settled by OpenVibe.Billing (never Tips' own money)",@@@ -15,7 +15,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0","pg": "^8.23.0"},"devDependencies": {@@@ -1162,9 +1162,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.107.4",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",-- "integrity": "sha512-tThN0m3KMncEHVcBOvE+ysHjsZTlVIDLG1P+/i2Mulmxfc/AUpebAXhpWWrut50OChZ8o+HeePxsSrPI8mUQOQ==",++ "version": "4.108.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",++ "integrity": "sha512-NPzwjxpzGIpFpIm4uw3+VwHfsxbJ2+cD9yuhRzSwolSwqv2kCmwaVtHJ0DA9gKO3K8PCBfIP3UpKzpuSbYEJ+g==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -27,7 +27,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0","pg": "^8.23.0"},"engines": {@@@ -123,21 +123,25 @@ function createWebRoutes({ domain, config, layout, userAuth, liveNow = { list: ares.type('application/xml').set('Cache-Control', cache.htmlHeaders({ maxAge: 900 })).send(seo.sitemapXml(urls));});// /llms.txt (llmstxt.org): a plain-markdown map of the site for language-model crawlers.++ // AGT-5: each of this site's own pages is linked at its Markdown twin (markdown: true, base: the site origin).r.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({name: SITE_NAME,++ markdown: true,++ base: config.baseUrl,summary: SITE_SUMMARY,details: 'A creator page lists the goals and a tip form that works without JavaScript; tips settle through Billing, and Tips owns the interaction, the goals and the overlay alerts. The home page lists creators whose pages are switched on. Reading any public page needs no account; tipping needs a signed-in supporter, and the dashboard, receipts, moderation, overlays, sign-in and the API are per-person and never listed here.',sections: [{ title: 'Start here', links: [{ title: 'Creators on OpenVibe.Tips', url: `${config.baseUrl}/`, note: 'every creator with their page switched on' },-- { title: 'What shipped on OpenVibe.Tips', url: `${config.baseUrl}/updates` },-- ] },-- { title: 'Machine-readable', links: [-- { title: 'Sitemap', url: `${config.baseUrl}/sitemap.xml`, note: 'the public pages, with lastmod' },-- { title: 'robots.txt', url: `${config.baseUrl}/robots.txt` },-- { title: 'llms-full.txt', url: `${config.baseUrl}/llms-full.txt`, note: 'this same map with the full text of each public page' },] },],++ // llmstxt.org's "## Optional": what an agent may skip when it needs a shorter context.++ optional: [++ { title: 'What shipped on OpenVibe.Tips', url: `${config.baseUrl}/updates` },++ { title: 'Sitemap', url: `${config.baseUrl}/sitemap.xml`, note: 'the public pages, with lastmod' },++ { title: 'robots.txt', url: `${config.baseUrl}/robots.txt` },++ { title: 'llms-full.txt', url: `${config.baseUrl}/llms-full.txt`, note: 'this same map with the full text of each public page' },++ ],})));// /llms-full.txt: the /llms.txt header plus the full plain text of every public page. The creator// pages are the same rows the sitemap lists, and the legal documents are the shared ones; nothing@@@ -144,8 +144,18 @@ const { boot, check, done } = require('./helpers/app');assert.strictEqual(llms.status, 200);assert.match(llms.headers.get('content-type'), /^text\/plain/);assert.match(llms.text, /^# /, 'llms.txt starts with a markdown heading');-- assert.match(llms.text, /\[Creators on OpenVibe\.Tips\]\(http:\/\/tips\.test\/\)/, 'llms.txt lists the main public pages');++ assert.match(llms.text, /\[Creators on OpenVibe\.Tips\]\(http:\/\/tips\.test\/index\.md\)/, 'llms.txt lists the home at its twin');assert.match(llms.text, /\[llms-full\.txt\]\(http:\/\/tips\.test\/llms-full\.txt\)/, 'llms.txt points at llms-full.txt');++ // AGT-5: every page at its Markdown twin, the Optional section last.++ assert.match(llms.text, /\n## Optional\n/);++ assert.match(llms.text, /\n## Optional\n\n[\s\S]*\[Sitemap\]\(http:\/\/tips\.test\/sitemap\.xml\)/);++ assert.strictEqual(llms.text.lastIndexOf('\n## '), llms.text.indexOf('\n## Optional'), 'Optional is the last section');++ const own = [...llms.text.matchAll(/\]\((http:\/\/tips\.test[^)\s]*)\)( \(HTML\))?/g)];++ assert.ok(own.length > 2, String(own.length));++ for (const [, url, html] of own) {++ const p = new URL(url).pathname;++ assert.ok(p.endsWith('.md') || /\.[a-z0-9]+$/.test(p) || html, `${url} is a twin, a file or marked (HTML)`);++ }const full = await get('/llms-full.txt');assert.strictEqual(full.status, 200);assert.match(full.headers.get('content-type'), /^text\/plain/);
@@ -473,5 +473,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
@@ -473,5 +473,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.222.0
- openvibe-contracts: v0.222.0
- openvibe-sdk: v0.82.0
- openvibe-sdk: v0.82.0
-- openvibe-shared: v4.107.4
+- openvibe-shared: v4.108.0
<!-- versions:end -->
<!-- versions:end -->
@@ -11,7 +11,7 @@
@@ -11,7 +11,7 @@
"database": "PostgreSQL 18 through PgBouncer (transaction mode) as the system of record, Valkey 9 for shared state (per-actor limits, overlay fan-out, overlay stream slots), ADR-035: database ov_tips from OpenVibe.Host's data role, schema in migrations/ (0001_initial expand, 0002_drop_legacy_import contract), async on openvibe-sdk/db v0.28.0, money paths serializable with row locks, PostgreSQL outbox and inbox.",
"database": "PostgreSQL 18 through PgBouncer (transaction mode) as the system of record, Valkey 9 for shared state (per-actor limits, overlay fan-out, overlay stream slots), ADR-035: database ov_tips from OpenVibe.Host's data role, schema in migrations/ (0001_initial expand, 0002_drop_legacy_import contract), async on openvibe-sdk/db v0.28.0, money paths serializable with row locks, PostgreSQL outbox and inbox.",
"contracts": "openvibe-contracts v0.222.0",
"contracts": "openvibe-contracts v0.222.0",
"sdk": "openvibe-sdk v0.82.0",
"sdk": "openvibe-sdk v0.82.0",
- "shared": "openvibe-shared v4.107.4",
+ "shared": "openvibe-shared v4.108.0",
"notes": "Runtime, API, SSR pages, overlays, the Billing settlement consumer and the events outbox are built, tested and deployed, together with /metrics (loopback only), truthful /api/ready (Billing and Events checks), supporter privacy (anonymous, hidden amount, private message), goal and supporter-page settings, export and erasure (tips.interaction.erased), moderation (word filter, hide/show, invited moderators, tips.interaction.moderated) and per-actor limits. Capabilities (18 tips.*) and payload schemas are in openvibe-contracts. Billing is the money ledger since the cutover (2026-10-17). A written internal threat review (docs/threat-review.md) with its fixes and test/security.test.js. Paid sounds (creators upload up to 12, supporters pay to play one on the overlay) since 2026-10-17. Not done: an independent security review.",
"notes": "Runtime, API, SSR pages, overlays, the Billing settlement consumer and the events outbox are built, tested and deployed, together with /metrics (loopback only), truthful /api/ready (Billing and Events checks), supporter privacy (anonymous, hidden amount, private message), goal and supporter-page settings, export and erasure (tips.interaction.erased), moderation (word filter, hide/show, invited moderators, tips.interaction.moderated) and per-actor limits. Capabilities (18 tips.*) and payload schemas are in openvibe-contracts. Billing is the money ledger since the cutover (2026-10-17). A written internal threat review (docs/threat-review.md) with its fixes and test/security.test.js. Paid sounds (creators upload up to 12, supporters pay to play one on the overlay) since 2026-10-17. Not done: an independent security review.",
"features": [
"features": [
"tips, paid messages, TTS and media requests settled by OpenVibe.Billing (never Tips' own money)",
"tips, paid messages, TTS and media requests settled by OpenVibe.Billing (never Tips' own money)",
@@ -15,7 +15,7 @@
@@ -15,7 +15,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -1162,9 +1162,9 @@
@@ -1162,9 +1162,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.107.4",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
- "integrity": "sha512-tThN0m3KMncEHVcBOvE+ysHjsZTlVIDLG1P+/i2Mulmxfc/AUpebAXhpWWrut50OChZ8o+HeePxsSrPI8mUQOQ==",
+ "version": "4.108.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
+ "integrity": "sha512-NPzwjxpzGIpFpIm4uw3+VwHfsxbJ2+cD9yuhRzSwolSwqv2kCmwaVtHJ0DA9gKO3K8PCBfIP3UpKzpuSbYEJ+g==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -27,7 +27,7 @@
@@ -27,7 +27,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -123,21 +123,25 @@ function createWebRoutes({ domain, config, layout, userAuth, liveNow = { list: a
@@ -123,21 +123,25 @@ function createWebRoutes({ domain, config, layout, userAuth, liveNow = { list: a
res.type('application/xml').set('Cache-Control', cache.htmlHeaders({ maxAge: 900 })).send(seo.sitemapXml(urls));
res.type('application/xml').set('Cache-Control', cache.htmlHeaders({ maxAge: 900 })).send(seo.sitemapXml(urls));
});
});
// /llms.txt (llmstxt.org): a plain-markdown map of the site for language-model crawlers.
// /llms.txt (llmstxt.org): a plain-markdown map of the site for language-model crawlers.
+ // AGT-5: each of this site's own pages is linked at its Markdown twin (markdown: true, base: the site origin).
r.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({
r.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({
name: SITE_NAME,
name: SITE_NAME,
+ markdown: true,
+ base: config.baseUrl,
summary: SITE_SUMMARY,
summary: SITE_SUMMARY,
details: 'A creator page lists the goals and a tip form that works without JavaScript; tips settle through Billing, and Tips owns the interaction, the goals and the overlay alerts. The home page lists creators whose pages are switched on. Reading any public page needs no account; tipping needs a signed-in supporter, and the dashboard, receipts, moderation, overlays, sign-in and the API are per-person and never listed here.',
details: 'A creator page lists the goals and a tip form that works without JavaScript; tips settle through Billing, and Tips owns the interaction, the goals and the overlay alerts. The home page lists creators whose pages are switched on. Reading any public page needs no account; tipping needs a signed-in supporter, and the dashboard, receipts, moderation, overlays, sign-in and the API are per-person and never listed here.',
sections: [
sections: [
{ title: 'Start here', links: [
{ title: 'Start here', links: [
{ title: 'Creators on OpenVibe.Tips', url: `${config.baseUrl}/`, note: 'every creator with their page switched on' },
{ title: 'Creators on OpenVibe.Tips', url: `${config.baseUrl}/`, note: 'every creator with their page switched on' },
- { title: 'What shipped on OpenVibe.Tips', url: `${config.baseUrl}/updates` },
- ] },
- { title: 'Machine-readable', links: [
- { title: 'Sitemap', url: `${config.baseUrl}/sitemap.xml`, note: 'the public pages, with lastmod' },
- { title: 'robots.txt', url: `${config.baseUrl}/robots.txt` },
- { title: 'llms-full.txt', url: `${config.baseUrl}/llms-full.txt`, note: 'this same map with the full text of each public page' },
] },
] },
],
],
+ // llmstxt.org's "## Optional": what an agent may skip when it needs a shorter context.
+ optional: [
+ { title: 'What shipped on OpenVibe.Tips', url: `${config.baseUrl}/updates` },
+ { title: 'Sitemap', url: `${config.baseUrl}/sitemap.xml`, note: 'the public pages, with lastmod' },
+ { title: 'robots.txt', url: `${config.baseUrl}/robots.txt` },
+ { title: 'llms-full.txt', url: `${config.baseUrl}/llms-full.txt`, note: 'this same map with the full text of each public page' },
+ ],
})));
})));
// /llms-full.txt: the /llms.txt header plus the full plain text of every public page. The creator
// /llms-full.txt: the /llms.txt header plus the full plain text of every public page. The creator
// pages are the same rows the sitemap lists, and the legal documents are the shared ones; nothing
// pages are the same rows the sitemap lists, and the legal documents are the shared ones; nothing
@@ -144,8 +144,18 @@ const { boot, check, done } = require('./helpers/app');
@@ -144,8 +144,18 @@ const { boot, check, done } = require('./helpers/app');
assert.strictEqual(llms.status, 200);
assert.strictEqual(llms.status, 200);
assert.match(llms.headers.get('content-type'), /^text\/plain/);
assert.match(llms.headers.get('content-type'), /^text\/plain/);
assert.match(llms.text, /^# /, 'llms.txt starts with a markdown heading');
assert.match(llms.text, /^# /, 'llms.txt starts with a markdown heading');
- assert.match(llms.text, /\[Creators on OpenVibe\.Tips\]\(http:\/\/tips\.test\/\)/, 'llms.txt lists the main public pages');
+ assert.match(llms.text, /\[Creators on OpenVibe\.Tips\]\(http:\/\/tips\.test\/index\.md\)/, 'llms.txt lists the home at its twin');
assert.match(llms.text, /\[llms-full\.txt\]\(http:\/\/tips\.test\/llms-full\.txt\)/, 'llms.txt points at llms-full.txt');
assert.match(llms.text, /\[llms-full\.txt\]\(http:\/\/tips\.test\/llms-full\.txt\)/, 'llms.txt points at llms-full.txt');
+ // AGT-5: every page at its Markdown twin, the Optional section last.
+ assert.match(llms.text, /\n## Optional\n/);
+ assert.match(llms.text, /\n## Optional\n\n[\s\S]*\[Sitemap\]\(http:\/\/tips\.test\/sitemap\.xml\)/);
+ assert.strictEqual(llms.text.lastIndexOf('\n## '), llms.text.indexOf('\n## Optional'), 'Optional is the last section');
+ const own = [...llms.text.matchAll(/\]\((http:\/\/tips\.test[^)\s]*)\)( \(HTML\))?/g)];
+ assert.ok(own.length > 2, String(own.length));
+ for (const [, url, html] of own) {
+ const p = new URL(url).pathname;
+ assert.ok(p.endsWith('.md') || /\.[a-z0-9]+$/.test(p) || html, `${url} is a twin, a file or marked (HTML)`);
+ }
const full = await get('/llms-full.txt');
const full = await get('/llms-full.txt');
assert.strictEqual(full.status, 200);
assert.strictEqual(full.status, 200);
assert.match(full.headers.get('content-type'), /^text\/plain/);
assert.match(full.headers.get('content-type'), /^text\/plain/);