@rubytech/create-maxy-code 0.1.118 → 0.1.120

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 (52) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/schema.cypher +95 -4
  3. package/payload/platform/package-lock.json +141 -125
  4. package/payload/platform/plugins/aeo/PLUGIN.md +1 -1
  5. package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.d.ts +2 -0
  6. package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.d.ts.map +1 -0
  7. package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.js +121 -0
  8. package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.js.map +1 -0
  9. package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.d.ts +2 -0
  10. package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.d.ts.map +1 -0
  11. package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.js +129 -0
  12. package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.js.map +1 -0
  13. package/payload/platform/plugins/aeo/mcp/dist/index.d.ts +2 -0
  14. package/payload/platform/plugins/aeo/mcp/dist/index.d.ts.map +1 -0
  15. package/payload/platform/plugins/aeo/mcp/dist/index.js +171 -0
  16. package/payload/platform/plugins/aeo/mcp/dist/index.js.map +1 -0
  17. package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.d.ts +27 -0
  18. package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.d.ts.map +1 -0
  19. package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.js +274 -0
  20. package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.js.map +1 -0
  21. package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.d.ts +5 -0
  22. package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.d.ts.map +1 -0
  23. package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.js +38 -0
  24. package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.js.map +1 -0
  25. package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.d.ts +48 -0
  26. package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.d.ts.map +1 -0
  27. package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.js +254 -0
  28. package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.js.map +1 -0
  29. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.d.ts +25 -0
  30. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.d.ts.map +1 -0
  31. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.js +78 -0
  32. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.js.map +1 -0
  33. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.d.ts +18 -0
  34. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.d.ts.map +1 -0
  35. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.js +56 -0
  36. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.js.map +1 -0
  37. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts +37 -0
  38. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts.map +1 -0
  39. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js +94 -0
  40. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js.map +1 -0
  41. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts +12 -1
  42. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts.map +1 -1
  43. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +34 -1
  44. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js.map +1 -1
  45. package/payload/platform/plugins/docs/references/aeo.md +2 -2
  46. package/payload/platform/plugins/docs/references/visitor-graph.md +82 -0
  47. package/payload/platform/plugins/memory/references/schema-base.md +44 -0
  48. package/payload/server/{chunk-FJCI6X3H.js → chunk-HCYM5FLU.js} +2 -0
  49. package/payload/server/maxy-edge.js +1 -1
  50. package/payload/server/public/privacy.html +66 -0
  51. package/payload/server/public/v.js +191 -0
  52. package/payload/server/server.js +473 -85
