openvibers/openvibe.computer Commits
Add llms-full.txt (#9)
Changes
@@@ -6,8 +6,20 @@ const { createSsoClient } = require('openvibe-sdk/sso');const { createRunClient } = require('./run-client');const { createApi } = require('./api');const { createPages } = require('./pages');++const seo = require('openvibe-shared/seo');const configLib = require('./config');++const SITE_NAME = 'OpenVibe.Computer';++const SUMMARY = 'A computer for you, your AI, or both. Start a browser desk on OpenVibe Run; Run owns sessions, placement and budgets.';++// The public pages, for /llms-full.txt: the same pages llms.txt and the sitemap list.++const PUBLIC_PAGES = [++ { title: SITE_NAME, url: '/', text: SUMMARY },++ { title: 'Computer templates', url: '/templates', text: 'Choose the browser profile behavior before Run places the desk: a browser, a throwaway browser, or a kept browser where the machine supports it.' },++ { title: 'Pricing', url: '/pricing', text: 'Your Link machines are free with no time limit. The browser and desktop allowances and the per-minute price after them come from service configuration; Run enforces the session budget.' },++ { title: 'Computer API docs', url: '/docs', text: 'Computer forwards each signed-in person\'s Network token to Run. GET and POST /api/v1/computers, GET /api/v1/computers/{id}, POST /api/v1/computers/{id}/stop, screenshot, act and handoff. Run owns budgets, placement and session state.' },++ { title: 'Updates', url: '/updates', text: 'The first front door uses Run browser sessions. Desktop, GPU and always-on sessions follow as placement and templates arrive in Run.' },++];++function createApp(opts = {}) {const config = opts.config || configLib.load();const sso = opts.sso || createSsoClient({ site: 'computer', baseUrl: config.baseUrl, clientId: config.clientId, clientSecret: config.clientSecret, networkUrl: config.networkUrl, networkInternalUrl: config.networkInternalUrl, secureCookies: config.secureCookies });@@@ -24,7 +36,12 @@ function createApp(opts = {}) {app.get('/robots.txt', (_req, res) => res.type('text/plain').send('User-agent: *\nAllow: /\nDisallow: /api/\nSitemap: https://openvibe.computer/sitemap.xml\n'));app.get('/sitemap.xml', (_req, res) => res.type('application/xml').send('<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>https://openvibe.computer/</loc></url><url><loc>https://openvibe.computer/templates</loc></url><url><loc>https://openvibe.computer/pricing</loc></url><url><loc>https://openvibe.computer/docs</loc></url><url><loc>https://openvibe.computer/updates</loc></url></urlset>'));{ const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'computer', service: 'computer', host: 'openvibe.computer', name: 'OpenVibe.Computer', profile: 'hosting' })); }-- app.get('/llms.txt', (_req, res) => res.type('text/plain').send('# OpenVibe.Computer\n\nA computer for you, your AI, or both. Run owns sessions, placement and budgets.\n\n- [Templates](/templates)\n- [Pricing](/pricing)\n- [API docs](/docs)\n- [OpenAPI](/openapi.json)\n- [Agent descriptor](/.well-known/agent.json)\n'));++ app.get('/llms.txt', (_req, res) => res.type('text/plain').send('# OpenVibe.Computer\n\nA computer for you, your AI, or both. Run owns sessions, placement and budgets.\n\n- [Templates](/templates)\n- [Pricing](/pricing)\n- [API docs](/docs)\n- [OpenAPI](/openapi.json)\n- [Agent descriptor](/.well-known/agent.json)\n- [Full text for language models](/llms-full.txt)\n'));++ // The same public pages with their text, for a model that wants the site in one read (llms-full.txt, llmstxt.org).++ app.get('/llms-full.txt', (_req, res) => res.type('text/plain').send(seo.llmsFull({++ site: SITE_NAME, summary: SUMMARY, base: 'https://openvibe.computer',++ sections: [{ title: 'Pages', pages: PUBLIC_PAGES }],++ })));app.get('/openapi.json', (_req, res) => res.sendFile(path.join(__dirname, '..', 'public', 'openapi.json')));app.get('/.well-known/agent.json', (_req, res) => res.sendFile(path.join(__dirname, '..', 'public', '.well-known', 'agent.json')));app.use('/api/v1', createApi({ run, sso }));@@@ -32,7 +32,15 @@ test('home renders signed out and signed in; sign-in and computer controls are vassert.match(page, /Take over/); assert.equal(calls[0][1], 'person-a'); assert.deepEqual(calls[1], ['get', 'person-a', 'ses_a']);for (const path of ['/templates', '/pricing', '/docs', '/updates']) { const r = await fetch(`${s.base}${path}`); assert.equal(r.status, 200, `${path} renders signed out`); }for (const path of ['/templates', '/pricing', '/docs', '/updates']) { const r = await fetch(`${s.base}${path}`, { headers: { cookie: 'ov_token=person-a' } }); assert.equal(r.status, 200, `${path} renders signed in`); }-- assert.equal((await fetch(`${s.base}/openapi.json`)).status, 200); assert.equal((await fetch(`${s.base}/.well-known/agent.json`)).status, 200); assert.equal((await fetch(`${s.base}/llms.txt`)).status, 200);++ assert.equal((await fetch(`${s.base}/openapi.json`)).status, 200); assert.equal((await fetch(`${s.base}/.well-known/agent.json`)).status, 200);++ const llms = await fetch(`${s.base}/llms.txt`); assert.equal(llms.status, 200);++ assert.match(await llms.text(), /\(\/llms-full\.txt\)/, 'llms.txt names the full text');++ const full = await fetch(`${s.base}/llms-full.txt`); assert.equal(full.status, 200);++ assert.match(full.headers.get('content-type'), /^text\/plain/);++ const fullText = await full.text();++ assert.ok(fullText.startsWith('# OpenVibe.Computer\n'), 'llms-full.txt opens with the site heading');++ assert.ok(fullText.length > 500, `llms-full.txt carries the pages (${fullText.length} characters)`);++ for (const path of ['/templates', '/pricing', '/docs', '/updates']) assert.ok(fullText.includes(`URL: https://openvibe.computer${path}`), `llms-full.txt lists ${path}`);{ const r = await fetch(`${s.base}/release.json`); assert.equal(r.status, 200, 'the navbar release watch polls /release.json'); assert.equal((await r.json()).service, 'computer'); }});
@@ -6,8 +6,20 @@ const { createSsoClient } = require('openvibe-sdk/sso');
@@ -6,8 +6,20 @@ const { createSsoClient } = require('openvibe-sdk/sso');
const { createRunClient } = require('./run-client');
const { createRunClient } = require('./run-client');
const { createApi } = require('./api');
const { createApi } = require('./api');
const { createPages } = require('./pages');
const { createPages } = require('./pages');
+const seo = require('openvibe-shared/seo');
const configLib = require('./config');
const configLib = require('./config');
+const SITE_NAME = 'OpenVibe.Computer';
+const SUMMARY = 'A computer for you, your AI, or both. Start a browser desk on OpenVibe Run; Run owns sessions, placement and budgets.';
+// The public pages, for /llms-full.txt: the same pages llms.txt and the sitemap list.
+const PUBLIC_PAGES = [
+ { title: SITE_NAME, url: '/', text: SUMMARY },
+ { title: 'Computer templates', url: '/templates', text: 'Choose the browser profile behavior before Run places the desk: a browser, a throwaway browser, or a kept browser where the machine supports it.' },
+ { title: 'Pricing', url: '/pricing', text: 'Your Link machines are free with no time limit. The browser and desktop allowances and the per-minute price after them come from service configuration; Run enforces the session budget.' },
+ { title: 'Computer API docs', url: '/docs', text: 'Computer forwards each signed-in person\'s Network token to Run. GET and POST /api/v1/computers, GET /api/v1/computers/{id}, POST /api/v1/computers/{id}/stop, screenshot, act and handoff. Run owns budgets, placement and session state.' },
+ { title: 'Updates', url: '/updates', text: 'The first front door uses Run browser sessions. Desktop, GPU and always-on sessions follow as placement and templates arrive in Run.' },
+];
+
function createApp(opts = {}) {
function createApp(opts = {}) {
const config = opts.config || configLib.load();
const config = opts.config || configLib.load();
const sso = opts.sso || createSsoClient({ site: 'computer', baseUrl: config.baseUrl, clientId: config.clientId, clientSecret: config.clientSecret, networkUrl: config.networkUrl, networkInternalUrl: config.networkInternalUrl, secureCookies: config.secureCookies });
const sso = opts.sso || createSsoClient({ site: 'computer', baseUrl: config.baseUrl, clientId: config.clientId, clientSecret: config.clientSecret, networkUrl: config.networkUrl, networkInternalUrl: config.networkInternalUrl, secureCookies: config.secureCookies });
@@ -24,7 +36,12 @@ function createApp(opts = {}) {
@@ -24,7 +36,12 @@ function createApp(opts = {}) {
app.get('/robots.txt', (_req, res) => res.type('text/plain').send('User-agent: *\nAllow: /\nDisallow: /api/\nSitemap: https://openvibe.computer/sitemap.xml\n'));
app.get('/robots.txt', (_req, res) => res.type('text/plain').send('User-agent: *\nAllow: /\nDisallow: /api/\nSitemap: https://openvibe.computer/sitemap.xml\n'));
app.get('/sitemap.xml', (_req, res) => res.type('application/xml').send('<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>https://openvibe.computer/</loc></url><url><loc>https://openvibe.computer/templates</loc></url><url><loc>https://openvibe.computer/pricing</loc></url><url><loc>https://openvibe.computer/docs</loc></url><url><loc>https://openvibe.computer/updates</loc></url></urlset>'));
app.get('/sitemap.xml', (_req, res) => res.type('application/xml').send('<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>https://openvibe.computer/</loc></url><url><loc>https://openvibe.computer/templates</loc></url><url><loc>https://openvibe.computer/pricing</loc></url><url><loc>https://openvibe.computer/docs</loc></url><url><loc>https://openvibe.computer/updates</loc></url></urlset>'));
{ const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'computer', service: 'computer', host: 'openvibe.computer', name: 'OpenVibe.Computer', profile: 'hosting' })); }
{ const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'computer', service: 'computer', host: 'openvibe.computer', name: 'OpenVibe.Computer', profile: 'hosting' })); }
- app.get('/llms.txt', (_req, res) => res.type('text/plain').send('# OpenVibe.Computer\n\nA computer for you, your AI, or both. Run owns sessions, placement and budgets.\n\n- [Templates](/templates)\n- [Pricing](/pricing)\n- [API docs](/docs)\n- [OpenAPI](/openapi.json)\n- [Agent descriptor](/.well-known/agent.json)\n'));
+ app.get('/llms.txt', (_req, res) => res.type('text/plain').send('# OpenVibe.Computer\n\nA computer for you, your AI, or both. Run owns sessions, placement and budgets.\n\n- [Templates](/templates)\n- [Pricing](/pricing)\n- [API docs](/docs)\n- [OpenAPI](/openapi.json)\n- [Agent descriptor](/.well-known/agent.json)\n- [Full text for language models](/llms-full.txt)\n'));
+ // The same public pages with their text, for a model that wants the site in one read (llms-full.txt, llmstxt.org).
+ app.get('/llms-full.txt', (_req, res) => res.type('text/plain').send(seo.llmsFull({
+ site: SITE_NAME, summary: SUMMARY, base: 'https://openvibe.computer',
+ sections: [{ title: 'Pages', pages: PUBLIC_PAGES }],
+ })));
app.get('/openapi.json', (_req, res) => res.sendFile(path.join(__dirname, '..', 'public', 'openapi.json')));
app.get('/openapi.json', (_req, res) => res.sendFile(path.join(__dirname, '..', 'public', 'openapi.json')));
app.get('/.well-known/agent.json', (_req, res) => res.sendFile(path.join(__dirname, '..', 'public', '.well-known', 'agent.json')));
app.get('/.well-known/agent.json', (_req, res) => res.sendFile(path.join(__dirname, '..', 'public', '.well-known', 'agent.json')));
app.use('/api/v1', createApi({ run, sso }));
app.use('/api/v1', createApi({ run, sso }));
@@ -32,7 +32,15 @@ test('home renders signed out and signed in; sign-in and computer controls are v
@@ -32,7 +32,15 @@ test('home renders signed out and signed in; sign-in and computer controls are v
assert.match(page, /Take over/); assert.equal(calls[0][1], 'person-a'); assert.deepEqual(calls[1], ['get', 'person-a', 'ses_a']);
assert.match(page, /Take over/); assert.equal(calls[0][1], 'person-a'); assert.deepEqual(calls[1], ['get', 'person-a', 'ses_a']);
for (const path of ['/templates', '/pricing', '/docs', '/updates']) { const r = await fetch(`${s.base}${path}`); assert.equal(r.status, 200, `${path} renders signed out`); }
for (const path of ['/templates', '/pricing', '/docs', '/updates']) { const r = await fetch(`${s.base}${path}`); assert.equal(r.status, 200, `${path} renders signed out`); }
for (const path of ['/templates', '/pricing', '/docs', '/updates']) { const r = await fetch(`${s.base}${path}`, { headers: { cookie: 'ov_token=person-a' } }); assert.equal(r.status, 200, `${path} renders signed in`); }
for (const path of ['/templates', '/pricing', '/docs', '/updates']) { const r = await fetch(`${s.base}${path}`, { headers: { cookie: 'ov_token=person-a' } }); assert.equal(r.status, 200, `${path} renders signed in`); }
- assert.equal((await fetch(`${s.base}/openapi.json`)).status, 200); assert.equal((await fetch(`${s.base}/.well-known/agent.json`)).status, 200); assert.equal((await fetch(`${s.base}/llms.txt`)).status, 200);
+ assert.equal((await fetch(`${s.base}/openapi.json`)).status, 200); assert.equal((await fetch(`${s.base}/.well-known/agent.json`)).status, 200);
+ const llms = await fetch(`${s.base}/llms.txt`); assert.equal(llms.status, 200);
+ assert.match(await llms.text(), /\(\/llms-full\.txt\)/, 'llms.txt names the full text');
+ const full = await fetch(`${s.base}/llms-full.txt`); assert.equal(full.status, 200);
+ assert.match(full.headers.get('content-type'), /^text\/plain/);
+ const fullText = await full.text();
+ assert.ok(fullText.startsWith('# OpenVibe.Computer\n'), 'llms-full.txt opens with the site heading');
+ assert.ok(fullText.length > 500, `llms-full.txt carries the pages (${fullText.length} characters)`);
+ for (const path of ['/templates', '/pricing', '/docs', '/updates']) assert.ok(fullText.includes(`URL: https://openvibe.computer${path}`), `llms-full.txt lists ${path}`);
{ const r = await fetch(`${s.base}/release.json`); assert.equal(r.status, 200, 'the navbar release watch polls /release.json'); assert.equal((await r.json()).service, 'computer'); }
{ const r = await fetch(`${s.base}/release.json`); assert.equal(r.status, 200, 'the navbar release watch polls /release.json'); assert.equal((await r.json()).service, 'computer'); }
});
});