@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.
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +95 -4
- package/payload/platform/package-lock.json +141 -125
- package/payload/platform/plugins/aeo/PLUGIN.md +1 -1
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.d.ts +2 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.js +121 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.d.ts +2 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.js +129 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/aeo/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/index.js +171 -0
- package/payload/platform/plugins/aeo/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.d.ts +27 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.js +274 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.js +38 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.d.ts +48 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.js +254 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.d.ts +25 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.js +78 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.d.ts +18 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.js +56 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts +37 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js +94 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts +12 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +34 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js.map +1 -1
- package/payload/platform/plugins/docs/references/aeo.md +2 -2
- package/payload/platform/plugins/docs/references/visitor-graph.md +82 -0
- package/payload/platform/plugins/memory/references/schema-base.md +44 -0
- package/payload/server/{chunk-FJCI6X3H.js → chunk-HCYM5FLU.js} +2 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/privacy.html +66 -0
- package/payload/server/public/v.js +191 -0
- 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,
|
|
@@ -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
|
+
})()
|