cbrowser 19.2.0 → 19.2.2

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 (70) hide show
  1. package/README.md +2 -2
  2. package/dist/analysis/accessibility-empathy.d.ts +6 -0
  3. package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
  4. package/dist/analysis/accessibility-empathy.js +28 -77
  5. package/dist/analysis/accessibility-empathy.js.map +1 -1
  6. package/dist/analysis/competitive-benchmark.js +2 -2
  7. package/dist/analysis/competitive-benchmark.js.map +1 -1
  8. package/dist/analysis/persona-comparison.d.ts.map +1 -1
  9. package/dist/analysis/persona-comparison.js +5 -1
  10. package/dist/analysis/persona-comparison.js.map +1 -1
  11. package/dist/browser.js +2 -2
  12. package/dist/browser.js.map +1 -1
  13. package/dist/cli.js +44 -7
  14. package/dist/cli.js.map +1 -1
  15. package/dist/daemon.d.ts +2 -0
  16. package/dist/daemon.d.ts.map +1 -1
  17. package/dist/daemon.js +8 -1
  18. package/dist/daemon.js.map +1 -1
  19. package/dist/mcp-server-remote.d.ts +3 -0
  20. package/dist/mcp-server-remote.d.ts.map +1 -1
  21. package/dist/mcp-server-remote.js +12 -1
  22. package/dist/mcp-server-remote.js.map +1 -1
  23. package/dist/mcp-tools/account-personas.d.ts +3 -2
  24. package/dist/mcp-tools/account-personas.d.ts.map +1 -1
  25. package/dist/mcp-tools/account-personas.js +13 -4
  26. package/dist/mcp-tools/account-personas.js.map +1 -1
  27. package/dist/mcp-tools/base/advanced-interaction-tools.js +1 -1
  28. package/dist/mcp-tools/base/advanced-interaction-tools.js.map +1 -1
  29. package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
  30. package/dist/mcp-tools/base/audit-tools.js +49 -15
  31. package/dist/mcp-tools/base/audit-tools.js.map +1 -1
  32. package/dist/mcp-tools/base/browser-state-tools.d.ts.map +1 -1
  33. package/dist/mcp-tools/base/browser-state-tools.js +25 -4
  34. package/dist/mcp-tools/base/browser-state-tools.js.map +1 -1
  35. package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
  36. package/dist/mcp-tools/base/cognitive-tools.js +14 -2
  37. package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
  38. package/dist/mcp-tools/base/gif-tools.d.ts +19 -0
  39. package/dist/mcp-tools/base/gif-tools.d.ts.map +1 -1
  40. package/dist/mcp-tools/base/gif-tools.js +53 -24
  41. package/dist/mcp-tools/base/gif-tools.js.map +1 -1
  42. package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
  43. package/dist/mcp-tools/base/persona-comparison-tools.js +44 -28
  44. package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
  45. package/dist/mcp-tools/base/values-tools.d.ts.map +1 -1
  46. package/dist/mcp-tools/base/values-tools.js +12 -0
  47. package/dist/mcp-tools/base/values-tools.js.map +1 -1
  48. package/dist/mcp-tools/base/visual-testing-tools.d.ts +17 -0
  49. package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
  50. package/dist/mcp-tools/base/visual-testing-tools.js +184 -148
  51. package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
  52. package/dist/mcp-tools/inline-image.d.ts +82 -0
  53. package/dist/mcp-tools/inline-image.d.ts.map +1 -0
  54. package/dist/mcp-tools/inline-image.js +163 -0
  55. package/dist/mcp-tools/inline-image.js.map +1 -0
  56. package/dist/personas.d.ts +37 -0
  57. package/dist/personas.d.ts.map +1 -1
  58. package/dist/personas.js +137 -3
  59. package/dist/personas.js.map +1 -1
  60. package/dist/security/audit-wrapper.d.ts.map +1 -1
  61. package/dist/security/audit-wrapper.js +1 -0
  62. package/dist/security/audit-wrapper.js.map +1 -1
  63. package/dist/security/script-gate.d.ts +33 -0
  64. package/dist/security/script-gate.d.ts.map +1 -0
  65. package/dist/security/script-gate.js +37 -0
  66. package/dist/security/script-gate.js.map +1 -0
  67. package/dist/security/tool-permissions.d.ts.map +1 -1
  68. package/dist/security/tool-permissions.js +4 -0
  69. package/dist/security/tool-permissions.js.map +1 -1
  70. package/package.json +1 -1
