web-agent-bridge 3.2.0 → 3.4.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.
Files changed (256) hide show
  1. package/LICENSE +84 -72
  2. package/README.ar.md +1304 -1152
  3. package/README.md +298 -1635
  4. package/bin/agent-runner.js +474 -474
  5. package/bin/cli.js +237 -138
  6. package/bin/wab-init.js +223 -0
  7. package/bin/wab.js +80 -80
  8. package/examples/azure-dns-wab.js +83 -0
  9. package/examples/bidi-agent.js +119 -119
  10. package/examples/cloudflare-wab-dns.js +121 -0
  11. package/examples/cpanel-wab-dns.js +114 -0
  12. package/examples/cross-site-agent.js +91 -91
  13. package/examples/dns-discovery-agent.js +166 -0
  14. package/examples/gcp-dns-wab.js +76 -0
  15. package/examples/governance-agent.js +169 -0
  16. package/examples/mcp-agent.js +94 -94
  17. package/examples/next-app-router/README.md +44 -44
  18. package/examples/plesk-wab-dns.js +103 -0
  19. package/examples/puppeteer-agent.js +108 -108
  20. package/examples/route53-wab-dns.js +144 -0
  21. package/examples/saas-dashboard/README.md +55 -55
  22. package/examples/safe-mode-agent.js +96 -0
  23. package/examples/shopify-hydrogen/README.md +74 -74
  24. package/examples/vision-agent.js +171 -171
  25. package/examples/wab-sign.js +74 -0
  26. package/examples/wab-verify.js +60 -0
  27. package/examples/wordpress-elementor/README.md +77 -77
  28. package/package.json +19 -6
  29. package/public/.well-known/agent-tools.json +180 -180
  30. package/public/.well-known/ai-assets.json +59 -59
  31. package/public/.well-known/security.txt +8 -0
  32. package/public/.well-known/wab.json +28 -0
  33. package/public/activate.html +368 -0
  34. package/public/adoption-metrics.html +188 -0
  35. package/public/agent-workspace.html +349 -349
  36. package/public/ai.html +198 -198
  37. package/public/api.html +413 -412
  38. package/public/azure-dns-integration.html +289 -0
  39. package/public/browser.html +486 -486
  40. package/public/cloudflare-integration.html +380 -0
  41. package/public/commander-dashboard.html +243 -243
  42. package/public/cookies.html +210 -210
  43. package/public/cpanel-integration.html +398 -0
  44. package/public/css/agent-workspace.css +1713 -1713
  45. package/public/css/premium.css +317 -317
  46. package/public/css/styles.css +1263 -1235
  47. package/public/dashboard.html +707 -706
  48. package/public/dns.html +436 -0
  49. package/public/docs.html +588 -587
  50. package/public/feed.xml +89 -89
  51. package/public/gcp-dns-integration.html +318 -0
  52. package/public/growth.html +465 -463
  53. package/public/index.html +1266 -982
  54. package/public/integrations.html +556 -0
  55. package/public/js/activate.js +145 -0
  56. package/public/js/agent-workspace.js +1740 -1740
  57. package/public/js/auth-nav.js +65 -31
  58. package/public/js/auth-redirect.js +12 -12
  59. package/public/js/cookie-consent.js +56 -56
  60. package/public/js/dns.js +438 -0
  61. package/public/js/wab-demo-page.js +721 -721
  62. package/public/js/ws-client.js +74 -74
  63. package/public/llms-full.txt +360 -360
  64. package/public/llms.txt +125 -125
  65. package/public/login.html +85 -85
  66. package/public/mesh-dashboard.html +328 -328
  67. package/public/openapi.json +669 -580
  68. package/public/phone-shield.html +281 -0
  69. package/public/plesk-integration.html +375 -0
  70. package/public/premium-dashboard.html +2489 -2489
  71. package/public/premium.html +793 -793
  72. package/public/privacy.html +297 -297
  73. package/public/provider-onboarding.html +172 -0
  74. package/public/provider-sandbox.html +134 -0
  75. package/public/providers.html +359 -0
  76. package/public/register.html +105 -105
  77. package/public/registrar-integrations.html +141 -0
  78. package/public/robots.txt +99 -87
  79. package/public/route53-integration.html +531 -0
  80. package/public/script/wab-consent.d.ts +36 -36
  81. package/public/script/wab-consent.js +104 -104
  82. package/public/script/wab-schema.js +131 -131
  83. package/public/script/wab.d.ts +108 -108
  84. package/public/script/wab.min.js +580 -580
  85. package/public/security.txt +8 -0
  86. package/public/shieldqr.html +231 -0
  87. package/public/sitemap.xml +6 -0
  88. package/public/terms.html +256 -256
  89. package/public/wab-trust.html +200 -0
  90. package/public/wab-vs-protocols.html +210 -0
  91. package/public/whitepaper.html +449 -0
  92. package/script/ai-agent-bridge.js +1754 -1754
  93. package/sdk/README.md +99 -99
  94. package/sdk/agent-mesh.js +449 -449
  95. package/sdk/auto-discovery.js +288 -0
  96. package/sdk/commander.js +262 -262
  97. package/sdk/governance.js +262 -0
  98. package/sdk/index.d.ts +464 -464
  99. package/sdk/index.js +25 -1
  100. package/sdk/multi-agent.js +318 -318
  101. package/sdk/package.json +2 -2
  102. package/sdk/safe-mode.js +221 -0
  103. package/sdk/safety-shield.js +219 -0
  104. package/sdk/schema-discovery.js +83 -83
  105. package/server/adapters/index.js +520 -520
  106. package/server/config/plans.js +367 -367
  107. package/server/config/secrets.js +102 -102
  108. package/server/control-plane/index.js +301 -301
  109. package/server/data-plane/index.js +354 -354
  110. package/server/index.js +670 -427
  111. package/server/llm/index.js +404 -404
  112. package/server/middleware/adminAuth.js +35 -35
  113. package/server/middleware/auth.js +50 -50
  114. package/server/middleware/featureGate.js +88 -88
  115. package/server/middleware/rateLimits.js +100 -100
  116. package/server/middleware/sensitiveAction.js +157 -0
  117. package/server/migrations/001_add_analytics_indexes.sql +7 -7
  118. package/server/migrations/002_premium_features.sql +418 -418
  119. package/server/migrations/003_ads_integer_cents.sql +33 -33
  120. package/server/migrations/004_agent_os.sql +158 -158
  121. package/server/migrations/005_marketplace_metering.sql +126 -126
  122. package/server/migrations/007_governance.sql +106 -0
  123. package/server/migrations/008_plans.sql +144 -0
  124. package/server/migrations/009_shieldqr.sql +30 -0
  125. package/server/migrations/010_extended_trust.sql +33 -0
  126. package/server/models/adapters/index.js +33 -33
  127. package/server/models/adapters/mysql.js +183 -183
  128. package/server/models/adapters/postgresql.js +172 -172
  129. package/server/models/adapters/sqlite.js +7 -7
  130. package/server/models/db.js +740 -681
  131. package/server/observability/failure-analysis.js +337 -337
  132. package/server/observability/index.js +394 -394
  133. package/server/protocol/capabilities.js +223 -223
  134. package/server/protocol/index.js +243 -243
  135. package/server/protocol/schema.js +584 -584
  136. package/server/registry/certification.js +271 -271
  137. package/server/registry/index.js +326 -326
  138. package/server/routes/admin-plans.js +76 -0
  139. package/server/routes/admin-premium.js +673 -671
  140. package/server/routes/admin-shieldqr.js +90 -0
  141. package/server/routes/admin-trust-monitor.js +83 -0
  142. package/server/routes/admin.js +549 -261
  143. package/server/routes/ads.js +130 -130
  144. package/server/routes/agent-workspace.js +540 -540
  145. package/server/routes/api.js +150 -150
  146. package/server/routes/auth.js +71 -71
  147. package/server/routes/billing.js +57 -45
  148. package/server/routes/commander.js +316 -316
  149. package/server/routes/demo-showcase.js +332 -332
  150. package/server/routes/demo-store.js +154 -0
  151. package/server/routes/discovery.js +2348 -417
  152. package/server/routes/gateway.js +173 -157
  153. package/server/routes/governance.js +208 -0
  154. package/server/routes/license.js +251 -240
  155. package/server/routes/mesh.js +469 -469
  156. package/server/routes/noscript.js +543 -543
  157. package/server/routes/plans.js +33 -0
  158. package/server/routes/premium-v2.js +686 -686
  159. package/server/routes/premium.js +724 -724
  160. package/server/routes/providers.js +650 -0
  161. package/server/routes/runtime.js +2148 -2147
  162. package/server/routes/shieldqr.js +88 -0
  163. package/server/routes/sovereign.js +465 -385
  164. package/server/routes/universal.js +200 -185
  165. package/server/routes/wab-api.js +850 -501
  166. package/server/runtime/container-worker.js +111 -111
  167. package/server/runtime/container.js +448 -448
  168. package/server/runtime/distributed-worker.js +362 -362
  169. package/server/runtime/event-bus.js +210 -210
  170. package/server/runtime/index.js +253 -253
  171. package/server/runtime/queue.js +599 -599
  172. package/server/runtime/replay.js +666 -666
  173. package/server/runtime/sandbox.js +266 -266
  174. package/server/runtime/scheduler.js +534 -534
  175. package/server/runtime/session-engine.js +293 -293
  176. package/server/runtime/state-manager.js +188 -188
  177. package/server/security/cross-site-redactor.js +196 -0
  178. package/server/security/dry-run.js +180 -0
  179. package/server/security/human-gate-rate-limit.js +147 -0
  180. package/server/security/human-gate-transports.js +178 -0
  181. package/server/security/human-gate.js +281 -0
  182. package/server/security/index.js +368 -368
  183. package/server/security/intent-engine.js +245 -0
  184. package/server/security/reward-guard.js +171 -0
  185. package/server/security/rollback-store.js +239 -0
  186. package/server/security/token-scope.js +404 -0
  187. package/server/security/url-policy.js +139 -0
  188. package/server/services/agent-chat.js +506 -506
  189. package/server/services/agent-learning.js +601 -575
  190. package/server/services/agent-memory.js +625 -625
  191. package/server/services/agent-mesh.js +555 -539
  192. package/server/services/agent-symphony.js +717 -717
  193. package/server/services/agent-tasks.js +1807 -1807
  194. package/server/services/api-key-engine.js +292 -261
  195. package/server/services/cluster.js +894 -894
  196. package/server/services/commander.js +738 -738
  197. package/server/services/edge-compute.js +440 -440
  198. package/server/services/email.js +233 -204
  199. package/server/services/governance.js +466 -0
  200. package/server/services/hosted-runtime.js +205 -205
  201. package/server/services/lfd.js +635 -635
  202. package/server/services/local-ai.js +389 -389
  203. package/server/services/marketplace.js +270 -270
  204. package/server/services/metering.js +182 -182
  205. package/server/services/modules/affiliate-intelligence.js +93 -93
  206. package/server/services/modules/agent-firewall.js +90 -90
  207. package/server/services/modules/bounty.js +89 -89
  208. package/server/services/modules/collective-bargaining.js +92 -92
  209. package/server/services/modules/dark-pattern.js +66 -66
  210. package/server/services/modules/gov-intelligence.js +45 -45
  211. package/server/services/modules/neural.js +55 -55
  212. package/server/services/modules/notary.js +49 -49
  213. package/server/services/modules/price-time-machine.js +86 -86
  214. package/server/services/modules/protocol.js +104 -104
  215. package/server/services/negotiation.js +439 -439
  216. package/server/services/plans.js +214 -0
  217. package/server/services/plugins.js +771 -771
  218. package/server/services/premium.js +1 -1
  219. package/server/services/price-intelligence.js +566 -566
  220. package/server/services/price-shield.js +1137 -1137
  221. package/server/services/provider-clients.js +740 -0
  222. package/server/services/reputation.js +465 -465
  223. package/server/services/search-engine.js +357 -357
  224. package/server/services/security.js +513 -513
  225. package/server/services/self-healing.js +843 -843
  226. package/server/services/shieldqr.js +322 -0
  227. package/server/services/sovereign-shield.js +542 -0
  228. package/server/services/ssl-inspector.js +42 -0
  229. package/server/services/ssl-monitor.js +167 -0
  230. package/server/services/stripe.js +205 -192
  231. package/server/services/swarm.js +788 -788
  232. package/server/services/universal-scraper.js +662 -661
  233. package/server/services/verification.js +481 -481
  234. package/server/services/vision.js +1163 -1163
  235. package/server/services/wab-crypto.js +178 -0
  236. package/server/utils/cache.js +125 -125
  237. package/server/utils/migrate.js +81 -81
  238. package/server/utils/safe-fetch.js +228 -0
  239. package/server/utils/secureFields.js +50 -50
  240. package/server/ws.js +161 -161
  241. package/templates/artisan-marketplace.yaml +104 -104
  242. package/templates/book-price-scout.yaml +98 -98
  243. package/templates/electronics-price-tracker.yaml +108 -108
  244. package/templates/flight-deal-hunter.yaml +113 -113
  245. package/templates/freelancer-direct.yaml +116 -116
  246. package/templates/grocery-price-compare.yaml +93 -93
  247. package/templates/hotel-direct-booking.yaml +113 -113
  248. package/templates/local-services.yaml +98 -98
  249. package/templates/olive-oil-tunisia.yaml +88 -88
  250. package/templates/organic-farm-fresh.yaml +101 -101
  251. package/templates/restaurant-direct.yaml +97 -97
  252. package/public/score.html +0 -263
  253. package/server/migrations/006_growth_suite.sql +0 -138
  254. package/server/routes/growth.js +0 -962
  255. package/server/services/fairness-engine.js +0 -409
  256. package/server/services/fairness.js +0 -420
