diff --git a/data/commands.json b/data/commands.json
index cf2569a..0cfa540 100644
--- a/data/commands.json
+++ b/data/commands.json
@@ -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": [],
@@ -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": [],
@@ -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",
diff --git a/public/style.css b/public/style.css
index bf0f8dc..530c51c 100644
--- a/public/style.css
+++ b/public/style.css
@@ -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; }
diff --git a/src/server.mjs b/src/server.mjs
index 2d93617..a7b8e7e 100644
--- a/src/server.mjs
+++ b/src/server.mjs
@@ -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();
diff --git a/src/site.mjs b/src/site.mjs
index ce046f3..a383c2d 100644
--- a/src/site.mjs
+++ b/src/site.mjs
@@ -87,8 +87,25 @@ ${extraHead}
`;
}
+// 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 ``;
+}
+
export function foot() {
- return `
+ return `${adUnit()}
+