package/README.md CHANGED
@@ -231,9 +231,9 @@ AI agents need boundaries. CBrowser classifies every action by risk level:
231
231
  | 🔴 **Red** | Submit, delete, purchase | Requires verification |
232
232
  | ⬛ **Black** | Bypass auth, inject scripts | Never executes |
233
233
 
234
- **How red is decided:** by the element an action would activate as well as by the selector text. The selector string is classified first, as before, so naming a dangerous action in a selector is refused even when the element reads harmless. The element is then classified however it is addressed: the same "Delete account" button is refused by its label, its id, its test id or a structural path, and an element with only a delegated click listener is judged by its own text. A click on a container is judged at the point it lands - beneath fixed bars and overlays, into open shadow roots, slots and same-origin frames, up to the control, pointer-cursor item or text-bearing item there - and the click is sent to that judged point. A refused click is never retried with a "healed" selector. Gated: `click`, `smart_click`, hover-click and the daemon's click; `press_key`, `type_text` and `keyboard` (Enter, including a typed or pressed newline, or Space on the focused control - followed into shadow roots and same-origin frames - and Enter in a field whose form submits one, judged together with a combobox's `aria-activedescendant` item); a `drag` whose press and release share a control; and the NL test runner's `click at X, Y`. Names below a control (icon labels, image alts, slotted and open-shadow text, `<label for>`) and the path a form submits to (a submitter's `formaction`, otherwise the form's `action`) feed the same keyword classification. `force: true` on the MCP `click`, `press_key`, `type_text` and `drag` tools (`smart_click` has none: use `click`), or `--force` on the CLI, proceeds and reports the zone.
234
+ **How red is decided:** by the element an action would activate as well as by the selector text. The selector string is classified first, as before, so naming a dangerous action in a selector is refused even when the element reads harmless. The element is then classified however it is addressed: the same "Delete account" button is refused by its label, its id, its test id or a structural path, and an element with only a delegated click listener is judged by its own text. A click on a container is judged at the point it lands - beneath fixed bars and overlays, into open shadow roots, slots and same-origin frames, up to the control, pointer-cursor item or text-bearing item there - and the click is sent to that judged point. A refused click is never retried with a "healed" selector. Gated: `click`, `smart_click`, hover-click and the daemon's click; `press_key`, `type_text` and `keyboard` (Enter, including a typed or pressed newline, or Space on the focused control - followed into shadow roots and same-origin frames - and Enter in a field whose form submits one, judged together with a combobox's `aria-activedescendant` item); a `drag` whose press and release share a control; and the NL test runner's `click at X, Y`. Caller-supplied page script is red by itself, whatever it does: `evaluate_script` over MCP, `evaluate`/`eval` on the CLI and the daemon's evaluate are refused without force and run nothing, because a script can activate any control without passing the element gate (`document.querySelector('#delete-account').click()` is the click `click` refuses, in one line). Names below a control (icon labels, image alts, slotted and open-shadow text, `<label for>`) and the path a form submits to (a submitter's `formaction`, otherwise the form's `action`) feed the same keyword classification. `force: true` on the MCP `click`, `press_key`, `type_text`, `drag` and `evaluate_script` tools (`smart_click` has none: use `click`), or `--force` on the CLI, proceeds and reports the zone.
235
235
 
