Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion data/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@
"group": "engines",
"summary": "launch an engine with its native defaults"
},
{
"name": "oneshot",
"aliases": [],
"group": "engines",
"summary": "run one prompt on one engine (CLI or API: zai, perplexity, fugu) and print the answer"
},
{
"name": "hooks",
"aliases": [],
"group": "engines",
"summary": "fan-out prompt runner: a signed webhook in, one signed result per engine back out"
},
{
"name": "herd",
"aliases": [],
Expand All @@ -49,6 +61,12 @@
"group": "runtime",
"summary": "the OpenFleet sysop tool: open a fleet, cap it, see the tree, stop a swarm, read the ledger (PRD 0016)"
},
{
"name": "work",
"aliases": [],
"group": "runtime",
"summary": "a queue of tasks that carry their own verify command, drained by headless workers; you see only the inbox (PRD 0021)"
},
{
"name": "handoff",
"aliases": [],
Expand Down Expand Up @@ -175,7 +193,7 @@
"name": "export",
"aliases": [],
"group": "account",
"summary": "operator only: export the app's users as CSV, optionally cleaned"
"summary": "operator only: export users as CSV (this app, or --all properties), optionally cleaned"
},
{
"name": "console",
Expand Down
5 changes: 5 additions & 0 deletions public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,8 @@ h1 {
.prose .prose-list li { margin-bottom: 10px; }
.prose .term { margin: 26px 0 28px; }
@media (min-width: 900px) { .prose .term-wide { width: calc(100% + 160px); max-width: none; } }

/* CrawlProof text_link frame above the footer: fluid, 40px, carries its own
"Sponsored" mark. */
.cp-ad { margin-top: 40px; margin-bottom: 40px; }
.cp-ad iframe { display: block; width: 100%; height: 40px; border: 0; }
2 changes: 1 addition & 1 deletion src/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function createApp() {
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'Content-Security-Policy':
`default-src 'self'; script-src 'self' https://crawlproof.com; style-src 'self' 'nonce-${res.locals.nonce}'; img-src 'self' data:; font-src 'self'; connect-src 'self' https://crawlproof.com; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'`,
`default-src 'self'; script-src 'self' https://crawlproof.com; style-src 'self' 'nonce-${res.locals.nonce}'; img-src 'self' data:; font-src 'self'; connect-src 'self' https://crawlproof.com; frame-src https://crawlproof.com; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'`,
'Permissions-Policy': 'camera=(), microphone=(), geolocation=(), interest-cohort=()',
});
next();
Expand Down
19 changes: 18 additions & 1 deletion src/site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,25 @@ ${extraHead}
<main id="main">`;
}

// One CrawlProof ad unit (slot 71955dcb, https://crawlproof.com/ads/slots) on
// every public page, above the footer. This site's CSP allows no inline style
// and only 'self' images, and ad.js renders into a srcdoc iframe that would
// inherit that policy, so it uses the script-free cross-origin frame instead:
// one frame-src entry, no third-party JS, nothing written to localStorage.
// text_link is the only fluid format (40px at any width, its own "Sponsored"
// mark), so it never crops on a phone and needs no viewport measurement.
export const AD_SLOT = '71955dcb-8cb9-46fe-8df5-e870d973a8e2';
export const AD_FRAME_SRC = `https://crawlproof.com/api/ads/frame?slot=${AD_SLOT}&format=text_link`;

export function adUnit() {
return `<aside class="cp-ad wrap" aria-label="Advertisement">
<iframe src="${esc(AD_FRAME_SRC)}" title="Advertisement" height="40" loading="lazy" scrolling="no" sandbox="allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"></iframe>
</aside>`;
}

export function foot() {
return `</main>
return `${adUnit()}
</main>
<footer class="foot">
<div class="wrap foot-grid">
<div class="foot-brand">
Expand Down
10 changes: 10 additions & 0 deletions test/site.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,13 @@ test('nav section links are absolute, so they work off the home page', async ()
assert.match(res.body, /href="\/#herd"/);
assert.doesNotMatch(res.body, /href="#herd"/);
});

test('every public page carries exactly one CrawlProof ad frame, allowed by the CSP', async () => {
for (const path of ['/', '/commands', '/blog']) {
const res = await get(path);
assert.equal(res.status, 200, path);
const frames = res.body.match(/crawlproof\.com\/api\/ads\/frame\?slot=71955dcb-8cb9-46fe-8df5-e870d973a8e2&amp;format=text_link/g) ?? [];
assert.equal(frames.length, 1, `${path} has one ad frame`);
assert.match(res.headers.get('content-security-policy'), /frame-src https:\/\/crawlproof\.com/);
}
});
Loading