From 10a9621a7bb2a66f49e1d865487471d2631e7438 Mon Sep 17 00:00:00 2001 From: Lakshman Turlapati Date: Sun, 23 Aug 2026 15:34:51 -0500 Subject: [PATCH 1/3] chore(license): relicense from BSL 1.1 to MIT Replace the Business Source License 1.1 text with the standard MIT license and update every license claim in the repo: SPDX fields in both package.json/package-lock.json pairs, the README and mcp README badges, showcase footers and privacy copy, the extension help panel, and the llms.txt/llms-full.txt crawler sources. Advertise the canonical LICENSE URL in the SoftwareApplication JSON-LD on the home and agents pages, and extend the crawler smoke and showcase build smoke to assert that metadata in prerendered HTML (en plus all 5 locales) and in the generated AEO files. --- .../20-PATTERNS.md | 2 +- .../258-02-PLAN.md | 4 +- .../36-REVIEW.md | 8 +- .../36-REVIEW.md | 8 +- .../53-TRANSCREATION.md | 2 +- .../260514-1nv-PLAN.md | 6 +- .../260514-1nv-SUMMARY.md | 6 +- LICENSE | 79 +++------- README.md | 4 +- extension/ui/control_panel.html | 2 +- mcp/README.md | 2 +- mcp/package-lock.json | 2 +- mcp/package.json | 2 +- package-lock.json | 2 +- package.json | 2 +- showcase/about.html | 2 +- showcase/angular/public/llms-full.txt | 4 +- showcase/angular/public/llms.txt | 4 +- showcase/angular/scripts/llms-full.source.md | 4 +- showcase/angular/scripts/llms.source.md | 4 +- showcase/angular/scripts/smoke-crawler.mjs | 53 ++++++- .../showcase-shell.component.html | 2 +- .../app/pages/agents/agents-page.component.ts | 4 + .../app/pages/home/home-page.component.html | 2 +- .../src/app/pages/home/home-page.component.ts | 1 + .../pages/lattice/lattice-page.component.html | 2 +- .../privacy-history-archive.component.html | 2 +- .../pages/privacy/privacy-page.component.html | 2 +- .../angular/src/locale/DO-NOT-TRANSLATE.md | 2 +- showcase/angular/src/locale/messages.de.xlf | 20 +-- showcase/angular/src/locale/messages.es.xlf | 20 +-- showcase/angular/src/locale/messages.ja.xlf | 20 +-- showcase/angular/src/locale/messages.xlf | 10 +- .../angular/src/locale/messages.zh-CN.xlf | 20 +-- .../angular/src/locale/messages.zh-TW.xlf | 20 +-- showcase/dashboard.html | 2 +- tests/showcase-build-smoke.test.js | 144 ++++++++++++++++-- 37 files changed, 309 insertions(+), 166 deletions(-) diff --git a/.planning/milestones/v0.11.0-phases/20-integration-cap-ui-docs-edge-cases/20-PATTERNS.md b/.planning/milestones/v0.11.0-phases/20-integration-cap-ui-docs-edge-cases/20-PATTERNS.md index 7bb889d77..529fdd1ea 100644 --- a/.planning/milestones/v0.11.0-phases/20-integration-cap-ui-docs-edge-cases/20-PATTERNS.md +++ b/.planning/milestones/v0.11.0-phases/20-integration-cap-ui-docs-edge-cases/20-PATTERNS.md @@ -787,7 +787,7 @@ check(/fsbTriggerHandleRefreshPollForTest/.test(src), 'test-only refresh-poll al "": { "name": "fsb-mcp-server", "version": "0.8.0", - "license": "BUSL-1.1" + "license": "MIT" } } } diff --git a/.planning/milestones/v0.9.62-phases/258-removal-migration-errors-package-0.9.0/258-02-PLAN.md b/.planning/milestones/v0.9.62-phases/258-removal-migration-errors-package-0.9.0/258-02-PLAN.md index ddf4c0319..496025922 100644 --- a/.planning/milestones/v0.9.62-phases/258-removal-migration-errors-package-0.9.0/258-02-PLAN.md +++ b/.planning/milestones/v0.9.62-phases/258-removal-migration-errors-package-0.9.0/258-02-PLAN.md @@ -129,7 +129,7 @@ From mcp/package.json (lines 1-6, the relevant top of file -- only line 3 `versi "name": "fsb-mcp-server", "version": "0.8.0", "description": "FSB Browser Automation MCP Server", - "license": "BUSL-1.1", + "license": "MIT", "author": "Lakshman Turlapati", ``` @@ -355,7 +355,7 @@ The build command runs `tsc` (compiles all .ts under mcp/src/ to mcp/build/) AND 6. Non-version content in each edited file is unchanged. Verified by: - mcp/src/version.ts: `grep -c "FSB_SERVER_NAME\\|FSB_EXTENSION_BRIDGE_PORT\\|FSB_EXTENSION_BRIDGE_URL\\|DEFAULT_HTTP_HOST\\|DEFAULT_HTTP_PORT\\|FSB_REGISTRY_NAME" mcp/src/version.ts` returns at least 6 (one per other constant). - - mcp/package.json: `node -e "const p = require('./mcp/package.json'); console.log(p.name, p.license, p.scripts.build.length > 0)"` prints `fsb-mcp-server BUSL-1.1 true`. + - mcp/package.json: `node -e "const p = require('./mcp/package.json'); console.log(p.name, p.license, p.scripts.build.length > 0)"` prints `fsb-mcp-server MIT true`. - mcp/server.json: `node -e "const s = require('./mcp/server.json'); console.log(s.name, s.title, s.packages.length, s.packages[0].registryType)"` prints `io.github.lakshmanturlapati/fsb-mcp-server FSB MCP Server 1 npm`. - tests/mcp-version-parity.test.js: `grep -c "extractRuntimeVersion\\|collectExplicitVersions\\|runCommand" tests/mcp-version-parity.test.js` returns at least 3 (one per helper function). diff --git a/.planning/milestones/v1.0.0-phases/36-codegen-pipeline-no-dead-entry-resolution/36-REVIEW.md b/.planning/milestones/v1.0.0-phases/36-codegen-pipeline-no-dead-entry-resolution/36-REVIEW.md index edb8d8333..b2a7bbed8 100644 --- a/.planning/milestones/v1.0.0-phases/36-codegen-pipeline-no-dead-entry-resolution/36-REVIEW.md +++ b/.planning/milestones/v1.0.0-phases/36-codegen-pipeline-no-dead-entry-resolution/36-REVIEW.md @@ -275,13 +275,13 @@ The override only works because the slug-suffix branch (lines 179-188) saves it. --- -#### LO-03: `package.json` version/license/badge metadata is internally inconsistent (pre-existing, surfaced by this phase's audit) +#### LO-03: `package.json` version/package filename metadata is internally inconsistent (pre-existing, surfaced by this phase's audit) -**File:** `package.json:3` (`"version": "0.9.90"`), `:71` (`"license": "BUSL-1.1"`), `:133` (badge `license-MIT`), `:24` (`package` script hardcodes `fsb-v0.9.90.zip`) +**File:** `package.json:3` (`"version": "0.9.90"`), `:71` (`"license": "MIT"`), `:133` (badge `license-MIT`), `:24` (`package` script hardcodes `fsb-v0.9.90.zip`) -**Issue:** The package declares `version: 0.9.90` while the milestone/requirements target v1.0.0; the SPDX `license` is `BUSL-1.1` but the README badge advertises `license-MIT`; the `package` zip name is hardcoded to the version string (drifts silently on bump). None is a Phase-36 regression, but the OpenTabs descriptors this phase vendors are stamped `license: 'MIT'` in-descriptor while the package is BUSL-1.1, so the license story now spans two values and deserves a deliberate note. Not a code defect. +**Issue:** The package declares `version: 0.9.90` while the milestone/requirements target v1.0.0, and the `package` zip name is hardcoded to the version string (drifts silently on bump). The SPDX field, README badge, and vendored OpenTabs descriptors now consistently advertise MIT. The remaining version drift is not a Phase-36 regression or a code defect. -**Fix:** Out of Phase-36 scope; recommend a metadata-reconciliation pass (align the SPDX license with the badge, parameterize the zip name) tracked separately. +**Fix:** Out of Phase-36 scope; recommend a version-metadata reconciliation pass that parameterizes the zip name. --- diff --git a/.planning/phases/36-codegen-pipeline-no-dead-entry-resolution/36-REVIEW.md b/.planning/phases/36-codegen-pipeline-no-dead-entry-resolution/36-REVIEW.md index edb8d8333..b2a7bbed8 100644 --- a/.planning/phases/36-codegen-pipeline-no-dead-entry-resolution/36-REVIEW.md +++ b/.planning/phases/36-codegen-pipeline-no-dead-entry-resolution/36-REVIEW.md @@ -275,13 +275,13 @@ The override only works because the slug-suffix branch (lines 179-188) saves it. --- -#### LO-03: `package.json` version/license/badge metadata is internally inconsistent (pre-existing, surfaced by this phase's audit) +#### LO-03: `package.json` version/package filename metadata is internally inconsistent (pre-existing, surfaced by this phase's audit) -**File:** `package.json:3` (`"version": "0.9.90"`), `:71` (`"license": "BUSL-1.1"`), `:133` (badge `license-MIT`), `:24` (`package` script hardcodes `fsb-v0.9.90.zip`) +**File:** `package.json:3` (`"version": "0.9.90"`), `:71` (`"license": "MIT"`), `:133` (badge `license-MIT`), `:24` (`package` script hardcodes `fsb-v0.9.90.zip`) -**Issue:** The package declares `version: 0.9.90` while the milestone/requirements target v1.0.0; the SPDX `license` is `BUSL-1.1` but the README badge advertises `license-MIT`; the `package` zip name is hardcoded to the version string (drifts silently on bump). None is a Phase-36 regression, but the OpenTabs descriptors this phase vendors are stamped `license: 'MIT'` in-descriptor while the package is BUSL-1.1, so the license story now spans two values and deserves a deliberate note. Not a code defect. +**Issue:** The package declares `version: 0.9.90` while the milestone/requirements target v1.0.0, and the `package` zip name is hardcoded to the version string (drifts silently on bump). The SPDX field, README badge, and vendored OpenTabs descriptors now consistently advertise MIT. The remaining version drift is not a Phase-36 regression or a code defect. -**Fix:** Out of Phase-36 scope; recommend a metadata-reconciliation pass (align the SPDX license with the badge, parameterize the zip name) tracked separately. +**Fix:** Out of Phase-36 scope; recommend a version-metadata reconciliation pass that parameterizes the zip name. --- diff --git a/.planning/phases/53-trans-unit-resync-stats-translation-transcreation-review/53-TRANSCREATION.md b/.planning/phases/53-trans-unit-resync-stats-translation-transcreation-review/53-TRANSCREATION.md index f8fee1a5a..ce23dc1ea 100644 --- a/.planning/phases/53-trans-unit-resync-stats-translation-transcreation-review/53-TRANSCREATION.md +++ b/.planning/phases/53-trans-unit-resync-stats-translation-transcreation-review/53-TRANSCREATION.md @@ -12,7 +12,7 @@ | `home.hero.subtitle` | Shorter, spoken marketing voice | | `home.hero.cta.getStarted` | CTA punch-up (e.g. “Empezar ahora” / “Jetzt starten”) | | `home.hero.cta.seeAction` | Natural “see it work” CTA | -| `home.cta.openSource.title` | Open-source + BSL 1.1 line, brand span intact | +| `home.cta.openSource.title` | Open-source + MIT line, brand span intact | | `home.cta.openSource.desc` | Fork/contribute framing | | `home.cta.openSource.github` | “View on GitHub” localized | | `about.hero.title` | “See FSB in Action” with orange span | diff --git a/.planning/quick/260514-1nv-showcase-stats-page-footer-only-easter-e/260514-1nv-PLAN.md b/.planning/quick/260514-1nv-showcase-stats-page-footer-only-easter-e/260514-1nv-PLAN.md index aaf9fdd76..8d740bd9b 100644 --- a/.planning/quick/260514-1nv-showcase-stats-page-footer-only-easter-e/260514-1nv-PLAN.md +++ b/.planning/quick/260514-1nv-showcase-stats-page-footer-only-easter-e/260514-1nv-PLAN.md @@ -143,10 +143,10 @@ From showcase/angular/src/app/layout/showcase-shell/showcase-shell.component.htm