236
- Not judged, by design: clicking or typing into a text field or a contenteditable editor, and choosing a value with a native checkbox, radio or select or a `role=checkbox`, `radio` or `switch` - the submit that commits them is judged, but a switch or checkbox that saves on its own as it changes commits immediately and is not refused; and a link's address (a link to `/buy` is navigation). Not gated: cognitive journeys and other engines that choose their own clicks, and `evaluate_script`. Known limits: labels inside closed shadow roots or drawn by CSS, controls inside cross-origin frames, icon-only controls with no accessible name, a confirmation button with neutral text ("Confirm", "OK") inside a dialog, a form with no `action` or an empty `formaction` (it submits to the page's own address, which is not judged), an `aria-activedescendant` id that only resolves outside the field's own document or shadow root (not followed; ARIA references do not cross roots), a Tab inside typed text that moves focus before a later Enter, one round trip between the check and the click in which a page could swap the element, and keyword false positives: a "Purchase history" tab, a "Buyer protection" link, a `role=option` value picker such as "Pay with PayPal", a harmless "Apply" in a form posting to `/checkout/discount`, and an inert heading such as "Checkout" clicked by id (refusing it costs one forced retry; missing a listener-only "Delete account" cannot be undone). Classification uses English keyword patterns; when the element cannot be read, the selector text alone decides.
236
+ Not judged, by design: clicking or typing into a text field or a contenteditable editor, and choosing a value with a native checkbox, radio or select or a `role=checkbox`, `radio` or `switch` - the submit that commits them is judged, but a switch or checkbox that saves on its own as it changes commits immediately and is not refused; and a link's address (a link to `/buy` is navigation). Not gated: cognitive journeys and other engines that choose their own clicks. Known limits: labels inside closed shadow roots or drawn by CSS, controls inside cross-origin frames, icon-only controls with no accessible name, a confirmation button with neutral text ("Confirm", "OK") inside a dialog, a form with no `action` or an empty `formaction` (it submits to the page's own address, which is not judged), an `aria-activedescendant` id that only resolves outside the field's own document or shadow root (not followed; ARIA references do not cross roots), a Tab inside typed text that moves focus before a later Enter, one round trip between the check and the click in which a page could swap the element, and keyword false positives: a "Purchase history" tab, a "Buyer protection" link, a `role=option` value picker such as "Pay with PayPal", a harmless "Apply" in a form posting to `/checkout/discount`, and an inert heading such as "Checkout" clicked by id (refusing it costs one forced retry; missing a listener-only "Delete account" cannot be undone). Classification uses English keyword patterns; when the element cannot be read, the selector text alone decides.
237
237
 
238
238
  ---
239
239
 
@@ -49,6 +49,12 @@ export declare function filterBarriersToViewport<T extends {
49
49
  };
50
50
  export declare function formatEmpathyAuditReport(result: EmpathyAuditResult): string;
51
51
  export declare function generateEmpathyAuditHtmlReport(result: EmpathyAuditResult): string;
