openvibers/openvibe.bot Commits
Bot: /updates — what shipped on openvibe.bot (the shared update log) (#117)
Changes
@@@ -15,6 +15,7 @@const shell = require('openvibe-shared/shell');const showcase = require('openvibe-shared/showcase');const ovServe = require('openvibe-shared/serve');++const frame = require('openvibe-shared/frame');const appIcon = require('openvibe-shared/app-icon');const SITE_NAME = 'OpenVibe.Bot';@@@ -100,13 +101,21 @@ function framePage({ config, title, description, canonicalPath = '/', body, headreturn shell.page({name: SITE_NAME, service: 'bot', lang: 'en', title, siteName: SITE_NAME, description, summary: description,canonical: `${config.baseUrl}${canonicalPath}`, robots: 'index, follow',-- navbar: nav, footer: { service: 'bot', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME }, home: '/',++ navbar: nav, footer: { service: 'bot', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' }, home: '/',navLinks: [{ label: 'Robots', href: '/robots' }, { label: 'Build', href: '/docs' }],head: [appIcon.headTags({ site: 'bot' }), '<link rel="manifest" href="/manifest.webmanifest">', head].filter(Boolean).join('\n'),body: `<div id="navbar-mount"></div>\n${body}`,});}++/** /updates: what shipped on OpenVibe.Bot, the network's shared update log (openvibe-shared/frame updatesBody). */++function renderUpdates({ config }) {++ return framePage({++ config, title: `What shipped on ${SITE_NAME}`, description: `Every change deployed to ${SITE_NAME}, newest first.`, canonicalPath: '/updates',++ body: `<main id="main" class="page">\n${frame.updatesBody({ service: 'bot', siteName: SITE_NAME })}\n</main>\n<script src="${ovServe.url('shipped.js')}" defer></script>`,++ });++}++function renderHome({ config, signedIn = false }) {const nav = {service: 'bot',@@@ -140,4 +149,4 @@ ${sections({ signedIn })}});}--module.exports = { renderHome, framePage, HOME_CSP, sections };++module.exports = { renderHome, renderUpdates, framePage, HOME_CSP, sections };@@@ -40,7 +40,7 @@ const { renderPanel, renderEmbedRefused, renderDrivePage, renderPairingPage, renconst { createMachineScreen } = require('../machine-screen');const { renderDocsIndex, renderDriversPage, renderProfilesPage, renderAutomationPage, renderComputersPage } = require('./docs');const { checkProfile } = require('../profiles');--const { renderHome, HOME_CSP } = require('./home');++const { renderHome, renderUpdates, HOME_CSP } = require('./home');const { renderDirectory, renderModels, renderModel, renderRobotPage, renderAddRobot, renderSubmit, renderSubmissions, modelOf } = require('./directory');const { LICENSES } = require('../submissions');@@@ -52,7 +52,7 @@ const TRY_PROFILE = loadProfiles().get('sim.rover');const SITE_TAGLINE = 'An open control panel for robots';// Every public GET page, for the sitemap. The interactive pages (/robots, /pair/, /panel/) need a session and// are Disallowed in robots.txt; the API and sign-in are not pages.--const PUBLIC_PAGES = ['/', '/try', '/robots', '/models', '/install', '/docs', '/docs/drivers', '/docs/profiles', '/docs/automation', '/docs/computers', '/terms', '/privacy', '/dmca'];++const PUBLIC_PAGES = ['/', '/try', '/robots', '/models', '/install', '/docs', '/docs/drivers', '/docs/profiles', '/docs/automation', '/docs/computers', '/updates', '/terms', '/privacy', '/dmca'];// The public pages an AI crawler can read, for /llms.txt and /llms-full.txt (llmstxt.org). Same pages as the// sitemap minus /install (a 302 to the installer script, not a page); no signed-in or per-robot page, nothing// under the API or /internal. `note` is the one-line llms.txt entry, `text` the page's full text in llms-full.txt,@@@ -106,6 +106,11 @@ const LLMS_PAGES = [note: 'The Node\'s computer plugin: levels set on the computer, approvals there, the agent switch and the API.',text: 'How to let your agents use your own computer through the OpenVibe Node\'s computer plugin. Installed with --computer, it runs as your own account, never root, and its only folder is your home. The level is set on the computer only: observe (screenshots, listing and reading files), ask (programs and writes each approved on the computer with openvibe-node approve), trusted (inside its folders without asking) or full. Programs are argv, never a shell line, time-limited and output-capped. The local stop drops it to observe. The switch Let my agent use this computer on openvibe.bot/machines lets your OpenVibe.Actor agent use it as you. Bot\'s machine commands take exec, fs and screen, and the computer\'s answer comes back in data.',},++ {++ path: '/updates', title: 'What shipped on OpenVibe.Bot',++ note: 'Every change deployed to OpenVibe.Bot, newest first, from the network\'s shared update log.',++ text: 'The update log of OpenVibe.Bot: every change deployed to the site, newest first, each line a commit from the OpenVibers repositories linked to the change itself. Batches of changes become Patch notes on openvibe.blog, and the whole network\'s log is on openvibe.network/updates.',++ },{path: '/terms', title: 'Terms of Service',note: 'The terms for using OpenVibe.Bot.',@@@ -259,6 +264,8 @@ function createWebRoutes(config, { domain = null, sim = null, onvif = null, limiconst frameCsp = (req, res, next) => { res.setHeader('Content-Security-Policy', HOME_CSP); next(); };r.get('/docs/docs.css', asset('docs.css', 'text/css'));r.get('/docs', frameCsp, (req, res) => res.type('html').send(renderDocsIndex({ config })));++ // What shipped on this site: the shared update log, as on every OpenVibe site.++ r.get('/updates', frameCsp, (req, res) => res.type('html').send(renderUpdates({ config })));r.get('/docs/drivers', frameCsp, (req, res) => res.type('html').send(renderDriversPage({ config })));r.get('/docs/profiles', frameCsp, (req, res) => res.type('html').send(renderProfilesPage({ config })));r.get('/docs/automation', frameCsp, (req, res) => res.type('html').send(renderAutomationPage({ config })));@@@ -155,6 +155,18 @@ const { HOME_CSP: SITE_CSP } = require('../server/web/home');for (const p of ['/docs', '/docs/drivers', '/docs/profiles']) assert.ok(xml.includes(`http://bot.test${p}<`), `sitemap has no ${p}`);});++ await check('GET /updates: the shared update log in the Frame, one heading, canonical', async () => {++ const r = await get('/updates');++ assert.strictEqual(r.status, 200);++ assert.strictEqual(r.headers.get('content-security-policy'), SITE_CSP);++ const html = await r.text();++ assert.ok(html.includes('<h1>What shipped on OpenVibe.Bot</h1>') && html.includes('data-ov-shipped="log" data-service="bot"'));++ assert.strictEqual((html.match(/<h1[\s>]/g) || []).length, 1);++ assert.match(html, /<script src="\/shared\/shipped\.js\?v=[0-9a-f]+" defer><\/script>/);++ assert.match(html, /<link rel="canonical" href="[^"]+\/updates">/);++ assert.match(await (await get('/sitemap.xml')).text(), /<loc>[^<]+\/updates<\/loc>/);++ });++await check('GET / links the build pages', async () => {const html = await (await get('/')).text();assert.match(html, /href="\/docs"/);
@@ -15,6 +15,7 @@
@@ -15,6 +15,7 @@
const shell = require('openvibe-shared/shell');
const shell = require('openvibe-shared/shell');
const showcase = require('openvibe-shared/showcase');
const showcase = require('openvibe-shared/showcase');
const ovServe = require('openvibe-shared/serve');
const ovServe = require('openvibe-shared/serve');
+const frame = require('openvibe-shared/frame');
const appIcon = require('openvibe-shared/app-icon');
const appIcon = require('openvibe-shared/app-icon');
const SITE_NAME = 'OpenVibe.Bot';
const SITE_NAME = 'OpenVibe.Bot';
@@ -100,13 +101,21 @@ function framePage({ config, title, description, canonicalPath = '/', body, head
@@ -100,13 +101,21 @@ function framePage({ config, title, description, canonicalPath = '/', body, head
return shell.page({
return shell.page({
name: SITE_NAME, service: 'bot', lang: 'en', title, siteName: SITE_NAME, description, summary: description,
name: SITE_NAME, service: 'bot', lang: 'en', title, siteName: SITE_NAME, description, summary: description,
canonical: `${config.baseUrl}${canonicalPath}`, robots: 'index, follow',
canonical: `${config.baseUrl}${canonicalPath}`, robots: 'index, follow',
- navbar: nav, footer: { service: 'bot', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME }, home: '/',
+ navbar: nav, footer: { service: 'bot', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' }, home: '/',
navLinks: [{ label: 'Robots', href: '/robots' }, { label: 'Build', href: '/docs' }],
navLinks: [{ label: 'Robots', href: '/robots' }, { label: 'Build', href: '/docs' }],
head: [appIcon.headTags({ site: 'bot' }), '<link rel="manifest" href="/manifest.webmanifest">', head].filter(Boolean).join('\n'),
head: [appIcon.headTags({ site: 'bot' }), '<link rel="manifest" href="/manifest.webmanifest">', head].filter(Boolean).join('\n'),
body: `<div id="navbar-mount"></div>\n${body}`,
body: `<div id="navbar-mount"></div>\n${body}`,
});
});
}
}
+/** /updates: what shipped on OpenVibe.Bot, the network's shared update log (openvibe-shared/frame updatesBody). */
+function renderUpdates({ config }) {
+ return framePage({
+ config, title: `What shipped on ${SITE_NAME}`, description: `Every change deployed to ${SITE_NAME}, newest first.`, canonicalPath: '/updates',
+ body: `<main id="main" class="page">\n${frame.updatesBody({ service: 'bot', siteName: SITE_NAME })}\n</main>\n<script src="${ovServe.url('shipped.js')}" defer></script>`,
+ });
+}
+
function renderHome({ config, signedIn = false }) {
function renderHome({ config, signedIn = false }) {
const nav = {
const nav = {
service: 'bot',
service: 'bot',
@@ -140,4 +149,4 @@ ${sections({ signedIn })}
@@ -140,4 +149,4 @@ ${sections({ signedIn })}
});
});
}
}
-module.exports = { renderHome, framePage, HOME_CSP, sections };
+module.exports = { renderHome, renderUpdates, framePage, HOME_CSP, sections };
@@ -40,7 +40,7 @@ const { renderPanel, renderEmbedRefused, renderDrivePage, renderPairingPage, ren
@@ -40,7 +40,7 @@ const { renderPanel, renderEmbedRefused, renderDrivePage, renderPairingPage, ren
const { createMachineScreen } = require('../machine-screen');
const { createMachineScreen } = require('../machine-screen');
const { renderDocsIndex, renderDriversPage, renderProfilesPage, renderAutomationPage, renderComputersPage } = require('./docs');
const { renderDocsIndex, renderDriversPage, renderProfilesPage, renderAutomationPage, renderComputersPage } = require('./docs');
const { checkProfile } = require('../profiles');
const { checkProfile } = require('../profiles');
-const { renderHome, HOME_CSP } = require('./home');
+const { renderHome, renderUpdates, HOME_CSP } = require('./home');
const { renderDirectory, renderModels, renderModel, renderRobotPage, renderAddRobot, renderSubmit, renderSubmissions, modelOf } = require('./directory');
const { renderDirectory, renderModels, renderModel, renderRobotPage, renderAddRobot, renderSubmit, renderSubmissions, modelOf } = require('./directory');
const { LICENSES } = require('../submissions');
const { LICENSES } = require('../submissions');
@@ -52,7 +52,7 @@ const TRY_PROFILE = loadProfiles().get('sim.rover');
@@ -52,7 +52,7 @@ const TRY_PROFILE = loadProfiles().get('sim.rover');
const SITE_TAGLINE = 'An open control panel for robots';
const SITE_TAGLINE = 'An open control panel for robots';
// Every public GET page, for the sitemap. The interactive pages (/robots, /pair/, /panel/) need a session and
// Every public GET page, for the sitemap. The interactive pages (/robots, /pair/, /panel/) need a session and
// are Disallowed in robots.txt; the API and sign-in are not pages.
// are Disallowed in robots.txt; the API and sign-in are not pages.
-const PUBLIC_PAGES = ['/', '/try', '/robots', '/models', '/install', '/docs', '/docs/drivers', '/docs/profiles', '/docs/automation', '/docs/computers', '/terms', '/privacy', '/dmca'];
+const PUBLIC_PAGES = ['/', '/try', '/robots', '/models', '/install', '/docs', '/docs/drivers', '/docs/profiles', '/docs/automation', '/docs/computers', '/updates', '/terms', '/privacy', '/dmca'];
// The public pages an AI crawler can read, for /llms.txt and /llms-full.txt (llmstxt.org). Same pages as the
// The public pages an AI crawler can read, for /llms.txt and /llms-full.txt (llmstxt.org). Same pages as the
// sitemap minus /install (a 302 to the installer script, not a page); no signed-in or per-robot page, nothing
// sitemap minus /install (a 302 to the installer script, not a page); no signed-in or per-robot page, nothing
// under the API or /internal. `note` is the one-line llms.txt entry, `text` the page's full text in llms-full.txt,
// under the API or /internal. `note` is the one-line llms.txt entry, `text` the page's full text in llms-full.txt,
@@ -106,6 +106,11 @@ const LLMS_PAGES = [
@@ -106,6 +106,11 @@ const LLMS_PAGES = [
note: 'The Node\'s computer plugin: levels set on the computer, approvals there, the agent switch and the API.',
note: 'The Node\'s computer plugin: levels set on the computer, approvals there, the agent switch and the API.',
text: 'How to let your agents use your own computer through the OpenVibe Node\'s computer plugin. Installed with --computer, it runs as your own account, never root, and its only folder is your home. The level is set on the computer only: observe (screenshots, listing and reading files), ask (programs and writes each approved on the computer with openvibe-node approve), trusted (inside its folders without asking) or full. Programs are argv, never a shell line, time-limited and output-capped. The local stop drops it to observe. The switch Let my agent use this computer on openvibe.bot/machines lets your OpenVibe.Actor agent use it as you. Bot\'s machine commands take exec, fs and screen, and the computer\'s answer comes back in data.',
text: 'How to let your agents use your own computer through the OpenVibe Node\'s computer plugin. Installed with --computer, it runs as your own account, never root, and its only folder is your home. The level is set on the computer only: observe (screenshots, listing and reading files), ask (programs and writes each approved on the computer with openvibe-node approve), trusted (inside its folders without asking) or full. Programs are argv, never a shell line, time-limited and output-capped. The local stop drops it to observe. The switch Let my agent use this computer on openvibe.bot/machines lets your OpenVibe.Actor agent use it as you. Bot\'s machine commands take exec, fs and screen, and the computer\'s answer comes back in data.',
},
},
+ {
+ path: '/updates', title: 'What shipped on OpenVibe.Bot',
+ note: 'Every change deployed to OpenVibe.Bot, newest first, from the network\'s shared update log.',
+ text: 'The update log of OpenVibe.Bot: every change deployed to the site, newest first, each line a commit from the OpenVibers repositories linked to the change itself. Batches of changes become Patch notes on openvibe.blog, and the whole network\'s log is on openvibe.network/updates.',
+ },
{
{
path: '/terms', title: 'Terms of Service',
path: '/terms', title: 'Terms of Service',
note: 'The terms for using OpenVibe.Bot.',
note: 'The terms for using OpenVibe.Bot.',
@@ -259,6 +264,8 @@ function createWebRoutes(config, { domain = null, sim = null, onvif = null, limi
@@ -259,6 +264,8 @@ function createWebRoutes(config, { domain = null, sim = null, onvif = null, limi
const frameCsp = (req, res, next) => { res.setHeader('Content-Security-Policy', HOME_CSP); next(); };
const frameCsp = (req, res, next) => { res.setHeader('Content-Security-Policy', HOME_CSP); next(); };
r.get('/docs/docs.css', asset('docs.css', 'text/css'));
r.get('/docs/docs.css', asset('docs.css', 'text/css'));
r.get('/docs', frameCsp, (req, res) => res.type('html').send(renderDocsIndex({ config })));
r.get('/docs', frameCsp, (req, res) => res.type('html').send(renderDocsIndex({ config })));
+ // What shipped on this site: the shared update log, as on every OpenVibe site.
+ r.get('/updates', frameCsp, (req, res) => res.type('html').send(renderUpdates({ config })));
r.get('/docs/drivers', frameCsp, (req, res) => res.type('html').send(renderDriversPage({ config })));
r.get('/docs/drivers', frameCsp, (req, res) => res.type('html').send(renderDriversPage({ config })));
r.get('/docs/profiles', frameCsp, (req, res) => res.type('html').send(renderProfilesPage({ config })));
r.get('/docs/profiles', frameCsp, (req, res) => res.type('html').send(renderProfilesPage({ config })));
r.get('/docs/automation', frameCsp, (req, res) => res.type('html').send(renderAutomationPage({ config })));
r.get('/docs/automation', frameCsp, (req, res) => res.type('html').send(renderAutomationPage({ config })));
@@ -155,6 +155,18 @@ const { HOME_CSP: SITE_CSP } = require('../server/web/home');
@@ -155,6 +155,18 @@ const { HOME_CSP: SITE_CSP } = require('../server/web/home');
for (const p of ['/docs', '/docs/drivers', '/docs/profiles']) assert.ok(xml.includes(`http://bot.test${p}<`), `sitemap has no ${p}`);
for (const p of ['/docs', '/docs/drivers', '/docs/profiles']) assert.ok(xml.includes(`http://bot.test${p}<`), `sitemap has no ${p}`);
});
});
+ await check('GET /updates: the shared update log in the Frame, one heading, canonical', async () => {
+ const r = await get('/updates');
+ assert.strictEqual(r.status, 200);
+ assert.strictEqual(r.headers.get('content-security-policy'), SITE_CSP);
+ const html = await r.text();
+ assert.ok(html.includes('<h1>What shipped on OpenVibe.Bot</h1>') && html.includes('data-ov-shipped="log" data-service="bot"'));
+ assert.strictEqual((html.match(/<h1[\s>]/g) || []).length, 1);
+ assert.match(html, /<script src="\/shared\/shipped\.js\?v=[0-9a-f]+" defer><\/script>/);
+ assert.match(html, /<link rel="canonical" href="[^"]+\/updates">/);
+ assert.match(await (await get('/sitemap.xml')).text(), /<loc>[^<]+\/updates<\/loc>/);
+ });
+
await check('GET / links the build pages', async () => {
await check('GET / links the build pages', async () => {
const html = await (await get('/')).text();
const html = await (await get('/')).text();
assert.match(html, /href="\/docs"/);
assert.match(html, /href="\/docs"/);