@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
@@ -2705,6 +2705,7 @@ var REMOTE_SESSION_SECRET_FILE = resolve(MAXY_DIR, "credentials", "remote-sessio
2705
2705
  var ADMIN_SESSION_SECRET_FILE = resolve(MAXY_DIR, "credentials", "admin-session-secret");
2706
2706
  var TELEGRAM_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-webhook-secret");
2707
2707
  var TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-admin-webhook-secret");
2708
+ var VISITOR_TOKEN_SECRET_FILE = resolve(MAXY_DIR, "credentials", "visitor-token-secret");
2708
2709
  var CLAUDE_CREDENTIALS_FILE = resolve(MAXY_DIR, ".claude", ".credentials.json");
2709
2710
 
2710
2711
  // app/lib/vnc-logger.ts
@@ -5476,6 +5477,7 @@ export {
5476
5477
  USERS_FILE,
5477
5478
  LOG_DIR,
5478
5479
  BIN_DIR,
5480
+ VISITOR_TOKEN_SECRET_FILE,
5479
5481
  CLAUDE_CREDENTIALS_FILE,
5480
5482
  vncLog,
5481
5483
  newCorrId,
@@ -13,7 +13,7 @@ import {
13
13
  sanitizeClientCorrId,
14
14
  vncLog,
15
15
  websockifyLog
16
- } from "./chunk-FJCI6X3H.js";
16
+ } from "./chunk-HCYM5FLU.js";
17
17
  import "./chunk-JSBRDJBE.js";
18
18
 
19
19
  // server/edge.ts
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Privacy</title>
7
+ <style>
8
+ :root { --fg:#222; --bg:#FAFAF8; --muted:#666; --accent:#7C8C72; }
9
+ * { box-sizing: border-box; }
10
+ body { font: 16px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif; margin: 0; color: var(--fg); background: var(--bg); }
11
+ main { max-width: 720px; margin: 0 auto; padding: 48px 24px 96px; }
12
+ h1 { margin-top: 0; font-size: 32px; letter-spacing: -0.01em; }
13
+ h2 { margin-top: 36px; font-size: 20px; }
14
+ p, li { color: #333; }
15
+ ul { padding-left: 20px; }
16
+ code { background: #eee; padding: 1px 5px; border-radius: 3px; font-size: 13px; }
17
+ table { border-collapse: collapse; width: 100%; margin: 12px 0; }
18
+ th, td { border-bottom: 1px solid #ddd; padding: 8px 6px; text-align: left; vertical-align: top; }
19
+ th { background: #f0f0ee; font-weight: 600; }
20
+ .muted { color: var(--muted); font-size: 14px; }
21
+ .accent { color: var(--accent); }
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <main>
26
+ <h1>Privacy</h1>
27
+ <p>This page describes the data this site collects about your visit, why, and how to ask for it to be removed.</p>
28
+
29
+ <h2>What we collect</h2>
30
+ <p>When you load a property listing page, a small first-party script (<code>/v.js</code>) records:</p>
31
+ <table>
32
+ <thead><tr><th>Field</th><th>Why</th><th>Stored as</th></tr></thead>
33
+ <tbody>
34
+ <tr><td>Page URL, page title, referrer</td><td>So we know which listings get visitors and where they came from.</td><td><code>:PageView</code> node</td></tr>
35
+ <tr><td>Visitor ID (random, browser-local)</td><td>So we can tell two visits from one device apart from two devices.</td><td><code>:AnonVisitor</code> node (anonymous), or linked to a <code>:Person</code> if you arrived via a personalised marketing link.</td></tr>
36
+ <tr><td>Session ID (random, per tab)</td><td>So one tab's activity stays together.</td><td><code>:Session</code> node</td></tr>
37
+ <tr><td>Clicks on tagged elements (gallery, floorplan, etc.)</td><td>So we know which parts of a listing get interaction.</td><td><code>:Click</code> node</td></tr>
38
+ <tr><td>Scroll depth — 25%, 50%, 75%, 100%</td><td>So we know how far you read.</td><td><code>:ScrollMilestone</code> node (one per page view, max depth recorded)</td></tr>
39
+ <tr><td>Time spent on a page (dwell)</td><td>Same.</td><td>Property of the <code>:PageView</code> node</td></tr>
40
+ </tbody>
41
+ </table>
42
+ <p class="muted">We do not collect: cursor positions, keystrokes, form contents, screen recordings, or anything from third-party tracking networks. The pixel is loaded from the same site as the listing, never from a third party.</p>
43
+
44
+ <h2>Cookies</h2>
45
+ <p>One cookie is set when you arrive via a personalised marketing link from the agent:</p>
46
+ <ul>
47
+ <li><code>mxy_v</code> — signed token containing your contact identifier. Set <code>HttpOnly</code>, <code>SameSite=Lax</code>, <code>Secure</code>. Lives for 30 days. Lets the site recognise you on later visits without you clicking the marketing link again.</li>
48
+ </ul>
49
+
50
+ <h2>Cross-session attribution</h2>
51
+ <p>If you visit anonymously today and later click a personalised marketing link from the agent, prior anonymous visits from the same browser are linked to your contact record. This is the only way an anonymous visit ever becomes named.</p>
52
+
53
+ <h2>Retention</h2>
54
+ <p>Visit data is kept for <strong>180 days</strong> by default, then removed automatically. The operator can change this — your acting agent will tell you the current value if you ask.</p>
55
+
56
+ <h2>Right to access and erasure</h2>
57
+ <p>To see what we hold or to ask for it to be removed, contact the agent. We honour GDPR Article 15 (access) and Article 17 (erasure). Erasure cascades: removing your contact removes every session, page view, click, and scroll milestone tied to your identity. Anonymous page views from browsers we cannot link to you are not removed individually — they cannot be identified, and they expire under the retention window above.</p>
58
+
59
+ <h2>Bots and crawlers</h2>
60
+ <p>Search engines and link checkers are filtered out by user-agent before any data is written. They are also excluded from rate-limit budgets so legitimate visitors are never blocked because a crawler hammered the endpoint.</p>
61
+
62
+ <h2>This page</h2>
63
+ <p class="muted">This is a static description. The behaviour above is implemented in <code>platform/ui/server/routes/visitor-event.ts</code> and <code>platform/ui/public/v.js</code>. The schema is declared in <code>platform/neo4j/schema.cypher</code> under the Visitor Graph section.</p>
64
+ </main>
65
+ </body>
66
+ </html>
@@ -0,0 +1,191 @@
1
+ /* Visitor-graph pixel (Task 357).
2
+ *
3
+ * Single IIFE. Plain JS. No build step. ~3 KB minified.
4
+ *
5
+ * Loaded by hosted listing pages:
6
+ *
7
+ * <script defer src="https://<brand>/v.js" data-site="<slug>"></script>
8
+ *
9
+ * Emits to POST /v/event:
10
+ *
11
+ * pageview — once per session per URL (idempotent within 5s for SPA nav)
12
+ * click — DOM clicks on elements with data-track="<label>", or
13
+ * anchors flagged data-track-gallery / data-track-floorplan
14
+ * scroll — roll-up: one event per milestone {25,50,75,100} crossed
15
+ * dwell — on visibility change to hidden / pagehide, sendBeacon
16
+ *
17
+ * Identity: the server resolves identity from the mxy_v cookie. The pixel
18
+ * sends visitorId from localStorage (mints one if absent) as a fallback so
19
+ * cross-tab and same-browser-no-cookie cases still cluster.
20
+ */
21
+ (function () {
22
+ if (typeof window === 'undefined' || typeof document === 'undefined') return
23
+ var script = document.currentScript
24
+ if (!script) return
25
+ var siteSlug = script.getAttribute('data-site') || ''
26
+ var endpoint = new URL('/v/event', script.src).toString()
27
+
28
+ var VISITOR_KEY = 'mxy_visitor_id'
29
+ var SESSION_KEY = 'mxy_session_id'
30
+ var SESSION_PV_KEY = 'mxy_session_pv'
31
+ var LAST_PV_KEY = 'mxy_last_pv'
32
+ var SCROLL_KEY = 'mxy_scroll_depth'
33
+ var DWELL_KEY = 'mxy_dwell_start'
34
+
35
+ function randomId() {
36
+ if (window.crypto && window.crypto.getRandomValues) {
37
+ var buf = new Uint8Array(16)
38
+ window.crypto.getRandomValues(buf)
39
+ return Array.prototype.map.call(buf, function (b) {
40
+ return ('0' + b.toString(16)).slice(-2)
41
+ }).join('')
42
+ }
43
+ return String(Date.now()) + '_' + Math.random().toString(36).slice(2, 10)
44
+ }
45
+
46
+ function getVisitorId() {
47
+ try {
48
+ var v = localStorage.getItem(VISITOR_KEY)
49
+ if (v) return v
50
+ var fresh = randomId()
51
+ localStorage.setItem(VISITOR_KEY, fresh)
52
+ return fresh
53
+ } catch (_e) {
54
+ return 'noltc-' + randomId()
55
+ }
56
+ }
57
+
58
+ function getSessionId() {
59
+ try {
60
+ var s = sessionStorage.getItem(SESSION_KEY)
61
+ if (s) return s
62
+ var fresh = randomId()
63
+ sessionStorage.setItem(SESSION_KEY, fresh)
64
+ return fresh
65
+ } catch (_e) {
66
+ return 'nots-' + randomId()
67
+ }
68
+ }
69
+
70
+ var visitorId = getVisitorId()
71
+ var sessionId = getSessionId()
72
+
73
+ function send(payload) {
74
+ payload.site = siteSlug
75
+ payload.vid = visitorId
76
+ payload.sid = sessionId
77
+ payload.url = location.href
78
+ payload.path = location.pathname
79
+ payload.title = (document.title || '').slice(0, 256)
80
+ payload.ts = Date.now()
81
+ try {
82
+ var body = JSON.stringify(payload)
83
+ if (navigator.sendBeacon) {
84
+ var blob = new Blob([body], { type: 'application/json' })
85
+ if (navigator.sendBeacon(endpoint, blob)) return
86
+ }
87
+ fetch(endpoint, {
88
+ method: 'POST',
89
+ headers: { 'Content-Type': 'application/json' },
90
+ body: body,
91
+ credentials: 'include',
92
+ keepalive: true,
93
+ }).catch(function () { /* fire-and-forget */ })
94
+ } catch (_e) {
95
+ /* silently drop — pixel must never break the page */
96
+ }
97
+ }
98
+
99
+ // ─────────── pageview (idempotent within 5s for SPA nav) ───────────
100
+ function emitPageview() {
101
+ try {
102
+ var last = sessionStorage.getItem(LAST_PV_KEY)
103
+ if (last) {
104
+ var parsed = JSON.parse(last)
105
+ if (parsed && parsed.url === location.href && Date.now() - parsed.ts < 5000) {
106
+ return
107
+ }
108
+ }
109
+ sessionStorage.setItem(LAST_PV_KEY, JSON.stringify({ url: location.href, ts: Date.now() }))
110
+ sessionStorage.setItem(SESSION_PV_KEY, randomId()) // PageView id for milestone roll-up
111
+ sessionStorage.setItem(SCROLL_KEY, '0')
112
+ } catch (_e) { /* localStorage / sessionStorage disabled */ }
113
+
114
+ var pvId = ''
115
+ try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
116
+ send({
117
+ type: 'pageview',
118
+ pvId: pvId,
119
+ referrer: document.referrer || '',
120
+ })
121
+ }
122
+
123
+ // ─────────── click tracking ───────────
124
+ document.addEventListener('click', function (ev) {
125
+ var el = ev.target && (ev.target.closest ? ev.target.closest('[data-track]') : null)
126
+ if (!el) return
127
+ var label = el.getAttribute('data-track') || 'unknown'
128
+ var pvId = ''
129
+ try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
130
+ send({
131
+ type: 'click',
132
+ label: label.slice(0, 64),
133
+ pvId: pvId,
134
+ href: el.getAttribute('href') || '',
135
+ })
136
+ }, true)
137
+
138
+ // ─────────── scroll milestones (roll-up) ───────────
139
+ var scrollScheduled = false
140
+ function onScroll() {
141
+ if (scrollScheduled) return
142
+ scrollScheduled = true
143
+ requestAnimationFrame(function () {
144
+ scrollScheduled = false
145
+ var doc = document.documentElement
146
+ var max = Math.max(doc.scrollHeight, document.body.scrollHeight) - window.innerHeight
147
+ if (max <= 0) return
148
+ var pct = Math.floor(((window.scrollY || doc.scrollTop) / max) * 100)
149
+ var threshold = pct >= 100 ? 100 : pct >= 75 ? 75 : pct >= 50 ? 50 : pct >= 25 ? 25 : 0
150
+ if (threshold === 0) return
151
+ try {
152
+ var prev = parseInt(sessionStorage.getItem(SCROLL_KEY) || '0', 10) || 0
153
+ if (threshold <= prev) return
154
+ sessionStorage.setItem(SCROLL_KEY, String(threshold))
155
+ } catch (_e) {}
156
+ var pvId = ''
157
+ try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
158
+ send({ type: 'scroll', depth: threshold, pvId: pvId })
159
+ })
160
+ }
161
+ window.addEventListener('scroll', onScroll, { passive: true })
162
+
163
+ // ─────────── dwell on unload / visibility change ───────────
164
+ try { sessionStorage.setItem(DWELL_KEY, String(Date.now())) } catch (_e) {}
165
+ function emitDwell() {
166
+ var start = 0
167
+ try { start = parseInt(sessionStorage.getItem(DWELL_KEY) || '0', 10) || 0 } catch (_e) {}
168
+ if (!start) return
169
+ var ms = Date.now() - start
170
+ if (ms < 250) return // ignore drive-bys
171
+ var pvId = ''
172
+ try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
173
+ send({ type: 'dwell', ms: ms, pvId: pvId })
174
+ }
175
+ document.addEventListener('visibilitychange', function () {
176
+ if (document.visibilityState === 'hidden') emitDwell()
177
+ })
178
+ window.addEventListener('pagehide', emitDwell)
179
+
180
+ // SPA navigation hooks — many listing sites are client-routed.
181
+ var origPushState = history.pushState
182
+ history.pushState = function () {
183
+ var r = origPushState.apply(this, arguments)
184
+ setTimeout(emitPageview, 0)
185
+ return r
186
+ }
187
+ window.addEventListener('popstate', emitPageview)
188
+
189
+ // Boot.
190
+ emitPageview()
191
+ })()