@@ -13,6 +13,7 @@ import {
13
13
  PLATFORM_ROOT,
14
14
  RFB_PORT,
15
15
  USERS_FILE,
16
+ VISITOR_TOKEN_SECRET_FILE,
16
17
  VNC_DISPLAY,
17
18
  WEBSOCKIFY_PORT,
18
19
  autoDeliverPremiumPlugins,
@@ -82,7 +83,7 @@ import {
82
83
  vncLog,
83
84
  walkPremiumBundles,
84
85
  writeAdminUserAndPerson
85
- } from "./chunk-FJCI6X3H.js";
86
+ } from "./chunk-HCYM5FLU.js";
86
87
  import {
87
88
  __commonJS,
88
89
  __toESM
@@ -587,8 +588,8 @@ var serveStatic = (options = { root: "" }) => {
587
588
  };
588
589
 
589
590
  // server/index.ts
590
- import { readFileSync as readFileSync19, existsSync as existsSync23, watchFile } from "fs";
591
- import { resolve as resolve21, join as join14, basename as basename5 } from "path";
591
+ import { readFileSync as readFileSync21, existsSync as existsSync24, watchFile } from "fs";
592
+ import { resolve as resolve21, join as join15, basename as basename5 } from "path";
592
593
  import { homedir as homedir4 } from "os";
593
594
  import { monitorEventLoopDelay } from "perf_hooks";
594
595
 
@@ -2025,8 +2026,8 @@ import { resolve } from "path";
2025
2026
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2026
2027
  var cache = /* @__PURE__ */ new Map();
2027
2028
  function enumerateValidAccountIds(accountsDir) {
2028
- const cached2 = cache.get(accountsDir);
2029
- if (cached2 !== void 0) return cached2;
2029
+ const cached3 = cache.get(accountsDir);
2030
+ if (cached3 !== void 0) return cached3;
2030
2031
  let names;
2031
2032
  try {
2032
2033
  names = readdirSync(accountsDir);
@@ -2913,8 +2914,8 @@ function watchForDisconnect(conn) {
2913
2914
  });
2914
2915
  }
2915
2916
  async function getGroupMeta(conn, jid) {
2916
- const cached2 = conn.groupMetaCache.get(jid);
2917
- if (cached2 && cached2.expires > Date.now()) return cached2;
2917
+ const cached3 = conn.groupMetaCache.get(jid);
2918
+ if (cached3 && cached3.expires > Date.now()) return cached3;
2918
2919
  if (!conn.sock) return null;
2919
2920
  console.error(`${TAG12} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
2920
2921
  try {
@@ -12174,6 +12175,83 @@ app34.get("/:rel{.*}", (c) => {
12174
12175
  });
12175
12176
  var sites_default = app34;
12176
12177
 
12178
+ // app/lib/visitor-token.ts
12179
+ import { createHmac, randomBytes, timingSafeEqual } from "crypto";
12180
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync7 } from "fs";
12181
+ import { dirname as dirname5 } from "path";
12182
+ var TOKEN_PREFIX = "v1.";
12183
+ var SECRET_BYTES = 32;
12184
+ var DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
12185
+ var cachedSecret = null;
12186
+ function getSecret() {
12187
+ if (cachedSecret) return cachedSecret;
12188
+ try {
12189
+ const hex2 = readFileSync18(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
12190
+ if (hex2.length === SECRET_BYTES * 2) {
12191
+ cachedSecret = Buffer.from(hex2, "hex");
12192
+ return cachedSecret;
12193
+ }
12194
+ } catch {
12195
+ }
12196
+ const fresh = randomBytes(SECRET_BYTES).toString("hex");
12197
+ try {
12198
+ mkdirSync4(dirname5(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
12199
+ writeFileSync7(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
12200
+ console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
12201
+ } catch {
12202
+ }
12203
+ const hex = readFileSync18(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
12204
+ cachedSecret = Buffer.from(hex, "hex");
12205
+ return cachedSecret;
12206
+ }
12207
+ function base64urlEncode(buf) {
12208
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
12209
+ }
12210
+ function base64urlDecode(s) {
12211
+ const padded = s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4);
12212
+ return Buffer.from(padded, "base64");
12213
+ }
12214
+ function signPayload(payloadJson, secret) {
12215
+ return base64urlEncode(createHmac("sha256", secret).update(payloadJson).digest());
12216
+ }
12217
+ function verifyVisitorToken(token, now = Date.now()) {
12218
+ if (!token || typeof token !== "string" || !token.startsWith(TOKEN_PREFIX)) {
12219
+ return { ok: false, reason: "malformed" };
12220
+ }
12221
+ const rest = token.slice(TOKEN_PREFIX.length);
12222
+ const dot = rest.indexOf(".");
12223
+ if (dot === -1) return { ok: false, reason: "malformed" };
12224
+ const payloadB64 = rest.slice(0, dot);
12225
+ const sigB64 = rest.slice(dot + 1);
12226
+ if (!payloadB64 || !sigB64) return { ok: false, reason: "malformed" };
12227
+ let payloadJson;
12228
+ try {
12229
+ payloadJson = base64urlDecode(payloadB64).toString("utf-8");
12230
+ } catch {
12231
+ return { ok: false, reason: "malformed" };
12232
+ }
12233
+ const expected = signPayload(payloadJson, getSecret());
12234
+ const expectedBuf = Buffer.from(expected, "utf-8");
12235
+ const provBuf = Buffer.from(sigB64, "utf-8");
12236
+ if (expectedBuf.length !== provBuf.length || !timingSafeEqual(expectedBuf, provBuf)) {
12237
+ return { ok: false, reason: "bad-sig" };
12238
+ }
12239
+ let payload;
12240
+ try {
12241
+ payload = JSON.parse(payloadJson);
12242
+ } catch {
12243
+ return { ok: false, reason: "malformed" };
12244
+ }
12245
+ if (!payload || typeof payload !== "object" || typeof payload.p !== "string" || typeof payload.e !== "number") {
12246
+ return { ok: false, reason: "malformed" };
12247
+ }
12248
+ const { p, e } = payload;
12249
+ if (now > e) return { ok: false, reason: "expired" };
12250
+ return { ok: true, personId: p, expiryMs: e };
12251
+ }
12252
+ var VISITOR_COOKIE_NAME = "mxy_v";
12253
+ var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
12254
+
12177
12255
  // server/routes/listings.ts
12178
12256
  var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
12179
12257
  var CHAT_SESSION_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/;
@@ -12182,6 +12260,7 @@ app35.get("/:slug/click", async (c) => {
12182
12260
  const slug = c.req.param("slug") ?? "";
12183
12261
  const rawSession = c.req.query("session") ?? "";
12184
12262
  const sessionKey = CHAT_SESSION_KEY_RE.test(rawSession) ? rawSession : "invalid";
12263
+ const tokenParam = c.req.query("v") ?? "";
12185
12264
  if (!SAFE_SLUG_RE.test(slug)) {
12186
12265
  console.error(`[property-card-click] reject reason=bad-slug-shape slug=${JSON.stringify(slug)} status=400`);
12187
12266
  return c.text("Bad Request", 400);
@@ -12218,11 +12297,318 @@ app35.get("/:slug/click", async (c) => {
12218
12297
  console.error(`[property-card-click] reject reason=listing-not-found slug=${slug} sessionKey=${sessionKey} accountId=${account.accountId} status=404`);
12219
12298
  return c.text("Not Found", 404);
12220
12299
  }
12300
+ if (tokenParam) {
12301
+ const verify = verifyVisitorToken(tokenParam);
12302
+ 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}`);
12308
+ } else {
12309
+ console.error(`[token-bind] reject reason=${verify.reason} sessionKey=${sessionKey} listingSlug=${slug}`);
12310
+ }
12311
+ }
12221
12312
  console.log(`[property-card-click] sessionKey=${sessionKey} listingSlug=${slug} ts=${(/* @__PURE__ */ new Date()).toISOString()}`);
12222
12313
  return c.redirect(pageUrl, 302);
12223
12314
  });
12224
12315
  var listings_default = app35;