52
+ /**
53
+ * Disability shorthand -> roster name. One table for every tool now lives in
54
+ * src/personas.ts (PERSONA_ALIASES); kept under this name for existing
55
+ * importers. (2026-10-09)
56
+ */
57
+ export declare const EMPATHY_PERSONA_ALIASES: Record<string, string>;
52
58
  /**
53
59
  * The published WCAG list, DERIVED from the barriers rather than accumulated
54
60
  * alongside them.
@@ -1 +1 @@
1
- {"version":3,"file":"accessibility-empathy.d.ts","sourceRoot":"","sources":["../../src/analysis/accessibility-empathy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmCH,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EASpB,MAAM,aAAa,CAAC;AAuKrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EACnH,QAAQ,EAAE,CAAC,EAAE,EACb,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAWhC;AAwjED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAmF3E;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAkTjF;AAqJD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,KAAK,CAAC;IAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,EAC7E,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,EAChC,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,EACnC,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,KAAK,GAC5B,MAAM,EAAE,CAkBV;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CA+Z7B"}
1
+ {"version":3,"file":"accessibility-empathy.d.ts","sourceRoot":"","sources":["../../src/analysis/accessibility-empathy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmCH,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EASpB,MAAM,aAAa,CAAC;AAuKrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EACnH,QAAQ,EAAE,CAAC,EAAE,EACb,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAWhC;AAwjED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAmF3E;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAkTjF;AAqJD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAmB,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,KAAK,CAAC;IAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,EAC7E,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,EAChC,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,EACnC,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,KAAK,GAC5B,MAAM,EAAE,CAkBV;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAsW7B"}
@@ -6,7 +6,7 @@
6
6
  import { weightKeyFor, barrierWeightFor, weightedSeverity } from "../visual/perceptual-transport.js";
7
7
  import { VERSION } from "../version.js";
8
8
  import { CBrowser } from "../browser.js";
9
- import { getAccessibilityPersona } from "../personas.js";
9
+ import { getAccessibilityPersona, PERSONA_ALIASES } from "../personas.js";
10
10
  import { runCognitiveJourney, isApiKeyConfigured, } from "../cognitive/index.js";
11
11
  import { calculatePerceptualScore, analyzePerceptualTransport, } from "../visual/perceptual-transport.js";
12
12
  import { buildOTCognitiveProfile, estimateCognitiveLoad, extractPageMetrics, } from "../visual/cognitive-transport.js";
@@ -2521,6 +2521,12 @@ function inferGoalType(goal) {
2521
2521
  return "navigate_to";
2522
2522
  return "complete_action";
2523
2523
  }
2524
+ /**
2525
+ * Disability shorthand -> roster name. One table for every tool now lives in
2526
+ * src/personas.ts (PERSONA_ALIASES); kept under this name for existing
2527
+ * importers. (2026-10-09)
2528
+ */
2529
+ export const EMPATHY_PERSONA_ALIASES = PERSONA_ALIASES;
2524
2530
  /**
2525
2531
  * The published WCAG list, DERIVED from the barriers rather than accumulated
2526
2532
  * alongside them.
@@ -2569,90 +2575,35 @@ export async function runEmpathyAudit(url, options) {
2569
2575
  const results = [];
2570
2576
  const allWcagViolations = new Set();
2571
2577
  const allBarriers = [];
2572
- // Map disability names to personas
2573
- // v14.2.5: Added elderly-user mapping (issue #190 - persona dropout)
2574
- const personaMap = {
2575
- "motor-tremor": "motor-impairment-tremor",
2576
- "motor": "motor-impairment-tremor",
2577
- "tremor": "motor-impairment-tremor",
2578
- "low-vision": "low-vision-magnified",
2579
- "vision": "low-vision-magnified",
2580
- "magnified": "low-vision-magnified",
2581
- "adhd": "cognitive-adhd",
2582
- "cognitive": "cognitive-adhd",
2583
- "attention": "cognitive-adhd",
2584
- "dyslexia": "dyslexic-user",
2585
- "dyslexic": "dyslexic-user",
2586
- "reading": "dyslexic-user",
2587
- "deaf": "deaf-user",
2588
- "hearing": "deaf-user",
2589
- "elderly": "elderly-low-vision",
2590
- "elderly-user": "elderly-low-vision", // v14.2.5: Added missing mapping
2591
- "elderly-low-vision": "elderly-low-vision",
2592
- "senior": "elderly-low-vision",
2593
- "old": "elderly-low-vision", // v14.2.5: Additional synonym
2594
- "color-blind": "color-blind-deuteranopia",
2595
- "colorblind": "color-blind-deuteranopia",
2596
- "deuteranopia": "color-blind-deuteranopia",
2597
- // v18.35.0: New research-backed cognitive disability personas
2598
- "autism": "autism-spectrum",
2599
- "autistic": "autism-spectrum",
2600
- "asd": "autism-spectrum",
2601
- "autism-spectrum": "autism-spectrum",
2602
- "intellectual-disability": "intellectual-disability",
2603
- "intellectual": "intellectual-disability",
2604
- "learning-disability": "intellectual-disability",
2605
- "aphasia": "aphasia-receptive",
2606
- "aphasia-receptive": "aphasia-receptive",
2607
- "wernicke": "aphasia-receptive",
2608
- "dyscalculia": "dyscalculia",
2609
- "numeracy": "dyscalculia",
2610
- };
2578
+ // Disability shorthand -> roster name (module-level so the MCP handler
2579
+ // resolves with the same table).
2580
+ const personaMap = EMPATHY_PERSONA_ALIASES;
2611
2581
  // Run audit for each disability type
2612
2582
  // v18.35.0: Accept any persona — wrap non-disability personas with default accessibility traits
2613
- for (const disability of disabilities) {
2614
- const personaName = personaMap[disability.toLowerCase()] || disability;
2583
+ // Every requested persona resolves through the one resolver BEFORE anything
2584
+ // launches. An unknown name used to fall through to a "persona" with no
2585
+ // traits that scored 0 -- the worst possible score -- with errors: [] and
2586
+ // isDisabilityPersona: true, which a dashboard reads as "the site failed
2587
+ // completely". It now throws UnknownPersonaError instead. (2026-10-09)
2588
+ try {
2589
+ const { loadAccountPersonas } = await import("../mcp-tools/account-personas.js");
2590
+ const { getSessionApiKey } = await import("../mcp-tools/base/cognitive-tools.js");
2591
+ await loadAccountPersonas(getSessionApiKey());
2592
+ }
2593
+ catch { /* falls back to disk and built-ins */ }
2594
+ const { resolvePersonaForTool } = await import("../personas.js");
2595
+ const resolvedNames = disabilities.map((d) => resolvePersonaForTool(d, personaMap, true).name);
2596
+ for (const [index, disability] of disabilities.entries()) {
2597
+ const personaName = resolvedNames[index];
2615
2598
  let persona = getAccessibilityPersona(personaName);
2616
2599
  let isDisabilityPersona = !!persona;
2617
2600
  if (!persona) {
2618
2601
  // Try to find as a regular cognitive persona and wrap it
2619
2602
  const { getAnyPersona, createCognitivePersona } = await import("../personas.js");
2620
2603
  let cognitivePersona = getAnyPersona(personaName);
2621
- // CMS custom persona fallback — fetch from API if not found locally
2622
- if (!cognitivePersona) {
2623
- try {
2624
- const { getSessionApiKey } = await import("../mcp-tools/base/cognitive-tools.js");
2625
- const apiKey = getSessionApiKey();
2626
- if (apiKey) {
2627
- const cmsUrl = process.env.CMS_URL || "http://localhost:3200";
2628
- const res = await fetch(`${cmsUrl}/api/personas`, {
2629
- headers: { "Authorization": `Bearer ${apiKey}` },
2630
- });
2631
- if (res.ok) {
2632
- const data = await res.json();
2633
- const cmsMatch = data.personas.find((p) => p.name.toLowerCase() === personaName.toLowerCase() ||
2634
- p.name.toLowerCase().replace(/\s+/g, '-') === personaName.toLowerCase() ||
2635
- p.name.toLowerCase().replace(/\s+/g, '_') === personaName.toLowerCase() ||
2636
- (p.slug && p.slug.toLowerCase() === personaName.toLowerCase()));
2637
- if (cmsMatch) {
2638
- const parsedTraits = typeof cmsMatch.traits === "string" ? JSON.parse(cmsMatch.traits) : cmsMatch.traits;
2639
- cognitivePersona = createCognitivePersona(cmsMatch.name, cmsMatch.name, parsedTraits, {
2640
- ageRange: cmsMatch.age_range || undefined,
2641
- });
2642
- // Attach CMS accessibility traits if stored
2643
- if (cmsMatch.accessibility_traits) {
2644
- const at = typeof cmsMatch.accessibility_traits === "string" ? JSON.parse(cmsMatch.accessibility_traits) : cmsMatch.accessibility_traits;
2645
- cognitivePersona.accessibilityTraits = at;
2646
- }
2647
- console.log(`[empathy_audit] Found custom CMS persona: "${cmsMatch.name}" with ${Object.keys(parsedTraits).length} traits${cmsMatch.accessibility_traits ? ' + disability modeling' : ''}`);
2648
- }
2649
- }
2650
- }
2651
- }
2652
- catch (e) {
2653
- console.debug(`[empathy_audit] CMS persona lookup failed: ${e.message}`);
2654
- }
2655
- }
2604
+ // No CMS fallback here any more: the account's CMS personas were loaded
2605
+ // into the scoped registry above, so getAnyPersona already sees them, and
2606
+ // a name nothing resolves was refused before the loop. (2026-10-09)
2656
2607
  if (cognitivePersona) {
2657
2608
  // Wrap cognitive persona as accessibility persona, inferring traits from name + cognitive traits
2658
2609
  const ct = cognitivePersona.cognitiveTraits || {};