openvibers/openvibe.media Commits
openvibe-shared v4.108.0 (was v4.107.4) (#306)
Changes
@@@ -129,7 +129,7 @@ server/storage.orphans.scan (monthly storage orphan report, service-wide, report only),storage.move.cleanup (the alert for placement moves whose delete keeps failing, service-wide)client-ip.js trust proxy = loopback; req.ip is the only client address--(openvibe-shared v4.107.4, openvibe-contracts v0.222.0, openvibe-sdk v0.82.0: pinned release tarballs, installed by npm)++(openvibe-shared v4.108.0, openvibe-contracts v0.222.0, openvibe-sdk v0.82.0: pinned release tarballs, installed by npm)scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)scripts/reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator toolsscripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals@@@ -757,5 +757,5 @@ restart; afterwards `sudo ovhost rollback media --to <sha>`.<!-- versions:start -->- openvibe-contracts: v0.222.0- openvibe-sdk: v0.82.0--- openvibe-shared: v4.107.4++- openvibe-shared: v4.108.0<!-- versions:end -->@@@ -8,7 +8,7 @@"domain": "openvibe.media","contracts": "openvibe-contracts v0.222.0","sdk": "openvibe-sdk v0.82.0",-- "shared": "openvibe-shared v4.107.4",++ "shared": "openvibe-shared v4.108.0","capabilities": ["media.object.upload","media.object.read",@@@ -18,7 +18,7 @@"multer": "^1.4.5-lts.1","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","sharp": "^0.33.4"},@@@ -2166,9 +2166,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",@@@ -22,7 +22,7 @@"multer": "^1.4.5-lts.1","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","sharp": "^0.33.4"},@@@ -81,31 +81,46 @@ function robotsTxt() {}) + `# Agent manifest: ${config.publicUrl}/.well-known/agent.json\n`;}++// The facts /llms-full.txt carries about the service itself (SEO-33).++const LLMS_ABOUT = [++ 'OpenVibe.Media is the media service of the OpenVibe network. It stores and serves the network\'s recorded live streams (VODs), clips, thumbnails, screenshots and uploaded files, and it cuts clips and records streams for the apps that ask it to, OpenVibe.Live and OpenRestream among them.',++ 'Recordings and clips are watched on OpenVibe.Video: https://openvibe.video/v/<id> for a recording and https://openvibe.video/c/<id> for a clip are their canonical pages, with chapters, captions, chat replay and the streamer\'s channel. Media\'s own /v/<id> and /c/<id> pages are noindex and name OpenVibe.Video as canonical. Asked without Accept: text/html, https://openvibe.media/v/<id> answers the video bytes (?raw=1 does the same), and a public recording\'s transcript and AI overview are at https://openvibe.media/v/<id>/transcript.json.',++ 'Clips marked "AI clip" were picked and cut automatically from a stream, not made by a person, and are credited to no one. Private and unlisted media is never listed in the index, the sitemap or this file.',++ 'Thumbnails are served from https://openvibe.media/t/<file>. The public reads above need no token; an app stores, records and cuts media through Media\'s API with its own token.',++].join('\n\n');++function llmsTxt() {const base = config.publicUrl;return seo.llmsTxt({name: 'OpenVibe.Media',++ // AGT-5: Media's own pages are linked at their Markdown twins (openvibe-shared 4.108.0 leaves files and other++ // sites' links as they are). What an agent may skip for a shorter context is in `optional`, written last.++ markdown: true,++ base,summary: LLMS_SUMMARY,details: [-- 'Most media here belongs to OpenVibe.Live, whose /vod/:id and /clip/:id pages are the canonical pages for it (chat, transcript, the streamer\'s channel); the watch pages here point there.',++ // 11-03: recordings and clips are watched on OpenVibe.Video now (Live's /vod and /clip redirect there).++ 'Recordings and clips are watched on OpenVibe.Video: https://openvibe.video/v/<id> and https://openvibe.video/c/<id> are their canonical pages (chapters, captions, chat replay, the streamer\'s channel). Media\'s own /v/<id> and /c/<id> pages are noindex and name those as canonical; asked without Accept: text/html, /v/<id> answers the video bytes. Live\'s /vod/<id> and /clip/<id> redirect to OpenVibe.Video.','Clips marked "AI clip" were picked and cut automatically from a stream, not made by a person.','Private and unlisted media is never listed here or in the sitemap.',].join(' '),sections: [{ title: 'Browse', links: [{ title: 'Media index', url: `${base}/`, note: 'every public video, clip, image and file, newest first, each linking to its source page' },-- { title: 'Sitemap', url: `${base}/sitemap.xml`, note: 'the watch pages Media is the canonical home of' },-- { title: 'Full text', url: `${base}/llms-full.txt`, note: 'those same pages with their full text, for language models' },] },{ title: 'URLs', links: [// Real pages, not templates (SEO-34: /v/{id} and /c/{id} as links answered 404); the shapes are in the notes.{ title: 'Browse public recordings and clips', url: `${base}/browse`, note: `each recording's page is ${base}/v/<id> (?raw=1 returns the video bytes), each clip's ${base}/c/<id>, and a public recording's transcript and AI overview ${base}/v/<id>/transcript.json` },] },-- { title: 'The network', links: [-- { title: 'OpenVibe.Live', url: 'https://openvibe.live/', note: 'live streams, channels, VOD and clip pages' },-- { title: 'OpenVibe.Community', url: 'https://openvibe.community/', note: 'pastes and discussion' },-- { title: 'OpenVibe.Network', url: 'https://openvibe.network/', note: 'accounts and the network directory' },-- ] },++ ],++ // llmstxt.org's "## Optional": what an agent may skip when it needs a shorter context.++ optional: [++ { title: 'Sitemap', url: `${base}/sitemap.xml`, note: 'the media index; the watch pages are in OpenVibe.Video\'s sitemap' },++ { title: 'Full text', url: `${base}/llms-full.txt`, note: 'what Media is and serves, in full, for language models' },++ { title: 'OpenVibe.Video', url: 'https://openvibe.video/', note: 'where recordings and clips are watched: their canonical pages' },++ { title: 'OpenVibe.Live', url: 'https://openvibe.live/', note: 'live streams and channels' },++ { title: 'OpenVibe.Community', url: 'https://openvibe.community/', note: 'pastes and discussion' },++ { title: 'OpenVibe.Network', url: 'https://openvibe.network/', note: 'accounts and the network directory' },],});}@@@ -175,7 +190,12 @@ async function llmsFullTxt() {base,maxBytes: LLMS_FULL_MAX_BYTES,sections: [-- { title: 'Browse', pages: [{ title: 'Media index', url: `${base}/`, text: 'Every public video, clip, image and file on OpenVibe.Media, newest first, each linking to its source page in the app that made it.' }] },++ { title: 'Browse', pages: [++ { title: 'Media index', url: `${base}/`, text: 'Every public video, clip, image and file on OpenVibe.Media, newest first, each linking to its source page in the app that made it.' },++ // SEO-33 (11-03): with the watch pages on OpenVibe.Video, this file was the index line alone (368++ // characters). What Media is, serves and where its media is watched, in full.++ { title: 'What OpenVibe.Media is', url: `${base}/browse`, text: LLMS_ABOUT },++ ] },{ title: 'Watch pages', pages: watchPages },],});@@@ -120,7 +120,13 @@ const express = require('express');assert.strictEqual(r.status, 200);assert.ok(r.body.startsWith('# OpenVibe.Media') && r.body.includes('https://media.test/sitemap.xml') && r.body.includes('AI clip'));assert.ok(r.body.includes('https://media.test/llms-full.txt'), 'the map links the full-text file');++ // AGT-5: Media's own pages are linked at their Markdown twins, and the secondary links end in Optional.++ assert.ok(r.body.includes('](https://media.test/index.md)'), 'the home link is not its twin');++ assert.ok(r.body.includes('](https://media.test/browse.md)'), 'a page link is not its twin');++ assert.ok(r.body.includes('\n## Optional\n'), 'no ## Optional section');assert.ok(!/\bfree\b/i.test(r.body), 'no cost claims');++ // 11-03: recordings and clips are watched on OpenVibe.Video, and the map says so (it named Live's pages).++ assert.ok(r.body.includes('https://openvibe.video/v/<id>') && !/OpenVibe\.Live, whose \/vod/.test(r.body), 'the canonical pages are on OpenVibe.Video');console.log('✅ /llms.txt maps the site');// ── llms-full.txt ──@@@ -131,6 +137,9 @@ const express = require('express');assert.ok(r.body.includes('### Games public') && r.body.includes('URL: https://media.test/v/1'), 'the full text of each indexable VOD page');assert.ok(r.body.includes('### Games clip') && r.body.includes('URL: https://media.test/c/11'), 'and each indexable clip page');assert.ok(r.body.includes('### Media index') && r.body.includes('URL: https://media.test/\n'), 'the index itself');++ // SEO-33 (11-03): what Media is, in full, whatever pages it is the canonical home of (it was 368 characters).++ assert.ok(r.body.includes('### What OpenVibe.Media is') && /stores and serves the network's recorded live streams/.test(r.body));++ assert.ok(r.body.includes('/v/<id>/transcript.json') && !/\bfree\b/i.test(r.body.split('### What OpenVibe.Media is')[1].split('\n### ')[0]));for (const never of ['/v/2', '/v/3', '/v/4', '/v/5', '/v/6', '/v/7', '/v/8', '/c/12', '/c/13', '/c/14', '/c/15']) {assert.ok(!r.body.includes(`URL: https://media.test${never}`), `${never} is not in llms-full`);}
@@ -129,7 +129,7 @@ server/
@@ -129,7 +129,7 @@ server/
storage.orphans.scan (monthly storage orphan report, service-wide, report only),
storage.orphans.scan (monthly storage orphan report, service-wide, report only),
storage.move.cleanup (the alert for placement moves whose delete keeps failing, service-wide)
storage.move.cleanup (the alert for placement moves whose delete keeps failing, service-wide)
client-ip.js trust proxy = loopback; req.ip is the only client address
client-ip.js trust proxy = loopback; req.ip is the only client address
-(openvibe-shared v4.107.4, openvibe-contracts v0.222.0, openvibe-sdk v0.82.0: pinned release tarballs, installed by npm)
+(openvibe-shared v4.108.0, openvibe-contracts v0.222.0, openvibe-sdk v0.82.0: pinned release tarballs, installed by npm)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
@@ -757,5 +757,5 @@ restart; afterwards `sudo ovhost rollback media --to <sha>`.
@@ -757,5 +757,5 @@ restart; afterwards `sudo ovhost rollback media --to <sha>`.
<!-- 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 -->
@@ -8,7 +8,7 @@
@@ -8,7 +8,7 @@
"domain": "openvibe.media",
"domain": "openvibe.media",
"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",
"capabilities": [
"capabilities": [
"media.object.upload",
"media.object.upload",
"media.object.read",
"media.object.read",
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
"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",
"sharp": "^0.33.4"
"sharp": "^0.33.4"
},
},
@@ -2166,9 +2166,9 @@
@@ -2166,9 +2166,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",
@@ -22,7 +22,7 @@
@@ -22,7 +22,7 @@
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
"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",
"sharp": "^0.33.4"
"sharp": "^0.33.4"
},
},
@@ -81,31 +81,46 @@ function robotsTxt() {
@@ -81,31 +81,46 @@ function robotsTxt() {
}) + `# Agent manifest: ${config.publicUrl}/.well-known/agent.json\n`;
}) + `# Agent manifest: ${config.publicUrl}/.well-known/agent.json\n`;
}
}
+// The facts /llms-full.txt carries about the service itself (SEO-33).
+const LLMS_ABOUT = [
+ 'OpenVibe.Media is the media service of the OpenVibe network. It stores and serves the network\'s recorded live streams (VODs), clips, thumbnails, screenshots and uploaded files, and it cuts clips and records streams for the apps that ask it to, OpenVibe.Live and OpenRestream among them.',
+ 'Recordings and clips are watched on OpenVibe.Video: https://openvibe.video/v/<id> for a recording and https://openvibe.video/c/<id> for a clip are their canonical pages, with chapters, captions, chat replay and the streamer\'s channel. Media\'s own /v/<id> and /c/<id> pages are noindex and name OpenVibe.Video as canonical. Asked without Accept: text/html, https://openvibe.media/v/<id> answers the video bytes (?raw=1 does the same), and a public recording\'s transcript and AI overview are at https://openvibe.media/v/<id>/transcript.json.',
+ 'Clips marked "AI clip" were picked and cut automatically from a stream, not made by a person, and are credited to no one. Private and unlisted media is never listed in the index, the sitemap or this file.',
+ 'Thumbnails are served from https://openvibe.media/t/<file>. The public reads above need no token; an app stores, records and cuts media through Media\'s API with its own token.',
+].join('\n\n');
+
function llmsTxt() {
function llmsTxt() {
const base = config.publicUrl;
const base = config.publicUrl;
return seo.llmsTxt({
return seo.llmsTxt({
name: 'OpenVibe.Media',
name: 'OpenVibe.Media',
+ // AGT-5: Media's own pages are linked at their Markdown twins (openvibe-shared 4.108.0 leaves files and other
+ // sites' links as they are). What an agent may skip for a shorter context is in `optional`, written last.
+ markdown: true,
+ base,
summary: LLMS_SUMMARY,
summary: LLMS_SUMMARY,
details: [
details: [
- 'Most media here belongs to OpenVibe.Live, whose /vod/:id and /clip/:id pages are the canonical pages for it (chat, transcript, the streamer\'s channel); the watch pages here point there.',
+ // 11-03: recordings and clips are watched on OpenVibe.Video now (Live's /vod and /clip redirect there).
+ 'Recordings and clips are watched on OpenVibe.Video: https://openvibe.video/v/<id> and https://openvibe.video/c/<id> are their canonical pages (chapters, captions, chat replay, the streamer\'s channel). Media\'s own /v/<id> and /c/<id> pages are noindex and name those as canonical; asked without Accept: text/html, /v/<id> answers the video bytes. Live\'s /vod/<id> and /clip/<id> redirect to OpenVibe.Video.',
'Clips marked "AI clip" were picked and cut automatically from a stream, not made by a person.',
'Clips marked "AI clip" were picked and cut automatically from a stream, not made by a person.',
'Private and unlisted media is never listed here or in the sitemap.',
'Private and unlisted media is never listed here or in the sitemap.',
].join(' '),
].join(' '),
sections: [
sections: [
{ title: 'Browse', links: [
{ title: 'Browse', links: [
{ title: 'Media index', url: `${base}/`, note: 'every public video, clip, image and file, newest first, each linking to its source page' },
{ title: 'Media index', url: `${base}/`, note: 'every public video, clip, image and file, newest first, each linking to its source page' },
- { title: 'Sitemap', url: `${base}/sitemap.xml`, note: 'the watch pages Media is the canonical home of' },
- { title: 'Full text', url: `${base}/llms-full.txt`, note: 'those same pages with their full text, for language models' },
] },
] },
{ title: 'URLs', links: [
{ title: 'URLs', links: [
// Real pages, not templates (SEO-34: /v/{id} and /c/{id} as links answered 404); the shapes are in the notes.
// Real pages, not templates (SEO-34: /v/{id} and /c/{id} as links answered 404); the shapes are in the notes.
{ title: 'Browse public recordings and clips', url: `${base}/browse`, note: `each recording's page is ${base}/v/<id> (?raw=1 returns the video bytes), each clip's ${base}/c/<id>, and a public recording's transcript and AI overview ${base}/v/<id>/transcript.json` },
{ title: 'Browse public recordings and clips', url: `${base}/browse`, note: `each recording's page is ${base}/v/<id> (?raw=1 returns the video bytes), each clip's ${base}/c/<id>, and a public recording's transcript and AI overview ${base}/v/<id>/transcript.json` },
] },
] },
- { title: 'The network', links: [
- { title: 'OpenVibe.Live', url: 'https://openvibe.live/', note: 'live streams, channels, VOD and clip pages' },
- { title: 'OpenVibe.Community', url: 'https://openvibe.community/', note: 'pastes and discussion' },
- { title: 'OpenVibe.Network', url: 'https://openvibe.network/', note: 'accounts and the network directory' },
- ] },
+ ],
+ // llmstxt.org's "## Optional": what an agent may skip when it needs a shorter context.
+ optional: [
+ { title: 'Sitemap', url: `${base}/sitemap.xml`, note: 'the media index; the watch pages are in OpenVibe.Video\'s sitemap' },
+ { title: 'Full text', url: `${base}/llms-full.txt`, note: 'what Media is and serves, in full, for language models' },
+ { title: 'OpenVibe.Video', url: 'https://openvibe.video/', note: 'where recordings and clips are watched: their canonical pages' },
+ { title: 'OpenVibe.Live', url: 'https://openvibe.live/', note: 'live streams and channels' },
+ { title: 'OpenVibe.Community', url: 'https://openvibe.community/', note: 'pastes and discussion' },
+ { title: 'OpenVibe.Network', url: 'https://openvibe.network/', note: 'accounts and the network directory' },
],
],
});
});
}
}
@@ -175,7 +190,12 @@ async function llmsFullTxt() {
@@ -175,7 +190,12 @@ async function llmsFullTxt() {
base,
base,
maxBytes: LLMS_FULL_MAX_BYTES,
maxBytes: LLMS_FULL_MAX_BYTES,
sections: [
sections: [
- { title: 'Browse', pages: [{ title: 'Media index', url: `${base}/`, text: 'Every public video, clip, image and file on OpenVibe.Media, newest first, each linking to its source page in the app that made it.' }] },
+ { title: 'Browse', pages: [
+ { title: 'Media index', url: `${base}/`, text: 'Every public video, clip, image and file on OpenVibe.Media, newest first, each linking to its source page in the app that made it.' },
+ // SEO-33 (11-03): with the watch pages on OpenVibe.Video, this file was the index line alone (368
+ // characters). What Media is, serves and where its media is watched, in full.
+ { title: 'What OpenVibe.Media is', url: `${base}/browse`, text: LLMS_ABOUT },
+ ] },
{ title: 'Watch pages', pages: watchPages },
{ title: 'Watch pages', pages: watchPages },
],
],
});
});
@@ -120,7 +120,13 @@ const express = require('express');
@@ -120,7 +120,13 @@ const express = require('express');
assert.strictEqual(r.status, 200);
assert.strictEqual(r.status, 200);
assert.ok(r.body.startsWith('# OpenVibe.Media') && r.body.includes('https://media.test/sitemap.xml') && r.body.includes('AI clip'));
assert.ok(r.body.startsWith('# OpenVibe.Media') && r.body.includes('https://media.test/sitemap.xml') && r.body.includes('AI clip'));
assert.ok(r.body.includes('https://media.test/llms-full.txt'), 'the map links the full-text file');
assert.ok(r.body.includes('https://media.test/llms-full.txt'), 'the map links the full-text file');
+ // AGT-5: Media's own pages are linked at their Markdown twins, and the secondary links end in Optional.
+ assert.ok(r.body.includes('](https://media.test/index.md)'), 'the home link is not its twin');
+ assert.ok(r.body.includes('](https://media.test/browse.md)'), 'a page link is not its twin');
+ assert.ok(r.body.includes('\n## Optional\n'), 'no ## Optional section');
assert.ok(!/\bfree\b/i.test(r.body), 'no cost claims');
assert.ok(!/\bfree\b/i.test(r.body), 'no cost claims');
+ // 11-03: recordings and clips are watched on OpenVibe.Video, and the map says so (it named Live's pages).
+ assert.ok(r.body.includes('https://openvibe.video/v/<id>') && !/OpenVibe\.Live, whose \/vod/.test(r.body), 'the canonical pages are on OpenVibe.Video');
console.log('✅ /llms.txt maps the site');
console.log('✅ /llms.txt maps the site');
// ── llms-full.txt ──
// ── llms-full.txt ──
@@ -131,6 +137,9 @@ const express = require('express');
@@ -131,6 +137,9 @@ const express = require('express');
assert.ok(r.body.includes('### Games public') && r.body.includes('URL: https://media.test/v/1'), 'the full text of each indexable VOD page');
assert.ok(r.body.includes('### Games public') && r.body.includes('URL: https://media.test/v/1'), 'the full text of each indexable VOD page');
assert.ok(r.body.includes('### Games clip') && r.body.includes('URL: https://media.test/c/11'), 'and each indexable clip page');
assert.ok(r.body.includes('### Games clip') && r.body.includes('URL: https://media.test/c/11'), 'and each indexable clip page');
assert.ok(r.body.includes('### Media index') && r.body.includes('URL: https://media.test/\n'), 'the index itself');
assert.ok(r.body.includes('### Media index') && r.body.includes('URL: https://media.test/\n'), 'the index itself');
+ // SEO-33 (11-03): what Media is, in full, whatever pages it is the canonical home of (it was 368 characters).
+ assert.ok(r.body.includes('### What OpenVibe.Media is') && /stores and serves the network's recorded live streams/.test(r.body));
+ assert.ok(r.body.includes('/v/<id>/transcript.json') && !/\bfree\b/i.test(r.body.split('### What OpenVibe.Media is')[1].split('\n### ')[0]));
for (const never of ['/v/2', '/v/3', '/v/4', '/v/5', '/v/6', '/v/7', '/v/8', '/c/12', '/c/13', '/c/14', '/c/15']) {
for (const never of ['/v/2', '/v/3', '/v/4', '/v/5', '/v/6', '/v/7', '/v/8', '/c/12', '/c/13', '/c/14', '/c/15']) {
assert.ok(!r.body.includes(`URL: https://media.test${never}`), `${never} is not in llms-full`);
assert.ok(!r.body.includes(`URL: https://media.test${never}`), `${never} is not in llms-full`);
}
}