openvibers/openvibe.bot Commits
openvibe-shared v4.108.0 (was v4.107.4) (#295)
Changes
@@@ -145,7 +145,7 @@ Sitemap entries use the release commit time for these code-backed pages.- OpenVibe.Network: user and service tokens, node tokens, pairing when the authority is `network`, and identity resolution (a `@username` to a subject, and subjects to names, for the panel's People card).- OpenRestream: the WHIP ingest base (`BOT_WHIP_BASE`) devices publish to.- OpenVibe.Billing: job usage readings go to `billing.usage.record` (`BOT_BILLING_URL`; Bot mints its own Network service token for audience `openvibe.billing` with `billing.usage.record`, and `BOT_BILLING_TOKEN` overrides it); unset, they wait in `run_usage_outbox`.--- `openvibe-contracts` v0.129.0, `openvibe-sdk` v0.82.0 and `openvibe-shared` v4.107.4 (package.json).++- `openvibe-contracts` v0.129.0, `openvibe-sdk` v0.82.0 and `openvibe-shared` v4.108.0 (package.json).- Account export and deletion (ADR-033): Network grants `events.subscription.manage` (openvibe.events) for the twosubscriptions created at boot, then, last and once the release is live, `network.account.export.contribute` and`network.account.deletion.confirm` (openvibe.network).@@@ -237,5 +237,5 @@ OpenVibe.Sites no longer generates `deploy/nginx/openvibe.bot.conf` (sites.json<!-- versions:start -->- openvibe-contracts: v0.277.0- openvibe-sdk: v0.82.0--- openvibe-shared: v4.107.4++- openvibe-shared: v4.108.0<!-- versions:end -->@@@ -7,11 +7,11 @@"domain": "openvibe.bot (www -> apex; /device and /control WebSockets; /install)","contracts": "openvibe-contracts v0.277.0","sdk": "openvibe-sdk v0.82.0",-- "shared": "openvibe-shared v4.107.4",++ "shared": "openvibe-shared v4.108.0","packages": ["openvibe-contracts v0.277.0","openvibe-sdk v0.82.0",-- "openvibe-shared v4.107.4"++ "openvibe-shared v4.108.0"],"capabilities": ["bot.robot.read",@@@ -15,7 +15,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.277.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","qrcode-generator": "^2.0.4","ws": "^8.18.0"@@@ -1164,9 +1164,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.277.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","qrcode-generator": "^2.0.4","ws": "^8.18.0"@@@ -67,6 +67,8 @@ const PUBLIC_PAGES = ['/', '/try', '/robots', '/models', '/docs', '/docs/driversconst LLMS_NAME = 'OpenVibe.Bot';const LLMS_SUMMARY = 'An open control panel for robots: pair a robot with one pasted command and drive it from any '+ 'browser, with roles, limits and a latched emergency stop on every command.';++const LLMS_NO_TWIN = new Set(['/try', '/setup/sd-card', '/terms', '/privacy', '/dmca']);++const LLMS_LEGAL = new Set(['/terms', '/privacy', '/dmca']);const LLMS_PAGES = [{path: '/', title: 'OpenVibe.Bot — drive your robot from any browser',@@@ -176,9 +178,18 @@ function createWebRoutes(config, { domain = null, sim = null, onvif = null, limi// /llms.txt and /llms-full.txt (llmstxt.org, openvibe-shared/seo): the public pages as a plain-text map for// language-model crawlers. Only the pages above; the signed-in pages, the API and /internal are not listed.// Same headers as robots.txt: text/plain and no explicit Cache-Control.++ // AGT-5: each page that has a Markdown twin is linked at it (openvibe-shared 4.108.0's markdown mode); the try page,++ // the SD-card setup and the legal pages have none and are marked HTML. The legal pages, the sitemap and the full text++ // are ## Optional.++ const llmsLink = (p) => ({ title: p.title, url: `${config.baseUrl}${p.path}`, note: p.note, ...(LLMS_NO_TWIN.has(p.path) ? { html: true } : {}) });r.get('/llms.txt', (req, res) => res.type('text/plain').send(seo.llmsTxt({-- name: LLMS_NAME, summary: LLMS_SUMMARY,-- sections: [{ title: 'Pages', links: LLMS_PAGES.map((p) => ({ title: p.title, url: `${config.baseUrl}${p.path}`, note: p.note })) }],++ name: LLMS_NAME, summary: LLMS_SUMMARY, markdown: true, base: config.baseUrl,++ sections: [{ title: 'Pages', links: LLMS_PAGES.filter((p) => !LLMS_LEGAL.has(p.path)).map(llmsLink) }],++ optional: [++ ...LLMS_PAGES.filter((p) => LLMS_LEGAL.has(p.path)).map(llmsLink),++ { title: 'Sitemap', url: `${config.baseUrl}/sitemap.xml` },++ { title: 'Full text for language models', url: `${config.baseUrl}/llms-full.txt` },++ ],})));r.get('/llms-full.txt', (req, res) => res.type('text/plain').send(seo.llmsFull({site: LLMS_NAME, summary: LLMS_SUMMARY, base: config.baseUrl,@@@ -26,14 +26,20 @@ const TITLES = ['OpenVibe.Bot — drive your robot from any browser', 'Build for}// llms.txt links the front page; llms-full.txt lists it as a page with an absolute URL.const index = await (await get('/llms.txt')).text();-- assert.ok(index.includes(`- [${TITLES[0]}](${BASE}/)`), 'llms.txt links the front page');++ assert.ok(index.includes(`- [${TITLES[0]}](${BASE}/index.md)`), 'llms.txt links the front page at its Markdown twin (AGT-5)');const full = await (await get('/llms-full.txt')).text();assert.ok(full.includes(`### ${TITLES[0]}`) && full.includes(`URL: ${BASE}/`), 'llms-full.txt lists the front page');});await check('GET /llms.txt: every public page by title and absolute URL, and no API/internal/signed-in path', async () => {const body = await (await get('/llms.txt')).text();-- PATHS.forEach((p, i) => assert.ok(body.includes(`- [${TITLES[i]}](${BASE}${p})`), `llms.txt lists ${p}`));++ // AGT-5: a page with a twin is linked at it; the legal pages have none and are marked HTML, under ## Optional.++ const legal = ['/terms', '/privacy', '/dmca'];++ const link = (p) => (legal.includes(p) ? `${BASE}${p}) (HTML)` : `${BASE}${p === '/' ? '/index.md' : `${p}.md`})`);++ PATHS.forEach((p, i) => assert.ok(body.includes(`- [${TITLES[i]}](${link(p)}`), `llms.txt lists ${p}`));++ const optional = body.split('\n## Optional\n')[1] || '';++ assert.ok(legal.every((p) => optional.includes(`${BASE}${p})`)) && optional.includes(`${BASE}/sitemap.xml`), 'the legal pages and the sitemap are optional');++ assert.ok(body.includes(`(${BASE}/try) (HTML)`), 'the try page has no twin');for (const bad of ['/api', '/internal', '/panel/']) assert.ok(!body.includes(bad), `llms.txt does not name ${bad}`);});
@@ -145,7 +145,7 @@ Sitemap entries use the release commit time for these code-backed pages.
@@ -145,7 +145,7 @@ Sitemap entries use the release commit time for these code-backed pages.
- OpenVibe.Network: user and service tokens, node tokens, pairing when the authority is `network`, and identity resolution (a `@username` to a subject, and subjects to names, for the panel's People card).
- OpenVibe.Network: user and service tokens, node tokens, pairing when the authority is `network`, and identity resolution (a `@username` to a subject, and subjects to names, for the panel's People card).
- OpenRestream: the WHIP ingest base (`BOT_WHIP_BASE`) devices publish to.
- OpenRestream: the WHIP ingest base (`BOT_WHIP_BASE`) devices publish to.
- OpenVibe.Billing: job usage readings go to `billing.usage.record` (`BOT_BILLING_URL`; Bot mints its own Network service token for audience `openvibe.billing` with `billing.usage.record`, and `BOT_BILLING_TOKEN` overrides it); unset, they wait in `run_usage_outbox`.
- OpenVibe.Billing: job usage readings go to `billing.usage.record` (`BOT_BILLING_URL`; Bot mints its own Network service token for audience `openvibe.billing` with `billing.usage.record`, and `BOT_BILLING_TOKEN` overrides it); unset, they wait in `run_usage_outbox`.
-- `openvibe-contracts` v0.129.0, `openvibe-sdk` v0.82.0 and `openvibe-shared` v4.107.4 (package.json).
+- `openvibe-contracts` v0.129.0, `openvibe-sdk` v0.82.0 and `openvibe-shared` v4.108.0 (package.json).
- Account export and deletion (ADR-033): Network grants `events.subscription.manage` (openvibe.events) for the two
- Account export and deletion (ADR-033): Network grants `events.subscription.manage` (openvibe.events) for the two
subscriptions created at boot, then, last and once the release is live, `network.account.export.contribute` and
subscriptions created at boot, then, last and once the release is live, `network.account.export.contribute` and
`network.account.deletion.confirm` (openvibe.network).
`network.account.deletion.confirm` (openvibe.network).
@@ -237,5 +237,5 @@ OpenVibe.Sites no longer generates `deploy/nginx/openvibe.bot.conf` (sites.json
@@ -237,5 +237,5 @@ OpenVibe.Sites no longer generates `deploy/nginx/openvibe.bot.conf` (sites.json
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.277.0
- openvibe-contracts: v0.277.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 -->
@@ -7,11 +7,11 @@
@@ -7,11 +7,11 @@
"domain": "openvibe.bot (www -> apex; /device and /control WebSockets; /install)",
"domain": "openvibe.bot (www -> apex; /device and /control WebSockets; /install)",
"contracts": "openvibe-contracts v0.277.0",
"contracts": "openvibe-contracts v0.277.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",
"packages": [
"packages": [
"openvibe-contracts v0.277.0",
"openvibe-contracts v0.277.0",
"openvibe-sdk v0.82.0",
"openvibe-sdk v0.82.0",
- "openvibe-shared v4.107.4"
+ "openvibe-shared v4.108.0"
],
],
"capabilities": [
"capabilities": [
"bot.robot.read",
"bot.robot.read",
@@ -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.277.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.277.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",
"qrcode-generator": "^2.0.4",
"qrcode-generator": "^2.0.4",
"ws": "^8.18.0"
"ws": "^8.18.0"
@@ -1164,9 +1164,9 @@
@@ -1164,9 +1164,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.277.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.277.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",
"qrcode-generator": "^2.0.4",
"qrcode-generator": "^2.0.4",
"ws": "^8.18.0"
"ws": "^8.18.0"
@@ -67,6 +67,8 @@ const PUBLIC_PAGES = ['/', '/try', '/robots', '/models', '/docs', '/docs/drivers
@@ -67,6 +67,8 @@ const PUBLIC_PAGES = ['/', '/try', '/robots', '/models', '/docs', '/docs/drivers
const LLMS_NAME = 'OpenVibe.Bot';
const LLMS_NAME = 'OpenVibe.Bot';
const LLMS_SUMMARY = 'An open control panel for robots: pair a robot with one pasted command and drive it from any '
const LLMS_SUMMARY = 'An open control panel for robots: pair a robot with one pasted command and drive it from any '
+ 'browser, with roles, limits and a latched emergency stop on every command.';
+ 'browser, with roles, limits and a latched emergency stop on every command.';
+const LLMS_NO_TWIN = new Set(['/try', '/setup/sd-card', '/terms', '/privacy', '/dmca']);
+const LLMS_LEGAL = new Set(['/terms', '/privacy', '/dmca']);
const LLMS_PAGES = [
const LLMS_PAGES = [
{
{
path: '/', title: 'OpenVibe.Bot — drive your robot from any browser',
path: '/', title: 'OpenVibe.Bot — drive your robot from any browser',
@@ -176,9 +178,18 @@ function createWebRoutes(config, { domain = null, sim = null, onvif = null, limi
@@ -176,9 +178,18 @@ function createWebRoutes(config, { domain = null, sim = null, onvif = null, limi
// /llms.txt and /llms-full.txt (llmstxt.org, openvibe-shared/seo): the public pages as a plain-text map for
// /llms.txt and /llms-full.txt (llmstxt.org, openvibe-shared/seo): the public pages as a plain-text map for
// language-model crawlers. Only the pages above; the signed-in pages, the API and /internal are not listed.
// language-model crawlers. Only the pages above; the signed-in pages, the API and /internal are not listed.
// Same headers as robots.txt: text/plain and no explicit Cache-Control.
// Same headers as robots.txt: text/plain and no explicit Cache-Control.
+ // AGT-5: each page that has a Markdown twin is linked at it (openvibe-shared 4.108.0's markdown mode); the try page,
+ // the SD-card setup and the legal pages have none and are marked HTML. The legal pages, the sitemap and the full text
+ // are ## Optional.
+ const llmsLink = (p) => ({ title: p.title, url: `${config.baseUrl}${p.path}`, note: p.note, ...(LLMS_NO_TWIN.has(p.path) ? { html: true } : {}) });
r.get('/llms.txt', (req, res) => res.type('text/plain').send(seo.llmsTxt({
r.get('/llms.txt', (req, res) => res.type('text/plain').send(seo.llmsTxt({
- name: LLMS_NAME, summary: LLMS_SUMMARY,
- sections: [{ title: 'Pages', links: LLMS_PAGES.map((p) => ({ title: p.title, url: `${config.baseUrl}${p.path}`, note: p.note })) }],
+ name: LLMS_NAME, summary: LLMS_SUMMARY, markdown: true, base: config.baseUrl,
+ sections: [{ title: 'Pages', links: LLMS_PAGES.filter((p) => !LLMS_LEGAL.has(p.path)).map(llmsLink) }],
+ optional: [
+ ...LLMS_PAGES.filter((p) => LLMS_LEGAL.has(p.path)).map(llmsLink),
+ { title: 'Sitemap', url: `${config.baseUrl}/sitemap.xml` },
+ { title: 'Full text for language models', url: `${config.baseUrl}/llms-full.txt` },
+ ],
})));
})));
r.get('/llms-full.txt', (req, res) => res.type('text/plain').send(seo.llmsFull({
r.get('/llms-full.txt', (req, res) => res.type('text/plain').send(seo.llmsFull({
site: LLMS_NAME, summary: LLMS_SUMMARY, base: config.baseUrl,
site: LLMS_NAME, summary: LLMS_SUMMARY, base: config.baseUrl,
@@ -26,14 +26,20 @@ const TITLES = ['OpenVibe.Bot — drive your robot from any browser', 'Build for
@@ -26,14 +26,20 @@ const TITLES = ['OpenVibe.Bot — drive your robot from any browser', 'Build for
}
}
// llms.txt links the front page; llms-full.txt lists it as a page with an absolute URL.
// llms.txt links the front page; llms-full.txt lists it as a page with an absolute URL.
const index = await (await get('/llms.txt')).text();
const index = await (await get('/llms.txt')).text();
- assert.ok(index.includes(`- [${TITLES[0]}](${BASE}/)`), 'llms.txt links the front page');
+ assert.ok(index.includes(`- [${TITLES[0]}](${BASE}/index.md)`), 'llms.txt links the front page at its Markdown twin (AGT-5)');
const full = await (await get('/llms-full.txt')).text();
const full = await (await get('/llms-full.txt')).text();
assert.ok(full.includes(`### ${TITLES[0]}`) && full.includes(`URL: ${BASE}/`), 'llms-full.txt lists the front page');
assert.ok(full.includes(`### ${TITLES[0]}`) && full.includes(`URL: ${BASE}/`), 'llms-full.txt lists the front page');
});
});
await check('GET /llms.txt: every public page by title and absolute URL, and no API/internal/signed-in path', async () => {
await check('GET /llms.txt: every public page by title and absolute URL, and no API/internal/signed-in path', async () => {
const body = await (await get('/llms.txt')).text();
const body = await (await get('/llms.txt')).text();
- PATHS.forEach((p, i) => assert.ok(body.includes(`- [${TITLES[i]}](${BASE}${p})`), `llms.txt lists ${p}`));
+ // AGT-5: a page with a twin is linked at it; the legal pages have none and are marked HTML, under ## Optional.
+ const legal = ['/terms', '/privacy', '/dmca'];
+ const link = (p) => (legal.includes(p) ? `${BASE}${p}) (HTML)` : `${BASE}${p === '/' ? '/index.md' : `${p}.md`})`);
+ PATHS.forEach((p, i) => assert.ok(body.includes(`- [${TITLES[i]}](${link(p)}`), `llms.txt lists ${p}`));
+ const optional = body.split('\n## Optional\n')[1] || '';
+ assert.ok(legal.every((p) => optional.includes(`${BASE}${p})`)) && optional.includes(`${BASE}/sitemap.xml`), 'the legal pages and the sitemap are optional');
+ assert.ok(body.includes(`(${BASE}/try) (HTML)`), 'the try page has no twin');
for (const bad of ['/api', '/internal', '/panel/']) assert.ok(!body.includes(bad), `llms.txt does not name ${bad}`);
for (const bad of ['/api', '/internal', '/panel/']) assert.ok(!body.includes(bad), `llms.txt does not name ${bad}`);
});
});