Project

GitHub Issues - BSL 1.1 License + MIT License ``` -The new "Stats" link is added inside this same ` diff --git a/showcase/angular/public/llms-full.txt b/showcase/angular/public/llms-full.txt index fa610882c..b96aaf8b6 100644 --- a/showcase/angular/public/llms-full.txt +++ b/showcase/angular/public/llms-full.txt @@ -3,7 +3,9 @@ ## 1. Project Description -FSB (Full Self-Browsing) is an open-source Chrome extension that automates the browser through natural language. You describe a task in plain English; FSB plans the clicks, types, and navigation to complete it. The extension runs in your browser with your own API keys, encrypted local storage, and opt-out anonymous usage telemetry for aggregate public stats only. Multi-model AI (xAI Grok, OpenAI, Anthropic Claude, Google Gemini, OpenRouter, LM Studio, custom OpenAI-compatible endpoints), 56 canonical browser tools, a 66-tool MCP surface, 142+ site-specific guides, reactive trigger watchers, real file uploads, and a 128-app native capability catalog for verified first-party API execution. Current release: FSB v0.9.90 (Chrome extension) with fsb-mcp-server 0.10.0 (npm MCP package). BSL 1.1 licensed. +FSB (Full Self-Browsing) is an open-source Chrome extension that automates the browser through natural language. You describe a task in plain English; FSB plans the clicks, types, and navigation to complete it. The extension runs in your browser with your own API keys, encrypted local storage, and opt-out anonymous usage telemetry for aggregate public stats only. Multi-model AI (xAI Grok, OpenAI, Anthropic Claude, Google Gemini, OpenRouter, LM Studio, custom OpenAI-compatible endpoints), 56 canonical browser tools, a 66-tool MCP surface, 142+ site-specific guides, reactive trigger watchers, real file uploads, and a 128-app native capability catalog for verified first-party API execution. Current release: FSB v0.9.90 (Chrome extension) with fsb-mcp-server 0.10.0 (npm MCP package). + +License: [MIT License](https://github.com/fullselfbrowsing/FSB/blob/main/LICENSE). FSB also works especially well as an MCP browser layer for AI agents. Claude Code, Codex, Cursor, VS Code, Windsurf, OpenClaw, Hermes, and other MCP clients can use FSB to operate a real Chrome browser, inspect page state, verify outcomes, and feed observations back into their own coding or autonomy loop. diff --git a/showcase/angular/public/llms.txt b/showcase/angular/public/llms.txt index 1dfaef712..44a4d4b6c 100644 --- a/showcase/angular/public/llms.txt +++ b/showcase/angular/public/llms.txt @@ -3,6 +3,8 @@ FSB (Full Self-Browsing) is an open-source Chrome extension that automates the browser through natural language. You describe a task; FSB plans the clicks, types, and navigation to complete it. Multi-model AI (xAI, OpenAI, Anthropic, Gemini, OpenRouter, LM Studio, local), 56 browser tools, a 66-tool MCP surface, 142+ site guides, local-first execution, and BYO API keys. Current release: FSB v0.9.90 (extension) with fsb-mcp-server 0.10.0 (npm). +License: [MIT License](https://github.com/fullselfbrowsing/FSB/blob/main/LICENSE). + FSB also works as an MCP browser layer for AI agents. Claude Code, Codex, Cursor, VS Code, Windsurf, OpenClaw, Hermes, and other MCP clients can use FSB to operate a real Chrome browser, inspect page state, verify outcomes, and keep the calling agent in the loop. The MCP surface includes trigger watchers (`trigger`, `stop_trigger`, `get_trigger_status`, `list_triggers`) for reactive DOM monitoring, real local file uploads (`upload_file`) and synthetic dropzone support (`drop_file`), and a 128-app native capability catalog where `search_capabilities` finds verified first-party API actions and `invoke_capability` runs them against the user's logged-in sessions with readiness labels such as `t1-ready`, `t1-guarded-fail-closed`, `learn-pending`, and `discovery-pending`, plus audit records. The vault boundary is explicit: credentials and payment methods resolve inside the encrypted Chrome extension store through tools such as `fill_credential` and `use_payment_method`; raw passwords, card numbers, and secrets do not cross the MCP bridge. One-command install: `npx -y fsb-mcp-server install --claude-code` (also `--claude-desktop`, `--cursor`, `--vscode`, `--windsurf`, `--codex`, `--all`). @@ -35,7 +37,7 @@ Unlike cloud browser agents that run in a headless container without your cookie - [Site Maps](https://full-selfbrowsing.com/sitemaps): Community site maps hub (under development) -- contribute well-tested site schemas that can become built-in browser knowledge. - [Legal](https://full-selfbrowsing.com/legal): Public non-indexed legal reference for automation posture, consent model, audit-log retention, and service-denylist rationale. It is intentionally excluded from sitemap and indexed SEO surfaces. - [Chrome Web Store](https://chromewebstore.google.com/detail/badgafnfchcihdfnjneklogedcdkmjfk): One-click extension install. -- [GitHub repository](https://github.com/fullselfbrowsing/FSB): BSL 1.1 source code, install instructions, issue tracker. +- [GitHub repository](https://github.com/fullselfbrowsing/FSB): MIT source code, install instructions, issue tracker. - [llms-full.txt](https://full-selfbrowsing.com/llms-full.txt): Long-form project dump for LLM ingestion -- capabilities, install, comparison framing. ## Demo Videos diff --git a/showcase/angular/scripts/llms-full.source.md b/showcase/angular/scripts/llms-full.source.md index d5a480eb9..285fac228 100644 --- a/showcase/angular/scripts/llms-full.source.md +++ b/showcase/angular/scripts/llms-full.source.md @@ -2,7 +2,9 @@ ## 1. Project Description -FSB (Full Self-Browsing) is an open-source Chrome extension that automates the browser through natural language. You describe a task in plain English; FSB plans the clicks, types, and navigation to complete it. The extension runs in your browser with your own API keys, encrypted local storage, and opt-out anonymous usage telemetry for aggregate public stats only. Multi-model AI (xAI Grok, OpenAI, Anthropic Claude, Google Gemini, OpenRouter, LM Studio, custom OpenAI-compatible endpoints), 56 canonical browser tools, a 66-tool MCP surface, 142+ site-specific guides, reactive trigger watchers, real file uploads, and a 128-app native capability catalog for verified first-party API execution. Current release: FSB v0.9.90 (Chrome extension) with fsb-mcp-server 0.10.0 (npm MCP package). BSL 1.1 licensed. +FSB (Full Self-Browsing) is an open-source Chrome extension that automates the browser through natural language. You describe a task in plain English; FSB plans the clicks, types, and navigation to complete it. The extension runs in your browser with your own API keys, encrypted local storage, and opt-out anonymous usage telemetry for aggregate public stats only. Multi-model AI (xAI Grok, OpenAI, Anthropic Claude, Google Gemini, OpenRouter, LM Studio, custom OpenAI-compatible endpoints), 56 canonical browser tools, a 66-tool MCP surface, 142+ site-specific guides, reactive trigger watchers, real file uploads, and a 128-app native capability catalog for verified first-party API execution. Current release: FSB v0.9.90 (Chrome extension) with fsb-mcp-server 0.10.0 (npm MCP package). + +License: [MIT License](https://github.com/fullselfbrowsing/FSB/blob/main/LICENSE). FSB also works especially well as an MCP browser layer for AI agents. Claude Code, Codex, Cursor, VS Code, Windsurf, OpenClaw, Hermes, and other MCP clients can use FSB to operate a real Chrome browser, inspect page state, verify outcomes, and feed observations back into their own coding or autonomy loop. diff --git a/showcase/angular/scripts/llms.source.md b/showcase/angular/scripts/llms.source.md index 0d3e9f144..1a457f7af 100644 --- a/showcase/angular/scripts/llms.source.md +++ b/showcase/angular/scripts/llms.source.md @@ -2,6 +2,8 @@ FSB (Full Self-Browsing) is an open-source Chrome extension that automates the browser through natural language. You describe a task; FSB plans the clicks, types, and navigation to complete it. Multi-model AI (xAI, OpenAI, Anthropic, Gemini, OpenRouter, LM Studio, local), 56 browser tools, a 66-tool MCP surface, 142+ site guides, local-first execution, and BYO API keys. Current release: FSB v0.9.90 (extension) with fsb-mcp-server 0.10.0 (npm). +License: [MIT License](https://github.com/fullselfbrowsing/FSB/blob/main/LICENSE). + FSB also works as an MCP browser layer for AI agents. Claude Code, Codex, Cursor, VS Code, Windsurf, OpenClaw, Hermes, and other MCP clients can use FSB to operate a real Chrome browser, inspect page state, verify outcomes, and keep the calling agent in the loop. The MCP surface includes trigger watchers (`trigger`, `stop_trigger`, `get_trigger_status`, `list_triggers`) for reactive DOM monitoring, real local file uploads (`upload_file`) and synthetic dropzone support (`drop_file`), and a 128-app native capability catalog where `search_capabilities` finds verified first-party API actions and `invoke_capability` runs them against the user's logged-in sessions with readiness labels such as `t1-ready`, `t1-guarded-fail-closed`, `learn-pending`, and `discovery-pending`, plus audit records. The vault boundary is explicit: credentials and payment methods resolve inside the encrypted Chrome extension store through tools such as `fill_credential` and `use_payment_method`; raw passwords, card numbers, and secrets do not cross the MCP bridge. One-command install: `npx -y fsb-mcp-server install --claude-code` (also `--claude-desktop`, `--cursor`, `--vscode`, `--windsurf`, `--codex`, `--all`). @@ -34,7 +36,7 @@ Unlike cloud browser agents that run in a headless container without your cookie - [Site Maps](https://full-selfbrowsing.com/sitemaps): Community site maps hub (under development) -- contribute well-tested site schemas that can become built-in browser knowledge. - [Legal](https://full-selfbrowsing.com/legal): Public non-indexed legal reference for automation posture, consent model, audit-log retention, and service-denylist rationale. It is intentionally excluded from sitemap and indexed SEO surfaces. - [Chrome Web Store](https://chromewebstore.google.com/detail/badgafnfchcihdfnjneklogedcdkmjfk): One-click extension install. -- [GitHub repository](https://github.com/fullselfbrowsing/FSB): BSL 1.1 source code, install instructions, issue tracker. +- [GitHub repository](https://github.com/fullselfbrowsing/FSB): MIT source code, install instructions, issue tracker. - [llms-full.txt](https://full-selfbrowsing.com/llms-full.txt): Long-form project dump for LLM ingestion -- capabilities, install, comparison framing. ## Demo Videos diff --git a/showcase/angular/scripts/smoke-crawler.mjs b/showcase/angular/scripts/smoke-crawler.mjs index f1841b3af..b2aaf693a 100644 --- a/showcase/angular/scripts/smoke-crawler.mjs +++ b/showcase/angular/scripts/smoke-crawler.mjs @@ -1,8 +1,8 @@ #!/usr/bin/env node // Phase 216 SMOKE-01..03: production-or-local crawler smoke. // Curls marketing routes under GPTBot UA; curls crawler files; resolves every -// sitemap . Asserts route-specific titles, canonicals, JSON-LD presence -// (home), Content-Type headers, and llms-full.txt size budget. +// sitemap . Asserts route-specific titles, canonicals, licensed JSON-LD +// software metadata, Content-Type headers, and llms-full.txt size budget. // // Run: // npm --prefix showcase/angular run smoke:crawler:local (local static build) @@ -15,6 +15,7 @@ const BASE_URL = (process.env.BASE_URL || 'https://full-selfbrowsing.com').replace(/\/$/, ''); const PROD_HOST = 'https://full-selfbrowsing.com'; +const LICENSE_URL = 'https://github.com/fullselfbrowsing/FSB/blob/main/LICENSE'; const UA = 'GPTBot'; const failures = []; @@ -74,12 +75,41 @@ const AEO_MUST_CONTAIN = [ 'Flight Booking: Powered by Codex MCP', 'OpenClaw Monitoring Doge Price', 'An Aha Moment by Claude Opus 4.6', + 'MIT License', + LICENSE_URL, ]; const AEO_MUST_NOT_CONTAIN = [ 'Watch FSB drive Google, search Amazon, and book travel autonomously.', 'Claude Opus 4.7', ]; +function extractJsonLdNodes(html) { + const nodes = []; + const parseErrors = []; + const scriptRe = /]*\btype=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi; + let match; + + function visit(value) { + if (Array.isArray(value)) { + value.forEach(visit); + return; + } + if (!value || typeof value !== 'object') return; + nodes.push(value); + if (value['@graph']) visit(value['@graph']); + } + + while ((match = scriptRe.exec(html)) !== null) { + try { + visit(JSON.parse(match[1])); + } catch (err) { + parseErrors.push(err.message); + } + } + + return { nodes, parseErrors }; +} + async function checkMarketingRoutes() { for (const { path, titleSubstr, canonical } of MARKETING_ASSERTIONS) { const url = `${BASE_URL}${path}`; @@ -95,12 +125,27 @@ async function checkMarketingRoutes() { record(r.body.includes(titleSubstr), `GET ${path} body contains title substring "${titleSubstr}"`, ''); record(r.body.includes(`href="${canonical}"`), `GET ${path} canonical href="${canonical}"`, ''); record(r.body.includes('`, ''); - if (path === '/') { + if (path === '/' || path === '/agents') { + const { nodes, parseErrors } = extractJsonLdNodes(r.body); record( r.body.includes('type="application/ld+json"'), - 'GET / contains JSON-LD