openvibers/openvibe.codes Commits

The openvibe.codes home shows OpenVibe Git (its repositories, newest first, and how to clone), a quick start for the harness and the public router, and the questions people ask (#94)

OP OpenVibers committed 22 days ago · 5e21203314 · 8 files, +203 −6

Changes

ddiff --git a/public/css/codes.css b/public/css/codes.css
iindex 2d2de84..30d3581 100644
---- a/public/css/codes.css
++++ b/public/css/codes.css
@@@ -80,3 +80,17 @@ meter.headroom { width: 8rem; max-width: 100%; vertical-align: middle; }
.page .sc-ic .ov-icon { --ovi-size: 28px !important; }
.codes-hero-code { margin: 0; padding: 16px; border-radius: 14px; border: 1px solid var(--border, #1f2d47); background: var(--bg-primary, #0a0f1c); font-size: 13px; line-height: 1.6; overflow: auto; }
++
++/* The home page: quick-start samples one under another, OpenVibe Git's repositories, and the FAQ. */
++#quick-start .sc-code { grid-template-columns: minmax(0, 1fr); }
++.codes-repos { list-style: none; margin: 0 0 12px; padding: 0; display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); }
++.codes-repos a { display: flex; flex-direction: column; gap: 3px; height: 100%; padding: 12px 14px; border-radius: 14px; border: 1px solid var(--border, #1f2d47); background: var(--bg-secondary, #101828); color: inherit; text-decoration: none; }
++.codes-repos a:hover, .codes-repos a:focus-visible { border-color: var(--accent, #3b82f6); }
++.codes-repos b { font-size: 14.5px; overflow-wrap: anywhere; }
++.codes-repos small { font-size: 13px; line-height: 1.4; color: var(--text-secondary, #96a7c2); }
++.codes-repos span { margin-top: auto; font-size: 12px; color: var(--text-muted, #7386a3); }
++.codes-clone { margin: 0; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--border, #1f2d47); background: var(--bg-primary, #0a0f1c); font-size: 13px; overflow: auto; }
++.codes-faq { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }
++.codes-faq > div { padding: 16px 18px; border-radius: 16px; border: 1px solid var(--border, #1f2d47); background: var(--bg-secondary, #101828); }
++.codes-faq h3 { margin: 0 0 6px; font-size: 15.5px; line-height: 1.35; }
++.codes-faq p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--text-secondary, #96a7c2); }
ddiff --git a/server/config.js b/server/config.js
iindex 41372ba..d4ac49a 100644
---- a/server/config.js
++++ b/server/config.js
@@@ -43,6 +43,12 @@ function load(env = process.env) {
networkUrl,
networkInternalUrl: trim(env.OV_NETWORK_INTERNAL_URL || 'http://127.0.0.1:4000'),
networkIssuer: trim(env.OV_NETWORK_ISSUER || networkUrl),
++ // OpenVibe Git (Forgejo, deploy/forgejo): its loopback address for the home page's repository list (public
++ // repositories only, no token), and the public address people use.
++ git: {
++ internalUrl: trim(env.CODES_GIT_INTERNAL_URL || 'http://127.0.0.1:5060'),
++ publicUrl: trim(env.CODES_GIT_URL || 'https://git.openvibe.codes'),
++ },
// The audience of the Network service tokens a first-party service calls /api/v1/tasks with (codes.task.*).
serviceAudience: env.CODES_AUDIENCE || 'openvibe.codes',
oauth: {
ddiff --git a/server/http/home.js b/server/http/home.js
nnew file mode 100644
iindex 0000000..3abc0b9
---- /dev/null
++++ b/server/http/home.js
@@@ -0,0 +1,105 @@
++'use strict';
++/**
++ * The home page's sections beyond the kit's generic ones: OpenVibe Git (the forge at git.openvibe.codes, read from its
++ * public API: how many repositories, which changed last), quick-start commands for the harness and the public router,
++ * and the questions people ask, which the FAQPage JSON-LD repeats. The repository list is read in the background at most
++ * every ten minutes and never awaited by a page; a forge that does not answer leaves the list out.
++ */
++const contracts = require('openvibe-contracts');
++const showcase = require('openvibe-shared/showcase');
++
++const esc = showcase.esc;
++
++function createGitReader({ internalUrl, publicUrl, enabled = true, fetchImpl = globalThis.fetch, ttlMs = 10 * 60_000, timeoutMs = 3000, limit = 8 } = {}) {
++ let value = null;
++ let at = 0;
++ let inflight = null;
++ const pub = String(publicUrl || 'https://git.openvibe.codes').replace(/\/+$/, '');
++ async function refresh() {
++ if (inflight) return inflight;
++ inflight = (async () => {
++ try {
++ const res = await fetchImpl(`${String(internalUrl).replace(/\/+$/, '')}/api/v1/repos/search?limit=${limit}&sort=updated&order=desc`, { headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(timeoutMs) });
++ if (res.ok) {
++ const body = await res.json();
++ const total = Number(res.headers.get('x-total-count')) || null;
++ const repos = (Array.isArray(body.data) ? body.data : [])
++ .filter((r) => r && !r.private && /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(String(r.full_name || '')))
++ .map((r) => ({ name: r.full_name, url: `${pub}/${r.full_name}`, updated: r.updated_at || null, language: r.language || '' }));
++ if (repos.length) value = { total: total && total >= repos.length ? total : null, repos, publicUrl: pub };
++ }
++ } catch { /* keep the last answer */ }
++ at = Date.now();
++ return value;
++ })().finally(() => { inflight = null; });
++ return inflight;
++ }
++ return {
++ peek() { if (enabled && Date.now() - at > ttlMs && !inflight) refresh().catch(() => {}); return value; }, // floating-ok: refresh catches its own errors
++ refresh,
++ /** Tests. */
++ _set(v) { value = v; at = Date.now(); },
++ };
++}
++
++// A repository's line: the tagline of the service it builds, from the pinned contracts, when one names it.
++const taglines = new Map(contracts.services.manifests.filter((m) => m && m.repository && m.site && m.site.tagline).map((m) => [m.repository, m.site.tagline]));
++function ago(iso, now = Date.now()) {
++ const t = Date.parse(iso || '');
++ if (!Number.isFinite(t)) return '';
++ const m = Math.max(0, Math.round((now - t) / 60000));
++ if (m < 60) return `${m} min ago`;
++ const h = Math.round(m / 60);
++ if (h < 48) return `${h} h ago`;
++ return `${Math.round(h / 24)} days ago`;
++}
++
++function gitSection(git, now = Date.now()) {
++ const pub = (git && git.publicUrl) || 'https://git.openvibe.codes';
++ const list = git && git.repos && git.repos.length ? `<ul class="codes-repos">${git.repos.map((r) => {
++ const line = taglines.get(r.name) || '';
++ return `<li><a href="${esc(r.url)}"><b>${esc(r.name.replace(/^OpenVibers\//, ''))}</b>${line ? `<small>${esc(line)}</small>` : ''}<span>${[r.language, r.updated ? `updated ${ago(r.updated, now)}` : ''].filter(Boolean).map(esc).join(' · ')}</span></a></li>`;
++ }).join('')}</ul>` : '';
++ const count = git && git.total ? `${git.total} repositories` : 'Every OpenVibe repository';
++ return `<section class="sc-sec" id="git" aria-labelledby="h-git"><h2 id="h-git">OpenVibe Git</h2>
++<p class="sc-lede">${esc(count)} on OpenVibe's own code forge at <a href="${esc(pub)}/">git.openvibe.codes</a>: browse, clone over HTTPS and sign in with your OpenVibe account, in the network's theme. The repositories are mirrored from GitHub every hour; GitHub stays the home of each one until it moves here.</p>
++${list}<pre tabindex="0" class="codes-clone"><code>git clone ${esc(pub)}/OpenVibers/OpenVibe.Codes.git</code></pre>
++<p class="sc-note"><a href="${esc(pub)}/OpenVibers">All OpenVibers repositories</a> · <a href="${esc(pub)}/user/login">Sign in with OpenVibe</a></p></section>`;
++}
++
++/** The harness from a terminal, and the public router any program can ask. */
++function quickStart({ install }) {
++ return showcase.code({
++ id: 'quick-start', title: 'Start in a minute',
++ lede: 'The harness runs on your machine with the agent CLIs and keys you already have. The router is public: ask it which agent fits a task, no key needed.',
++ samples: [
++ { label: 'Install and run', lang: 'bash', code: `${install}
++openvibe-codes agents # which agents this machine can run
++cd my-project
++openvibe-codes run "add a test for the date parser"
++openvibe-codes run --permission read "where is the session cookie set?"
++openvibe-codes runs # every run, kept to show or resume` },
++ { label: 'Ask the router (public API)', lang: 'bash', code: `curl -s https://openvibe.codes/api/v1/harnesses/route \\
++ -H 'Content-Type: application/json' -d '{ "task": "review" }'
++# → { "selected": "claude-code:claude-code-sonnet",
++# "reasons": [ "no marginal cost (already-paid capacity or a free allowance)", … ],
++# "candidates": [ … every agent, with why it was or was not picked ] }` },
++ ],
++ });
++}
++
++// The questions people ask. The first three are Codes' own in the contracts (the answers OpenVibe.Help shows).
++const EXTRA_FAQ = [
++ { q: 'Does it cost anything?', a: 'The harness is free and open source. Each agent runs with your own CLI and keys, so you pay its provider as you already do; the router prefers capacity you have already paid for and free allowances.' },
++ { q: 'What happens when an agent gets stuck?', a: 'A failed, rate-limited or silent agent is stopped and the task goes to the next one with a note: the task, why the last agent stopped, the files it changed and the state of the tree.' },
++ { q: 'What is OpenVibe Git?', a: 'OpenVibe\'s own code forge at git.openvibe.codes, where you sign in with your OpenVibe account. It holds every public OpenVibe repository, mirrored from GitHub every hour.' },
++];
++function faqPairs() {
++ const m = contracts.services.get('codes');
++ const own = ((m && m.site && m.site.faq) || []).filter((f) => f && f.question && f.answer).map((f) => ({ q: f.question, a: f.answer }));
++ const seen = new Set();
++ return [...own, ...EXTRA_FAQ].filter((p) => !seen.has(p.q) && seen.add(p.q));
++}
++const faq = () => `<section class="sc-sec" id="faq" aria-labelledby="h-faq"><h2 id="h-faq">Questions people ask</h2><div class="codes-faq">${faqPairs().map((p) => `<div><h3>${esc(p.q)}</h3><p>${esc(p.a)}</p></div>`).join('')}</div></section>`;
++
++module.exports = { createGitReader, gitSection, quickStart, faq, faqPairs, ago };
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 10bc2a9..28fd983 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -24,6 +24,8 @@ const { html, raw, table, notice } = require('../render/html');
const { send } = require('../render/layout');
const { markdown } = require('../render/markdown');
const placement = require('../domain/harness-placement');
++const home = require('./home');
++const seo = require('openvibe-shared/seo');
const ROOT = path.join(__dirname, '..', '..');
const GITHUB = 'https://github.com/OpenVibers';
@@@ -76,12 +78,16 @@ function createPageRoutes(ctx) {
const PUBLIC_CACHE = cache.htmlHeaders({ maxAge: 300 });
const governance = loadGovernance();
const page = (req, res, o, status = 200) => send(res, status, { viewer: req.viewer, config, path: req.originalUrl, ...o });
++ // OpenVibe Git's public repositories for the home (read in the background, never awaited here); tests set ctx.git.
++ const gitReader = home.createGitReader({ internalUrl: config.git.internalUrl, publicUrl: config.git.publicUrl, enabled: config.nodeEnv !== 'test' });
// ── Home: code with any agent ───────────────────────────
r.get('/', (req, res) => {
const offers = harnesses.list();
const tasks = Object.keys(placement.TASKS);
-- const snippet = `$ openvibe-codes run "add a test for the date parser"
++ // What a run prints (bin/openvibe-codes.js), as an example: Codex hits its limit and Claude Code finishes.
++ const snippet = `# an example run: one agent hits its limit, the next one finishes
++$ openvibe-codes run "add a test for the date parser"
run run_20261008… · ~/my-project
▶ codex · session 0199…
! You have hit your usage limit.
@@@ -93,7 +99,7 @@ Added test/date.test.js: three cases, all pass.
✔ done (48 s · $0.0400 · 31k in / 1.2k out)`;
page(req, res, {
index: true, cache: PUBLIC_CACHE,
-- jsonLd: homeJsonLd(config),
++ jsonLd: [...homeJsonLd(config), seo.jsonLd.faq(home.faqPairs())],
styles: [showcase.STYLESHEET],
body: html`${raw(showcase.hero({
eyebrow: 'OpenVibe.Codes · the open coding-agent harness · alpha',
@@@ -116,6 +122,8 @@ ${raw(showcase.steps({
{ title: 'Hand-offs', text: 'When an agent fails, hits its usage limit or goes quiet, the task moves to the next one with a note: the task, the files touched and the state of the tree.' },
],
}))}
++${raw(home.quickStart({ install: INSTALL }))}
++${raw(home.gitSection((ctx.git || gitReader).peek()))}
<section class="sc-sec" aria-labelledby="h-improve"><h2 id="h-improve">Improve OpenVibe</h2>
<p class="sc-lede">OpenVibe is built in the open, ${repositories().running.length} services and their shared libraries, every one on GitHub. Pick a repository, describe the change, and send a pull request; the harness's job is to make that one step.</p>
<p><a class="sc-btn sc-primary" href="/improve">Start contributing</a> <a class="sc-btn" href="/policy/contributing">Read the guide</a></p></section>
@@@ -127,7 +135,9 @@ ${table(['Piece', 'State'], [
[html`<a href="/harnesses">The agent catalog</a>`, 'Live: every harness and model with its capabilities, prices and limits.'],
[html`<code>GET /api/v1/harnesses</code>, <code>POST /api/v1/harnesses/route</code>`, 'Live: public, no key needed; per-address and per-caller limits.'],
[html`<a href="/improve">Improve OpenVibe</a>: a repository, a change on a branch with its test, a pull request that CI checks and a maintainer reviews`, 'Live: by hand, or with the harness doing the change on your machine.'],
++ [html`<a href="${config.git.publicUrl}/">OpenVibe Git</a>: OpenVibe's own code forge`, 'Live: every public OpenVibe repository, mirrored from GitHub hourly, with OpenVibe sign-in and HTTPS cloning. GitHub stays canonical until a repository moves here.'],
])}</section>
++${raw(home.faq())}
${raw(showcase.cta({ title: 'Building an app instead?', text: 'Projects, keys, grants and the API docs of every OpenVibe service live on OpenVibe.Services.', actions: [{ label: 'Open the developer platform', href: SERVICES_ORIGIN, primary: true }, { label: 'Meet the agents', href: '/harnesses' }] }))}`,
});
});
ddiff --git a/test/home.test.js b/test/home.test.js
nnew file mode 100644
iindex 0000000..2c244db
---- /dev/null
++++ b/test/home.test.js
@@@ -0,0 +1,61 @@
++'use strict';
++/**
++ * The home page's own sections (server/http/home.js): OpenVibe Git from the forge's public API (left out when it never
++ * answered), the quick start with the release-tag install line and the public router, and a FAQ that its FAQPage JSON-LD
++ * repeats word for word. The git reader keeps only public repositories with a plain owner/name.
++ */
++const assert = require('assert');
++const http = require('http');
++const home = require('../server/http/home');
++const { boot, check, done } = require('./helpers/boot');
++
++(async () => {
++ await check('the git reader: public repositories, newest first as the forge answers, with the total it reports', async () => {
++ const srv = http.createServer((req, res) => {
++ assert.match(req.url, /^\/api\/v1\/repos\/search\?limit=8&sort=updated&order=desc$/);
++ res.setHeader('Content-Type', 'application/json');
++ res.setHeader('X-Total-Count', '52');
++ res.end(JSON.stringify({ ok: true, data: [
++ { full_name: 'OpenVibers/OpenVibe.Codes', private: false, updated_at: '2026-10-22T10:00:00Z', language: 'JavaScript' },
++ { full_name: 'OpenVibers/secret', private: true },
++ { full_name: 'bad name/<x>', private: false },
++ ] }));
++ });
++ await new Promise((r) => srv.listen(0, '127.0.0.1', r));
++ try {
++ const reader = home.createGitReader({ internalUrl: `http://127.0.0.1:${srv.address().port}`, publicUrl: 'https://git.openvibe.codes' });
++ const v = await reader.refresh();
++ assert.strictEqual(v.total, 52);
++ assert.deepStrictEqual(v.repos.map((r) => r.url), ['https://git.openvibe.codes/OpenVibers/OpenVibe.Codes']);
++ } finally { srv.close(); }
++ const down = home.createGitReader({ internalUrl: 'http://127.0.0.1:9', timeoutMs: 500 });
++ assert.strictEqual(await down.refresh(), null, 'a forge that does not answer gives no list');
++ assert.strictEqual(home.ago('2026-10-22T10:00:00Z', Date.parse('2026-10-22T12:00:00Z')), '2 h ago');
++ });
++
++ const t = await boot();
++ try {
++ await check('the home: quick start, OpenVibe Git, a FAQ and its JSON-LD', async () => {
++ let r = await t.get('/');
++ assert.strictEqual(r.status, 200);
++ assert.match(r.text, /id="quick-start"/);
++ assert.match(r.text, /npm install -g https:\/\/codeload\.github\.com\/OpenVibers\/OpenVibe\.Codes\/tar\.gz\/refs\/tags\/v\d+\.\d+\.\d+/);
++ assert.match(r.text, /https:\/\/openvibe\.codes\/api\/v1\/harnesses\/route/);
++ assert.match(r.text, /id="git"/, 'OpenVibe Git is on the home');
++ assert.ok(!/class="codes-repos"/.test(r.text), 'no repository list while the forge has not answered');
++ assert.ok(r.text.includes('git clone https://git.openvibe.codes/OpenVibers/OpenVibe.Codes.git'));
++ assert.ok(r.text.includes('# an example run'), 'the hero transcript says it is an example');
++ const blocks = [...r.text.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].map((m) => JSON.parse(m[1]));
++ const faq = blocks.find((b) => b['@type'] === 'FAQPage');
++ assert.ok(faq && faq.mainEntity.length === home.faqPairs().length && faq.mainEntity.length >= 5, 'a FAQPage node with every question');
++ faq.mainEntity.forEach((q) => assert.ok(r.text.includes(`<h3>${q.name.replace(/'/g, '&#39;')}</h3>`), `the page shows "${q.name}"`));
++ assert.strictEqual((r.text.match(/<h1[\s>]/g) || []).length, 1, 'one h1');
++
++ t.ctx.git = { peek: () => ({ total: 52, publicUrl: 'https://git.openvibe.codes', repos: [{ name: 'OpenVibers/OpenVibe.Codes', url: 'https://git.openvibe.codes/OpenVibers/OpenVibe.Codes', updated: new Date().toISOString(), language: 'JavaScript' }] }) };
++ r = await t.get('/');
++ assert.match(r.text, /52 repositories on OpenVibe&#39;s own code forge|52 repositories on OpenVibe's own code forge/);
++ assert.ok(r.text.includes('href="https://git.openvibe.codes/OpenVibers/OpenVibe.Codes"') && r.text.includes('<small>Code with any agent</small>'), 'a repository with its service tagline');
++ });
++ } finally { await t.close(); }
++ done();
++})().catch((e) => { console.error(e); process.exit(1); });
ddiff --git a/test/layout.test.js b/test/layout.test.js
iindex dca81ff..e5835a7 100644
---- a/test/layout.test.js
++++ b/test/layout.test.js
@@@ -46,7 +46,7 @@ const BOOST = /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#mai
assert.ok(head.includes('<link rel="canonical" href="https://openvibe.codes/">'), 'canonical');
assert.ok(head.includes('<meta name="robots" content="index, follow">'), 'robots');
const ld = [...head.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].map((m) => JSON.parse(m[1])['@type']);
-- assert.deepStrictEqual(ld, ['WebSite', 'WebApplication', 'WebPage'], 'the home JSON-LD');
++ assert.deepStrictEqual(ld, ['WebSite', 'WebApplication', 'WebPage', 'FAQPage'], 'the home JSON-LD (the FAQPage repeats the questions the page shows)');
for (const tag of ['<meta property="og:title"', 'data-ov-icon="codes"', '/css/codes.css?v=', '/shared/theme-loader.js', '/shared/navbar.js', '/shared/footer.js', '<meta name="referrer"']) {
assert.ok(head.includes(tag), `head has ${tag}`);
}
ddiff --git a/test/perf-budget.test.js b/test/perf-budget.test.js
iindex d39afb9..a816ca0 100644
---- a/test/perf-budget.test.js
++++ b/test/perf-budget.test.js
@@@ -12,13 +12,13 @@ const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
-- htmlRawKB: 30, // measured 27.0 (fresh database; the showcase home, 2026-09-29: sections + six inline ring icons)
-- htmlBrotliKB: 7.5, // 6.7
++ htmlRawKB: 35, // 31.5 with the home's quick start, OpenVibe Git and six FAQs with their FAQPage JSON-LD (2026-10-22; in production the forge's eight repositories add about 2.5); measured 27.0 (fresh database; the showcase home, 2026-09-29: sections + six inline ring icons)
++ htmlBrotliKB: 9, // 7.7 with those sections (2026-10-22); 6.7
jsFiles: 5, // 5 (theme-loader, web-runtime, navbar, footer, boost: openvibe-shared/shell adds web-runtime, 2026-10-04)
jsRawKB: 250, // 246.2 with openvibe-shared 4.2.x (Ask Actor in the navbar, 2026-10-15); 239.1 (212.2 before openvibe-shared/shell's web-runtime.js)
jsBrotliKB: 60, // 59.1 with openvibe-shared 4.4.0 (every site in the launcher, 2026-10-17); 56.3 (49.9 before web-runtime.js)
cssFiles: 2, // 1
-- cssRawKB: 19, // 17.3 (codes.css + the cached /shared/showcase.css)
++ cssRawKB: 21, // about 19.2 with the repository cards and FAQ styles (2026-10-22); 17.3 (codes.css + the cached /shared/showcase.css)
cssBrotliKB: 5, // 4.1
externalFiles: 2, // 0
};
ddiff --git a/test/security-ssrf.test.js b/test/security-ssrf.test.js
iindex c2572d7..f5e31bc 100644
---- a/test/security-ssrf.test.js
++++ b/test/security-ssrf.test.js
@@@ -43,6 +43,7 @@ const INTERNAL = [`http://127.0.0.1:3000${PROBE}`, `http://2130706433${PROBE}`,
'server/auth/sso.js': 'Network OAuth token and revoke (configured)',
'server/domain/run-client.js': 'OpenVibe.Run (CODES_RUN_URL, configured) and the Network token endpoint; job ids only, never a URL from a person',
'server/http/run.js': 'Network projects API (configured) with the viewer\'s token, and the Run job stream URL run-client.js builds on its own base',
++ 'server/http/home.js': 'OpenVibe Git\'s public repository search (CODES_GIT_INTERNAL_URL, configured loopback Forgejo) for the home page; no token',
};
const root = path.join(__dirname, '..');
const found = [];