openvibers/openvibe.media Commits

openvibe-shared v4.108.0 (was v4.107.4) (#306)

OP OpenVibers committed 10 days ago · d83d603dd6 · 6 files, +46 −17

* openvibe-shared v4.108.0 (was v4.107.4)

Opened by scripts/pin-bump.js in OpenVibers/OpenVibe.Shared after the v4.108.0 release.

* llms.txt links each page at its Markdown twin, with an ## Optional section (AGT-5)

* llms.txt names OpenVibe.Video as the home of recordings and clips; llms-full.txt says what Media is (SEO-33)

The map said Live's /vod and /clip pages were canonical; both redirect to OpenVibe.Video now, and Media's own watch
pages name Video as canonical. With every watch page elsewhere, llms-full.txt was the index line alone (368
characters): it now carries what Media stores and serves, where it is watched, the raw and transcript URLs.

Changes

ddiff --git a/README.md b/README.md
iindex 60d6a1e..19d825c 100644
---- a/README.md
++++ b/README.md
@@@ -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 tools
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>`.
<!-- versions:start -->
- openvibe-contracts: v0.222.0
- openvibe-sdk: v0.82.0
--- openvibe-shared: v4.107.4
++- openvibe-shared: v4.108.0
<!-- versions:end -->
ddiff --git a/STATUS.json b/STATUS.json
iindex 9399631..bcc8a23 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -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",
ddiff --git a/package-lock.json b/package-lock.json
iindex 0c3e1ee..d390495 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -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",
ddiff --git a/package.json b/package.json
iindex aa8f6f5..aec37f6 100644
---- a/package.json
++++ b/package.json
@@@ -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"
},
ddiff --git a/server/public/crawl.js b/server/public/crawl.js
iindex 2142327..286d198 100644
---- a/server/public/crawl.js
++++ b/server/public/crawl.js
@@@ -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 },
],
});
ddiff --git a/test/crawl.test.js b/test/crawl.test.js
iindex fdfe108..45b5aa0 100644
---- a/test/crawl.test.js
++++ b/test/crawl.test.js
@@@ -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`);
}