@rubytech/create-maxy-code 0.1.123 → 0.1.126

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 (65) hide show
  1. package/dist/index.js +49 -4
  2. package/package.json +2 -2
  3. package/payload/platform/lib/graph-style/dist/index.d.ts +78 -0
  4. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/graph-style/dist/index.js +296 -0
  6. package/payload/platform/lib/graph-style/dist/index.js.map +1 -0
  7. package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +114 -0
  8. package/payload/platform/lib/graph-style/src/index.ts +307 -0
  9. package/payload/platform/lib/graph-style/tsconfig.json +9 -0
  10. package/payload/platform/lib/graph-style/vitest.config.ts +9 -0
  11. package/payload/platform/neo4j/schema.cypher +50 -0
  12. package/payload/platform/package-lock.json +318 -0
  13. package/payload/platform/package.json +3 -2
  14. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  15. package/payload/platform/plugins/docs/references/visitor-graph.md +5 -4
  16. package/payload/platform/plugins/docs/references/voice-mirror-guide.md +60 -0
  17. package/payload/platform/plugins/email/skills/email-composition/SKILL.md +6 -2
  18. package/payload/platform/plugins/graph-viewer/.claude-plugin/plugin.json +8 -0
  19. package/payload/platform/plugins/graph-viewer/PLUGIN.md +56 -0
  20. package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts +7 -0
  21. package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts.map +1 -0
  22. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +74 -0
  23. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -0
  24. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts +5 -0
  25. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts.map +1 -0
  26. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js +43 -0
  27. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js.map +1 -0
  28. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts +28 -0
  29. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts.map +1 -0
  30. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js +73 -0
  31. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js.map +1 -0
  32. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts +40 -0
  33. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts.map +1 -0
  34. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js +77 -0
  35. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js.map +1 -0
  36. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts +45 -0
  37. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -0
  38. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +170 -0
  39. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -0
  40. package/payload/platform/plugins/graph-viewer/mcp/package.json +25 -0
  41. package/payload/platform/plugins/graph-viewer/mcp/vitest.config.ts +8 -0
  42. package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +35 -0
  43. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +17 -0
  44. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
  46. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +11 -2
  47. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
  48. package/payload/platform/plugins/memory/references/schema-base.md +12 -4
  49. package/payload/platform/plugins/venture-studio/skills/investor-data-room/SKILL.md +2 -0
  50. package/payload/platform/scripts/check-plugin-tools-mcp-consistency.mjs +136 -0
  51. package/payload/premium-plugins/venture-studio/skills/investor-data-room/SKILL.md +2 -0
  52. package/payload/server/public/assets/{admin-D6IfAzYY.js → admin-FcRHAL-3.js} +7 -7
  53. package/payload/server/public/assets/{data-BGbQyufe.js → data-Ds37mflX.js} +1 -1
  54. package/payload/server/public/assets/{graph-D-1lRTeL.js → graph-CmWRhaiS.js} +1 -1
  55. package/payload/server/public/assets/graph-labels-Ch2r00Gt.js +1 -0
  56. package/payload/server/public/assets/page-BOtNny_4.js +51 -0
  57. package/payload/server/public/assets/page-BcHhJXUt.js +1 -0
  58. package/payload/server/public/data.html +3 -3
  59. package/payload/server/public/graph.html +3 -3
  60. package/payload/server/public/index.html +4 -4
  61. package/payload/server/public/privacy.html +2 -2
  62. package/payload/server/server.js +236 -92
  63. package/payload/server/public/assets/graph-labels-BRXJHNYE.js +0 -1
  64. package/payload/server/public/assets/page-B4oirCvn.js +0 -1
  65. package/payload/server/public/assets/page-FmJ7PIYx.js +0 -51
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Shared palette + display-string primitives for any surface that renders
3
+ * the Neo4j graph. Both the live `/graph` page in platform/ui and the
4
+ * graph-viewer MCP plugin's PNG renderer import from here so a node looks
5
+ * the same wherever it appears: same colour family per label, same short
6
+ * canvas label per node.
7
+ *
8
+ * UI-only constants (filter-popover sets, zoom-tier wrap helpers,
9
+ * agent-action label sets) stay in platform/ui/app/lib/graph-labels.ts and
10
+ * platform/ui/app/graph/display-helpers.ts — they re-export the symbols
11
+ * defined here. The two parity tests (this lib's __tests__ and the UI's
12
+ * unchanged colour-stability tests) keep the surfaces in lockstep.
13
+ *
14
+ * Sources of truth:
15
+ * - Label list: .docs/neo4j.md "Schema (Phase 0)" + "Standards Compliance"
16
+ * - Brand colour for LocalBusiness: brand.json defaultColors.primary
17
+ * (resolved at runtime; this module supplies the schema-default fallback)
18
+ */
19
+
20
+ /**
21
+ * Label → display colour mapping. Closed list of node labels written by
22
+ * the Maxy MCP write paths. A new label without an entry here renders in
23
+ * FALLBACK_LABEL_COLOUR and is omitted from the legend.
24
+ */
25
+ export const GRAPH_LABEL_COLOURS: Record<string, string> = {
26
+ // Business identity — slate-blue family
27
+ LocalBusiness: '#4F6B8A',
28
+ Service: '#6B85A0',
29
+ PriceSpecification: '#8AA0B8',
30
+ OpeningHoursSpecification: '#A8BACE',
31
+
32
+ // Estate-agent ontology. Listing/Property share the business slate-blue
33
+ // family; Viewing/Offer are transactional events placed in a warm amber
34
+ // family distinct from Agent and from the dusty-rose Task/Project/Event
35
+ // hues so the legend reads them as a separate band.
36
+ Listing: '#3F5670',
37
+ Property: '#5C7A99',
38
+ Viewing: '#D4A574',
39
+ Offer: '#B08850',
40
+
41
+ // External business entity — schema:Organization, written by memory-write
42
+ // as the Supplier label. Warm tobacco — distinct hue from internal-business
43
+ // slates so external entities are scannable at a glance.
44
+ Organization: '#8A6B47',
45
+
46
+ // People — terracotta family (warm, human)
47
+ Person: '#B86E4A',
48
+ UserProfile: '#D08960',
49
+ Preference: '#DFA67A',
50
+ AdminUser: '#8C5230',
51
+ AccessGrant: '#66381F',
52
+
53
+ // Knowledge — moss family (organic)
54
+ KnowledgeDocument: '#6E8A5A',
55
+ // chunked WhatsApp/messaging archive parent. Sits in the
56
+ // knowledge moss family because it's structurally a document parent
57
+ // (HAS_SECTION → :Section:Conversation chunks); the conversational
58
+ // plum family is reserved for the live Conversation/Message labels.
59
+ ConversationArchive: '#82A06A',
60
+ Section: '#8AA876',
61
+ Chunk: '#A6C194',
62
+ DigitalDocument: '#5A7548',
63
+ CreativeWork: '#B5C9A2',
64
+ Question: '#C7D6B5',
65
+ FAQPage: '#95B385',
66
+ DefinedTerm: '#76906A',
67
+ Review: '#3F5A2E',
68
+ ImageObject: '#A0BB8C',
69
+
70
+ // Conversational — muted plum/lavender family.
71
+ // Pairwise-distinctness invariants enforced by the UI test
72
+ // graph-labels.test.ts; UserMessage matches Preference (Person/family cue,
73
+ // the one intentional duplicate in the registry).
74
+ Conversation: '#6B5A85',
75
+ AdminConversation: '#4F4070',
76
+ PublicConversation: '#8A75A8',
77
+ Message: '#6B7280',
78
+ UserMessage: '#DFA67A',
79
+ AssistantMessage: '#C9A876',
80
+ ToolCall: '#B5ABCB',
81
+
82
+ // Tasks / projects / events — dusty rose family
83
+ Task: '#B0617A',
84
+ Project: '#8E4A60',
85
+ Event: '#C68095',
86
+
87
+ // Workflows — muted teal
88
+ Workflow: '#3A6F77',
89
+ WorkflowStep: '#5A8E96',
90
+ WorkflowRun: '#7AAAB2',
91
+ StepResult: '#9CC4CB',
92
+
93
+ // Email — muted moss, hue-adjacent to Knowledge but lower-saturation
94
+ // and warmer; never co-rendered with KnowledgeDocument.
95
+ Email: '#6F7F4A',
96
+ EmailAccount: '#91A063',
97
+
98
+ // Public-agent projection — burnished bronze sits between
99
+ // people-terracotta and email-moss but shares neither hue.
100
+ Agent: '#B8893D',
101
+ }
102
+
103
+ export const FALLBACK_LABEL_COLOUR = '#94A3B8'
104
+
105
+ export const ALL_GRAPH_LABELS: readonly string[] = Object.freeze(
106
+ Object.keys(GRAPH_LABEL_COLOURS)
107
+ )
108
+
109
+ /**
110
+ * Select the sublabel whose colour should drive the node hue. Walks the
111
+ * label array from index 1 (skipping the base label at index 0) and
112
+ * returns the first sublabel that has a registered colour; falls back to
113
+ * labels[0]. Pure — drift-counting lives in resolveNodeColour.
114
+ */
115
+ export function pickDisplayLabel(labels: readonly string[]): string | null {
116
+ for (let i = 1; i < labels.length; i++) {
117
+ if (Object.prototype.hasOwnProperty.call(GRAPH_LABEL_COLOURS, labels[i])) {
118
+ return labels[i]
119
+ }
120
+ }
121
+ return labels[0] ?? null
122
+ }
123
+
124
+ export function colourForLabel(label: string): string {
125
+ return GRAPH_LABEL_COLOURS[label] ?? FALLBACK_LABEL_COLOUR
126
+ }
127
+
128
+ export interface ResolvedNodeColour {
129
+ displayLabel: string | null
130
+ colour: string
131
+ driftCandidates: number
132
+ }
133
+
134
+ /**
135
+ * Single-source colour resolver. driftCandidates counts registered
136
+ * sublabels in labels[1..]; a node with two registered sublabels has
137
+ * driftCandidates = 2 and its rendered hue is order-dependent.
138
+ */
139
+ export function resolveNodeColour(labels: readonly string[]): ResolvedNodeColour {
140
+ const displayLabel = pickDisplayLabel(labels)
141
+ const colour = displayLabel ? colourForLabel(displayLabel) : FALLBACK_LABEL_COLOUR
142
+ let driftCandidates = 0
143
+ for (let i = 1; i < labels.length; i++) {
144
+ if (Object.prototype.hasOwnProperty.call(GRAPH_LABEL_COLOURS, labels[i])) {
145
+ driftCandidates++
146
+ }
147
+ }
148
+ return { displayLabel, colour, driftCandidates }
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Display strings — short canvas label + untruncated display name.
153
+ // Verbatim from platform/ui/app/graph/display-helpers.ts at extraction time.
154
+ // UI helpers that wrap on zoom tier, pack into multi-line canvas labels,
155
+ // or compose tooltips stay in display-helpers.ts and call into these.
156
+ // ---------------------------------------------------------------------------
157
+
158
+ /**
159
+ * Structural type used by display-string helpers. Mirrors the shape of
160
+ * GraphNode without coupling to it.
161
+ */
162
+ export interface GraphNodeLike {
163
+ id: string
164
+ labels: string[]
165
+ properties: Record<string, unknown>
166
+ }
167
+
168
+ const ISO_MINUTE_RE = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})/
169
+ const ISO_SECOND_RE = /^\d{4}-\d{2}-\d{2}T(\d{2}:\d{2}:\d{2})/
170
+
171
+ /**
172
+ * YYYY-MM-DD HH:mm from an ISO 8601 prefix. Reads the prefix directly so
173
+ * timezone-stored UTC timestamps render identically across operator
174
+ * timezones; parsing to Date + reformatting would flip the day.
175
+ */
176
+ export function formatRunTimestamp(iso: string): string | null {
177
+ const m = iso.match(ISO_MINUTE_RE)
178
+ if (!m) return null
179
+ return `${m[1]} ${m[2]}`
180
+ }
181
+
182
+ /** HH:mm:ss from an ISO 8601 prefix. Same tz-stable contract. */
183
+ export function formatMessageTimestamp(iso: string): string | null {
184
+ const m = iso.match(ISO_SECOND_RE)
185
+ if (!m) return null
186
+ return m[1]
187
+ }
188
+
189
+ /**
190
+ * 24-char canvas label for a node. Label precedence per label family
191
+ * (ToolCall.toolName, WorkflowRun startedAt formatted, WorkflowStep.label,
192
+ * Person givenName+familyName compose, Agent displayName/slug, Message
193
+ * role+timestamp, ConversationArchive.title), then a generic fallthrough
194
+ * across curated keys (name, title, summary, givenName, subject, text),
195
+ * then a Conversation-specific `Conv <id8>` fallback so unpopulated
196
+ * conversations don't render as the literal label.
197
+ */
198
+ export function pickShortLabel(node: GraphNodeLike): string {
199
+ const props = node.properties
200
+ const primaryLabel = node.labels[0]
201
+ if (primaryLabel === 'ToolCall') {
202
+ const v = props.toolName
203
+ if (typeof v === 'string' && v.length > 0) {
204
+ return v.length > 24 ? v.slice(0, 24) + '…' : v
205
+ }
206
+ } else if (primaryLabel === 'WorkflowRun') {
207
+ const v = props.startedAt
208
+ if (typeof v === 'string' && v.length > 0) {
209
+ const ts = formatRunTimestamp(v)
210
+ if (ts) return ts
211
+ }
212
+ } else if (primaryLabel === 'WorkflowStep') {
213
+ const v = props.label
214
+ if (typeof v === 'string' && v.length > 0) {
215
+ return v.length > 24 ? v.slice(0, 24) + '…' : v
216
+ }
217
+ } else if (primaryLabel === 'Person') {
218
+ const given = typeof props.givenName === 'string' ? props.givenName : ''
219
+ const family = typeof props.familyName === 'string' ? props.familyName : ''
220
+ const full = [given, family].filter((s) => s.length > 0).join(' ')
221
+ if (full.length > 0) {
222
+ return full.length > 24 ? full.slice(0, 24) + '…' : full
223
+ }
224
+ } else if (primaryLabel === 'Agent') {
225
+ const dn = typeof props.displayName === 'string' ? props.displayName : ''
226
+ const slug = typeof props.slug === 'string' ? props.slug : ''
227
+ const v = dn.length > 0 ? dn : slug
228
+ if (v.length > 0) {
229
+ return v.length > 24 ? v.slice(0, 24) + '…' : v
230
+ }
231
+ } else if (primaryLabel === 'Message') {
232
+ const role = typeof props.role === 'string' ? props.role : ''
233
+ const roleShort =
234
+ role === 'user' ? 'user'
235
+ : role === 'assistant' ? 'asst'
236
+ : role === 'system' ? 'sys'
237
+ : role === 'tool' ? 'tool'
238
+ : null
239
+ const createdAt = typeof props.createdAt === 'string' ? props.createdAt : ''
240
+ if (roleShort && createdAt) {
241
+ const ts = formatMessageTimestamp(createdAt)
242
+ if (ts) return `${roleShort} ${ts}`
243
+ }
244
+ } else if (primaryLabel === 'ConversationArchive') {
245
+ const t = typeof props.title === 'string' ? props.title : ''
246
+ if (t.length > 0) {
247
+ return t.length > 24 ? t.slice(0, 24) + '…' : t
248
+ }
249
+ return primaryLabel
250
+ }
251
+ for (const k of ['name', 'title', 'summary', 'givenName', 'subject', 'text']) {
252
+ const v = props[k]
253
+ if (typeof v === 'string' && v.length > 0) {
254
+ return v.length > 24 ? v.slice(0, 24) + '…' : v
255
+ }
256
+ }
257
+ if (primaryLabel === 'Conversation') {
258
+ const id = typeof props.conversationId === 'string' ? props.conversationId : null
259
+ if (id) return `Conv ${id.slice(0, 8)}`
260
+ }
261
+ return primaryLabel ?? '…'
262
+ }
263
+
264
+ /**
265
+ * Untruncated sibling of pickShortLabel. Used by side-panel headings and
266
+ * tooltips where the 24-char canvas-overlap cap does not apply.
267
+ */
268
+ export function pickDisplayName(node: GraphNodeLike): string {
269
+ const props = node.properties
270
+ const primaryLabel = node.labels[0]
271
+ if (primaryLabel === 'ToolCall') {
272
+ const v = props.toolName
273
+ if (typeof v === 'string' && v.length > 0) return v
274
+ } else if (primaryLabel === 'WorkflowRun') {
275
+ const v = props.startedAt
276
+ if (typeof v === 'string' && v.length > 0) {
277
+ const ts = formatRunTimestamp(v)
278
+ if (ts) return ts
279
+ }
280
+ } else if (primaryLabel === 'WorkflowStep') {
281
+ const v = props.label
282
+ if (typeof v === 'string' && v.length > 0) return v
283
+ } else if (primaryLabel === 'Person') {
284
+ const given = typeof props.givenName === 'string' ? props.givenName : ''
285
+ const family = typeof props.familyName === 'string' ? props.familyName : ''
286
+ const full = [given, family].filter((s) => s.length > 0).join(' ')
287
+ if (full.length > 0) return full
288
+ } else if (primaryLabel === 'Agent') {
289
+ const dn = typeof props.displayName === 'string' ? props.displayName : ''
290
+ const slug = typeof props.slug === 'string' ? props.slug : ''
291
+ const v = dn.length > 0 ? dn : slug
292
+ if (v.length > 0) return v
293
+ } else if (primaryLabel === 'ConversationArchive') {
294
+ const t = typeof props.title === 'string' ? props.title : ''
295
+ if (t.length > 0) return t
296
+ return primaryLabel
297
+ }
298
+ for (const k of ['name', 'title', 'summary', 'givenName', 'subject', 'text']) {
299
+ const v = props[k]
300
+ if (typeof v === 'string' && v.length > 0) return v
301
+ }
302
+ if (primaryLabel === 'Conversation') {
303
+ const id = typeof props.conversationId === 'string' ? props.conversationId : null
304
+ if (id) return `Conv ${id.slice(0, 8)}`
305
+ }
306
+ return primaryLabel ?? '…'
307
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["src/__tests__"]
9
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ globals: false,
7
+ include: ["src/__tests__/**/*.test.ts"],
8
+ },
9
+ });
@@ -1174,6 +1174,56 @@ FOR (i:ImageObject) ON (i.accountId);
1174
1174
  CREATE INDEX postal_address_postcode IF NOT EXISTS
