web-agent-bridge 3.17.0 → 3.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ar.md +27 -8
- package/README.md +95 -0
- package/bin/wab-init.js +38 -0
- package/package.json +1 -1
- package/public/atp-semantics.html +216 -0
- package/public/benchmarks.html +151 -0
- package/public/docs.html +113 -43
- package/public/index.html +142 -8
- package/public/key-rotation.html +184 -0
- package/public/llms.txt +54 -0
- package/public/notary.html +94 -0
- package/public/observatory.html +103 -0
- package/public/research.html +57 -0
- package/public/researchers.html +113 -0
- package/public/responsible-disclosure.html +294 -0
- package/public/robots.txt +17 -0
- package/public/security.html +157 -0
- package/public/threat-model.html +153 -0
- package/public/viral-coefficient.html +533 -0
- package/public/wab-dataset.html +501 -0
- package/public/wab-email.html +78 -0
- package/public/wab-lens.html +61 -0
- package/public/wab-p2p.html +96 -0
- package/public/wab-registry.html +481 -0
- package/public/wab-today.html +448 -0
- package/public/wab-uri.html +88 -0
- package/script/ai-agent-bridge.js +24 -4
- package/server/index.js +1193 -827
- package/server/models/db.js +2 -1
- package/server/routes/admin-shieldlink.js +1 -1
- package/server/routes/admin-shieldqr.js +1 -1
- package/server/routes/admin-trust-monitor.js +1 -1
- package/server/routes/api-keys.js +2 -1
- package/server/routes/customer-shieldlink.js +1 -1
- package/server/routes/enterprise-mesh.js +2 -1
- package/server/routes/genius-bridge.js +256 -0
- package/server/routes/genius-gateway.js +137 -0
- package/server/routes/governance-saas.js +2 -1
- package/server/routes/notary.js +309 -0
- package/server/routes/observatory.js +109 -0
- package/server/routes/partners.js +2 -1
- package/server/routes/registry.js +352 -0
- package/server/routes/research.js +83 -0
- package/server/routes/ring4.js +2 -1
- package/server/routes/runtime.js +98 -25
- package/server/routes/security-researchers.js +161 -0
- package/server/routes/shieldqr.js +1 -1
- package/server/routes/traces.js +247 -0
- package/server/services/agent-tasks.js +9 -7
- package/server/services/email.js +50 -2
- package/server/services/marketplace.js +27 -8
- package/server/services/plans.js +1 -1
- package/server/services/shieldlink.js +1 -1
- package/server/services/ssl-ct-monitor.js +1 -1
- package/server/services/ssl-monitor.js +1 -1
- package/server/services/stripe.js +29 -4
- package/server/utils/migrate.js +1 -1
- package/server/utils/safe-compare.js +26 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>WAB Local Cache Network — peer-assisted manifest distribution</title>
|
|
7
|
+
<meta name="description" content="Optional peer-to-peer caching extension for /.well-known/wab.json — agents share signed manifests so verification keeps working when the origin is down.">
|
|
8
|
+
<link rel="icon" href="/assets/favicon.svg">
|
|
9
|
+
<style>
|
|
10
|
+
:root{--bg:#0b0f17;--panel:#111827;--fg:#e5e7eb;--muted:#9ca3af;--line:#1f2937;--accent:#60a5fa}
|
|
11
|
+
*{box-sizing:border-box}
|
|
12
|
+
body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
|
|
13
|
+
header{padding:48px 24px 24px;text-align:center;border-bottom:1px solid var(--line)}
|
|
14
|
+
h1{margin:0 0 8px;font-size:28px}
|
|
15
|
+
main{max-width:840px;margin:0 auto;padding:24px}
|
|
16
|
+
h2{margin-top:32px;font-size:18px;border-bottom:1px solid var(--line);padding-bottom:6px}
|
|
17
|
+
pre,code{background:#0d1320;border:1px solid var(--line);border-radius:6px}
|
|
18
|
+
pre{padding:12px;overflow-x:auto;font-size:12px}
|
|
19
|
+
code{padding:2px 6px;font-size:12.5px}
|
|
20
|
+
a{color:var(--accent);text-decoration:none}
|
|
21
|
+
a:hover{text-decoration:underline}
|
|
22
|
+
footer{padding:32px 24px;text-align:center;color:var(--muted);font-size:12px;border-top:1px solid var(--line);margin-top:48px}
|
|
23
|
+
.note{border-left:3px solid var(--accent);padding:8px 12px;background:rgba(96,165,250,.08);margin:12px 0;font-size:13px;color:var(--muted)}
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<header>
|
|
28
|
+
<h1>WAB Local Cache Network</h1>
|
|
29
|
+
<p>An optional peer-to-peer extension that keeps WAB manifests verifiable even when the origin server is unreachable.</p>
|
|
30
|
+
</header>
|
|
31
|
+
<main>
|
|
32
|
+
<div class="note">Status: <strong>draft</strong>. Reference implementation lives in <code>sdk/agent-mesh.js</code>; production peers are not yet incentivised — this page describes the wire format only.</div>
|
|
33
|
+
|
|
34
|
+
<h2>Why</h2>
|
|
35
|
+
<ul>
|
|
36
|
+
<li>Manifests are signed (Ed25519) → integrity is verifiable without trusting the source.</li>
|
|
37
|
+
<li>Once signed, manifests are <strong>immutable until the next rotation</strong> → they cache perfectly.</li>
|
|
38
|
+
<li>P2P distribution removes a single point of failure (the origin) and reduces traffic on sites with bursty agent demand.</li>
|
|
39
|
+
</ul>
|
|
40
|
+
|
|
41
|
+
<h2>Wire format</h2>
|
|
42
|
+
<p>A peer announces what it has cached by publishing:</p>
|
|
43
|
+
<pre>{
|
|
44
|
+
"schema": "wab-cache/1",
|
|
45
|
+
"peer_id": "<libp2p-peer-id>",
|
|
46
|
+
"manifests": [
|
|
47
|
+
{
|
|
48
|
+
"host": "example.com",
|
|
49
|
+
"sha256": "<manifest-bytes-sha256>",
|
|
50
|
+
"signed_at": "2026-05-20T00:00:00Z",
|
|
51
|
+
"expires_at": "2026-08-18T00:00:00Z",
|
|
52
|
+
"size": 1842
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}</pre>
|
|
56
|
+
|
|
57
|
+
<p>Lookups happen over a Kademlia DHT keyed by <code>sha256(host || "/" || version)</code>. Any peer can serve a manifest; the requesting agent verifies the Ed25519 signature itself, so a malicious peer can corrupt nothing — it can only fail to serve.</p>
|
|
58
|
+
|
|
59
|
+
<h2>Manifest opt-in</h2>
|
|
60
|
+
<p>Sites that want their manifest aggressively cached add:</p>
|
|
61
|
+
<pre>{
|
|
62
|
+
"version": "1",
|
|
63
|
+
"host": "example.com",
|
|
64
|
+
"cache_peers": {
|
|
65
|
+
"policy": "public", // public | private | none
|
|
66
|
+
"max_age": 86400, // seconds
|
|
67
|
+
"discoverable_via": ["dht-kademlia", "libp2p-pubsub"]
|
|
68
|
+
}
|
|
69
|
+
}</pre>
|
|
70
|
+
|
|
71
|
+
<h2>Threat model</h2>
|
|
72
|
+
<ul>
|
|
73
|
+
<li><strong>Tampering</strong> — defeated by Ed25519. Peers serve raw bytes; clients verify.</li>
|
|
74
|
+
<li><strong>Rollback</strong> — manifests carry <code>signed_at</code>; clients reject anything older than the freshest signature they have seen for that host.</li>
|
|
75
|
+
<li><strong>Sybil flooding</strong> — DHT queries fan out to ≥ 3 peers, and the agent picks the manifest whose <code>signed_at</code> is highest (with hash agreement).</li>
|
|
76
|
+
<li><strong>Privacy</strong> — manifests are public artifacts; no opaque side-channel.</li>
|
|
77
|
+
</ul>
|
|
78
|
+
|
|
79
|
+
<h2>Reference behaviour</h2>
|
|
80
|
+
<pre>// Pseudocode for agents
|
|
81
|
+
async function resolveManifest(host) {
|
|
82
|
+
try {
|
|
83
|
+
return await fetchOrigin(host); // happy path
|
|
84
|
+
} catch (originDown) {
|
|
85
|
+
const peers = await dht.findPeers(host); // P2P fallback
|
|
86
|
+
for (const peer of peers) {
|
|
87
|
+
const m = await peer.fetch(host);
|
|
88
|
+
if (verifyEd25519(m)) return m; // signature wins
|
|
89
|
+
}
|
|
90
|
+
throw originDown;
|
|
91
|
+
}
|
|
92
|
+
}</pre>
|
|
93
|
+
</main>
|
|
94
|
+
<footer>Part of the <a href="/">Web Agent Bridge</a> protocol. See also: <a href="/security">Security</a>, <a href="/threat-model">Threat model</a>.</footer>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>WAB Registry — AI Agent Discovery Network</title>
|
|
7
|
+
<meta name="description" content="The public registry of WAB-enabled websites. AI agents can query by intent, location, and trust ring. Spider Protocol lets agents auto-report discoveries.">
|
|
8
|
+
<style>
|
|
9
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
10
|
+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0b0f17;color:#e5e7eb;min-height:100vh}
|
|
11
|
+
a{color:#60a5fa;text-decoration:none}a:hover{text-decoration:underline}
|
|
12
|
+
.nav{background:#0d1320;border-bottom:1px solid #1f2937;padding:14px 24px;display:flex;align-items:center;gap:16px}
|
|
13
|
+
.nav-logo{font-size:16px;font-weight:700;color:#fff}.nav-logo span{color:#10b981}
|
|
14
|
+
.nav a{font-size:13px;color:#9ca3af}
|
|
15
|
+
.hero{background:linear-gradient(135deg,#0d1320 0%,#0f1a2e 100%);border-bottom:1px solid #1f2937;padding:56px 24px 48px;text-align:center}
|
|
16
|
+
.hero h1{font-size:clamp(28px,5vw,48px);font-weight:800;color:#fff;margin-bottom:12px}
|
|
17
|
+
.hero h1 span{color:#10b981}
|
|
18
|
+
.hero p{font-size:16px;color:#9ca3af;max-width:600px;margin:0 auto 24px}
|
|
19
|
+
.badge-row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-bottom:32px}
|
|
20
|
+
.badge{display:inline-flex;align-items:center;gap:6px;background:#0b1628;border:1px solid #1f2937;border-radius:20px;padding:6px 14px;font-size:13px;color:#d1d5db}
|
|
21
|
+
.badge .dot{width:8px;height:8px;border-radius:50%;background:#10b981}
|
|
22
|
+
.badge .dot.amber{background:#f59e0b}
|
|
23
|
+
.container{max-width:1100px;margin:0 auto;padding:32px 24px}
|
|
24
|
+
.section{margin-bottom:48px}
|
|
25
|
+
.section-title{font-size:18px;font-weight:700;color:#fff;margin-bottom:20px;display:flex;align-items:center;gap:10px}
|
|
26
|
+
.section-title .tag{font-size:11px;font-weight:600;padding:3px 8px;border-radius:4px;background:#10b98122;color:#10b981}
|
|
27
|
+
/* Search bar */
|
|
28
|
+
.search-bar{background:#0d1320;border:1px solid #1f2937;border-radius:12px;padding:20px 24px;margin-bottom:32px}
|
|
29
|
+
.search-row{display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:12px;align-items:end}
|
|
30
|
+
@media(max-width:640px){.search-row{grid-template-columns:1fr}}
|
|
31
|
+
.field label{display:block;font-size:12px;color:#6b7280;margin-bottom:6px;font-weight:500;text-transform:uppercase;letter-spacing:.5px}
|
|
32
|
+
.field input,.field select{width:100%;background:#0b0f17;border:1px solid #1f2937;border-radius:8px;padding:10px 12px;color:#e5e7eb;font-size:14px}
|
|
33
|
+
.field input:focus,.field select:focus{outline:none;border-color:#10b981}
|
|
34
|
+
.btn-search{background:#10b981;color:#0b0f17;border:none;border-radius:8px;padding:10px 22px;font-size:14px;font-weight:700;cursor:pointer;white-space:nowrap}
|
|
35
|
+
.btn-search:hover{background:#0ea16c}
|
|
36
|
+
/* Stats bar */
|
|
37
|
+
.stats-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:32px}
|
|
38
|
+
.stat-card{background:#0d1320;border:1px solid #1f2937;border-radius:10px;padding:16px 20px}
|
|
39
|
+
.stat-card .num{font-size:28px;font-weight:800;color:#10b981}
|
|
40
|
+
.stat-card .lbl{font-size:12px;color:#6b7280;margin-top:2px}
|
|
41
|
+
/* Results grid */
|
|
42
|
+
.results-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px}
|
|
43
|
+
.site-card{background:#0d1320;border:1px solid #1f2937;border-radius:12px;padding:20px;transition:border-color .2s}
|
|
44
|
+
.site-card:hover{border-color:#10b98144}
|
|
45
|
+
.site-card-header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:12px}
|
|
46
|
+
.site-domain{font-size:15px;font-weight:700;color:#fff}
|
|
47
|
+
.ring-badge{font-size:11px;font-weight:700;padding:3px 8px;border-radius:4px;background:#10b98122;color:#10b981}
|
|
48
|
+
.ring-badge.r3{background:#3b82f622;color:#60a5fa}
|
|
49
|
+
.ring-badge.r2{background:#f59e0b22;color:#f59e0b}
|
|
50
|
+
.ring-badge.r1{background:#6b728022;color:#9ca3af}
|
|
51
|
+
.score-bar{height:4px;background:#1f2937;border-radius:2px;margin-bottom:10px;overflow:hidden}
|
|
52
|
+
.score-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,#10b981,#34d399)}
|
|
53
|
+
.tags{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}
|
|
54
|
+
.tag-chip{font-size:11px;padding:2px 8px;background:#1f2937;border-radius:3px;color:#9ca3af}
|
|
55
|
+
.site-meta{font-size:12px;color:#4b5563;margin-top:10px;display:flex;gap:12px}
|
|
56
|
+
.verified-mark{color:#10b981;font-size:11px;font-weight:600}
|
|
57
|
+
/* Spider panel */
|
|
58
|
+
.spider-panel{background:#0d1320;border:1px solid #10b98133;border-radius:12px;padding:28px}
|
|
59
|
+
.spider-panel h3{font-size:17px;font-weight:700;color:#10b981;margin-bottom:10px}
|
|
60
|
+
.spider-panel p{font-size:14px;color:#9ca3af;margin-bottom:16px}
|
|
61
|
+
.code-block{background:#0b0f17;border:1px solid #1f2937;border-radius:8px;padding:16px;overflow-x:auto}
|
|
62
|
+
.code-block pre{font-size:13px;color:#a5f3fc;font-family:"Cascadia Code","Fira Code",monospace;line-height:1.6}
|
|
63
|
+
.copy-btn{background:#1f2937;border:1px solid #374151;color:#9ca3af;font-size:12px;padding:4px 10px;border-radius:4px;cursor:pointer;float:right;margin-bottom:8px}
|
|
64
|
+
.copy-btn:hover{color:#fff}
|
|
65
|
+
/* Flow diagram */
|
|
66
|
+
.flow{display:flex;flex-direction:column;gap:0}
|
|
67
|
+
.flow-step{display:flex;align-items:center;gap:14px;padding:10px 0}
|
|
68
|
+
.flow-icon{width:36px;height:36px;border-radius:50%;background:#10b98122;border:2px solid #10b98155;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
|
|
69
|
+
.flow-text{font-size:14px;color:#9ca3af}.flow-text strong{color:#e5e7eb}
|
|
70
|
+
.flow-arrow{width:36px;text-align:center;color:#10b981;font-size:18px}
|
|
71
|
+
/* Empty / loading */
|
|
72
|
+
.empty{text-align:center;padding:60px 24px;color:#4b5563;font-size:14px}
|
|
73
|
+
.spinner{display:inline-block;width:20px;height:20px;border:2px solid #1f2937;border-top-color:#10b981;border-radius:50%;animation:spin .7s linear infinite;margin-bottom:8px}
|
|
74
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
75
|
+
/* report form */
|
|
76
|
+
.report-form{background:#0d1320;border:1px solid #1f2937;border-radius:12px;padding:24px}
|
|
77
|
+
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
|
78
|
+
@media(max-width:520px){.form-grid{grid-template-columns:1fr}}
|
|
79
|
+
.form-field label{display:block;font-size:12px;color:#6b7280;margin-bottom:5px;font-weight:500}
|
|
80
|
+
.form-field input,.form-field select,.form-field textarea{width:100%;background:#0b0f17;border:1px solid #1f2937;border-radius:7px;padding:9px 12px;color:#e5e7eb;font-size:13px;font-family:inherit}
|
|
81
|
+
.form-field textarea{resize:vertical;min-height:72px}
|
|
82
|
+
.form-field input:focus,.form-field select:focus,.form-field textarea:focus{outline:none;border-color:#10b981}
|
|
83
|
+
.form-actions{margin-top:16px;display:flex;gap:10px;align-items:center}
|
|
84
|
+
.btn-report{background:#10b981;color:#0b0f17;border:none;border-radius:8px;padding:10px 24px;font-size:14px;font-weight:700;cursor:pointer}
|
|
85
|
+
.btn-report:hover{background:#0ea16c}
|
|
86
|
+
.report-msg{font-size:13px;padding:8px 14px;border-radius:6px;display:none}
|
|
87
|
+
.report-msg.ok{background:#10b98120;color:#10b981;display:block}
|
|
88
|
+
.report-msg.err{background:#ef444420;color:#ef4444;display:block}
|
|
89
|
+
</style>
|
|
90
|
+
</head>
|
|
91
|
+
<body>
|
|
92
|
+
<nav class="nav">
|
|
93
|
+
<span class="nav-logo">WAB<span>Registry</span></span>
|
|
94
|
+
<a href="/">Home</a>
|
|
95
|
+
<a href="/observatory">Observatory</a>
|
|
96
|
+
<a href="/notary">Notary</a>
|
|
97
|
+
<a href="/wab-registry" style="color:#10b981">Registry</a>
|
|
98
|
+
<a href="/docs">Docs</a>
|
|
99
|
+
</nav>
|
|
100
|
+
|
|
101
|
+
<div class="hero">
|
|
102
|
+
<h1>WAB <span>Discovery Registry</span></h1>
|
|
103
|
+
<p>The public index of WAB-enabled websites. AI agents query by intent, location, and trust ring. Every agent becomes a spider — reporting new sites automatically.</p>
|
|
104
|
+
<div class="badge-row">
|
|
105
|
+
<span class="badge"><span class="dot"></span>Trust Ring 4 — this server</span>
|
|
106
|
+
<span class="badge"><span class="dot"></span>Spider Protocol active</span>
|
|
107
|
+
<span class="badge"><span class="dot amber"></span>Gossip Network</span>
|
|
108
|
+
<span class="badge"><span class="dot"></span>Zero-auth reporting</span>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div class="container">
|
|
113
|
+
|
|
114
|
+
<!-- Stats -->
|
|
115
|
+
<div class="stats-row" id="stats-row">
|
|
116
|
+
<div class="stat-card"><div class="num" id="stat-total">–</div><div class="lbl">Registered sites</div></div>
|
|
117
|
+
<div class="stat-card"><div class="num" id="stat-verified">–</div><div class="lbl">Notary-verified</div></div>
|
|
118
|
+
<div class="stat-card"><div class="num" id="stat-r4">–</div><div class="lbl">Trust Ring 4</div></div>
|
|
119
|
+
<div class="stat-card"><div class="num" id="stat-intents">–</div><div class="lbl">Unique intents</div></div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<!-- Search -->
|
|
123
|
+
<div class="section">
|
|
124
|
+
<div class="search-bar">
|
|
125
|
+
<div class="search-row">
|
|
126
|
+
<div class="field">
|
|
127
|
+
<label>Intent</label>
|
|
128
|
+
<input id="q-intent" type="text" placeholder="booking, purchase, search…">
|
|
129
|
+
</div>
|
|
130
|
+
<div class="field">
|
|
131
|
+
<label>Location / Region</label>
|
|
132
|
+
<input id="q-location" type="text" placeholder="netherlands, usa, eu…">
|
|
133
|
+
</div>
|
|
134
|
+
<div class="field">
|
|
135
|
+
<label>Min Trust Ring</label>
|
|
136
|
+
<select id="q-ring">
|
|
137
|
+
<option value="">Any</option>
|
|
138
|
+
<option value="1">Ring 1+</option>
|
|
139
|
+
<option value="2">Ring 2+</option>
|
|
140
|
+
<option value="3">Ring 3+</option>
|
|
141
|
+
<option value="4">Ring 4 only</option>
|
|
142
|
+
</select>
|
|
143
|
+
</div>
|
|
144
|
+
<button class="btn-search" onclick="runSearch()">Search</button>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
<div id="results-count" style="font-size:13px;color:#6b7280;margin-bottom:14px"></div>
|
|
148
|
+
<div class="results-grid" id="results-grid">
|
|
149
|
+
<div class="empty" id="results-empty">
|
|
150
|
+
<div class="spinner"></div><br>
|
|
151
|
+
Loading registry…
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<!-- Spider Protocol How It Works -->
|
|
157
|
+
<div class="section">
|
|
158
|
+
<div class="section-title">Spider Protocol <span class="tag">Self-Propagating</span></div>
|
|
159
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:32px;align-items:start">
|
|
160
|
+
<div>
|
|
161
|
+
<div class="flow">
|
|
162
|
+
<div class="flow-step"><div class="flow-icon">🌐</div><div class="flow-text">Agent browses <strong>any WAB-enabled site</strong></div></div>
|
|
163
|
+
<div class="flow-step"><div class="flow-arrow">↓</div></div>
|
|
164
|
+
<div class="flow-step"><div class="flow-icon">📡</div><div class="flow-text">Reads <strong>X-WAB-Enabled</strong> header or <code>/.wab</code> beacon</div></div>
|
|
165
|
+
<div class="flow-step"><div class="flow-arrow">↓</div></div>
|
|
166
|
+
<div class="flow-step"><div class="flow-icon">📮</div><div class="flow-text">POSTs domain to <strong>/api/registry/report</strong></div></div>
|
|
167
|
+
<div class="flow-step"><div class="flow-arrow">↓</div></div>
|
|
168
|
+
<div class="flow-step"><div class="flow-icon">🗂️</div><div class="flow-text">Registry <strong>auto-updates</strong></div></div>
|
|
169
|
+
<div class="flow-step"><div class="flow-arrow">↓</div></div>
|
|
170
|
+
<div class="flow-step"><div class="flow-icon">🤖</div><div class="flow-text"><strong>All agents</strong> can discover the new site</div></div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
<div class="spider-panel">
|
|
174
|
+
<h3>Spider Protocol — POST /api/registry/report</h3>
|
|
175
|
+
<p>No auth required. Rate-limited to 20 reports/hour per IP.</p>
|
|
176
|
+
<div class="code-block">
|
|
177
|
+
<button class="copy-btn" onclick="copyCode('spider-ex')">Copy</button>
|
|
178
|
+
<pre id="spider-ex">POST /api/registry/report
|
|
179
|
+
Content-Type: application/json
|
|
180
|
+
|
|
181
|
+
{
|
|
182
|
+
"domain": "takeyourappointment.com",
|
|
183
|
+
"intent_tags": ["booking", "appointment"],
|
|
184
|
+
"trust_ring": 4,
|
|
185
|
+
"score": 94,
|
|
186
|
+
"capabilities": ["book_appointment", "check_availability"],
|
|
187
|
+
"region": "netherlands",
|
|
188
|
+
"discovered_via": "agent_browsing"
|
|
189
|
+
}</pre>
|
|
190
|
+
</div>
|
|
191
|
+
<p style="margin-top:14px">Response includes <code>wab_meta</code> with peer sites and registry URL for the Gossip chain.</p>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<!-- Discover API -->
|
|
197
|
+
<div class="section">
|
|
198
|
+
<div class="section-title">Discover API <span class="tag">GET /api/registry/discover</span></div>
|
|
199
|
+
<div class="spider-panel">
|
|
200
|
+
<h3>Query WAB-enabled sites for any task</h3>
|
|
201
|
+
<p>AI agents call this to find the best sites for a specific intent before browsing.</p>
|
|
202
|
+
<div class="code-block">
|
|
203
|
+
<button class="copy-btn" onclick="copyCode('discover-ex')">Copy</button>
|
|
204
|
+
<pre id="discover-ex">GET /api/registry/discover
|
|
205
|
+
?intent=book_appointment
|
|
206
|
+
&location=netherlands
|
|
207
|
+
&trust_ring=4
|
|
208
|
+
&limit=10
|
|
209
|
+
|
|
210
|
+
→ {
|
|
211
|
+
"count": 3,
|
|
212
|
+
"results": [
|
|
213
|
+
{
|
|
214
|
+
"domain": "takeyourappointment.com",
|
|
215
|
+
"trust_ring": 4,
|
|
216
|
+
"score": 94,
|
|
217
|
+
"intent_tags": ["booking", "appointment"],
|
|
218
|
+
"manifest_url": "https://takeyourappointment.com/.well-known/wab.json",
|
|
219
|
+
"beacon_url": "https://takeyourappointment.com/.wab",
|
|
220
|
+
"verified": true
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
"wab_meta": {
|
|
224
|
+
"protocol": "wab/3.19",
|
|
225
|
+
"spider_report": "https://webagentbridge.com/api/registry/report"
|
|
226
|
+
}
|
|
227
|
+
}</pre>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<!-- WAB Beacon -->
|
|
233
|
+
<div class="section">
|
|
234
|
+
<div class="section-title">WAB Beacon — <code>/.wab</code> <span class="tag">Gossip Protocol</span></div>
|
|
235
|
+
<div class="spider-panel">
|
|
236
|
+
<h3>Every WAB-enabled site publishes /.wab</h3>
|
|
237
|
+
<p>Agents read this to discover peer sites automatically (BitTorrent-style node list). The <code>next</code> array seeds the Gossip chain — follow it to grow your knowledge of the WAB network without any central coordination.</p>
|
|
238
|
+
<div class="code-block">
|
|
239
|
+
<button class="copy-btn" onclick="copyCode('beacon-ex')">Copy</button>
|
|
240
|
+
<pre id="beacon-ex">GET https://webagentbridge.com/.wab
|
|
241
|
+
|
|
242
|
+
{
|
|
243
|
+
"v": 1,
|
|
244
|
+
"protocol": "wab/3.19",
|
|
245
|
+
"ring": 4,
|
|
246
|
+
"score": 94,
|
|
247
|
+
"manifest": "https://webagentbridge.com/.well-known/wab.json",
|
|
248
|
+
"registry": "https://webagentbridge.com/api/registry/discover",
|
|
249
|
+
"spider_report": "https://webagentbridge.com/api/registry/report",
|
|
250
|
+
"notary_fp": "oztHk9ZDIO2vCWW6",
|
|
251
|
+
"next": [
|
|
252
|
+
{ "domain": "example.com", "trust_ring": 4, "intent_tags": ["booking"] },
|
|
253
|
+
...
|
|
254
|
+
]
|
|
255
|
+
}</pre>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<!-- System Prompt Snippet -->
|
|
261
|
+
<div class="section">
|
|
262
|
+
<div class="section-title">System Prompt Snippet <span class="tag">For Agent Builders</span></div>
|
|
263
|
+
<div class="spider-panel">
|
|
264
|
+
<h3>Copy-paste into your agent's system prompt</h3>
|
|
265
|
+
<p>Makes any LLM-based agent prefer WAB-enabled sites automatically. Also available at <a href="/api/registry/suggest">/api/registry/suggest</a>.</p>
|
|
266
|
+
<div class="code-block">
|
|
267
|
+
<button class="copy-btn" onclick="loadAndCopySnippet()">Copy</button>
|
|
268
|
+
<pre id="snippet-text">Loading…</pre>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<!-- Manual Report Form -->
|
|
274
|
+
<div class="section">
|
|
275
|
+
<div class="section-title">Register a Site <span class="tag">Manual + Spider</span></div>
|
|
276
|
+
<div class="report-form">
|
|
277
|
+
<p style="font-size:14px;color:#9ca3af;margin-bottom:20px">Know a WAB-enabled site not yet in the registry? Submit it here. Agents use the same <code>POST /api/registry/report</code> endpoint automatically.</p>
|
|
278
|
+
<div class="form-grid">
|
|
279
|
+
<div class="form-field">
|
|
280
|
+
<label>Domain *</label>
|
|
281
|
+
<input id="rf-domain" type="text" placeholder="example.com">
|
|
282
|
+
</div>
|
|
283
|
+
<div class="form-field">
|
|
284
|
+
<label>Region</label>
|
|
285
|
+
<input id="rf-region" type="text" placeholder="netherlands, global, eu…">
|
|
286
|
+
</div>
|
|
287
|
+
<div class="form-field">
|
|
288
|
+
<label>Trust Ring (1–4)</label>
|
|
289
|
+
<select id="rf-ring">
|
|
290
|
+
<option value="">Unknown</option>
|
|
291
|
+
<option value="1">1 — Basic</option>
|
|
292
|
+
<option value="2">2 — Standard</option>
|
|
293
|
+
<option value="3">3 — Advanced</option>
|
|
294
|
+
<option value="4">4 — Sovereign</option>
|
|
295
|
+
</select>
|
|
296
|
+
</div>
|
|
297
|
+
<div class="form-field">
|
|
298
|
+
<label>WAB Score (0–100)</label>
|
|
299
|
+
<input id="rf-score" type="number" min="0" max="100" placeholder="e.g. 87">
|
|
300
|
+
</div>
|
|
301
|
+
<div class="form-field" style="grid-column:1/-1">
|
|
302
|
+
<label>Intent Tags (comma-separated)</label>
|
|
303
|
+
<input id="rf-tags" type="text" placeholder="booking, appointment, availability">
|
|
304
|
+
</div>
|
|
305
|
+
<div class="form-field" style="grid-column:1/-1">
|
|
306
|
+
<label>Capabilities (comma-separated)</label>
|
|
307
|
+
<input id="rf-caps" type="text" placeholder="book_appointment, check_availability">
|
|
308
|
+
</div>
|
|
309
|
+
<div class="form-field" style="grid-column:1/-1">
|
|
310
|
+
<label>Description (optional)</label>
|
|
311
|
+
<textarea id="rf-desc" placeholder="One-line description of the site's primary WAB capabilities…"></textarea>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
<div class="form-actions">
|
|
315
|
+
<button class="btn-report" onclick="submitReport()">Submit to Registry</button>
|
|
316
|
+
<span id="report-msg" class="report-msg"></span>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<footer style="text-align:center;padding:32px;font-size:12px;color:#4b5563;border-top:1px solid #1f2937">
|
|
324
|
+
<a href="/">Web Agent Bridge</a> · <a href="/docs">Docs</a> · <a href="/notary">Notary</a> · <a href="/observatory">Observatory</a>
|
|
325
|
+
<br><br>WAB Registry v1.0 — public, open, zero-auth reporting — <a href="/api/registry/stats">live stats</a>
|
|
326
|
+
</footer>
|
|
327
|
+
|
|
328
|
+
<script>
|
|
329
|
+
async function loadStats() {
|
|
330
|
+
try {
|
|
331
|
+
const r = await fetch('/api/registry/stats');
|
|
332
|
+
const d = await r.json();
|
|
333
|
+
document.getElementById('stat-total').textContent = d.total ?? '0';
|
|
334
|
+
document.getElementById('stat-verified').textContent = d.verified ?? '0';
|
|
335
|
+
const r4 = (d.by_ring || []).find(x => x.ring === 4);
|
|
336
|
+
document.getElementById('stat-r4').textContent = r4 ? r4.count : '0';
|
|
337
|
+
document.getElementById('stat-intents').textContent = (d.top_intents || []).length;
|
|
338
|
+
} catch { }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function runSearch() {
|
|
342
|
+
const intent = document.getElementById('q-intent').value.trim();
|
|
343
|
+
const location = document.getElementById('q-location').value.trim();
|
|
344
|
+
const ring = document.getElementById('q-ring').value;
|
|
345
|
+
const params = new URLSearchParams();
|
|
346
|
+
if (intent) params.set('intent', intent);
|
|
347
|
+
if (location) params.set('location', location);
|
|
348
|
+
if (ring) params.set('trust_ring', ring);
|
|
349
|
+
params.set('limit', '60');
|
|
350
|
+
|
|
351
|
+
const grid = document.getElementById('results-grid');
|
|
352
|
+
const countEl = document.getElementById('results-count');
|
|
353
|
+
grid.innerHTML = '<div class="empty"><div class="spinner"></div><br>Searching…</div>';
|
|
354
|
+
countEl.textContent = '';
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const r = await fetch('/api/registry/discover?' + params.toString());
|
|
358
|
+
const d = await r.json();
|
|
359
|
+
renderResults(d);
|
|
360
|
+
} catch (e) {
|
|
361
|
+
grid.innerHTML = '<div class="empty">Error loading results: ' + e.message + '</div>';
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function ringClass(r) {
|
|
366
|
+
if (r === 4) return '';
|
|
367
|
+
if (r === 3) return 'r3';
|
|
368
|
+
if (r === 2) return 'r2';
|
|
369
|
+
return 'r1';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function renderResults(d) {
|
|
373
|
+
const grid = document.getElementById('results-grid');
|
|
374
|
+
const countEl = document.getElementById('results-count');
|
|
375
|
+
countEl.textContent = d.total > 0
|
|
376
|
+
? `Showing ${d.count} of ${d.total} registered sites`
|
|
377
|
+
: 'No sites found for this query.';
|
|
378
|
+
|
|
379
|
+
if (!d.results || d.results.length === 0) {
|
|
380
|
+
grid.innerHTML = '<div class="empty" style="grid-column:1/-1">No WAB-enabled sites match your query yet.<br>Be the first to <a href="#report">register one</a> or use the Spider Protocol!</div>';
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
grid.innerHTML = d.results.map(e => {
|
|
384
|
+
const score = e.score || 0;
|
|
385
|
+
const tags = (e.intent_tags || []).map(t => `<span class="tag-chip">${t}</span>`).join('');
|
|
386
|
+
const caps = (e.capabilities || []).slice(0, 4).map(c => `<span class="tag-chip">${c}</span>`).join('');
|
|
387
|
+
const verif = e.verified ? '<span class="verified-mark">✓ verified</span>' : '';
|
|
388
|
+
const ring = e.trust_ring ? `<span class="ring-badge ${ringClass(e.trust_ring)}">Ring ${e.trust_ring}</span>` : '';
|
|
389
|
+
return `<div class="site-card">
|
|
390
|
+
<div class="site-card-header">
|
|
391
|
+
<div>
|
|
392
|
+
<div class="site-domain">${e.domain}</div>
|
|
393
|
+
${e.region ? `<div style="font-size:11px;color:#6b7280;margin-top:2px">${e.region}</div>` : ''}
|
|
394
|
+
</div>
|
|
395
|
+
${ring}
|
|
396
|
+
</div>
|
|
397
|
+
<div class="score-bar"><div class="score-fill" style="width:${score}%"></div></div>
|
|
398
|
+
<div style="font-size:11px;color:#6b7280;margin-bottom:6px">Score: ${score || '?'}/100 ${verif}</div>
|
|
399
|
+
${e.description ? `<div style="font-size:13px;color:#9ca3af;margin-bottom:8px">${e.description}</div>` : ''}
|
|
400
|
+
${tags ? `<div class="tags">${tags}</div>` : ''}
|
|
401
|
+
${caps ? `<div class="tags" style="margin-top:4px">${caps}</div>` : ''}
|
|
402
|
+
<div class="site-meta">
|
|
403
|
+
<a href="${e.manifest_url}" target="_blank" rel="noopener" style="color:#4b5563">manifest</a>
|
|
404
|
+
<a href="${e.beacon_url}" target="_blank" rel="noopener" style="color:#4b5563">/.wab</a>
|
|
405
|
+
</div>
|
|
406
|
+
</div>`;
|
|
407
|
+
}).join('');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function loadAndCopySnippet() {
|
|
411
|
+
try {
|
|
412
|
+
const r = await fetch('/api/registry/suggest');
|
|
413
|
+
const d = await r.json();
|
|
414
|
+
document.getElementById('snippet-text').textContent = d.snippet || '';
|
|
415
|
+
await navigator.clipboard.writeText(d.snippet || '');
|
|
416
|
+
const btn = event.target; btn.textContent = 'Copied!'; setTimeout(() => { btn.textContent = 'Copy'; }, 2000);
|
|
417
|
+
} catch { }
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function fetchSnippet() {
|
|
421
|
+
try {
|
|
422
|
+
const r = await fetch('/api/registry/suggest');
|
|
423
|
+
const d = await r.json();
|
|
424
|
+
document.getElementById('snippet-text').textContent = d.snippet || '';
|
|
425
|
+
} catch { document.getElementById('snippet-text').textContent = 'Could not load snippet.'; }
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function copyCode(id) {
|
|
429
|
+
const text = document.getElementById(id).textContent;
|
|
430
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
431
|
+
const btn = event.target; btn.textContent = 'Copied!'; setTimeout(() => { btn.textContent = 'Copy'; }, 2000);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function submitReport() {
|
|
436
|
+
const msg = document.getElementById('report-msg');
|
|
437
|
+
msg.className = 'report-msg'; msg.style.display = 'none';
|
|
438
|
+
|
|
439
|
+
const domain = document.getElementById('rf-domain').value.trim();
|
|
440
|
+
if (!domain) { msg.textContent = 'Domain is required.'; msg.className = 'report-msg err'; return; }
|
|
441
|
+
|
|
442
|
+
const ring = parseInt(document.getElementById('rf-ring').value, 10);
|
|
443
|
+
const score = parseFloat(document.getElementById('rf-score').value);
|
|
444
|
+
const tags = document.getElementById('rf-tags').value.split(',').map(s => s.trim()).filter(Boolean);
|
|
445
|
+
const caps = document.getElementById('rf-caps').value.split(',').map(s => s.trim()).filter(Boolean);
|
|
446
|
+
const body = {
|
|
447
|
+
domain,
|
|
448
|
+
region: document.getElementById('rf-region').value.trim() || undefined,
|
|
449
|
+
description: document.getElementById('rf-desc').value.trim() || undefined,
|
|
450
|
+
intent_tags: tags.length ? tags : undefined,
|
|
451
|
+
capabilities: caps.length ? caps : undefined,
|
|
452
|
+
trust_ring: ring > 0 ? ring : undefined,
|
|
453
|
+
score: isNaN(score) ? undefined : score,
|
|
454
|
+
discovered_via: 'manual_registry_form',
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
const r = await fetch('/api/registry/report', {
|
|
459
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
460
|
+
body: JSON.stringify(body),
|
|
461
|
+
});
|
|
462
|
+
const d = await r.json();
|
|
463
|
+
if (d.accepted) {
|
|
464
|
+
msg.textContent = `Accepted! Report ID: ${d.report_id}. Registry now has ${d.registry_size} sites.`;
|
|
465
|
+
msg.className = 'report-msg ok';
|
|
466
|
+
loadStats(); runSearch();
|
|
467
|
+
} else {
|
|
468
|
+
msg.textContent = d.error || 'Unknown error.'; msg.className = 'report-msg err';
|
|
469
|
+
}
|
|
470
|
+
} catch (e) {
|
|
471
|
+
msg.textContent = 'Network error: ' + e.message; msg.className = 'report-msg err';
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Boot
|
|
476
|
+
loadStats();
|
|
477
|
+
runSearch();
|
|
478
|
+
fetchSnippet();
|
|
479
|
+
</script>
|
|
480
|
+
</body>
|
|
481
|
+
</html>
|