@rubytech/create-maxy-code 0.1.125 → 0.1.127

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 (78) hide show
  1. package/dist/index.js +23 -1
  2. package/package.json +2 -2
  3. package/payload/platform/config/brand.json +20 -1
  4. package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts +33 -0
  5. package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts.map +1 -0
  6. package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js +119 -0
  7. package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js.map +1 -0
  8. package/payload/platform/lib/aeo-llms-txt-writer/src/index.ts +172 -0
  9. package/payload/platform/lib/aeo-llms-txt-writer/tsconfig.json +8 -0
  10. package/payload/platform/lib/graph-style/dist/index.d.ts +78 -0
  11. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -0
  12. package/payload/platform/lib/graph-style/dist/index.js +296 -0
  13. package/payload/platform/lib/graph-style/dist/index.js.map +1 -0
  14. package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +114 -0
  15. package/payload/platform/lib/graph-style/src/index.ts +307 -0
  16. package/payload/platform/lib/graph-style/tsconfig.json +9 -0
  17. package/payload/platform/lib/graph-style/vitest.config.ts +9 -0
  18. package/payload/platform/package-lock.json +318 -0
  19. package/payload/platform/package.json +3 -2
  20. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  21. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  22. package/payload/platform/plugins/admin/PLUGIN.md +4 -1
  23. package/payload/platform/plugins/admin/mcp/dist/index.js +73 -0
  24. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  25. package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts +34 -0
  26. package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts.map +1 -0
  27. package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js +176 -0
  28. package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js.map +1 -0
  29. package/payload/platform/plugins/admin/skills/publish-site/SKILL.md +19 -57
  30. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts +5 -38
  31. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts.map +1 -1
  32. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js +7 -114
  33. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js.map +1 -1
  34. package/payload/platform/plugins/docs/references/visitor-graph.md +4 -3
  35. package/payload/platform/plugins/graph-viewer/.claude-plugin/plugin.json +8 -0
  36. package/payload/platform/plugins/graph-viewer/PLUGIN.md +56 -0
  37. package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts +7 -0
  38. package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts.map +1 -0
  39. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +74 -0
  40. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -0
  41. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts +5 -0
  42. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts.map +1 -0
  43. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js +43 -0
  44. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js.map +1 -0
  45. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts +28 -0
  46. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts.map +1 -0
  47. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js +73 -0
  48. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js.map +1 -0
  49. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts +40 -0
  50. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts.map +1 -0
  51. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js +77 -0
  52. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js.map +1 -0
  53. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts +45 -0
  54. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -0
  55. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +170 -0
  56. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -0
  57. package/payload/platform/plugins/graph-viewer/mcp/package.json +25 -0
  58. package/payload/platform/plugins/graph-viewer/mcp/vitest.config.ts +8 -0
  59. package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +35 -0
  60. package/payload/platform/scripts/check-plugin-tools-mcp-consistency.mjs +136 -0
  61. package/payload/platform/templates/specialists/agents/content-producer.md +3 -3
  62. package/payload/server/public/assets/{admin-D6IfAzYY.js → admin-FcRHAL-3.js} +7 -7
  63. package/payload/server/public/assets/{data-BGbQyufe.js → data-Ds37mflX.js} +1 -1
  64. package/payload/server/public/assets/{graph-D-1lRTeL.js → graph-CmWRhaiS.js} +1 -1
  65. package/payload/server/public/assets/graph-labels-Ch2r00Gt.js +1 -0
  66. package/payload/server/public/assets/page-BOtNny_4.js +51 -0
  67. package/payload/server/public/assets/page-BcHhJXUt.js +1 -0
  68. package/payload/server/public/consent.css +97 -0
  69. package/payload/server/public/consent.js +259 -0
  70. package/payload/server/public/data.html +3 -3
  71. package/payload/server/public/graph.html +3 -3
  72. package/payload/server/public/index.html +4 -4
  73. package/payload/server/public/privacy.html +68 -5
  74. package/payload/server/public/v.js +53 -0
  75. package/payload/server/server.js +465 -176
  76. package/payload/server/public/assets/graph-labels-BRXJHNYE.js +0 -1
  77. package/payload/server/public/assets/page-B4oirCvn.js +0 -1
  78. package/payload/server/public/assets/page-FmJ7PIYx.js +0 -51
@@ -365,6 +365,239 @@ var require_dist = __commonJS({
365
365
  }
366
366
  });
367
367
 
