openvibers/openvibe.computer Commits

Add llms-full.txt (#9)

OP OpenVibers committed 4 days ago · f483b721bf · 2 files, +27 −2

llms.txt lists the templates, pricing, API docs and updates pages, but
the site had no /llms-full.txt. Build it from the same page list with
openvibe-shared/seo.llmsFull, and name it in llms.txt.

The test asks for 200 text/plain, the site heading and each page URL.

Changes

ddiff --git a/server/app.js b/server/app.js
iindex a84733a..0e98e25 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -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 }));
ddiff --git a/test/computer.test.js b/test/computer.test.js
iindex 30a9086..9a37a16 100644
---- a/test/computer.test.js
++++ b/test/computer.test.js
@@@ -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']);
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'); }
});