@@ -0,0 +1,438 @@
1
+ // ═══════════ Bilingual toggle ═══════════
2
+ window.setLang = function (lang) {
3
+ const isAr = lang === 'ar';
4
+ document.documentElement.lang = isAr ? 'ar' : 'en';
5
+ document.documentElement.dir = isAr ? 'rtl' : 'ltr';
6
+ const enBtn = document.getElementById('enBtn');
7
+ const arBtn = document.getElementById('arBtn');
8
+ if (enBtn) enBtn.classList.toggle('active', !isAr);
9
+ if (arBtn) arBtn.classList.toggle('active', isAr);
10
+ document.querySelectorAll('[data-en]').forEach((el) => {
11
+ el.textContent = isAr ? (el.getAttribute('data-ar') || el.textContent) : (el.getAttribute('data-en') || el.textContent);
12
+ });
13
+ };
14
+
15
+ // ═══════════ Live DoH Verifier ═══════════
16
+ window.verifyDns = async function () {
17
+ const domain = (document.getElementById('dnsDomain').value || '').trim().replace(/^https?:\/\//, '').replace(/\/.*$/, '');
18
+ const resolver = document.getElementById('dnsResolver').value;
19
+ const status = document.getElementById('dnsStatus');
20
+ const out = document.getElementById('dnsOut');
21
+ if (!domain) {
22
+ status.innerHTML = '<span class="danger">Please enter a domain.</span>';
23
+ return;
24
+ }
25
+ const fqdn = '_wab.' + domain;
26
+ status.innerHTML = '<span class="warn">Querying ' + fqdn + ' …</span>';
27
+ out.textContent = '';
28
+ try {
29
+ const url = resolver + '?name=' + encodeURIComponent(fqdn) + '&type=TXT';
30
+ const res = await fetch(url, { headers: { accept: 'application/dns-json' } });
31
+ const data = await res.json();
32
+ out.textContent = JSON.stringify(data, null, 2);
33
+ const answers = (data.Answer || []).filter((a) => a.type === 16);
34
+ if (!answers.length) {
35
+ status.innerHTML = '<span class="danger">No _wab TXT record found for <b>' + domain + '</b>. The domain has not enabled WAB DNS Discovery (yet).</span>';
36
+ return;
37
+ }
38
+ const value = answers.map((a) => (a.data || '').replace(/^"|"$/g, '').replace(/" "/g, '')).join(' ');
39
+ const versionMatch = /v=([^;\s]+)/.exec(value);
40
+ const endpointMatch = /endpoint=([^;\s]+)/.exec(value);
41
+ if (versionMatch && endpointMatch && versionMatch[1].startsWith('wab')) {
42
+ status.innerHTML = '<span class="ok">✓ Valid WAB record found. Version: <b>' + versionMatch[1] + '</b> · Endpoint: <a href="' + endpointMatch[1] + '" target="_blank" style="color:#7dd3fc">' + endpointMatch[1] + '</a></span>';
43
+ } else {
44
+ status.innerHTML = '<span class="warn">TXT record found but it does not match the WAB format (v=wab1; endpoint=…).</span>';
45
+ }
46
+ } catch (err) {
47
+ status.innerHTML = '<span class="danger">Lookup failed: ' + ((err && err.message) || err) + '</span>';
48
+ }
49
+ };
50
+
51
+ window.copyExample = function () {
52
+ const txt = 'v=wab1; endpoint=https://yourdomain.com/.well-known/wab.json';
53
+ navigator.clipboard.writeText(txt).then(() => {
54
+ const s = document.getElementById('dnsStatus');
55
+ s.innerHTML = '<span class="ok">✓ Example record copied to clipboard.</span>';
56
+ });
57
+ };
58
+
59
+ function _statePill(label, ok) {
60
+ return '<span style="display:inline-block;margin:3px 6px 3px 0;padding:4px 9px;border-radius:999px;font-size:.74rem;border:1px solid ' +
61
+ (ok ? 'rgba(74,222,128,.5);color:#4ade80;background:rgba(74,222,128,.12)' : 'rgba(248,113,113,.4);color:#f87171;background:rgba(248,113,113,.1)') +
62
+ '">' + label + '</span>';
63
+ }
64
+
65
+ function renderProof(data) {
66
+ const status = document.getElementById('proofStatus');
67
+ const out = document.getElementById('proofOut');
68
+ const txt = document.getElementById('proofTxt');
69
+ const wab = document.getElementById('proofWabJson');
70
+ const use = document.getElementById('useCaseValue');
71
+ const badges = document.getElementById('stateBadges');
72
+ const discoverPathBadge = document.getElementById('proofDiscoverPathBadge');
73
+ const usageKpi = document.getElementById('usageKpiValue');
74
+ if (!status || !out || !txt || !wab || !use || !badges) return;
75
+
76
+ const states = data.statuses || {};
77
+ badges.innerHTML = [
78
+ _statePill('Registered', states.registered === 'yes'),
79
+ _statePill('DNS Verified', states.dns_verified === 'yes'),
80
+ _statePill('Agent-Ready', states.agent_ready === 'yes'),
81
+ _statePill('Production', states.production === 'yes'),
82
+ ].join('');
83
+
84
+ const rawTxt = ((data.dns && data.dns.records) || [])[0] || '—';
85
+ txt.textContent = rawTxt;
86
+ const wabUrl = data.wab_json && data.wab_json.url;
87
+ wab.innerHTML = wabUrl ? ('<a href="' + wabUrl + '" target="_blank" style="color:#7dd3fc">' + wabUrl + '</a>') : '—';
88
+ use.textContent = (data.wab_json && data.wab_json.use_case) || '—';
89
+ out.textContent = JSON.stringify(data, null, 2);
90
+
91
+ const agentOk = data.execution_proof && data.execution_proof.ok;
92
+ const coreOk = data.dns && data.dns.ok && data.wab_json && data.wab_json.ok;
93
+
94
+ if (discoverPathBadge) {
95
+ const discoverStep = data.execution_proof && data.execution_proof.steps
96
+ ? data.execution_proof.steps.find((s) => s.key === 'agent_discover_call')
97
+ : null;
98
+ const detail = discoverStep && typeof discoverStep.detail === 'string' ? discoverStep.detail : '';
99
+ const usedFallback = detail.includes('fallback /agent-bridge.json succeeded');
100
+ const usedPrimary = detail.includes('GET /api/wab/discover succeeded');
101
+
102
+ if (usedFallback) {
103
+ discoverPathBadge.style.display = 'block';
104
+ discoverPathBadge.innerHTML = '<span style="display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(250,204,21,.15);border:1px solid rgba(250,204,21,.45);color:#fde68a;font-size:.78rem;font-weight:700;letter-spacing:.03em">Fallback Used: /agent-bridge.json</span>';
105
+ } else if (usedPrimary) {
106
+ discoverPathBadge.style.display = 'block';
107
+ discoverPathBadge.innerHTML = '<span style="display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(34,197,94,.15);border:1px solid rgba(34,197,94,.45);color:#86efac;font-size:.78rem;font-weight:700;letter-spacing:.03em">Primary Path: /api/wab/discover</span>';
108
+ } else if (discoverStep && discoverStep.ok === false) {
109
+ const detailText = detail ? (' — ' + detail.replace(/"/g, '&quot;')) : '';
110
+ discoverPathBadge.style.display = 'block';
111
+ discoverPathBadge.innerHTML = '<span style="display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(239,68,68,.15);border:1px solid rgba(239,68,68,.45);color:#fca5a5;font-size:.78rem;font-weight:700;letter-spacing:.03em" title="' + detail.replace(/"/g, '&quot;') + '">Discovery Path Failed' + detailText + '</span>';
112
+ } else {
113
+ discoverPathBadge.style.display = 'none';
114
+ discoverPathBadge.innerHTML = '';
115
+ }
116
+ }
117
+
118
+ status.innerHTML = '<span class="' + ((agentOk || coreOk) ? 'ok' : 'danger') + '">' +
119
+ ((agentOk || coreOk)
120
+ ? '✓ Verifiable proof ready.'
121
+ : '✗ Verification incomplete. Check DNS record, endpoint, and agent flow.') +
122
+ '</span>';
123
+
124
+ if (usageKpi) {
125
+ if (data && data.kpi) {
126
+ usageKpi.textContent =
127
+ 'value_score=' + (data.kpi.value_score != null ? data.kpi.value_score : '—') +
128
+ ' · actions=' + (data.kpi.discovered_actions_count != null ? data.kpi.discovered_actions_count : '—') +
129
+ ' · business_cmds=' + (data.kpi.business_commands_count != null ? data.kpi.business_commands_count : '—') +
130
+ ' · e2e_ms=' + (data.kpi.end_to_end_ms != null ? data.kpi.end_to_end_ms : '—');
131
+ } else {
132
+ usageKpi.textContent = '—';
133
+ }
134
+ }
135
+ }
136
+
137
+ window.verifyLiveProof = async function () {
138
+ const domain = (document.getElementById('dnsDomain').value || '').trim().replace(/^https?:\/\//, '').replace(/\/.*$/, '');
139
+ const status = document.getElementById('proofStatus');
140
+ if (!status) return;
141
+ if (!domain) {
142
+ status.innerHTML = '<span class="danger">Please enter a domain.</span>';
143
+ return;
144
+ }
145
+ status.innerHTML = '<span class="warn">Running live verification…</span>';
146
+ try {
147
+ const res = await fetch('/api/discovery/verify-live?domain=' + encodeURIComponent(domain));
148
+ const data = await res.json();
149
+ renderProof(data);
150
+ } catch (err) {
151
+ status.innerHTML = '<span class="danger">Verification failed: ' + ((err && err.message) || err) + '</span>';
152
+ }
153
+ };
154
+
155
+ window.testWithAgent = async function () {
156
+ const domain = (document.getElementById('dnsDomain').value || '').trim().replace(/^https?:\/\//, '').replace(/\/.*$/, '');
157
+ const status = document.getElementById('proofStatus');
158
+ if (!status) return;
159
+ if (!domain) {
160
+ status.innerHTML = '<span class="danger">Please enter a domain.</span>';
161
+ return;
162
+ }
163
+ status.innerHTML = '<span class="warn">Running agent flow (discover → ping)…</span>';
164
+ try {
165
+ const res = await fetch('/api/discovery/test-agent?domain=' + encodeURIComponent(domain));
166
+ const data = await res.json();
167
+ renderProof(data);
168
+ } catch (err) {
169
+ status.innerHTML = '<span class="danger">Agent test failed: ' + ((err && err.message) || err) + '</span>';
170
+ }
171
+ };
172
+
173
+ window.runUsageProof = async function () {
174
+ const domain = (document.getElementById('dnsDomain').value || '').trim().replace(/^https?:\/\//, '').replace(/\/.*$/, '');
175
+ const apiKey = (document.getElementById('dnsUsageApiKey') && document.getElementById('dnsUsageApiKey').value || '').trim();
176
+ const preferredUseCase = (document.getElementById('dnsUsageUseCase') && document.getElementById('dnsUsageUseCase').value || '').trim();
177
+ const status = document.getElementById('proofStatus');
178
+ if (!status) return;
179
+ if (!domain) {
180
+ status.innerHTML = '<span class="danger">Please enter a domain.</span>';
181
+ return;
182
+ }
183
+ status.innerHTML = '<span class="warn">Running usage proof (' + (apiKey ? 'real execution' : 'readiness only') + ')…</span>';
184
+ try {
185
+ const res = await fetch('/api/discovery/usage-proof', {
186
+ method: 'POST',
187
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
188
+ body: JSON.stringify({
189
+ domain,
190
+ api_key: apiKey,
191
+ preferred_use_case: preferredUseCase || undefined,
192
+ }),
193
+ });
194
+ const data = await res.json();
195
+ renderProof(data);
196
+ if (data && data.usage_proof) {
197
+ const ok = data.usage_proof.ok || data.usage_proof.readiness_ok;
198
+ status.innerHTML = '<span class="' + (ok ? 'ok' : 'danger') + '">' +
199
+ (data.usage_proof.detail || (ok ? 'Usage proof completed.' : 'Usage proof failed.')) +
200
+ '</span>';
201
+ }
202
+ loadUsageTrend(domain).catch(() => {});
203
+ } catch (err) {
204
+ status.innerHTML = '<span class="danger">Usage proof failed: ' + ((err && err.message) || err) + '</span>';
205
+ }
206
+ };
207
+
208
+ async function loadUsageTrend(domain) {
209
+ const summary = document.getElementById('usageTrendSummary');
210
+ const list = document.getElementById('usageTrendList');
211
+ if (!summary || !list) return;
212
+
213
+ const cleanDomain = (domain || '').trim().replace(/^https?:\/\//, '').replace(/\/.*$/, '');
214
+ if (!cleanDomain) {
215
+ summary.textContent = 'Enter a domain to load usage trend.';
216
+ list.textContent = '—';
217
+ return;
218
+ }
219
+
220
+ summary.textContent = 'Loading usage trend…';
221
+ list.textContent = '';
222
+
223
+ try {
224
+ const res = await fetch('/api/discovery/usage-proof-runs?domain=' + encodeURIComponent(cleanDomain) + '&limit=20');
225
+ const data = await res.json();
226
+ const runs = Array.isArray(data && data.runs) ? data.runs : [];
227
+
228
+ if (!runs.length) {
229
+ summary.textContent = 'No usage proof runs yet for this domain.';
230
+ list.textContent = 'Run Usage Proof to start collecting trend data.';
231
+ return;
232
+ }
233
+
234
+ const avgScore = Math.round(runs.reduce((acc, r) => acc + Number(r.value_score || 0), 0) / runs.length);
235
+ const execSuccessCount = runs.filter((r) => r.execution_succeeded).length;
236
+ const readinessCount = runs.filter((r) => r.readiness_ok).length;
237
+ summary.textContent = 'runs=' + runs.length + ' · avg_score=' + avgScore + ' · readiness_ok=' + readinessCount + ' · execution_ok=' + execSuccessCount;
238
+
239
+ list.innerHTML = runs.slice(0, 20).map((r) => {
240
+ const state = r.execution_succeeded ? 'exec-ok' : (r.readiness_ok ? 'ready' : 'fail');
241
+ const action = r.selected_action || '—';
242
+ const t = r.created_at || 'unknown-time';
243
+ return '<div>[' + state + '] score=' + Number(r.value_score || 0) + ' · action=' + escapeHtml(action) + ' · ms=' + (r.end_to_end_ms != null ? r.end_to_end_ms : '—') + ' · ' + escapeHtml(t) + '</div>';
244
+ }).join('');
245
+ } catch (err) {
246
+ summary.textContent = 'Failed to load usage trend.';
247
+ list.textContent = ((err && err.message) || err);
248
+ }
249
+ }
250
+
251
+ window.toggleAdvanced = function () {
252
+ const blocks = document.querySelectorAll('.advanced-block');
253
+ blocks.forEach((el) => {
254
+ el.style.display = (el.style.display === 'none' || !el.style.display) ? 'block' : 'none';
255
+ });
256
+ };
257
+
258
+ // ═══════════ Canonical records — live status ═══════════
259
+ const RR_TYPE = { TXT: 16, CAA: 257, A: 1, AAAA: 28, CNAME: 5 };
260
+
261
+ function _decodeCAARdata(hex) {
262
+ try {
263
+ const bytes = hex.replace(/\\#\s*\d+\s*/, '').replace(/\s+/g, '');
264
+ const buf = bytes.match(/.{1,2}/g).map((b) => parseInt(b, 16));
265
+ const tagLen = buf[1];
266
+ let tag = '';
267
+ let val = '';
268
+ for (let i = 0; i < tagLen; i++) tag += String.fromCharCode(buf[2 + i]);
269
+ for (let i = 2 + tagLen; i < buf.length; i++) val += String.fromCharCode(buf[i]);
270
+ return { tag, value: val };
271
+ } catch {
272
+ return null;
273
+ }
274
+ }
275
+
276
+ function _normalizeAnswer(answer, type) {
277
+ const data = answer.data || '';
278
+ if (type === 'CAA') {
279
+ const decoded = _decodeCAARdata(data);
280
+ if (decoded) return decoded.tag + ' ' + decoded.value;
281
+ return data;
282
+ }
283
+ return data.replace(/^"|"$/g, '').replace(/"\s*"/g, '');
284
+ }
285
+
286
+ async function _doh(name, type) {
287
+ const url = 'https://cloudflare-dns.com/dns-query?name=' +
288
+ encodeURIComponent(name) + '&type=' + encodeURIComponent(type) + '&do=1';
289
+ const res = await fetch(url, { headers: { accept: 'application/dns-json' } });
290
+ const data = await res.json();
291
+ const want = RR_TYPE[type];
292
+ const answers = (data.Answer || []).filter((a) => a.type === want)
293
+ .map((a) => _normalizeAnswer(a, type));
294
+ Object.defineProperty(answers, '_ad', { value: !!data.AD, enumerable: false });
295
+ return answers;
296
+ }
297
+
298
+ async function checkDnssecForWab() {
299
+ const el = document.getElementById('dnssecLiveStatus');
300
+ const rowState = document.getElementById('dnssecRowState');
301
+ if (!el) return;
302
+ try {
303
+ const ans = await _doh('_wab.webagentbridge.com', 'TXT');
304
+ if (ans._ad) {
305
+ el.className = 'ok';
306
+ el.textContent = '✓ DNSSEC validated (AD=1) at resolver';
307
+ if (rowState) {
308
+ rowState.className = 'ok';
309
+ rowState.textContent = '✓ DNSSEC validated';
310
+ }
311
+ } else {
312
+ el.className = 'warn';
313
+ el.textContent = '⚠ DNSSEC not yet enabled on this zone (AD=0). Roadmap: enable DS at registrar.';
314
+ }
315
+ } catch {
316
+ el.className = 'warn';
317
+ el.textContent = '… could not verify (DoH unreachable)';
318
+ }
319
+ }
320
+
321
+ async function checkCanonicalRecords() {
322
+ const rows = document.querySelectorAll('#recordsTable tr[data-record]');
323
+ const summary = document.getElementById('recordsLiveStatus');
324
+ let pass = 0;
325
+ let fail = 0;
326
+ const tasks = Array.from(rows).map(async (row) => {
327
+ const cell = row.querySelector('.live-cell');
328
+ const name = row.dataset.record;
329
+ const type = row.dataset.rtype;
330
+ const match = row.dataset.match;
331
+ try {
332
+ const answers = await _doh(name, type);
333
+ const hit = answers.some((a) => a.toLowerCase().includes(match.toLowerCase()));
334
+ if (hit) {
335
+ cell.innerHTML = '<span class="ok" title="Verified live via Cloudflare DoH">✓ live</span>';
336
+ pass++;
337
+ } else {
338
+ cell.innerHTML = '<span class="danger" title="Record not yet propagated or missing">✗ missing</span>';
339
+ fail++;
340
+ }
341
+ } catch {
342
+ cell.innerHTML = '<span class="warn" title="Lookup failed">… error</span>';
343
+ }
344
+ });
345
+ await Promise.allSettled(tasks);
346
+ const total = pass + fail;
347
+ summary.innerHTML = '<span class="' + (fail === 0 ? 'ok' : 'warn') + '">' +
348
+ (fail === 0
349
+ ? '✓ All ' + total + ' canonical records verified live (Cloudflare DoH).'
350
+ : '⚠ ' + pass + '/' + total + ' records live — ' + fail + ' missing or propagating.') + '</span>';
351
+ }
352
+
353
+ async function loadLiveAdoption() {
354
+ const status = document.getElementById('liveAdoptionStatus');
355
+ const list = document.getElementById('liveAdoptionList');
356
+ if (!status || !list) return;
357
+
358
+ status.innerHTML = '<span class="warn">Loading live registry…</span>';
359
+ list.style.display = 'none';
360
+ list.innerHTML = '';
361
+
362
+ try {
363
+ const res = await fetch('/api/discovery/registry?limit=24');
364
+ const data = await res.json();
365
+ const entries = Array.isArray(data && data.listings) ? data.listings : [];
366
+
367
+ if (!entries.length) {
368
+ status.innerHTML = '<span class="warn">No businesses are currently listed. New registrations will appear here automatically.</span>';
369
+ return;
370
+ }
371
+
372
+ const cards = entries.map((entry) => {
373
+ const safeName = escapeHtml(entry.name || entry.domain || 'Unnamed site');
374
+ const safeDomain = escapeHtml(entry.domain || '');
375
+ const safeDescription = escapeHtml(entry.description || 'WAB-enabled website');
376
+ const safeCategory = escapeHtml(entry.category || 'general');
377
+ const score = Number.isFinite(Number(entry.neutrality_score)) ? Number(entry.neutrality_score) : 0;
378
+
379
+ return '<article class="live-item">' +
380
+ '<h4>' + safeName + '</h4>' +
381
+ '<p>' + safeDescription + '</p>' +
382
+ '<div class="live-meta">' + safeDomain + ' · ' + safeCategory + ' · score ' + score + '</div>' +
383
+ '</article>';
384
+ }).join('');
385
+
386
+ list.innerHTML = cards;
387
+ list.style.display = 'grid';
388
+ status.innerHTML = '<span class="ok">✓ Live list loaded from real registrations.</span>';
389
+ } catch (err) {
390
+ status.innerHTML = '<span class="danger">Failed to load live registry: ' + ((err && err.message) || err) + '</span>';
391
+ }
392
+ }
393
+
394
+ function escapeHtml(value) {
395
+ return String(value)
396
+ .replace(/&/g, '&amp;')
397
+ .replace(/</g, '&lt;')
398
+ .replace(/>/g, '&gt;')
399
+ .replace(/\"/g, '&quot;')
400
+ .replace(/'/g, '&#39;');
401
+ }
402
+
403
+ function bindHandlers() {
404
+ const enBtn = document.getElementById('enBtn');
405
+ const arBtn = document.getElementById('arBtn');
406
+ const verifyBtn = document.getElementById('dnsVerifyBtn');
407
+ const copyBtn = document.getElementById('dnsCopyBtn');
408
+ const advancedBtn = document.getElementById('dnsAdvancedToggleBtn');
409
+ const proofVerifyBtn = document.getElementById('dnsProofVerifyBtn');
410
+ const proofAgentBtn = document.getElementById('dnsProofAgentBtn');
411
+ const usageProofBtn = document.getElementById('dnsUsageProofBtn');
412
+
413
+ if (enBtn) enBtn.addEventListener('click', () => window.setLang('en'));
414
+ if (arBtn) arBtn.addEventListener('click', () => window.setLang('ar'));
415
+ if (verifyBtn) verifyBtn.addEventListener('click', () => window.verifyDns());
416
+ if (copyBtn) copyBtn.addEventListener('click', () => window.copyExample());
417
+ if (advancedBtn) advancedBtn.addEventListener('click', () => window.toggleAdvanced());
418
+ if (proofVerifyBtn) proofVerifyBtn.addEventListener('click', () => window.verifyLiveProof());
419
+ if (proofAgentBtn) proofAgentBtn.addEventListener('click', () => window.testWithAgent());
420
+ if (usageProofBtn) usageProofBtn.addEventListener('click', () => window.runUsageProof());
421
+ }
422
+
423
+ document.addEventListener('DOMContentLoaded', () => {
424
+ bindHandlers();
425
+ checkCanonicalRecords().catch(() => {});
426
+ checkDnssecForWab().catch(() => {});
427
+ loadLiveAdoption().catch(() => {});
428
+ const initDomain = (document.getElementById('dnsDomain') && document.getElementById('dnsDomain').value || '').trim();
429
+ loadUsageTrend(initDomain).catch(() => {});
430
+ const navbar = document.getElementById('navbar');
431
+ window.addEventListener('scroll', () => {
432
+ if (!navbar) return;
433
+ navbar.style.background = window.scrollY > 50
434
+ ? 'rgba(7, 13, 25, 0.92)'
435
+ : 'rgba(7, 13, 25, 0.78)';
436
+ });
437
+ });
438
+