368
+ // ../lib/graph-style/dist/index.js
369
+ var require_dist2 = __commonJS({
370
+ "../lib/graph-style/dist/index.js"(exports) {
371
+ "use strict";
372
+ Object.defineProperty(exports, "__esModule", { value: true });
373
+ exports.ALL_GRAPH_LABELS = exports.FALLBACK_LABEL_COLOUR = exports.GRAPH_LABEL_COLOURS = void 0;
374
+ exports.pickDisplayLabel = pickDisplayLabel2;
375
+ exports.colourForLabel = colourForLabel2;
376
+ exports.resolveNodeColour = resolveNodeColour2;
377
+ exports.formatRunTimestamp = formatRunTimestamp;
378
+ exports.formatMessageTimestamp = formatMessageTimestamp;
379
+ exports.pickShortLabel = pickShortLabel;
380
+ exports.pickDisplayName = pickDisplayName;
381
+ exports.GRAPH_LABEL_COLOURS = {
382
+ // Business identity — slate-blue family
383
+ LocalBusiness: "#4F6B8A",
384
+ Service: "#6B85A0",
385
+ PriceSpecification: "#8AA0B8",
386
+ OpeningHoursSpecification: "#A8BACE",
387
+ // Estate-agent ontology. Listing/Property share the business slate-blue
388
+ // family; Viewing/Offer are transactional events placed in a warm amber
389
+ // family distinct from Agent and from the dusty-rose Task/Project/Event
390
+ // hues so the legend reads them as a separate band.
391
+ Listing: "#3F5670",
392
+ Property: "#5C7A99",
393
+ Viewing: "#D4A574",
394
+ Offer: "#B08850",
395
+ // External business entity — schema:Organization, written by memory-write
396
+ // as the Supplier label. Warm tobacco — distinct hue from internal-business
397
+ // slates so external entities are scannable at a glance.
398
+ Organization: "#8A6B47",
399
+ // People — terracotta family (warm, human)
400
+ Person: "#B86E4A",
401
+ UserProfile: "#D08960",
402
+ Preference: "#DFA67A",
403
+ AdminUser: "#8C5230",
404
+ AccessGrant: "#66381F",
405
+ // Knowledge — moss family (organic)
406
+ KnowledgeDocument: "#6E8A5A",
407
+ // chunked WhatsApp/messaging archive parent. Sits in the
408
+ // knowledge moss family because it's structurally a document parent
409
+ // (HAS_SECTION → :Section:Conversation chunks); the conversational
410
+ // plum family is reserved for the live Conversation/Message labels.
411
+ ConversationArchive: "#82A06A",
412
+ Section: "#8AA876",
413
+ Chunk: "#A6C194",
414
+ DigitalDocument: "#5A7548",
415
+ CreativeWork: "#B5C9A2",
416
+ Question: "#C7D6B5",
417
+ FAQPage: "#95B385",
418
+ DefinedTerm: "#76906A",
419
+ Review: "#3F5A2E",
420
+ ImageObject: "#A0BB8C",
421
+ // Conversational — muted plum/lavender family.
422
+ // Pairwise-distinctness invariants enforced by the UI test
423
+ // graph-labels.test.ts; UserMessage matches Preference (Person/family cue,
424
+ // the one intentional duplicate in the registry).
425
+ Conversation: "#6B5A85",
426
+ AdminConversation: "#4F4070",
427
+ PublicConversation: "#8A75A8",
428
+ Message: "#6B7280",
429
+ UserMessage: "#DFA67A",
430
+ AssistantMessage: "#C9A876",
431
+ ToolCall: "#B5ABCB",
432
+ // Tasks / projects / events — dusty rose family
433
+ Task: "#B0617A",
434
+ Project: "#8E4A60",
435
+ Event: "#C68095",
436
+ // Workflows — muted teal
437
+ Workflow: "#3A6F77",
438
+ WorkflowStep: "#5A8E96",
439
+ WorkflowRun: "#7AAAB2",
440
+ StepResult: "#9CC4CB",
441
+ // Email — muted moss, hue-adjacent to Knowledge but lower-saturation
442
+ // and warmer; never co-rendered with KnowledgeDocument.
443
+ Email: "#6F7F4A",
444
+ EmailAccount: "#91A063",
445
+ // Public-agent projection — burnished bronze sits between
446
+ // people-terracotta and email-moss but shares neither hue.
447
+ Agent: "#B8893D"
448
+ };
449
+ exports.FALLBACK_LABEL_COLOUR = "#94A3B8";
450
+ exports.ALL_GRAPH_LABELS = Object.freeze(Object.keys(exports.GRAPH_LABEL_COLOURS));
451
+ function pickDisplayLabel2(labels) {
452
+ for (let i = 1; i < labels.length; i++) {
453
+ if (Object.prototype.hasOwnProperty.call(exports.GRAPH_LABEL_COLOURS, labels[i])) {
454
+ return labels[i];
455
+ }
456
+ }
457
+ return labels[0] ?? null;
458
+ }
459
+ function colourForLabel2(label) {
460
+ return exports.GRAPH_LABEL_COLOURS[label] ?? exports.FALLBACK_LABEL_COLOUR;
461
+ }
462
+ function resolveNodeColour2(labels) {
463
+ const displayLabel = pickDisplayLabel2(labels);
464
+ const colour = displayLabel ? colourForLabel2(displayLabel) : exports.FALLBACK_LABEL_COLOUR;
465
+ let driftCandidates = 0;
466
+ for (let i = 1; i < labels.length; i++) {
467
+ if (Object.prototype.hasOwnProperty.call(exports.GRAPH_LABEL_COLOURS, labels[i])) {
468
+ driftCandidates++;
469
+ }
470
+ }
471
+ return { displayLabel, colour, driftCandidates };
472
+ }
473
+ var ISO_MINUTE_RE = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})/;
474
+ var ISO_SECOND_RE = /^\d{4}-\d{2}-\d{2}T(\d{2}:\d{2}:\d{2})/;
475
+ function formatRunTimestamp(iso) {
476
+ const m = iso.match(ISO_MINUTE_RE);
477
+ if (!m)
478
+ return null;
479
+ return `${m[1]} ${m[2]}`;
480
+ }
481
+ function formatMessageTimestamp(iso) {
482
+ const m = iso.match(ISO_SECOND_RE);
483
+ if (!m)
484
+ return null;
485
+ return m[1];
486
+ }
487
+ function pickShortLabel(node) {
488
+ const props = node.properties;
489
+ const primaryLabel = node.labels[0];
490
+ if (primaryLabel === "ToolCall") {
491
+ const v = props.toolName;
492
+ if (typeof v === "string" && v.length > 0) {
493
+ return v.length > 24 ? v.slice(0, 24) + "\u2026" : v;
494
+ }
495
+ } else if (primaryLabel === "WorkflowRun") {
496
+ const v = props.startedAt;
497
+ if (typeof v === "string" && v.length > 0) {
498
+ const ts = formatRunTimestamp(v);
499
+ if (ts)
500
+ return ts;
501
+ }
502
+ } else if (primaryLabel === "WorkflowStep") {
503
+ const v = props.label;
504
+ if (typeof v === "string" && v.length > 0) {
505
+ return v.length > 24 ? v.slice(0, 24) + "\u2026" : v;
506
+ }
507
+ } else if (primaryLabel === "Person") {
508
+ const given = typeof props.givenName === "string" ? props.givenName : "";
509
+ const family = typeof props.familyName === "string" ? props.familyName : "";
510
+ const full = [given, family].filter((s) => s.length > 0).join(" ");
511
+ if (full.length > 0) {
512
+ return full.length > 24 ? full.slice(0, 24) + "\u2026" : full;
513
+ }
514
+ } else if (primaryLabel === "Agent") {
515
+ const dn = typeof props.displayName === "string" ? props.displayName : "";
516
+ const slug = typeof props.slug === "string" ? props.slug : "";
517
+ const v = dn.length > 0 ? dn : slug;
518
+ if (v.length > 0) {
519
+ return v.length > 24 ? v.slice(0, 24) + "\u2026" : v;
520
+ }
521
+ } else if (primaryLabel === "Message") {
522
+ const role = typeof props.role === "string" ? props.role : "";
523
+ const roleShort = role === "user" ? "user" : role === "assistant" ? "asst" : role === "system" ? "sys" : role === "tool" ? "tool" : null;
524
+ const createdAt = typeof props.createdAt === "string" ? props.createdAt : "";
525
+ if (roleShort && createdAt) {
526
+ const ts = formatMessageTimestamp(createdAt);
527
+ if (ts)
528
+ return `${roleShort} ${ts}`;
529
+ }
530
+ } else if (primaryLabel === "ConversationArchive") {
531
+ const t = typeof props.title === "string" ? props.title : "";
532
+ if (t.length > 0) {
533
+ return t.length > 24 ? t.slice(0, 24) + "\u2026" : t;
534
+ }
535
+ return primaryLabel;
536
+ }
537
+ for (const k of ["name", "title", "summary", "givenName", "subject", "text"]) {
538
+ const v = props[k];
539
+ if (typeof v === "string" && v.length > 0) {
540
+ return v.length > 24 ? v.slice(0, 24) + "\u2026" : v;
541
+ }
542
+ }
543
+ if (primaryLabel === "Conversation") {
544
+ const id = typeof props.conversationId === "string" ? props.conversationId : null;
545
+ if (id)
546
+ return `Conv ${id.slice(0, 8)}`;
547
+ }
548
+ return primaryLabel ?? "\u2026";
549
+ }
550
+ function pickDisplayName(node) {
551
+ const props = node.properties;
552
+ const primaryLabel = node.labels[0];
553
+ if (primaryLabel === "ToolCall") {
554
+ const v = props.toolName;
555
+ if (typeof v === "string" && v.length > 0)
556
+ return v;
557
+ } else if (primaryLabel === "WorkflowRun") {
558
+ const v = props.startedAt;
559
+ if (typeof v === "string" && v.length > 0) {
560
+ const ts = formatRunTimestamp(v);
561
+ if (ts)
562
+ return ts;
563
+ }
564
+ } else if (primaryLabel === "WorkflowStep") {
565
+ const v = props.label;
566
+ if (typeof v === "string" && v.length > 0)
567
+ return v;
568
+ } else if (primaryLabel === "Person") {
569
+ const given = typeof props.givenName === "string" ? props.givenName : "";
570
+ const family = typeof props.familyName === "string" ? props.familyName : "";
571
+ const full = [given, family].filter((s) => s.length > 0).join(" ");
572
+ if (full.length > 0)
573
+ return full;
574
+ } else if (primaryLabel === "Agent") {
575
+ const dn = typeof props.displayName === "string" ? props.displayName : "";
576
+ const slug = typeof props.slug === "string" ? props.slug : "";
577
+ const v = dn.length > 0 ? dn : slug;
578
+ if (v.length > 0)
579
+ return v;
580
+ } else if (primaryLabel === "ConversationArchive") {
581
+ const t = typeof props.title === "string" ? props.title : "";
582
+ if (t.length > 0)
583
+ return t;
584
+ return primaryLabel;
585
+ }
586
+ for (const k of ["name", "title", "summary", "givenName", "subject", "text"]) {
587
+ const v = props[k];
588
+ if (typeof v === "string" && v.length > 0)
589
+ return v;
590
+ }
591
+ if (primaryLabel === "Conversation") {
592
+ const id = typeof props.conversationId === "string" ? props.conversationId : null;
593
+ if (id)
594
+ return `Conv ${id.slice(0, 8)}`;
595
+ }
596
+ return primaryLabel ?? "\u2026";
597
+ }
598
+ }
599
+ });
600
+
368
601
  // node_modules/hono/dist/utils/mime.js
