@rubytech/create-realagent-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.
- package/dist/index.js +23 -1
- package/package.json +2 -2
- package/payload/platform/config/brand.json +20 -1
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts +33 -0
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js +119 -0
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js.map +1 -0
- package/payload/platform/lib/aeo-llms-txt-writer/src/index.ts +172 -0
- package/payload/platform/lib/aeo-llms-txt-writer/tsconfig.json +8 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts +78 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-style/dist/index.js +296 -0
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +114 -0
- package/payload/platform/lib/graph-style/src/index.ts +307 -0
- package/payload/platform/lib/graph-style/tsconfig.json +9 -0
- package/payload/platform/lib/graph-style/vitest.config.ts +9 -0
- package/payload/platform/package-lock.json +318 -0
- package/payload/platform/package.json +3 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +4 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +73 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts +34 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js +176 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js.map +1 -0
- package/payload/platform/plugins/admin/skills/publish-site/SKILL.md +19 -57
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts +5 -38
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts.map +1 -1
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js +7 -114
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js.map +1 -1
- package/payload/platform/plugins/docs/references/visitor-graph.md +4 -3
- package/payload/platform/plugins/graph-viewer/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/graph-viewer/PLUGIN.md +56 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts +7 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +74 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js +43 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts +28 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js +73 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts +40 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js +77 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts +45 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +170 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/package.json +25 -0
- package/payload/platform/plugins/graph-viewer/mcp/vitest.config.ts +8 -0
- package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +35 -0
- package/payload/platform/scripts/check-plugin-tools-mcp-consistency.mjs +136 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +3 -3
- package/payload/premium-plugins/real-agent/plugins/buyers/PLUGIN.md +6 -2
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js +11 -5
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.d.ts +9 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.js +66 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.d.ts +12 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.js +21 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts +0 -5
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js +6 -27
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/index.ts +17 -5
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/lib/visitor-token.ts +79 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/mint-visitor-token.ts +35 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-backfill-from-logs.ts +6 -33
- package/payload/premium-plugins/real-agent/plugins/buyers/skills/property-recommender/SKILL.md +1 -10
- package/payload/premium-plugins/real-agent/plugins/leads/skills/lead-nurturing/SKILL.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/leads/skills/morning-round/SKILL.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/vendors/skills/vendor-updates/SKILL.md +1 -1
- package/payload/server/public/assets/{admin-D6IfAzYY.js → admin-FcRHAL-3.js} +7 -7
- package/payload/server/public/assets/{data-BGbQyufe.js → data-Ds37mflX.js} +1 -1
- package/payload/server/public/assets/{graph-D-1lRTeL.js → graph-CmWRhaiS.js} +1 -1
- package/payload/server/public/assets/graph-labels-Ch2r00Gt.js +1 -0
- package/payload/server/public/assets/page-BOtNny_4.js +51 -0
- package/payload/server/public/assets/page-BcHhJXUt.js +1 -0
- package/payload/server/public/consent.css +97 -0
- package/payload/server/public/consent.js +259 -0
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/public/privacy.html +68 -5
- package/payload/server/public/v.js +53 -0
- package/payload/server/server.js +465 -176
- package/payload/server/public/assets/graph-labels-BRXJHNYE.js +0 -1
- package/payload/server/public/assets/page-B4oirCvn.js +0 -1
- package/payload/server/public/assets/page-FmJ7PIYx.js +0 -51
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Visitor-token minting — admin-side surface (Task 362).
|
|
2
|
+
//
|
|
3
|
+
// Mirrors the mint half of platform/ui/app/lib/visitor-token.ts. Verification
|
|
4
|
+
// still lives in the UI server (the only verifier; mxy_v cookie + /v/event +
|
|
5
|
+
// /listings/:slug/click consume tokens). Both processes share one secret file
|
|
6
|
+
// under <MAXY_DIR>/credentials/visitor-token-secret via the wx-create pattern,
|
|
7
|
+
// so whichever process reads first mints; the other reads the same bytes.
|
|
8
|
+
//
|
|
9
|
+
// MAXY_DIR is resolved from process.env.LOG_DIR's parent — the buyers MCP is
|
|
10
|
+
// spawned with LOG_DIR=<MAXY_DIR>/logs (see buyers/PLUGIN.md env block), so
|
|
11
|
+
// dirname(LOG_DIR) is the brand-scoped config dir whether the brand is maxy,
|
|
12
|
+
// realagent, or any future brand.
|
|
13
|
+
|
|
14
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
15
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { dirname, resolve } from "node:path";
|
|
17
|
+
|
|
18
|
+
const TOKEN_PREFIX = "v1.";
|
|
19
|
+
const SECRET_BYTES = 32;
|
|
20
|
+
const DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
21
|
+
|
|
22
|
+
interface TokenPayload {
|
|
23
|
+
p: string;
|
|
24
|
+
e: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let cachedSecret: Buffer | null = null;
|
|
28
|
+
|
|
29
|
+
function secretFilePath(): string {
|
|
30
|
+
const logDir = process.env.LOG_DIR;
|
|
31
|
+
if (!logDir) {
|
|
32
|
+
throw new Error("[buyers] LOG_DIR unset — cannot resolve visitor-token secret path");
|
|
33
|
+
}
|
|
34
|
+
return resolve(dirname(logDir), "credentials", "visitor-token-secret");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getSecret(): Buffer {
|
|
38
|
+
if (cachedSecret) return cachedSecret;
|
|
39
|
+
const path = secretFilePath();
|
|
40
|
+
try {
|
|
41
|
+
const hex = readFileSync(path, "utf-8").trim();
|
|
42
|
+
if (hex.length === SECRET_BYTES * 2) {
|
|
43
|
+
cachedSecret = Buffer.from(hex, "hex");
|
|
44
|
+
return cachedSecret;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
/* fall through to mint */
|
|
48
|
+
}
|
|
49
|
+
const fresh = randomBytes(SECRET_BYTES).toString("hex");
|
|
50
|
+
try {
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
52
|
+
writeFileSync(path, fresh, { mode: 0o600, flag: "wx" });
|
|
53
|
+
console.error(`[buyers] visitor-token secret minted path=${path}`);
|
|
54
|
+
} catch {
|
|
55
|
+
// Race with the UI server (or a peer reader) between read and write;
|
|
56
|
+
// the second read below picks up their value.
|
|
57
|
+
}
|
|
58
|
+
const hex = readFileSync(path, "utf-8").trim();
|
|
59
|
+
cachedSecret = Buffer.from(hex, "hex");
|
|
60
|
+
return cachedSecret;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function base64urlEncode(buf: Buffer): string {
|
|
64
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface MintOpts {
|
|
68
|
+
personId: string;
|
|
69
|
+
expiryMs?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function mintVisitorToken(opts: MintOpts): { token: string; expiryMs: number } {
|
|
73
|
+
const expiryMs = opts.expiryMs ?? Date.now() + DEFAULT_TTL_MS;
|
|
74
|
+
const payload: TokenPayload = { p: opts.personId, e: expiryMs };
|
|
75
|
+
const payloadJson = JSON.stringify(payload);
|
|
76
|
+
const payloadB64 = base64urlEncode(Buffer.from(payloadJson, "utf-8"));
|
|
77
|
+
const sig = base64urlEncode(createHmac("sha256", getSecret()).update(payloadJson).digest());
|
|
78
|
+
return { token: `${TOKEN_PREFIX}${payloadB64}.${sig}`, expiryMs };
|
|
79
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// mint-visitor-token — admin-side mint of a signed visitor token bound to
|
|
2
|
+
// a known :Person elementId. The agent appends `&v=<token>` to listing-click
|
|
3
|
+
// URLs in outbound marketing surfaces (morning-round, lead-nurturing,
|
|
4
|
+
// vendor-updates). Verification stays in the UI server; this tool only mints.
|
|
5
|
+
|
|
6
|
+
import { mintVisitorToken } from "../lib/visitor-token.js";
|
|
7
|
+
|
|
8
|
+
export interface MintParams {
|
|
9
|
+
accountId: string;
|
|
10
|
+
personId: string;
|
|
11
|
+
ttlDays?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MintResult {
|
|
15
|
+
token: string;
|
|
16
|
+
expiryMs: number;
|
|
17
|
+
expiresAt: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEFAULT_TTL_DAYS = 30;
|
|
21
|
+
const MAX_TTL_DAYS = 365;
|
|
22
|
+
|
|
23
|
+
export async function mintVisitorTokenTool(p: MintParams): Promise<MintResult> {
|
|
24
|
+
if (!p.personId || typeof p.personId !== "string") {
|
|
25
|
+
throw new Error("personId required");
|
|
26
|
+
}
|
|
27
|
+
const ttlDays = Math.max(1, Math.min(p.ttlDays ?? DEFAULT_TTL_DAYS, MAX_TTL_DAYS));
|
|
28
|
+
const expiryMs = Date.now() + ttlDays * 24 * 60 * 60 * 1000;
|
|
29
|
+
const minted = mintVisitorToken({ personId: p.personId, expiryMs });
|
|
30
|
+
return {
|
|
31
|
+
token: minted.token,
|
|
32
|
+
expiryMs: minted.expiryMs,
|
|
33
|
+
expiresAt: new Date(minted.expiryMs).toISOString(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
// -> MATCH/MERGE Session, CREATE Click {label:'card', listingSlug}
|
|
13
13
|
// MERGE Session-[:HAS_EVENT]->Click
|
|
14
14
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// Recovery path: used when a live POST /v/event write was lost (process
|
|
16
|
+
// restart, dropped connection) or for ad-hoc forensics. The graph is
|
|
17
|
+
// the canonical surface — these log lines are the join key, not the
|
|
18
|
+
// aggregation path.
|
|
18
19
|
//
|
|
19
20
|
// Idempotency: re-running over the same log window does not duplicate
|
|
20
21
|
// Recommendation nodes (MERGE on the exact sessionKey + slug + ts tuple).
|
|
21
22
|
// Click nodes are immutable event records by design; if you re-run on a
|
|
22
|
-
// window already processed you get duplicates.
|
|
23
|
-
// cutover.
|
|
23
|
+
// window already processed you get duplicates.
|
|
24
24
|
|
|
25
25
|
import { readFileSync } from "node:fs";
|
|
26
26
|
import { getSession } from "../lib/neo4j.js";
|
|
@@ -42,8 +42,6 @@ export interface BackfillResult {
|
|
|
42
42
|
clickedParsed: number;
|
|
43
43
|
clickedWritten: number;
|
|
44
44
|
unparseable: number;
|
|
45
|
-
parityCheck: "ok" | "mismatch";
|
|
46
|
-
parityDelta: { recommended: number; clicked: number };
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
interface RecLine { kind: "rec"; sessionKey: string; listingSlug: string; ts: string }
|
|
@@ -144,31 +142,8 @@ export async function visitorBackfillFromLogs(p: BackfillParams): Promise<Backfi
|
|
|
144
142
|
clickedWritten += Number(row?.get("written") ?? 0);
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
const parityRes = await session.run(
|
|
148
|
-
`MATCH (rec:Recommendation {accountId: $accountId})
|
|
149
|
-
WHERE rec.recommendedAt >= datetime($since) AND rec.recommendedAt < datetime($until)
|
|
150
|
-
WITH count(DISTINCT rec) AS recCount
|
|
151
|
-
OPTIONAL MATCH (cl:Click {accountId: $accountId, label: 'card'})
|
|
152
|
-
WHERE cl.occurredAt >= datetime($since) AND cl.occurredAt < datetime($until)
|
|
153
|
-
AND cl.backfilled = true
|
|
154
|
-
RETURN recCount, count(DISTINCT cl) AS clickCount`,
|
|
155
|
-
{
|
|
156
|
-
accountId: p.accountId,
|
|
157
|
-
since: windowStart ?? new Date(0).toISOString(),
|
|
158
|
-
until: windowEnd ? new Date(Date.parse(windowEnd) + 1).toISOString() : new Date().toISOString(),
|
|
159
|
-
},
|
|
160
|
-
);
|
|
161
|
-
const head = parityRes.records[0];
|
|
162
|
-
const recGraph = Number(head?.get("recCount") ?? 0);
|
|
163
|
-
const clickGraph = Number(head?.get("clickCount") ?? 0);
|
|
164
|
-
const parityDelta = {
|
|
165
|
-
recommended: recGraph - rec.length,
|
|
166
|
-
clicked: clickGraph - click.length,
|
|
167
|
-
};
|
|
168
|
-
const parityCheck: "ok" | "mismatch" = parityDelta.recommended === 0 && parityDelta.clicked === 0 ? "ok" : "mismatch";
|
|
169
|
-
|
|
170
145
|
console.error(
|
|
171
|
-
`[visitor-backfill] done
|
|
146
|
+
`[visitor-backfill] done recParsed=${rec.length} recWritten=${recommendedWritten} clickParsed=${click.length} clickWritten=${clickedWritten} unparseable=${unparseable}`,
|
|
172
147
|
);
|
|
173
148
|
|
|
174
149
|
return {
|
|
@@ -180,8 +155,6 @@ export async function visitorBackfillFromLogs(p: BackfillParams): Promise<Backfi
|
|
|
180
155
|
clickedParsed: click.length,
|
|
181
156
|
clickedWritten,
|
|
182
157
|
unparseable,
|
|
183
|
-
parityCheck,
|
|
184
|
-
parityDelta,
|
|
185
158
|
};
|
|
186
159
|
} finally {
|
|
187
160
|
await session.close();
|
package/payload/premium-plugins/real-agent/plugins/buyers/skills/property-recommender/SKILL.md
CHANGED
|
@@ -104,16 +104,7 @@ The Hono route at `/listings/:slug/click` emits the companion line on each card-
|
|
|
104
104
|
[property-card-click] sessionKey=<sk> listingSlug=<slug> ts=<iso>
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
# cards emitted per session
|
|
111
|
-
awk -F'sessionKey=' '/\[property-recommended\]/{split($2,a," "); print a[1]}' server.log | sort | uniq -c
|
|
112
|
-
# cards clicked per session
|
|
113
|
-
awk -F'sessionKey=' '/\[property-card-click\]/{split($2,a," "); print a[1]}' server.log | sort | uniq -c
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Sessions appearing in the first count but not the second never clicked through. A high uniform zero is the load-bearing alarm — the recommendation is not earning the click. Sessions where `sessionKey=invalid` appears on the click line indicate a malformed `session` query param on the redirect URL (skill template defect); the click still landed but cannot be joined to its fire log.
|
|
107
|
+
CTR is read from the graph via the `visitor-recommendation-ctr` MCP tool, which joins `:Recommendation` and `:Click` nodes by `sessionKey` + `listingSlug` over the requested window. The log lines remain the join key for `visitor-backfill-from-logs` (one-shot importer for late-arriving sessions) and for ad-hoc forensics; they are not the aggregation surface. Sessions where `sessionKey=invalid` appears on the click line indicate a malformed `session` query param on the redirect URL (skill template defect); the click still landed but cannot be joined to its fire log.
|
|
117
108
|
|
|
118
109
|
## Visitor-graph integration (Task 357)
|
|
119
110
|
|
|
@@ -140,7 +140,7 @@ Encourage the user to capture:
|
|
|
140
140
|
|
|
141
141
|
Call `visitor-engagement-score` with the default 30-day window to rank `:Person` contacts by engagement: visit count × recency × scroll depth × dwell time. The output is the nurture queue. Top of the queue gets outreach today; bottom decays into low-touch.
|
|
142
142
|
|
|
143
|
-
When composing outbound messages that include a listing URL,
|
|
143
|
+
When composing outbound messages that include a listing URL, mint a token via the `mint-visitor-token` tool against the recipient's `:Person` elementId (`mint-visitor-token({ personId })`) and append the returned `token` as `&v=<token>` to the click URL. Without the token the recipient's click is anonymous, defeating the point of the nurture loop. Mint a fresh token per outbound message — TTL defaults to 30 days.
|
|
144
144
|
|
|
145
145
|
The signal that says "this person stopped engaging": their score drops two consecutive weeks. That's a prompt for a pattern-break message (different listing, different framing), not a follow-up of the same thread.
|
|
146
146
|
|
|
@@ -79,5 +79,5 @@ Before the morning round, call `visitor-recent-by-person` for every `:Person` in
|
|
|
79
79
|
|
|
80
80
|
Also call `visitor-recent-by-page` for any listing that picks up activity from previously-unidentified visitors — these are anonymous prospects worth surfacing to the operator even when no `:Person` is attached.
|
|
81
81
|
|
|
82
|
-
Outbound URLs in the briefing (links the operator might click to send to the contact) are minted via `mint-visitor-token`
|
|
82
|
+
Outbound URLs in the briefing (links the operator might click to send to the contact) are minted via the `mint-visitor-token` tool — call it with the recipient's `:Person` elementId (`mint-visitor-token({ personId })`) and append the returned `token` as `&v=<token>` to the existing `/listings/<slug>/click?session=<sk>` URL. The token binds the recipient's browser to the `:Person` on click; never inline the secret yourself, always call the tool. Mint a fresh token per outbound URL — TTL defaults to 30 days and tokens are cheap to re-issue.
|
|
83
83
|
|
|
@@ -162,5 +162,5 @@ The weekly vendor email reports on viewing activity against the vendor's listing
|
|
|
162
162
|
|
|
163
163
|
The vendor sees real engagement, not just enquiry count. Tone stays factual — "twelve known visitors and twenty-three anonymous browsers" is honest; "interest is strong" is editorial. Let the numbers speak.
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
When the update embeds a link to the vendor's listing, mint a token via `mint-visitor-token({ personId })` against the vendor's `:Person` elementId and append the returned `token` as `&v=<token>` to the `/listings/<slug>/click?session=<sk>` URL. The vendor is a `:Person` like any other contact — binding their click attributes their own engagement with the marketing page (did they open the report, did they actually look at their listing) to the same visitor graph the buyer side feeds. Mint a fresh token per outbound update; TTL defaults to 30 days.
|
|
166
166
|
|