1175
1175
  FOR (a:PostalAddress) ON (a.postalCode);
1176
1176
 
1177
+ // ----------------------------------------------------------
1178
+ // Voice Mirror (Task 356) — authorial-voice capture + application
1179
+ // ----------------------------------------------------------
1180
+ // :VoiceProfile holds the distilled style card for one :AdminUser.
1181
+ // One profile per AdminUser; the (accountId, adminUserId) pair is unique.
1182
+ // :VoiceEdit captures operator edits on agent drafts as feedback,
1183
+ // stamped with a Haiku-summarised intent; many edits feed back into
1184
+ // the next distillation.
1185
+ //
1186
+ // `authorshipMode` is an enum property on content-bearing labels
1187
+ // (`:KnowledgeDocument`, `:Email`, `:Message`, `:SocialPost`) with
1188
+ // values `human-only | human-led-agent-assisted | agent-led-human-reviewed
1189
+ // | agent-only | unknown`. Legacy nodes default to `unknown` until
1190
+ // reviewed via the voice-mirror backfill flow. The index lets the
1191
+ // distillation walk filter on `authorshipMode = 'human-only'` cheaply.
1192
+
1193
+ CREATE CONSTRAINT voice_profile_admin_unique IF NOT EXISTS
1194
+ FOR (v:VoiceProfile) REQUIRE (v.accountId, v.adminUserId) IS UNIQUE;
1195
+
1196
+ CREATE INDEX voice_profile_account IF NOT EXISTS
1197
+ FOR (v:VoiceProfile) ON (v.accountId);
1198
+
1199
+ CREATE INDEX voice_edit_admin IF NOT EXISTS
1200
+ FOR (e:VoiceEdit) ON (e.adminUserId);
1201
+
1202
+ CREATE INDEX knowledge_document_authorship IF NOT EXISTS
1203
+ FOR (k:KnowledgeDocument) ON (k.authorshipMode);
1204
+
1205
+ CREATE INDEX message_authorship IF NOT EXISTS
1206
+ FOR (m:Message) ON (m.authorshipMode);
1207
+
1208
+ CREATE INDEX email_authorship IF NOT EXISTS
1209
+ FOR (e:Email) ON (e.authorshipMode);
1210
+
1211
+ CREATE INDEX social_post_authorship IF NOT EXISTS
1212
+ FOR (s:SocialPost) ON (s.authorshipMode);
1213
+
1214
+ // :Section authorshipMode — the corpus walk includes :Section:Conversation
1215
+ // chat-archive chunks. Index on the base :Section label so the per-label
1216
+ // filter on authorshipMode is cheap. The conjunction with the :Conversation
1217
+ // sub-label is applied at query time, not at index time.
1218
+ CREATE INDEX section_authorship IF NOT EXISTS
1219
+ FOR (s:Section) ON (s.authorshipMode);
1220
+
1221
+ CREATE INDEX email_account IF NOT EXISTS
1222
+ FOR (e:Email) ON (e.accountId);
1223
+
1224
+ CREATE INDEX social_post_account IF NOT EXISTS
1225
+ FOR (s:SocialPost) ON (s.accountId);
1226
+
1177
1227
  // ----------------------------------------------------------
1178
1228
  // AEOAudit — Answer Engine Optimisation audit history (Task 354).
1179
1229
  // Written by the aeo plugin's aeo-audit-page tool when invoked