369
602
  var getMimeType = (filename, mimes = baseMimes) => {
370
603
  const regexp = /\.([a-zA-Z0-9]+?)$/;
@@ -9943,97 +10176,8 @@ var graph_search_default = app19;
9943
10176
  import neo4j from "neo4j-driver";
9944
10177
 
9945
10178
  // app/lib/graph-labels.ts
9946
- var GRAPH_LABEL_COLOURS = {
9947
- // Business identity slate-blue family
9948
- LocalBusiness: "#4F6B8A",
9949
- Service: "#6B85A0",
9950
- PriceSpecification: "#8AA0B8",
9951
- OpeningHoursSpecification: "#A8BACE",
9952
- // Estate-agent ontology (Task 358). :Listing and :Property
9953
- // share the business slate-blue family (they describe a business's
9954
- // inventory). :Viewing and :Offer are transactional events — placed in
9955
- // a warm amber family distinct from Agent (#B8893D) and the dusty-rose
9956
- // Task/Project/Event hues so the legend reads them as a separate band.
9957
- Listing: "#3F5670",
9958
- Property: "#5C7A99",
9959
- Viewing: "#D4A574",
9960
- Offer: "#B08850",
9961
- // External business entity — schema:Organization, written by memory-write
9962
- // as the Supplier label (per platform/plugins/memory/references/schema-base.md).
9963
- // Warm tobacco — distinct hue from internal-business slates so external
9964
- // entities are scannable at a glance.
9965
- Organization: "#8A6B47",
9966
- // People — terracotta family (warm, human)
9967
- Person: "#B86E4A",
9968
- UserProfile: "#D08960",
9969
- Preference: "#DFA67A",
9970
- AdminUser: "#8C5230",
9971
- AccessGrant: "#66381F",
9972
- // Knowledge — moss family (organic)
9973
- KnowledgeDocument: "#6E8A5A",
9974
- // chunked WhatsApp/messaging archive parent. Sits in the
9975
- // knowledge moss family because it's structurally a document parent
9976
- // (HAS_SECTION → :Section:Conversation chunks); the conversational
9977
- // plum family is reserved for the live Conversation/Message labels.
9978
- ConversationArchive: "#82A06A",
9979
- Section: "#8AA876",
9980
- Chunk: "#A6C194",
9981
- DigitalDocument: "#5A7548",
9982
- CreativeWork: "#B5C9A2",
9983
- Question: "#C7D6B5",
9984
- FAQPage: "#95B385",
9985
- DefinedTerm: "#76906A",
9986
- Review: "#3F5A2E",
9987
- ImageObject: "#A0BB8C",
9988
- // Conversational — muted plum/lavender family
9989
- //
9990
- // Pairwise-distinctness invariants (carried forward
9991
- // palette):
9992
- // - Four sublabelled conversation/message labels (AdminConversation,
9993
- // PublicConversation, UserMessage, AssistantMessage) are pairwise
9994
- // distinct on canvas.
9995
- // - UserMessage matches Preference (Person/family cue: a user-authored
9996
- // message inherits the person family colour). This is the one
9997
- // intentional duplicate in the registry — see the
9998
- // `INTENTIONAL_DUPLICATES` set in `graph-labels.test.ts`.
9999
- // - Conversation base distinct from both AdminConversation and
10000
- // PublicConversation so base-Conversation rows don't visually
10001
- // collide with the sublabelled split.
10002
- // - Message base distinct from PublicConversation (`#A78BFA`
10003
- // collision class).
10004
- Conversation: "#6B5A85",
10005
- AdminConversation: "#4F4070",
10006
- PublicConversation: "#8A75A8",
10007
- Message: "#6B7280",
10008
- UserMessage: "#DFA67A",
10009
- AssistantMessage: "#C9A876",
10010
- ToolCall: "#B5ABCB",
10011
- // Tasks / projects / events — dusty rose family
10012
- Task: "#B0617A",
10013
- Project: "#8E4A60",
10014
- Event: "#C68095",
10015
- // Workflows — muted teal
10016
- Workflow: "#3A6F77",
10017
- WorkflowStep: "#5A8E96",
10018
- WorkflowRun: "#7AAAB2",
10019
- StepResult: "#9CC4CB",
10020
- // Email — muted moss (hue-adjacent to Knowledge but lower-saturation
10021
- // and warmer; never co-rendered with KnowledgeDocument so visual
10022
- // confusion doesn't apply, but kept distinguishable for the legend)
10023
- Email: "#6F7F4A",
10024
- EmailAccount: "#91A063",
10025
- // Public-agent projection — burnished bronze sits between
10026
- // people-terracotta and email-moss but shares neither hue, signalling
10027
- // "operator-defined persona that traverses to KnowledgeDocuments,
10028
- // Conversations, and other entities" without colliding with any existing
10029
- // family. Pairs with the four owned KnowledgeDocument projections rendered
10030
- // in the moss family — visual contrast cues the operator that the agent
10031
- // node is a different kind of thing from its IDENTITY/SOUL/KNOWLEDGE docs.
10032
- Agent: "#B8893D"
10033
- };
10034
- var ALL_GRAPH_LABELS = Object.freeze(
10035
- Object.keys(GRAPH_LABEL_COLOURS)
10036
- );
10179
+ var import_dist2 = __toESM(require_dist2(), 1);
10180
+ var import_dist3 = __toESM(require_dist2(), 1);
10037
10181
  var HIDDEN_BY_DEFAULT_LABELS = Object.freeze(
10038
10182
  /* @__PURE__ */ new Set(["Chunk", "GraphPreference"])
10039
10183
  );
@@ -10089,7 +10233,7 @@ var EXCLUDED_EDGE_TYPES = Object.freeze(
10089
10233
  ])