12225
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
+ }
12336
+
12337
+ // server/routes/visitor-event.ts
12338
+ var app36 = new Hono();
12339
+ var BOT_UA_RE = /\b(bot|crawl|spider|slurp|headlesschrome|phantomjs|googlebot|bingbot|yandex|baiduspider|ahrefsbot|semrushbot|mj12bot|dotbot|petalbot)\b/i;
12340
+ var buckets = /* @__PURE__ */ new Map();
12341
+ var RATE_LIMIT = 60;
12342
+ var RATE_WINDOW_MS = 6e4;
12343
+ function rateLimit(ip, now = Date.now()) {
12344
+ const b = buckets.get(ip);
12345
+ if (!b || now >= b.resetAt) {
12346
+ buckets.set(ip, { count: 1, resetAt: now + RATE_WINDOW_MS });
12347
+ return { ok: true, resetIn: RATE_WINDOW_MS };
12348
+ }
12349
+ b.count += 1;
12350
+ if (b.count > RATE_LIMIT) return { ok: false, resetIn: b.resetAt - now };
12351
+ return { ok: true, resetIn: b.resetAt - now };
12352
+ }
12353
+ setInterval(() => {
12354
+ const now = Date.now();
12355
+ for (const [ip, b] of buckets) {
12356
+ if (now >= b.resetAt) buckets.delete(ip);
12357
+ }
12358
+ }, 5 * RATE_WINDOW_MS).unref?.();
12359
+ var VALID_TYPES = /* @__PURE__ */ new Set(["pageview", "click", "scroll", "dwell"]);
12360
+ var ID_RE = /^[A-Za-z0-9_-]{6,128}$/;
12361
+ function parseEvent(body) {
12362
+ if (!body || typeof body !== "object") return null;
12363
+ const e = body;
12364
+ if (typeof e.type !== "string" || !VALID_TYPES.has(e.type)) return null;
12365
+ if (typeof e.sid !== "string" || !ID_RE.test(e.sid)) return null;
12366
+ if (typeof e.vid !== "string" || !ID_RE.test(e.vid)) return null;
12367
+ if (typeof e.url !== "string" || e.url.length === 0 || e.url.length > 2048) return null;
12368
+ if (typeof e.ts !== "number" || !Number.isFinite(e.ts)) return null;
12369
+ return e;
12370
+ }
12371
+ function getCookie(c, name) {
12372
+ const raw = c.req.header("cookie");
12373
+ if (!raw) return null;
12374
+ for (const part of raw.split(";")) {
12375
+ const [k, ...rest] = part.trim().split("=");
12376
+ if (k === name) return rest.join("=");
12377
+ }
12378
+ return null;
12379
+ }
12380
+ function getOrigin(c) {
12381
+ return c.req.header("origin") ?? c.req.header("referer") ?? "";
12382
+ }
12383
+ function setCorsHeaders(c, origin) {
12384
+ if (origin) c.header("Access-Control-Allow-Origin", origin);
12385
+ c.header("Access-Control-Allow-Credentials", "true");
12386
+ c.header("Access-Control-Allow-Methods", "POST, OPTIONS");
12387
+ c.header("Access-Control-Allow-Headers", "Content-Type");
12388
+ c.header("Vary", "Origin");
12389
+ }
12390
+ function originAllowed(origin, allowlist) {
12391
+ if (!allowlist || allowlist.length === 0) return true;
12392
+ if (!origin) return true;
12393
+ try {
12394
+ const u = new URL(origin);
12395
+ const host = u.host;
12396
+ return allowlist.some((entry) => {
12397
+ if (entry === host) return true;
12398
+ if (entry.startsWith("*.")) return host.endsWith(entry.slice(1));
12399
+ return false;
12400
+ });
12401
+ } catch {
12402
+ return false;
12403
+ }
12404
+ }
12405
+ app36.options("/event", (c) => {
12406
+ const origin = getOrigin(c);
12407
+ setCorsHeaders(c, origin);
12408
+ return c.body(null, 204);
12409
+ });
12410
+ app36.post("/event", async (c) => {
12411
+ const origin = getOrigin(c);
12412
+ setCorsHeaders(c, origin);
12413
+ const ua = c.req.header("user-agent") ?? "";
12414
+ if (BOT_UA_RE.test(ua)) {
12415
+ console.error(`[v-event-error] reason=bot-ua ua=${JSON.stringify(ua.slice(0, 120))}`);
12416
+ return c.body(null, 204);
12417
+ }
12418
+ const ip = c.req.header("x-forwarded-for")?.split(",")[0].trim() || c.req.header("x-real-ip") || "unknown";
12419
+ const rl = rateLimit(ip);
12420
+ if (!rl.ok) {
12421
+ console.error(`[v-event-error] reason=rate-limit ip=${ip} resetInMs=${rl.resetIn}`);
12422
+ c.header("Retry-After", String(Math.ceil(rl.resetIn / 1e3)));
12423
+ return c.text("Too Many Requests", 429);
12424
+ }
12425
+ const account = resolveAccount();
12426
+ if (!account) {
12427
+ console.error(`[v-event-error] reason=no-account ip=${ip}`);
12428
+ return c.text("Not Found", 404);
12429
+ }
12430
+ const brand = readBrandConfig();
12431
+ if (!originAllowed(origin, brand?.publishedSiteOrigins)) {
12432
+ console.error(`[v-event-error] reason=origin-mismatch origin=${JSON.stringify(origin)} ip=${ip}`);
12433
+ return c.text("Forbidden", 403);
12434
+ }
12435
+ let raw;
12436
+ try {
12437
+ raw = await c.req.json();
12438
+ } catch {
12439
+ console.error(`[v-event-error] reason=bad-json ip=${ip}`);
12440
+ return c.text("Bad Request", 400);
12441
+ }
12442
+ const event = parseEvent(raw);
12443
+ if (!event) {
12444
+ console.error(`[v-event-error] reason=bad-event ip=${ip}`);
12445
+ return c.text("Bad Request", 400);
12446
+ }
12447
+ const cookieVal = getCookie(c, VISITOR_COOKIE_NAME);
12448
+ let personId = null;
12449
+ if (cookieVal) {
12450
+ const v = verifyVisitorToken(cookieVal);
12451
+ if (v.ok) {
12452
+ personId = v.personId;
12453
+ } else {
12454
+ console.error(`[token-bind] reject reason=${v.reason} sessionId=${event.sid} via=cookie`);
12455
+ }
12456
+ }
12457
+ const session = getSession();
12458
+ try {
12459
+ await writeEvent({
12460
+ session,
12461
+ accountId: account.accountId,
12462
+ personId,
12463
+ event,
12464
+ origin
12465
+ });
12466
+ } catch (err) {
12467
+ const message = err instanceof Error ? err.message : String(err);
12468
+ console.error(`[v-event-error] reason=neo4j sessionId=${event.sid} err=${JSON.stringify(message)}`);
12469
+ await session.close().catch(() => {
12470
+ });
12471
+ return c.text("Service Unavailable", 503);
12472
+ } finally {
12473
+ await session.close().catch(() => {
12474
+ });
12475
+ }
12476
+ 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 ?? "-"}`
12478
+ );
12479
+ return c.body(null, 204);
12480
+ });
12481
+ async function writeEvent(opts) {
12482
+ const { session, accountId, personId, event } = opts;
12483
+ const occurredAt = new Date(event.ts).toISOString();
12484
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12485
+ if (event.type === "pageview") {
12486
+ await session.run(
12487
+ `MERGE (s:Session {accountId: $accountId, sessionId: $sid})
12488
+ ON CREATE SET s.startedAt = datetime($occurredAt), s.lastSeenAt = datetime($occurredAt)
12489
+ ON MATCH SET s.lastSeenAt = datetime($occurredAt)
12490
+ MERGE (pg:Page {accountId: $accountId, url: $url})
12491
+ ON CREATE SET pg.firstSeenAt = datetime($occurredAt), pg.title = $title, pg.site = $site
12492
+ ON MATCH SET pg.title = $title
12493
+ CREATE (pv:PageView {
12494
+ accountId: $accountId,
12495
+ pageViewId: $pvId,
12496
+ occurredAt: datetime($occurredAt),
12497
+ referrer: $referrer,
12498
+ path: $path,
12499
+ createdAt: datetime($now)
12500
+ })
12501
+ MERGE (s)-[:HAS_EVENT]->(pv)
12502
+ MERGE (pv)-[:OF_PAGE]->(pg)
12503
+ WITH s, pv
12504
+ OPTIONAL MATCH (l:Listing {accountId: $accountId, slug: $site})
12505
+ WHERE l.scope = 'public'
12506
+ FOREACH (_ IN CASE WHEN l IS NULL THEN [] ELSE [1] END |
12507
+ MERGE (pv)-[:OF_LISTING]->(l)
12508
+ )`,
12509
+ {
12510
+ accountId,
12511
+ sid: event.sid,
12512
+ url: event.url,
12513
+ title: event.title ?? "",
12514
+ site: event.site ?? "",
12515
+ pvId: event.pvId ?? `${event.sid}_${event.ts}`,
12516
+ path: event.path ?? "",
12517
+ referrer: event.referrer ?? "",
12518
+ occurredAt,
12519
+ now,
12520
+ personId
12521
+ }
12522
+ );
12523
+ } else if (event.type === "click") {
12524
+ await session.run(
12525
+ `MERGE (s:Session {accountId: $accountId, sessionId: $sid})
12526
+ ON CREATE SET s.startedAt = datetime($occurredAt), s.lastSeenAt = datetime($occurredAt)
12527
+ ON MATCH SET s.lastSeenAt = datetime($occurredAt)
12528
+ CREATE (cl:Click {
12529
+ accountId: $accountId,
12530
+ label: $label,
12531
+ href: $href,
12532
+ pageViewId: $pvId,
12533
+ occurredAt: datetime($occurredAt)
12534
+ })
12535
+ MERGE (s)-[:HAS_EVENT]->(cl)`,
12536
+ {
12537
+ accountId,
12538
+ sid: event.sid,
12539
+ label: (event.label ?? "unknown").slice(0, 64),
12540
+ href: (event.href ?? "").slice(0, 2048),
12541
+ pvId: event.pvId ?? "",
12542
+ occurredAt
12543
+ }
12544
+ );
12545
+ } else if (event.type === "scroll") {
12546
+ await session.run(
12547
+ `MERGE (s:Session {accountId: $accountId, sessionId: $sid})
12548
+ ON CREATE SET s.startedAt = datetime($occurredAt), s.lastSeenAt = datetime($occurredAt)
12549
+ ON MATCH SET s.lastSeenAt = datetime($occurredAt)
12550
+ MERGE (sm:ScrollMilestone {accountId: $accountId, sessionId: $sid, pageViewId: $pvId})
12551
+ ON CREATE SET sm.maxDepth = $depth, sm.firstAt = datetime($occurredAt), sm.lastAt = datetime($occurredAt)
12552
+ ON MATCH SET sm.maxDepth = CASE WHEN sm.maxDepth >= $depth THEN sm.maxDepth ELSE $depth END,
12553
+ sm.lastAt = datetime($occurredAt)
12554
+ MERGE (s)-[:HAS_EVENT]->(sm)`,
12555
+ { accountId, sid: event.sid, pvId: event.pvId ?? "", depth: event.depth ?? 0, occurredAt }
12556
+ );
12557
+ } else if (event.type === "dwell") {
12558
+ await session.run(
12559
+ `MATCH (s:Session {accountId: $accountId, sessionId: $sid})
12560
+ MATCH (pv:PageView {accountId: $accountId, pageViewId: $pvId})
12561
+ SET pv.dwellMs = coalesce(pv.dwellMs, 0) + $ms,
12562
+ pv.dwellLastAt = datetime($occurredAt)
12563
+ SET s.lastSeenAt = datetime($occurredAt)`,
12564
+ { accountId, sid: event.sid, pvId: event.pvId ?? "", ms: event.ms ?? 0, occurredAt }
12565
+ );
12566
+ }
12567
+ if (personId) {
12568
+ await session.run(
12569
+ `MATCH (p:Person {accountId: $accountId}) WHERE elementId(p) = $personId
12570
+ MATCH (s:Session {accountId: $accountId, sessionId: $sid})
12571
+ MERGE (p)-[:VISITED {accountId: $accountId}]->(s)`,
12572
+ { accountId, personId, sid: event.sid }
12573
+ );
12574
+ const mergeResult = await session.run(
12575
+ `MATCH (p:Person {accountId: $accountId}) WHERE elementId(p) = $personId
12576
+ OPTIONAL MATCH (av:AnonVisitor {accountId: $accountId, visitorId: $vid})
12577
+ WITH p, av
12578
+ WHERE av IS NOT NULL
12579
+ MERGE (p)-[:OWNS_VISITOR {accountId: $accountId}]->(av)
12580
+ WITH p, av
12581
+ OPTIONAL MATCH (av)-[r:VISITED]->(prior:Session)
12582
+ FOREACH (_ IN CASE WHEN prior IS NULL THEN [] ELSE [1] END |
12583
+ MERGE (p)-[:VISITED {accountId: $accountId}]->(prior)
12584
+ )
12585
+ WITH r, prior
12586
+ FOREACH (_ IN CASE WHEN r IS NULL THEN [] ELSE [1] END |
12587
+ DELETE r
12588
+ )
12589
+ RETURN count(prior) AS reattributed`,
12590
+ { accountId, personId, vid: event.vid }
12591
+ );
12592
+ const reattributed = mergeResult.records[0]?.get("reattributed") ?? 0;
12593
+ if (typeof reattributed === "number" ? reattributed > 0 : Number(reattributed) > 0) {
12594
+ console.log(
12595
+ `[anonvisitor-merge] anonVisitorId=${event.vid} person=${personId} sessionsReattributed=${reattributed}`
12596
+ );
12597
+ }
12598
+ } else {
12599
+ await session.run(
12600
+ `MERGE (av:AnonVisitor {accountId: $accountId, visitorId: $vid})
12601
+ ON CREATE SET av.firstSeenAt = datetime($occurredAt), av.lastSeenAt = datetime($occurredAt)
12602
+ ON MATCH SET av.lastSeenAt = datetime($occurredAt)
12603
+ WITH av
12604
+ MATCH (s:Session {accountId: $accountId, sessionId: $sid})
12605
+ MERGE (av)-[:VISITED {accountId: $accountId}]->(s)`,
12606
+ { accountId, vid: event.vid, sid: event.sid, occurredAt }
12607
+ );
12608
+ }
12609
+ }
12610
+ var visitor_event_default = app36;
12611
+
12226
12612
  // app/lib/graph-health.ts
12227
12613
  var HOUR_MS = 60 * 60 * 1e3;
12228
12614
  var timer = null;
@@ -12365,7 +12751,7 @@ function broadcastAdminShutdown(reason) {
12365
12751
 
12366
12752
  // ../lib/entitlement/src/index.ts
12367
12753
  import { createPublicKey, createHash as createHash3, verify as cryptoVerify } from "crypto";
12368
- import { existsSync as existsSync22, readFileSync as readFileSync18, statSync as statSync8 } from "fs";
12754
+ import { existsSync as existsSync23, readFileSync as readFileSync20, statSync as statSync8 } from "fs";
12369
12755
  import { resolve as resolve20 } from "path";
12370
12756
 
12371
12757
  // ../lib/entitlement/src/canonicalize.ts
@@ -12414,7 +12800,7 @@ function resolveEntitlement(brand, account) {
12414
12800
  return logResolved(implicitTrust(account), null);
12415
12801
  }
12416
12802
  const entitlementPath = resolve20(brand.configDir, "entitlement.json");
12417
- if (!existsSync22(entitlementPath)) {
12803
+ if (!existsSync23(entitlementPath)) {
12418
12804
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
12419
12805
  }
12420
12806
  const stat7 = statSync8(entitlementPath);
@@ -12429,7 +12815,7 @@ function resolveEntitlement(brand, account) {
12429
12815
  function verifyAndResolve(brand, entitlementPath, account) {
12430
12816
  let pubkeyPem;
12431
12817
  try {
12432
- pubkeyPem = readFileSync18(pubkeyPath(brand), "utf-8");
12818
+ pubkeyPem = readFileSync20(pubkeyPath(brand), "utf-8");
12433
12819
  } catch (err) {
12434
12820
  return logResolved(anonymousFallback("pubkey-missing"), {
12435
12821
  reason: "pubkey-missing"
@@ -12443,7 +12829,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
12443
12829
  }
12444
12830
  let envelope;
12445
12831
  try {
12446
- envelope = JSON.parse(readFileSync18(entitlementPath, "utf-8"));
12832
+ envelope = JSON.parse(readFileSync20(entitlementPath, "utf-8"));
12447
12833
  } catch {
12448
12834
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
12449
12835
  }
@@ -12604,14 +12990,14 @@ function clientFrom(c) {
12604
12990
  );
12605
12991
  }
12606
12992
  var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
12607
- var BRAND_JSON_PATH = PLATFORM_ROOT7 ? join14(PLATFORM_ROOT7, "config", "brand.json") : "";
12993
+ var BRAND_JSON_PATH = PLATFORM_ROOT7 ? join15(PLATFORM_ROOT7, "config", "brand.json") : "";
12608
12994
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
12609
- if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
12995
+ if (BRAND_JSON_PATH && !existsSync24(BRAND_JSON_PATH)) {
12610
12996
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
12611
12997
  }
12612
- if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
12998
+ if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
12613
12999
  try {
12614
- const parsed = JSON.parse(readFileSync19(BRAND_JSON_PATH, "utf-8"));
13000
+ const parsed = JSON.parse(readFileSync21(BRAND_JSON_PATH, "utf-8"));
12615
13001
  BRAND = { ...BRAND, ...parsed };
12616
13002
  } catch (err) {
12617
13003
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -12630,11 +13016,11 @@ var brandLoginOpts = {
12630
13016
  bodyFont: BRAND.defaultFonts?.body,
12631
13017
  logoContainsName: !!BRAND.logoContainsName
12632
13018
  };
12633
- var ALIAS_DOMAINS_PATH = join14(homedir4(), BRAND.configDir, "alias-domains.json");
13019
+ var ALIAS_DOMAINS_PATH = join15(homedir4(), BRAND.configDir, "alias-domains.json");
12634
13020
  function loadAliasDomains() {
12635
13021
  try {
12636
- if (!existsSync23(ALIAS_DOMAINS_PATH)) return null;
12637
- const parsed = JSON.parse(readFileSync19(ALIAS_DOMAINS_PATH, "utf-8"));
13022
+ if (!existsSync24(ALIAS_DOMAINS_PATH)) return null;
13023
+ const parsed = JSON.parse(readFileSync21(ALIAS_DOMAINS_PATH, "utf-8"));
12638
13024
  if (!Array.isArray(parsed)) {
12639
13025
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
12640
13026
  return null;
@@ -12658,9 +13044,9 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
12658
13044
  function isPublicHost(host) {
12659
13045
  return host.startsWith("public.") || aliasDomains.has(host);
12660
13046
  }
12661
- var app36 = new Hono();
12662
- app36.use("*", clientIpMiddleware);
12663
- app36.use("*", async (c, next) => {
13047
+ var app37 = new Hono();
13048
+ app37.use("*", clientIpMiddleware);
13049
+ app37.use("*", async (c, next) => {
12664
13050
  await next();
12665
13051
  c.header("X-Content-Type-Options", "nosniff");
12666
13052
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
@@ -12670,7 +13056,7 @@ app36.use("*", async (c, next) => {
12670
13056
  );
12671
13057
  });
12672
13058
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
12673
- app36.use("*", async (c, next) => {
13059
+ app37.use("*", async (c, next) => {
12674
13060
  if (!HTTP_LOG_PATHS.has(c.req.path)) {
12675
13061
  await next();
12676
13062
  return;
@@ -12703,7 +13089,7 @@ var PUBLIC_ALLOWED_PREFIXES = [
12703
13089
  "/sites/"
12704
13090
  ];
12705
13091
  var PUBLIC_ALLOWED_EXACT = ["/favicon.ico"];
12706
- app36.use("*", async (c, next) => {
13092
+ app37.use("*", async (c, next) => {
12707
13093
  const host = (c.req.header("host") ?? "").split(":")[0];
12708
13094
  if (!isPublicHost(host)) {
12709
13095
  await next();
@@ -12743,7 +13129,7 @@ function resolveRemoteAuthOpts() {
12743
13129
  return brandLoginOpts;
12744
13130
  }
12745
13131
  var MAX_LOGIN_BODY = 8 * 1024;
12746
- app36.post("/__remote-auth/login", async (c) => {
13132
+ app37.post("/__remote-auth/login", async (c) => {
12747
13133
  const client = clientFrom(c);
12748
13134
  const clientIp = client.ip || "unknown";
12749
13135
  if (!requestIsTlsTerminated(c)) {
@@ -12788,7 +13174,7 @@ app36.post("/__remote-auth/login", async (c) => {
12788
13174
  }
12789
13175
  });
12790
13176
  });
12791
- app36.get("/__remote-auth/logout", (c) => {
13177
+ app37.get("/__remote-auth/logout", (c) => {
12792
13178
  const client = clientFrom(c);
12793
13179
  const clientIp = client.ip || "unknown";
12794
13180
  console.error(`[remote-auth] logout ip=${clientIp}`);
@@ -12801,7 +13187,7 @@ app36.get("/__remote-auth/logout", (c) => {
12801
13187
  }
12802
13188
  });
12803
13189
  });
12804
- app36.post("/__remote-auth/change-password", async (c) => {
13190
+ app37.post("/__remote-auth/change-password", async (c) => {
12805
13191
  const client = clientFrom(c);
12806
13192
  const clientIp = client.ip || "unknown";
12807
13193
  const rateLimited = checkRateLimit(client);
@@ -12852,13 +13238,13 @@ app36.post("/__remote-auth/change-password", async (c) => {
12852
13238
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "change", changeError: "Failed to save password", redirect }), 200);
12853
13239
  }
12854
13240
  });
12855
- app36.get("/__remote-auth/setup", (c) => {
13241
+ app37.get("/__remote-auth/setup", (c) => {
12856
13242
  if (isRemoteAuthConfigured()) {
12857
13243
  return c.redirect("/");
12858
13244
  }
12859
13245
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup" }), 200);
12860
13246
  });
12861
- app36.post("/__remote-auth/set-initial-password", async (c) => {
13247
+ app37.post("/__remote-auth/set-initial-password", async (c) => {
12862
13248
  if (isRemoteAuthConfigured()) {
12863
13249
  return c.redirect("/");
12864
13250
  }
@@ -12896,10 +13282,10 @@ app36.post("/__remote-auth/set-initial-password", async (c) => {
12896
13282
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
12897
13283
  }
12898
13284
  });
12899
- app36.get("/api/remote-auth/status", (c) => {
13285
+ app37.get("/api/remote-auth/status", (c) => {
12900
13286
  return c.json({ configured: isRemoteAuthConfigured() });
12901
13287
  });
12902
- app36.post("/api/remote-auth/set-password", async (c) => {
13288
+ app37.post("/api/remote-auth/set-password", async (c) => {
12903
13289
  let body;
12904
13290
  try {
12905
13291
  body = await c.req.json();
@@ -12930,9 +13316,9 @@ app36.post("/api/remote-auth/set-password", async (c) => {
12930
13316
  return c.json({ error: "Failed to save password" }, 500);
12931
13317
  }
12932
13318
  });
12933
- app36.route("/api/_client-error", client_error_default);
13319
+ app37.route("/api/_client-error", client_error_default);
12934
13320
  console.log("[client-error-route] mounted");
12935
- app36.use("*", async (c, next) => {
13321
+ app37.use("*", async (c, next) => {
12936
13322
  const host = (c.req.header("host") ?? "").split(":")[0];
12937
13323
  const path2 = c.req.path;
12938
13324
  if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/")) {
@@ -12965,11 +13351,11 @@ app36.use("*", async (c, next) => {
12965
13351
  console.error(`[remote-auth] login required ip=${clientIp} path=${path2} ${disambig}`);
12966
13352
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
12967
13353
  });
12968
- app36.route("/api/health", health_default);
12969
- app36.route("/api/chat", chat_default);
12970
- app36.route("/api/whatsapp", whatsapp_default);
12971
- app36.route("/api/onboarding", onboarding_default);
12972
- app36.route("/api/admin", admin_default);
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);
12973
13359
  var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
12974
13360
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
12975
13361
  var IMAGE_MIME = {
@@ -12981,7 +13367,7 @@ var IMAGE_MIME = {
12981
13367
  ".svg": "image/svg+xml",
12982
13368
  ".ico": "image/x-icon"
12983
13369
  };
12984
- app36.get("/agent-assets/:slug/:filename", (c) => {
13370
+ app37.get("/agent-assets/:slug/:filename", (c) => {
12985
13371
  const slug = c.req.param("slug");
12986
13372
  const filename = c.req.param("filename");
12987
13373
  if (!SAFE_SLUG_RE2.test(slug)) {
@@ -13003,20 +13389,20 @@ app36.get("/agent-assets/:slug/:filename", (c) => {
13003
13389
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
13004
13390
  return c.text("Forbidden", 403);
13005
13391
  }
13006
- if (!existsSync23(filePath)) {
13392
+ if (!existsSync24(filePath)) {
13007
13393
  console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
13008
13394
  return c.text("Not found", 404);
13009
13395
  }
13010
13396
  const ext = "." + filename.split(".").pop()?.toLowerCase();
13011
13397
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
13012
13398
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
13013
- const body = readFileSync19(filePath);
13399
+ const body = readFileSync21(filePath);
13014
13400
  return c.body(body, 200, {
13015
13401
  "Content-Type": contentType,
13016
13402
  "Cache-Control": "public, max-age=3600"
13017
13403
  });
13018
13404
  });
13019
- app36.get("/generated/:filename", (c) => {
13405
+ app37.get("/generated/:filename", (c) => {
13020
13406
  const filename = c.req.param("filename");
13021
13407
  if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
13022
13408
  console.error(`[generated] serve file=${filename} status=403`);
@@ -13033,27 +13419,28 @@ app36.get("/generated/:filename", (c) => {
13033
13419
  console.error(`[generated] serve file=${filename} status=403`);
13034
13420
  return c.text("Forbidden", 403);
13035
13421
  }
13036
- if (!existsSync23(filePath)) {
13422
+ if (!existsSync24(filePath)) {
13037
13423
  console.error(`[generated] serve file=${filename} status=404`);
13038
13424
  return c.text("Not found", 404);
13039
13425
  }
13040
13426
  const ext = "." + filename.split(".").pop()?.toLowerCase();
13041
13427
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
13042
13428
  console.log(`[generated] serve file=${filename} status=200`);
13043
- const body = readFileSync19(filePath);
13429
+ const body = readFileSync21(filePath);
13044
13430
  return c.body(body, 200, {
13045
13431
  "Content-Type": contentType,
13046
13432
  "Cache-Control": "public, max-age=86400"
13047
13433
  });
13048
13434
  });
13049
- app36.route("/sites", sites_default);
13050
- app36.route("/listings", listings_default);
13435
+ app37.route("/sites", sites_default);
13436
+ app37.route("/listings", listings_default);
13437
+ app37.route("/v", visitor_event_default);
13051
13438
  var htmlCache = /* @__PURE__ */ new Map();
13052
13439
  var brandLogoPath = "/brand/maxy-monochrome.png";
13053
13440
  var brandIconPath = "/brand/maxy-monochrome.png";
13054
- if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
13441
+ if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
13055
13442
  try {
13056
- const fullBrand = JSON.parse(readFileSync19(BRAND_JSON_PATH, "utf-8"));
13443
+ const fullBrand = JSON.parse(readFileSync21(BRAND_JSON_PATH, "utf-8"));
13057
13444
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
13058
13445
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
13059
13446
  } catch {
@@ -13070,9 +13457,9 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
13070
13457
  function readInstalledVersion() {
13071
13458
  try {
13072
13459
  if (!PLATFORM_ROOT7) return "unknown";
13073
- const versionFile = join14(PLATFORM_ROOT7, "config", `.${BRAND.hostname}-version`);
13074
- if (!existsSync23(versionFile)) return "unknown";
13075
- const content = readFileSync19(versionFile, "utf-8").trim();
13460
+ const versionFile = join15(PLATFORM_ROOT7, "config", `.${BRAND.hostname}-version`);
13461
+ if (!existsSync24(versionFile)) return "unknown";
13462
+ const content = readFileSync21(versionFile, "utf-8").trim();
13076
13463
  return content || "unknown";
13077
13464
  } catch {
13078
13465
  return "unknown";
@@ -13113,7 +13500,7 @@ var clientErrorReporterScript = `<script>
13113
13500
  function cachedHtml(file) {
13114
13501
  let html = htmlCache.get(file);
13115
13502
  if (!html) {
13116
- html = readFileSync19(resolve21(process.cwd(), "public", file), "utf-8");
13503
+ html = readFileSync21(resolve21(process.cwd(), "public", file), "utf-8");
13117
13504
  const productNameEsc = escapeHtml(BRAND.productName);
13118
13505
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
13119
13506
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -13129,26 +13516,26 @@ ${clientErrorReporterScript}
13129
13516
  }
13130
13517
  var brandedHtmlCache = /* @__PURE__ */ new Map();
13131
13518
  function loadBrandingCache(agentSlug) {
13132
- const configDir2 = join14(homedir4(), BRAND.configDir);
13519
+ const configDir2 = join15(homedir4(), BRAND.configDir);
13133
13520
  try {
13134
- const accountJsonPath = join14(configDir2, "account.json");
13135
- if (!existsSync23(accountJsonPath)) return null;
13136
- const account = JSON.parse(readFileSync19(accountJsonPath, "utf-8"));
13521
+ const accountJsonPath = join15(configDir2, "account.json");
13522
+ if (!existsSync24(accountJsonPath)) return null;
13523
+ const account = JSON.parse(readFileSync21(accountJsonPath, "utf-8"));
13137
13524
  const accountId = account.accountId;
13138
13525
  if (!accountId) return null;
13139
- const cachePath = join14(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
13140
- if (!existsSync23(cachePath)) return null;
13141
- return JSON.parse(readFileSync19(cachePath, "utf-8"));
13526
+ const cachePath = join15(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
13527
+ if (!existsSync24(cachePath)) return null;
13528
+ return JSON.parse(readFileSync21(cachePath, "utf-8"));
13142
13529
  } catch {
13143
13530
  return null;
13144
13531
  }
13145
13532
  }
13146
13533
  function resolveDefaultSlug() {
13147
13534
  try {
13148
- const configDir2 = join14(homedir4(), BRAND.configDir);
13149
- const accountJsonPath = join14(configDir2, "account.json");
13150
- if (!existsSync23(accountJsonPath)) return null;
13151
- const account = JSON.parse(readFileSync19(accountJsonPath, "utf-8"));
13535
+ const configDir2 = join15(homedir4(), BRAND.configDir);
13536
+ const accountJsonPath = join15(configDir2, "account.json");
13537
+ if (!existsSync24(accountJsonPath)) return null;
13538
+ const account = JSON.parse(readFileSync21(accountJsonPath, "utf-8"));
13152
13539
  return account.defaultAgent || null;
13153
13540
  } catch {
13154
13541
  return null;
@@ -13157,11 +13544,11 @@ function resolveDefaultSlug() {
13157
13544
  function brandedPublicHtml(agentSlug) {
13158
13545
  const baseHtml = cachedHtml("public.html");
13159
13546
  if (!agentSlug) return baseHtml;
13160
- const cached2 = brandedHtmlCache.get(agentSlug);
13547
+ const cached3 = brandedHtmlCache.get(agentSlug);
13161
13548
  const branding = loadBrandingCache(agentSlug);
13162
13549
  if (!branding) return baseHtml;
13163
13550
  const brandHash = JSON.stringify(branding).length;
13164
- if (cached2 && cached2.mtime === brandHash) return cached2.html;
13551
+ if (cached3 && cached3.mtime === brandHash) return cached3.html;
13165
13552
  const title = escapeHtml(branding.name);
13166
13553
  const description = branding.tagline ? escapeHtml(branding.tagline) : "";
13167
13554
  const themeColor = branding.primaryColor || "";
@@ -13184,7 +13571,7 @@ function brandedPublicHtml(agentSlug) {
13184
13571
  function escapeHtml(s) {
13185
13572
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13186
13573
  }
13187
- app36.get("/", (c) => {
13574
+ app37.get("/", (c) => {
13188
13575
  const host = (c.req.header("host") ?? "").split(":")[0];
13189
13576
  if (isPublicHost(host)) {
13190
13577
  const defaultSlug = resolveDefaultSlug();
@@ -13192,12 +13579,12 @@ app36.get("/", (c) => {
13192
13579
  }
13193
13580
  return c.html(cachedHtml("index.html"));
13194
13581
  });
13195
- app36.get("/public", (c) => {
13582
+ app37.get("/public", (c) => {
13196
13583
  const host = (c.req.header("host") ?? "").split(":")[0];
13197
13584
  if (isPublicHost(host)) return c.text("Not found", 404);
13198
13585
  return c.html(cachedHtml("public.html"));
13199
13586
  });
13200
- app36.get("/chat", (c) => {
13587
+ app37.get("/chat", (c) => {
13201
13588
  const host = (c.req.header("host") ?? "").split(":")[0];
13202
13589
  if (isPublicHost(host)) return c.text("Not found", 404);
13203
13590
  return c.html(cachedHtml("public.html"));
@@ -13216,12 +13603,12 @@ async function logViewerFetch(c, next) {
13216
13603
  duration_ms: Date.now() - start
13217
13604
  });
13218
13605
  }
13219
- app36.use("/vnc-viewer.html", logViewerFetch);
13220
- app36.use("/vnc-popout.html", logViewerFetch);
13221
- app36.get("/vnc-popout.html", (c) => {
13606
+ app37.use("/vnc-viewer.html", logViewerFetch);
13607
+ app37.use("/vnc-popout.html", logViewerFetch);
13608
+ app37.get("/vnc-popout.html", (c) => {
13222
13609
  let html = htmlCache.get("vnc-popout.html");
13223
13610
  if (!html) {
13224
- html = readFileSync19(resolve21(process.cwd(), "public", "vnc-popout.html"), "utf-8");
13611
+ html = readFileSync21(resolve21(process.cwd(), "public", "vnc-popout.html"), "utf-8");
13225
13612
  const name = escapeHtml(BRAND.productName);
13226
13613
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
13227
13614
  html = html.replace("</head>", ` ${brandScript}
@@ -13231,7 +13618,7 @@ app36.get("/vnc-popout.html", (c) => {
13231
13618
  }
13232
13619
  return c.html(html);
13233
13620
  });
13234
- app36.post("/api/vnc/client-event", async (c) => {
13621
+ app37.post("/api/vnc/client-event", async (c) => {
13235
13622
  let body;
13236
13623
  try {
13237
13624
  body = await c.req.json();
@@ -13252,25 +13639,25 @@ app36.post("/api/vnc/client-event", async (c) => {
13252
13639
  });
13253
13640
  return c.json({ ok: true });
13254
13641
  });
13255
- app36.get("/g/:slug", (c) => {
13642
+ app37.get("/g/:slug", (c) => {
13256
13643
  return c.html(brandedPublicHtml());
13257
13644
  });
13258
- app36.get("/graph", (c) => {
13645
+ app37.get("/graph", (c) => {
13259
13646
  const host = (c.req.header("host") ?? "").split(":")[0];
13260
13647
  if (isPublicHost(host)) return c.text("Not found", 404);
13261
13648
  return c.html(cachedHtml("graph.html"));
13262
13649
  });
13263
- app36.get("/sessions", (c) => {
13650
+ app37.get("/sessions", (c) => {
13264
13651
  const host = (c.req.header("host") ?? "").split(":")[0];
13265
13652
  if (isPublicHost(host)) return c.text("Not found", 404);
13266
13653
  return c.html(cachedHtml("sessions.html"));
13267
13654
  });
13268
- app36.get("/data", (c) => {
13655
+ app37.get("/data", (c) => {
13269
13656
  const host = (c.req.header("host") ?? "").split(":")[0];
13270
13657
  if (isPublicHost(host)) return c.text("Not found", 404);
13271
13658
  return c.html(cachedHtml("data.html"));
13272
13659
  });
13273
- app36.get("/:slug", async (c, next) => {
13660
+ app37.get("/:slug", async (c, next) => {
13274
13661
  const slug = c.req.param("slug");
13275
13662
  if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
13276
13663
  const branding = loadBrandingCache(slug);
@@ -13280,15 +13667,15 @@ app36.get("/:slug", async (c, next) => {
13280
13667
  await next();
13281
13668
  });
13282
13669
  if (brandFaviconPath !== "/favicon.ico") {
13283
- app36.get("/favicon.ico", (c) => {
13670
+ app37.get("/favicon.ico", (c) => {
13284
13671
  c.header("Cache-Control", "public, max-age=300");
13285
13672
  return c.redirect(brandFaviconPath, 302);
13286
13673
  });
13287
13674
  }
13288
- app36.use("/*", serveStatic({ root: "./public" }));
13675
+ app37.use("/*", serveStatic({ root: "./public" }));
13289
13676
  var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
13290
13677
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
13291
- var httpServer = serve({ fetch: app36.fetch, port, hostname });
13678
+ var httpServer = serve({ fetch: app37.fetch, port, hostname });
13292
13679
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
13293
13680
  {
13294
13681
  const loopHist = monitorEventLoopDelay({ resolution: 50 });
@@ -13313,7 +13700,8 @@ var SUBAPP_MANIFEST = [
13313
13700
  { prefix: "/api/onboarding", file: "server/routes/onboarding.ts", subapp: onboarding_default },
13314
13701
  { prefix: "/api/admin", file: "server/routes/admin/index.ts", subapp: admin_default },
13315
13702
  { prefix: "/api/_client-error", file: "server/routes/client-error.ts", subapp: client_error_default },
13316
- { prefix: "/listings", file: "server/routes/listings.ts", subapp: listings_default }
13703
+ { prefix: "/listings", file: "server/routes/listings.ts", subapp: listings_default },
13704
+ { prefix: "/v", file: "server/routes/visitor-event.ts", subapp: visitor_event_default }
13317
13705
  ];
13318
13706
  for (const m of SUBAPP_MANIFEST) {
13319
13707
  const routeCount = Array.isArray(m.subapp.routes) ? m.subapp.routes.length : 0;
@@ -13321,7 +13709,7 @@ for (const m of SUBAPP_MANIFEST) {
13321
13709
  }
13322
13710
  try {
13323
13711
  const registered = [];
13324
- for (const r of app36.routes ?? []) {
13712
+ for (const r of app37.routes ?? []) {
13325
13713
  if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
13326
13714
  if (AGENT_SLUG_PATTERN.test(r.path)) {
13327
13715
  registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
@@ -13343,8 +13731,8 @@ try {
13343
13731
  }
13344
13732
  (async () => {
13345
13733
  try {
13346
- if (!existsSync23(USERS_FILE)) return;
13347
- const usersRaw = readFileSync19(USERS_FILE, "utf-8").trim();
13734
+ if (!existsSync24(USERS_FILE)) return;
13735
+ const usersRaw = readFileSync21(USERS_FILE, "utf-8").trim();
13348
13736
  if (!usersRaw) return;
13349
13737
  const users = JSON.parse(usersRaw);
13350
13738
  const userId = users[0]?.userId;
@@ -13436,7 +13824,7 @@ if (bootAccountConfig?.whatsapp) {
13436
13824
  }
13437
13825
  init({
13438
13826
  configDir: configDirForWhatsApp,
13439
- platformRoot: resolve21(process.env.MAXY_PLATFORM_ROOT ?? join14(__dirname, "..")),
13827
+ platformRoot: resolve21(process.env.MAXY_PLATFORM_ROOT ?? join15(__dirname, "..")),
13440
13828
  accountConfig: bootAccountConfig,
13441
13829
  onMessage: async (msg) => {
13442
13830
  if (process.env.WHATSAPP_PTY_BRIDGE_ENABLED === "true" && msg.text && !msg.isOwnerMirror) {