openvibers/openvibe.exchange Commits

Answer the companions and privacy/keys twins, drop the owner-only console link (#132)

OP OpenVibers committed 4 days ago · 33d8ac6c30 · 3 files, +47 −2

llms.txt links /companions.md and /privacy/keys.md, but the agent layer
had no renderer for either. Add both, signed out: the gallery through the
same view the page uses, and the keys page as the sign-in prompt a
signed-out visitor actually gets.

/console is owner-only (404 to anyone else), so it has nothing public to
twin. Its llms.txt link is dropped instead of pointing agents at a 404.

Changes

ddiff --git a/server/agent-pages.js b/server/agent-pages.js
iindex d2d57f6..0765382 100644
---- a/server/agent-pages.js
++++ b/server/agent-pages.js
@@@ -3,6 +3,7 @@
// own render through the same views and layout the HTML routes use, always signed out: a chat, a try box and the
// 18+ confirmation never appear in a twin.
const views = require('./views/exchange');
++const companionViews = require('./views/companions');
const arenaViews = require('./views/arena');
const { CATEGORIES } = require('./arena/category');
const { ARENA_KIND } = require('./bench');
@@@ -13,7 +14,7 @@ const { homeJsonLd } = require('./http/discovery');
const HOME_DESCRIPTION = 'OpenVibe.Exchange: every AI model, one account. Live prices, speed and uptime of every offer, from the families on sale today and more as their providers join; chat with one model or three side by side; one OpenAI- and Anthropic-compatible endpoint.';
function createAgentPages(ctx) {
-- const { config, ai, bench, history } = ctx;
++ const { config, ai, bench, history, characters } = ctx;
const page = (path, o) => ({ html: renderPage({ viewer: { kind: 'anonymous' }, config, path, agentLayer: true, ...o }), title: o.title });
async function home(path) {
@@@ -83,6 +84,30 @@ function createAgentPages(ctx) {
]);
return page(p, { index: true, title: 'Status', description: 'Uptime per offer, the model changelog, and incidents.', body: raw(views.statusPage({ catalog, usage, changes })) });
}
++ if (p === '/companions') {
++ // The public gallery, always the signed-out view: no "Your characters", no adult cards.
++ const [gallery, tagList] = await Promise.all([
++ characters.gallery({ sort: 'new', q: '', tags: [], limit: 48, allowAdult: false }),
++ characters.allTags(24),
++ ]);
++ return page(p, {
++ index: true, title: 'Characters',
++ description: 'Character cards on OpenVibe.Exchange: bring a card in as Character Card V2 or V3, talk to it, and take it with you again.',
++ body: raw(companionViews.galleryPage({
++ items: gallery.items, total: gallery.total, q: '', tags: [], sort: 'new', tagList,
++ viewer: { kind: 'anonymous' }, adult: false, mine: [], defaults: '',
++ })),
++ });
++ }
++ if (p === '/privacy/keys') {
++ // Signed out, /privacy/keys only sends a person to sign in; the twin says that and nothing of an
++ // account, a credential or a key.
++ return page(p, {
++ index: false, title: 'Your keys and accounts',
++ description: 'Sign in to manage your own provider keys and supported browser session cookies.',
++ body: raw('<div class="ex-wrap ex-narrow"><header class="ex-companions-head"><h1>Your keys and accounts</h1><p class="ex-lede">Sign in to manage your own provider keys and supported browser session cookies.</p><p class="ex-head-actions"><a class="ovk-btn ovk-btn--primary" href="/auth/login?next=%2Fprivacy%2Fkeys">Sign in with OpenVibe</a></p></header></div>'),
++ });
++ }
return null;
}
ddiff --git a/server/http/discovery.js b/server/http/discovery.js
iindex 12e3b62..b3171bb 100644
---- a/server/http/discovery.js
++++ b/server/http/discovery.js
@@@ -127,7 +127,6 @@ function createDiscoveryRoutes(ctx) {
{ title: 'Full text for language models', url: abs('/llms-full.txt') },
{ title: 'Release metadata (JSON)', url: abs('/release.json') },
{ title: 'AI router API', url: 'https://ai.openvibe.services', note: 'the API and run receipts' },
-- { title: 'AI operator console', url: abs('/console'), note: 'owner-only controls for providers, models, routes, quotas, usage and runs' },
{ title: 'Your keys and accounts', url: abs('/privacy/keys'), note: 'manage your own provider keys and supported browser session cookies' },
{ title: 'OpenVibe Link', url: 'https://openvibe.link', note: 'offer a machine to the network' },
],
ddiff --git a/test/agent-layer.test.js b/test/agent-layer.test.js
iindex 6cb445b..4044b10 100644
---- a/test/agent-layer.test.js
++++ b/test/agent-layer.test.js
@@@ -49,6 +49,27 @@ const { createFakeAi } = require('./helpers/ai');
assert.strictEqual((await t.get('/no-such-page.md')).status, 404);
});
++ await check('the companions and privacy/keys twins answer Markdown with their headings', async () => {
++ const companions = await t.get('/companions.md');
++ assert.strictEqual(companions.status, 200);
++ assert.match(companions.headers.get('content-type'), /^text\/markdown/);
++ assert.ok(companions.text.includes('# Characters'), 'the gallery twin names the page heading');
++
++ const keys = await t.get('/privacy/keys.md');
++ assert.strictEqual(keys.status, 200);
++ assert.match(keys.headers.get('content-type'), /^text\/markdown/);
++ assert.ok(keys.text.includes('# Your keys and accounts'), 'the keys twin names the page heading');
++ assert.ok(keys.text.includes('Sign in'), 'signed out, it says what the page says to a signed-out visitor');
++ assert.ok(!/api key|session cookie value|secret/i.test(keys.text), 'and nothing of an account credential');
++
++ // The owner-only console has no public page, so it has no twin and no llms.txt link.
++ assert.strictEqual((await t.get('/console.md')).status, 404);
++ const llms = await t.get('/llms.txt');
++ assert.ok(llms.text.includes('[The gallery](https://openvibe.exchange/companions.md)'));
++ assert.ok(llms.text.includes('[Your keys and accounts](https://openvibe.exchange/privacy/keys.md)'));
++ assert.ok(!llms.text.includes('/console'));
++ });
++
await check('/.well-known/security.txt is not swallowed by the layer', async () => {
const r = await t.get('/.well-known/security.txt');
assert.notStrictEqual(r.status, 200, 'the layer only takes agent.json');