10090
10234
  );
10091
10235
  function isKnownLabel(label) {
10092
- return Object.prototype.hasOwnProperty.call(GRAPH_LABEL_COLOURS, label);
10236
+ return Object.prototype.hasOwnProperty.call(import_dist3.GRAPH_LABEL_COLOURS, label);
10093
10237
  }
10094
10238
  function isHiddenByDefault(label) {
10095
10239
  return HIDDEN_BY_DEFAULT_LABELS.has(label);
@@ -12252,11 +12396,161 @@ function verifyVisitorToken(token, now = Date.now()) {
12252
12396
  var VISITOR_COOKIE_NAME = "mxy_v";
12253
12397
  var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
12254
12398
 
12399
+ // app/lib/brand-config.ts
12400
+ import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
12401
+ import { join as join14 } from "path";
12402
+ var cached2 = null;
12403
+ var cachedAttempted = false;
12404
+ function readBrandConfig() {
12405
+ if (cachedAttempted) return cached2;
12406
+ cachedAttempted = true;
12407
+ const platformRoot = process.env.MAXY_PLATFORM_ROOT;
12408
+ if (!platformRoot) return null;
12409
+ const brandPath = join14(platformRoot, "config", "brand.json");
12410
+ if (!existsSync22(brandPath)) return null;
12411
+ try {
12412
+ cached2 = JSON.parse(readFileSync19(brandPath, "utf-8"));
12413
+ return cached2;
12414
+ } catch {
12415
+ return null;
12416
+ }
12417
+ }
12418
+
12419
+ // server/routes/visitor-consent.ts
12420
+ var app35 = new Hono();
12421
+ var CONSENT_COOKIE_NAME = "mxy_consent";
12422
+ var CONSENT_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
12423
+ var DEFAULT_CONSENT_COPY = {
12424
+ title: "Before you browse",
12425
+ body: "We use a small first-party script to count page views, scrolls and clicks on this listing. Nothing is shared with third parties. You can change your mind at any time.",
12426
+ accept: "Accept",
12427
+ reject: "Reject",
12428
+ manage: "Manage cookies",
12429
+ policyLinkText: "Read the cookie policy",
12430
+ policyHref: "/privacy.html#cookies"
12431
+ };
12432
+ var DEFAULT_CONSENT_PALETTE = {
12433
+ bg: "#FFFFFF",
12434
+ fg: "#1F1F1F",
12435
+ accent: "#1F1F1F",
12436
+ accentFg: "#FFFFFF",
12437
+ border: "#E5E5E2"
12438
+ };
12439
+ function getOrigin(c) {
12440
+ return c.req.header("origin") ?? c.req.header("referer") ?? "";
12441
+ }
12442
+ function setCorsHeaders(c, origin) {
12443
+ if (origin) c.header("Access-Control-Allow-Origin", origin);
12444
+ c.header("Access-Control-Allow-Credentials", "true");
12445
+ c.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
12446
+ c.header("Access-Control-Allow-Headers", "Content-Type");
12447
+ c.header("Vary", "Origin");
12448
+ }
12449
+ function parseConsent(body) {
12450
+ if (!body || typeof body !== "object") return null;
12451
+ const b = body;
12452
+ if (b.decision !== "yes" && b.decision !== "no") return null;
12453
+ const v = typeof b.v === "string" && b.v.length > 0 && b.v.length <= 2048 ? b.v : null;
12454
+ return { decision: b.decision, v };
12455
+ }
12456
+ function siteSlugFromReferer(referer) {
12457
+ try {
12458
+ const u = new URL(referer);
12459
+ const parts = u.pathname.split("/").filter(Boolean);
12460
+ return parts[parts.length - 1]?.slice(0, 120) ?? "";
12461
+ } catch {
12462
+ return "";
12463
+ }
12464
+ }
12465
+ app35.options("/consent", (c) => {
12466
+ const origin = getOrigin(c);
12467
+ setCorsHeaders(c, origin);
12468
+ return c.body(null, 204);
12469
+ });
12470
+ app35.options("/brand-config", (c) => {
12471
+ const origin = getOrigin(c);
12472
+ setCorsHeaders(c, origin);
12473
+ return c.body(null, 204);
12474
+ });
12475
+ app35.post("/consent", async (c) => {
12476
+ const origin = getOrigin(c);
12477
+ setCorsHeaders(c, origin);
12478
+ let raw;
12479
+ try {
12480
+ raw = await c.req.json();
12481
+ } catch {
12482
+ console.error("[consent-error] reason=bad-json");
12483
+ return c.text("Bad Request", 400);
12484
+ }
12485
+ const parsed = parseConsent(raw);
12486
+ if (!parsed) {
12487
+ console.error("[consent-error] reason=bad-body");
12488
+ return c.text("Bad Request", 400);
12489
+ }
12490
+ const referer = c.req.header("referer") ?? "";
12491
+ const site = siteSlugFromReferer(referer);
12492
+ const brand = readBrandConfig();
12493
+ const brandName = brand?.productName ?? "-";
12494
+ const cookies = [];
12495
+ cookies.push(
12496
+ `${CONSENT_COOKIE_NAME}=${parsed.decision}; Max-Age=${CONSENT_COOKIE_MAX_AGE_SECONDS}; Path=/; SameSite=Lax; Secure`
12497
+ );
12498
+ let tokenBound = false;
12499
+ if (parsed.decision === "yes" && parsed.v) {
12500
+ const verify = verifyVisitorToken(parsed.v);
12501
+ if (verify.ok) {
12502
+ cookies.push(
12503
+ `${VISITOR_COOKIE_NAME}=${parsed.v}; Max-Age=${VISITOR_COOKIE_MAX_AGE_SECONDS}; Path=/; SameSite=Lax; Secure; HttpOnly`
12504
+ );
12505
+ tokenBound = true;
12506
+ console.log(`[token-bind] via=consent site=${site} person=${verify.personId}`);
12507
+ } else {
12508
+ console.error(`[token-bind] reject reason=${verify.reason} via=consent site=${site}`);
12509
+ }
12510
+ }
12511
+ for (const cookie of cookies) c.header("Set-Cookie", cookie, { append: true });
12512
+ console.log(`[consent] ${parsed.decision} site=${site} brand=${brandName} tokenBound=${tokenBound}`);
12513
+ return c.body(null, 204);
12514
+ });
12515
+ app35.get("/brand-config", (c) => {
12516
+ const origin = getOrigin(c);
12517
+ setCorsHeaders(c, origin);
12518
+ const brand = readBrandConfig();
12519
+ const consent = brand?.consent ?? {};
12520
+ const copy = { ...DEFAULT_CONSENT_COPY, ...consent.copy ?? {} };
12521
+ const palette = { ...DEFAULT_CONSENT_PALETTE, ...consent.palette ?? {} };
12522
+ c.header("Cache-Control", "public, max-age=300");
12523
+ return c.json({ consent: { copy, palette } });
12524
+ });
12525
+ var visitor_consent_default = app35;
12526
+
12255
12527
  // server/routes/listings.ts
12528
+ function getCookie(headerValue, name) {
12529
+ if (!headerValue) return null;
12530
+ for (const part of headerValue.split(";")) {
12531
+ const [k, ...rest] = part.trim().split("=");
12532
+ if (k === name) return rest.join("=");
12533
+ }
12534
+ return null;
12535
+ }
12536
+ function appendConsentParams(pageUrl, token) {
12537
+ try {
12538
+ const u = new URL(pageUrl);
12539
+ u.searchParams.set("consent", "needed");
12540
+ u.searchParams.set("v", token);
12541
+ return u.toString();
12542
+ } catch {
12543
+ const hashIdx = pageUrl.indexOf("#");
12544
+ const hash = hashIdx >= 0 ? pageUrl.slice(hashIdx) : "";
12545
+ const base = hashIdx >= 0 ? pageUrl.slice(0, hashIdx) : pageUrl;
12546
+ const sep4 = base.indexOf("?") >= 0 ? "&" : "?";
12547
+ return base + sep4 + `consent=needed&v=${encodeURIComponent(token)}` + hash;
12548
+ }
12549
+ }
12256
12550
  var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
12257
12551
  var CHAT_SESSION_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/;
12258
- var app35 = new Hono();
12259
- app35.get("/:slug/click", async (c) => {
12552
+ var app36 = new Hono();
12553
+ app36.get("/:slug/click", async (c) => {
12260
12554
  const slug = c.req.param("slug") ?? "";
12261
12555
  const rawSession = c.req.query("session") ?? "";
12262
12556
  const sessionKey = CHAT_SESSION_KEY_RE.test(rawSession) ? rawSession : "invalid";
@@ -12297,45 +12591,33 @@ app35.get("/:slug/click", async (c) => {
12297
12591
  console.error(`[property-card-click] reject reason=listing-not-found slug=${slug} sessionKey=${sessionKey} accountId=${account.accountId} status=404`);
12298
12592
  return c.text("Not Found", 404);
12299
12593
  }
12594
+ let redirectUrl = pageUrl;
12595
+ const consentCookie = getCookie(c.req.header("cookie"), CONSENT_COOKIE_NAME);
12596
+ const consented = consentCookie === "yes";
12300
12597
  if (tokenParam) {
12301
12598
  const verify = verifyVisitorToken(tokenParam);
12302
12599
  if (verify.ok) {
12303
- c.header(
12304
- "Set-Cookie",
12305
- `${VISITOR_COOKIE_NAME}=${tokenParam}; Max-Age=${VISITOR_COOKIE_MAX_AGE_SECONDS}; Path=/; SameSite=Lax; Secure; HttpOnly`
12306
- );
12307
- console.log(`[token-bind] sessionKey=${sessionKey} person=${verify.personId} listingSlug=${slug}`);
12600
+ if (consented) {
12601
+ c.header(
12602
+ "Set-Cookie",
12603
+ `${VISITOR_COOKIE_NAME}=${tokenParam}; Max-Age=${VISITOR_COOKIE_MAX_AGE_SECONDS}; Path=/; SameSite=Lax; Secure; HttpOnly`
12604
+ );
12605
+ console.log(`[token-bind] sessionKey=${sessionKey} person=${verify.personId} listingSlug=${slug}`);
12606
+ } else {
12607
+ redirectUrl = appendConsentParams(pageUrl, tokenParam);
12608
+ console.error(`[token-bind] reject reason=no-consent sessionKey=${sessionKey} person=${verify.personId} listingSlug=${slug}`);
12609
+ }
12308
12610
  } else {
12309
12611
  console.error(`[token-bind] reject reason=${verify.reason} sessionKey=${sessionKey} listingSlug=${slug}`);
12310
12612
  }
12311
12613
  }
12312
- console.log(`[property-card-click] sessionKey=${sessionKey} listingSlug=${slug} ts=${(/* @__PURE__ */ new Date()).toISOString()}`);
12313
- return c.redirect(pageUrl, 302);
12614
+ console.log(`[property-card-click] sessionKey=${sessionKey} listingSlug=${slug} consent=${consentCookie ?? "absent"} ts=${(/* @__PURE__ */ new Date()).toISOString()}`);
12615
+ return c.redirect(redirectUrl, 302);
12314
12616
  });
12315
- var listings_default = app35;
12316
-
12317
- // app/lib/brand-config.ts
12318
- import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
12319
- import { join as join14 } from "path";
12320
- var cached2 = null;
12321
- var cachedAttempted = false;
12322
- function readBrandConfig() {
12323
- if (cachedAttempted) return cached2;
12324
- cachedAttempted = true;
12325
- const platformRoot = process.env.MAXY_PLATFORM_ROOT;
12326
- if (!platformRoot) return null;
12327
- const brandPath = join14(platformRoot, "config", "brand.json");
12328
- if (!existsSync22(brandPath)) return null;
12329
- try {
12330
- cached2 = JSON.parse(readFileSync19(brandPath, "utf-8"));
12331
- return cached2;
12332
- } catch {
12333
- return null;
12334
- }
12335
- }
12617
+ var listings_default = app36;
12336
12618
 
12337
12619
  // server/routes/visitor-event.ts
12338
- var app36 = new Hono();
12620
+ var app37 = new Hono();
12339
12621
  var BOT_UA_RE = /\b(bot|crawl|spider|slurp|headlesschrome|phantomjs|googlebot|bingbot|yandex|baiduspider|ahrefsbot|semrushbot|mj12bot|dotbot|petalbot)\b/i;
12340
12622
  var buckets = /* @__PURE__ */ new Map();
12341
12623
  var RATE_LIMIT = 60;
@@ -12368,7 +12650,7 @@ function parseEvent(body) {
12368
12650
  if (typeof e.ts !== "number" || !Number.isFinite(e.ts)) return null;
12369
12651
  return e;
12370
12652
  }
12371
- function getCookie(c, name) {
12653
+ function getCookie2(c, name) {
12372
12654
  const raw = c.req.header("cookie");
12373
12655
  if (!raw) return null;
12374
12656
  for (const part of raw.split(";")) {
@@ -12377,10 +12659,10 @@ function getCookie(c, name) {
12377
12659
  }
12378
12660
  return null;
12379
12661
  }
12380
- function getOrigin(c) {
12662
+ function getOrigin2(c) {
12381
12663
  return c.req.header("origin") ?? c.req.header("referer") ?? "";
12382
12664
  }
12383
- function setCorsHeaders(c, origin) {
12665
+ function setCorsHeaders2(c, origin) {
12384
12666
  if (origin) c.header("Access-Control-Allow-Origin", origin);
12385
12667
  c.header("Access-Control-Allow-Credentials", "true");
12386
12668
  c.header("Access-Control-Allow-Methods", "POST, OPTIONS");
@@ -12402,14 +12684,14 @@ function originAllowed(origin, allowlist) {
12402
12684
  return false;
12403
12685
  }
12404
12686
  }
12405
- app36.options("/event", (c) => {
12406
- const origin = getOrigin(c);
12407
- setCorsHeaders(c, origin);
12687
+ app37.options("/event", (c) => {
12688
+ const origin = getOrigin2(c);
12689
+ setCorsHeaders2(c, origin);
12408
12690
  return c.body(null, 204);
12409
12691
  });
12410
- app36.post("/event", async (c) => {
12411
- const origin = getOrigin(c);
12412
- setCorsHeaders(c, origin);
12692
+ app37.post("/event", async (c) => {
12693
+ const origin = getOrigin2(c);
12694
+ setCorsHeaders2(c, origin);
12413
12695
  const ua = c.req.header("user-agent") ?? "";
12414
12696
  if (BOT_UA_RE.test(ua)) {
12415
12697
  console.error(`[v-event-error] reason=bot-ua ua=${JSON.stringify(ua.slice(0, 120))}`);
@@ -12432,6 +12714,11 @@ app36.post("/event", async (c) => {
12432
12714
  console.error(`[v-event-error] reason=origin-mismatch origin=${JSON.stringify(origin)} ip=${ip}`);
12433
12715
  return c.text("Forbidden", 403);
12434
12716
  }
12717
+ const consentCookie = getCookie2(c, CONSENT_COOKIE_NAME);
12718
+ if (consentCookie !== "yes") {
12719
+ console.log(`[v-event] drop reason=no-consent consent=${consentCookie ?? "absent"} ip=${ip} origin=${JSON.stringify(origin)}`);
12720
+ return c.body(null, 204);
12721
+ }
12435
12722
  let raw;
12436
12723
  try {
12437
12724
  raw = await c.req.json();
@@ -12444,7 +12731,7 @@ app36.post("/event", async (c) => {
12444
12731
  console.error(`[v-event-error] reason=bad-event ip=${ip}`);
12445
12732
  return c.text("Bad Request", 400);
12446
12733
  }
12447
- const cookieVal = getCookie(c, VISITOR_COOKIE_NAME);
12734
+ const cookieVal = getCookie2(c, VISITOR_COOKIE_NAME);
12448
12735
  let personId = null;
12449
12736
  if (cookieVal) {
12450
12737
  const v = verifyVisitorToken(cookieVal);
@@ -12474,7 +12761,7 @@ app36.post("/event", async (c) => {
12474
12761
  });
12475
12762
  }
12476
12763
  console.log(
12477
- `[visitor-event] type=${event.type} sessionId=${event.sid} visitorId=${event.vid} personId=${personId ?? "-"} path=${event.path ?? ""} label=${event.label ?? "-"} depth=${event.depth ?? "-"} ms=${event.ms ?? "-"}`
12764
+ `[visitor-event] type=${event.type} sessionId=${event.sid} visitorId=${event.vid} personId=${personId ?? "-"} consent=${consentCookie} path=${event.path ?? ""} label=${event.label ?? "-"} depth=${event.depth ?? "-"} ms=${event.ms ?? "-"}`
12478
12765
  );
12479
12766
  return c.body(null, 204);
12480
12767
  });
@@ -12607,7 +12894,7 @@ async function writeEvent(opts) {
12607
12894
  );
12608
12895
  }
12609
12896
  }
12610
- var visitor_event_default = app36;
12897
+ var visitor_event_default = app37;
12611
12898
 
12612
12899
  // app/lib/graph-health.ts
12613
12900
  var HOUR_MS = 60 * 60 * 1e3;
@@ -13044,9 +13331,9 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
13044
13331
  function isPublicHost(host) {
13045
13332
  return host.startsWith("public.") || aliasDomains.has(host);
13046
13333
  }
13047
- var app37 = new Hono();
13048
- app37.use("*", clientIpMiddleware);
13049
- app37.use("*", async (c, next) => {
13334
+ var app38 = new Hono();
13335
+ app38.use("*", clientIpMiddleware);
13336
+ app38.use("*", async (c, next) => {
13050
13337
  await next();
13051
13338
  c.header("X-Content-Type-Options", "nosniff");
13052
13339
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
@@ -13056,7 +13343,7 @@ app37.use("*", async (c, next) => {
13056
13343
  );
13057
13344
  });
13058
13345
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
13059
- app37.use("*", async (c, next) => {
13346
+ app38.use("*", async (c, next) => {
13060
13347
  if (!HTTP_LOG_PATHS.has(c.req.path)) {
13061
13348
  await next();
13062
13349
  return;
@@ -13089,7 +13376,7 @@ var PUBLIC_ALLOWED_PREFIXES = [
13089
13376
  "/sites/"
13090
13377
  ];
13091
13378
  var PUBLIC_ALLOWED_EXACT = ["/favicon.ico"];
13092
- app37.use("*", async (c, next) => {
13379
+ app38.use("*", async (c, next) => {
13093
13380
  const host = (c.req.header("host") ?? "").split(":")[0];
13094
13381
  if (!isPublicHost(host)) {
13095
13382
  await next();
@@ -13129,7 +13416,7 @@ function resolveRemoteAuthOpts() {
13129
13416
  return brandLoginOpts;
13130
13417
  }
13131
13418
  var MAX_LOGIN_BODY = 8 * 1024;
13132
- app37.post("/__remote-auth/login", async (c) => {
13419
+ app38.post("/__remote-auth/login", async (c) => {
13133
13420
  const client = clientFrom(c);
13134
13421
  const clientIp = client.ip || "unknown";
13135
13422
  if (!requestIsTlsTerminated(c)) {
@@ -13174,7 +13461,7 @@ app37.post("/__remote-auth/login", async (c) => {
13174
13461
  }
13175
13462
  });
13176
13463
  });
13177
- app37.get("/__remote-auth/logout", (c) => {
13464
+ app38.get("/__remote-auth/logout", (c) => {
13178
13465
  const client = clientFrom(c);
13179
13466
  const clientIp = client.ip || "unknown";
13180
13467
  console.error(`[remote-auth] logout ip=${clientIp}`);
@@ -13187,7 +13474,7 @@ app37.get("/__remote-auth/logout", (c) => {
13187
13474
  }
13188
13475
  });
13189
13476
  });
13190
- app37.post("/__remote-auth/change-password", async (c) => {
13477
+ app38.post("/__remote-auth/change-password", async (c) => {
13191
13478
  const client = clientFrom(c);
13192
13479
  const clientIp = client.ip || "unknown";
13193
13480
  const rateLimited = checkRateLimit(client);
@@ -13238,13 +13525,13 @@ app37.post("/__remote-auth/change-password", async (c) => {
13238
13525
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "change", changeError: "Failed to save password", redirect }), 200);
13239
13526
  }
13240
13527
  });
13241
- app37.get("/__remote-auth/setup", (c) => {
13528
+ app38.get("/__remote-auth/setup", (c) => {
13242
13529
  if (isRemoteAuthConfigured()) {
13243
13530
  return c.redirect("/");
13244
13531
  }
13245
13532
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup" }), 200);
13246
13533
  });
13247
- app37.post("/__remote-auth/set-initial-password", async (c) => {
13534
+ app38.post("/__remote-auth/set-initial-password", async (c) => {
13248
13535
  if (isRemoteAuthConfigured()) {
13249
13536
  return c.redirect("/");
13250
13537
  }
@@ -13282,10 +13569,10 @@ app37.post("/__remote-auth/set-initial-password", async (c) => {
13282
13569
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
13283
13570
  }
13284
13571
  });
13285
- app37.get("/api/remote-auth/status", (c) => {
13572
+ app38.get("/api/remote-auth/status", (c) => {
13286
13573
  return c.json({ configured: isRemoteAuthConfigured() });
13287
13574
  });
13288
- app37.post("/api/remote-auth/set-password", async (c) => {
13575
+ app38.post("/api/remote-auth/set-password", async (c) => {
13289
13576
  let body;
13290
13577
  try {
13291
13578
  body = await c.req.json();
@@ -13316,9 +13603,9 @@ app37.post("/api/remote-auth/set-password", async (c) => {
13316
13603
  return c.json({ error: "Failed to save password" }, 500);
13317
13604
  }
13318
13605
  });
13319
- app37.route("/api/_client-error", client_error_default);
13606
+ app38.route("/api/_client-error", client_error_default);
13320
13607
  console.log("[client-error-route] mounted");
13321
- app37.use("*", async (c, next) => {
13608
+ app38.use("*", async (c, next) => {
13322
13609
  const host = (c.req.header("host") ?? "").split(":")[0];
13323
13610
  const path2 = c.req.path;
13324
13611
  if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/")) {
@@ -13351,11 +13638,11 @@ app37.use("*", async (c, next) => {
13351
13638
  console.error(`[remote-auth] login required ip=${clientIp} path=${path2} ${disambig}`);
13352
13639
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
13353
13640
  });
13354
- app37.route("/api/health", health_default);
13355
- app37.route("/api/chat", chat_default);
13356
- app37.route("/api/whatsapp", whatsapp_default);
13357
- app37.route("/api/onboarding", onboarding_default);
13358
- app37.route("/api/admin", admin_default);
13641
+ app38.route("/api/health", health_default);
13642
+ app38.route("/api/chat", chat_default);
13643
+ app38.route("/api/whatsapp", whatsapp_default);
13644
+ app38.route("/api/onboarding", onboarding_default);
13645
+ app38.route("/api/admin", admin_default);
13359
13646
  var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
13360
13647
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
13361
13648
  var IMAGE_MIME = {
@@ -13367,7 +13654,7 @@ var IMAGE_MIME = {
13367
13654
  ".svg": "image/svg+xml",
13368
13655
  ".ico": "image/x-icon"
13369
13656
  };
13370
- app37.get("/agent-assets/:slug/:filename", (c) => {
13657
+ app38.get("/agent-assets/:slug/:filename", (c) => {
13371
13658
  const slug = c.req.param("slug");
13372
13659
  const filename = c.req.param("filename");
13373
13660
  if (!SAFE_SLUG_RE2.test(slug)) {
@@ -13402,7 +13689,7 @@ app37.get("/agent-assets/:slug/:filename", (c) => {
13402
13689
  "Cache-Control": "public, max-age=3600"
13403
13690
  });
13404
13691
  });
13405
- app37.get("/generated/:filename", (c) => {
13692
+ app38.get("/generated/:filename", (c) => {
13406
13693
  const filename = c.req.param("filename");
13407
13694
  if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
13408
13695
  console.error(`[generated] serve file=${filename} status=403`);
@@ -13432,9 +13719,10 @@ app37.get("/generated/:filename", (c) => {
13432
13719
  "Cache-Control": "public, max-age=86400"
13433
13720
  });
13434
13721
  });
13435
- app37.route("/sites", sites_default);
13436
- app37.route("/listings", listings_default);
13437
- app37.route("/v", visitor_event_default);
13722
+ app38.route("/sites", sites_default);
13723
+ app38.route("/listings", listings_default);
13724
+ app38.route("/v", visitor_event_default);
13725
+ app38.route("/v", visitor_consent_default);
13438
13726
  var htmlCache = /* @__PURE__ */ new Map();
13439
13727
  var brandLogoPath = "/brand/maxy-monochrome.png";
13440
13728
  var brandIconPath = "/brand/maxy-monochrome.png";
@@ -13571,7 +13859,7 @@ function brandedPublicHtml(agentSlug) {
13571
13859
  function escapeHtml(s) {
13572
13860
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13573
13861
  }
13574
- app37.get("/", (c) => {
13862
+ app38.get("/", (c) => {
13575
13863
  const host = (c.req.header("host") ?? "").split(":")[0];
13576
13864
  if (isPublicHost(host)) {
13577
13865
  const defaultSlug = resolveDefaultSlug();
@@ -13579,12 +13867,12 @@ app37.get("/", (c) => {
13579
13867
  }
13580
13868
  return c.html(cachedHtml("index.html"));
13581
13869
  });
13582
- app37.get("/public", (c) => {
13870
+ app38.get("/public", (c) => {
13583
13871
  const host = (c.req.header("host") ?? "").split(":")[0];
13584
13872
  if (isPublicHost(host)) return c.text("Not found", 404);
13585
13873
  return c.html(cachedHtml("public.html"));
13586
13874
  });
13587
- app37.get("/chat", (c) => {
13875
+ app38.get("/chat", (c) => {
13588
13876
  const host = (c.req.header("host") ?? "").split(":")[0];
13589
13877
  if (isPublicHost(host)) return c.text("Not found", 404);
13590
13878
  return c.html(cachedHtml("public.html"));
@@ -13603,9 +13891,9 @@ async function logViewerFetch(c, next) {
13603
13891
  duration_ms: Date.now() - start
13604
13892
  });
13605
13893
  }
13606
- app37.use("/vnc-viewer.html", logViewerFetch);
13607
- app37.use("/vnc-popout.html", logViewerFetch);
13608
- app37.get("/vnc-popout.html", (c) => {
13894
+ app38.use("/vnc-viewer.html", logViewerFetch);
13895
+ app38.use("/vnc-popout.html", logViewerFetch);
13896
+ app38.get("/vnc-popout.html", (c) => {
13609
13897
  let html = htmlCache.get("vnc-popout.html");
13610
13898
  if (!html) {
13611
13899
  html = readFileSync21(resolve21(process.cwd(), "public", "vnc-popout.html"), "utf-8");
@@ -13618,7 +13906,7 @@ app37.get("/vnc-popout.html", (c) => {
13618
13906
  }
13619
13907
  return c.html(html);
13620
13908
  });
13621
- app37.post("/api/vnc/client-event", async (c) => {
13909
+ app38.post("/api/vnc/client-event", async (c) => {
13622
13910
  let body;
13623
13911
  try {
13624
13912
  body = await c.req.json();
@@ -13639,25 +13927,25 @@ app37.post("/api/vnc/client-event", async (c) => {
13639
13927
  });
13640
13928
  return c.json({ ok: true });
13641
13929
  });
13642
- app37.get("/g/:slug", (c) => {
13930
+ app38.get("/g/:slug", (c) => {
13643
13931
  return c.html(brandedPublicHtml());
13644
13932
  });
13645
- app37.get("/graph", (c) => {
13933
+ app38.get("/graph", (c) => {
13646
13934
  const host = (c.req.header("host") ?? "").split(":")[0];
13647
13935
  if (isPublicHost(host)) return c.text("Not found", 404);
13648
13936
  return c.html(cachedHtml("graph.html"));
13649
13937
  });
13650
- app37.get("/sessions", (c) => {
13938
+ app38.get("/sessions", (c) => {
13651
13939
  const host = (c.req.header("host") ?? "").split(":")[0];
13652
13940
  if (isPublicHost(host)) return c.text("Not found", 404);
13653
13941
  return c.html(cachedHtml("sessions.html"));
13654
13942
  });
13655
- app37.get("/data", (c) => {
13943
+ app38.get("/data", (c) => {
13656
13944
  const host = (c.req.header("host") ?? "").split(":")[0];
13657
13945
  if (isPublicHost(host)) return c.text("Not found", 404);
13658
13946
  return c.html(cachedHtml("data.html"));
13659
13947
  });
13660
- app37.get("/:slug", async (c, next) => {
13948
+ app38.get("/:slug", async (c, next) => {
13661
13949
  const slug = c.req.param("slug");
13662
13950
  if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
13663
13951
  const branding = loadBrandingCache(slug);
@@ -13667,15 +13955,15 @@ app37.get("/:slug", async (c, next) => {
13667
13955
  await next();
13668
13956
  });
13669
13957
  if (brandFaviconPath !== "/favicon.ico") {
13670
- app37.get("/favicon.ico", (c) => {
13958
+ app38.get("/favicon.ico", (c) => {
13671
13959
  c.header("Cache-Control", "public, max-age=300");
13672
13960
  return c.redirect(brandFaviconPath, 302);
13673
13961
  });
13674
13962
  }
13675
- app37.use("/*", serveStatic({ root: "./public" }));
13963
+ app38.use("/*", serveStatic({ root: "./public" }));
13676
13964
  var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
13677
13965
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
13678
- var httpServer = serve({ fetch: app37.fetch, port, hostname });
13966
+ var httpServer = serve({ fetch: app38.fetch, port, hostname });
13679
13967
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
13680
13968
  {
13681
13969
  const loopHist = monitorEventLoopDelay({ resolution: 50 });
@@ -13701,7 +13989,8 @@ var SUBAPP_MANIFEST = [
13701
13989
  { prefix: "/api/admin", file: "server/routes/admin/index.ts", subapp: admin_default },
13702
13990
  { prefix: "/api/_client-error", file: "server/routes/client-error.ts", subapp: client_error_default },
13703
13991
  { prefix: "/listings", file: "server/routes/listings.ts", subapp: listings_default },
13704
- { prefix: "/v", file: "server/routes/visitor-event.ts", subapp: visitor_event_default }
13992
+ { prefix: "/v", file: "server/routes/visitor-event.ts", subapp: visitor_event_default },
13993
+ { prefix: "/v", file: "server/routes/visitor-consent.ts", subapp: visitor_consent_default }
13705
13994
  ];
13706
13995
  for (const m of SUBAPP_MANIFEST) {
13707
13996
  const routeCount = Array.isArray(m.subapp.routes) ? m.subapp.routes.length : 0;
@@ -13709,7 +13998,7 @@ for (const m of SUBAPP_MANIFEST) {
13709
13998
  }
13710
13999
  try {
13711
14000
  const registered = [];
13712
- for (const r of app37.routes ?? []) {
14001
+ for (const r of app38.routes ?? []) {
13713
14002
  if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
13714
14003
  if (AGENT_SLUG_PATTERN.test(r.path)) {
13715
14004
  registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });