@rubytech/create-maxy-code 0.1.60 → 0.1.62

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 (163) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  3. package/payload/platform/plugins/brochures/skills/property-brochure/SKILL.md +26 -3
  4. package/payload/platform/plugins/brochures/skills/property-brochure/references/registers.md +1 -1
  5. package/payload/platform/plugins/docs/references/admin-session.md +2 -2
  6. package/payload/platform/plugins/docs/references/internals.md +1 -1
  7. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  8. package/payload/platform/plugins/epc/.claude-plugin/plugin.json +17 -0
  9. package/payload/platform/plugins/epc/PLUGIN.md +85 -0
  10. package/payload/platform/plugins/epc/mcp/dist/index.d.ts +2 -0
  11. package/payload/platform/plugins/epc/mcp/dist/index.d.ts.map +1 -0
  12. package/payload/platform/plugins/epc/mcp/dist/index.js +117 -0
  13. package/payload/platform/plugins/epc/mcp/dist/index.js.map +1 -0
  14. package/payload/platform/plugins/epc/mcp/dist/lib/crypto.d.ts +3 -0
  15. package/payload/platform/plugins/epc/mcp/dist/lib/crypto.d.ts.map +1 -0
  16. package/payload/platform/plugins/epc/mcp/dist/lib/crypto.js +72 -0
  17. package/payload/platform/plugins/epc/mcp/dist/lib/crypto.js.map +1 -0
  18. package/payload/platform/plugins/epc/mcp/dist/lib/epc-api.d.ts +60 -0
  19. package/payload/platform/plugins/epc/mcp/dist/lib/epc-api.d.ts.map +1 -0
  20. package/payload/platform/plugins/epc/mcp/dist/lib/epc-api.js +181 -0
  21. package/payload/platform/plugins/epc/mcp/dist/lib/epc-api.js.map +1 -0
  22. package/payload/platform/plugins/epc/mcp/dist/lib/file-crypto.d.ts +3 -0
  23. package/payload/platform/plugins/epc/mcp/dist/lib/file-crypto.d.ts.map +1 -0
  24. package/payload/platform/plugins/epc/mcp/dist/lib/file-crypto.js +49 -0
  25. package/payload/platform/plugins/epc/mcp/dist/lib/file-crypto.js.map +1 -0
  26. package/payload/platform/plugins/epc/mcp/dist/lib/key-store.d.ts +15 -0
  27. package/payload/platform/plugins/epc/mcp/dist/lib/key-store.d.ts.map +1 -0
  28. package/payload/platform/plugins/epc/mcp/dist/lib/key-store.js +130 -0
  29. package/payload/platform/plugins/epc/mcp/dist/lib/key-store.js.map +1 -0
  30. package/payload/platform/plugins/epc/mcp/dist/lib/neo4j.d.ts +5 -0
  31. package/payload/platform/plugins/epc/mcp/dist/lib/neo4j.d.ts.map +1 -0
  32. package/payload/platform/plugins/epc/mcp/dist/lib/neo4j.js +38 -0
  33. package/payload/platform/plugins/epc/mcp/dist/lib/neo4j.js.map +1 -0
  34. package/payload/platform/plugins/epc/mcp/dist/tools/key-deregister.d.ts +4 -0
  35. package/payload/platform/plugins/epc/mcp/dist/tools/key-deregister.d.ts.map +1 -0
  36. package/payload/platform/plugins/epc/mcp/dist/tools/key-deregister.js +9 -0
  37. package/payload/platform/plugins/epc/mcp/dist/tools/key-deregister.js.map +1 -0
  38. package/payload/platform/plugins/epc/mcp/dist/tools/key-list.d.ts +4 -0
  39. package/payload/platform/plugins/epc/mcp/dist/tools/key-list.d.ts.map +1 -0
  40. package/payload/platform/plugins/epc/mcp/dist/tools/key-list.js +10 -0
  41. package/payload/platform/plugins/epc/mcp/dist/tools/key-list.js.map +1 -0
  42. package/payload/platform/plugins/epc/mcp/dist/tools/key-register.d.ts +5 -0
  43. package/payload/platform/plugins/epc/mcp/dist/tools/key-register.d.ts.map +1 -0
  44. package/payload/platform/plugins/epc/mcp/dist/tools/key-register.js +36 -0
  45. package/payload/platform/plugins/epc/mcp/dist/tools/key-register.js.map +1 -0
  46. package/payload/platform/plugins/epc/mcp/dist/tools/lookup.d.ts +10 -0
  47. package/payload/platform/plugins/epc/mcp/dist/tools/lookup.d.ts.map +1 -0
  48. package/payload/platform/plugins/epc/mcp/dist/tools/lookup.js +30 -0
  49. package/payload/platform/plugins/epc/mcp/dist/tools/lookup.js.map +1 -0
  50. package/payload/platform/plugins/epc/mcp/package-lock.json +2566 -0
  51. package/payload/platform/plugins/epc/mcp/package.json +21 -0
  52. package/payload/platform/plugins/epc/mcp/src/__tests__/epc-api.test.ts +251 -0
  53. package/payload/platform/plugins/epc/mcp/src/__tests__/key-roundtrip.test.ts +113 -0
  54. package/payload/platform/plugins/epc/mcp/src/__tests__/lookup.test.ts +181 -0
  55. package/payload/platform/plugins/epc/mcp/src/__tests__/schema-parity.test.ts +54 -0
  56. package/payload/platform/plugins/epc/mcp/src/index.ts +156 -0
  57. package/payload/platform/plugins/epc/mcp/src/lib/crypto.ts +79 -0
  58. package/payload/platform/plugins/epc/mcp/src/lib/epc-api.ts +241 -0
  59. package/payload/platform/plugins/epc/mcp/src/lib/file-crypto.ts +55 -0
  60. package/payload/platform/plugins/epc/mcp/src/lib/key-store.ts +172 -0
  61. package/payload/platform/plugins/epc/mcp/src/lib/neo4j.ts +47 -0
  62. package/payload/platform/plugins/epc/mcp/src/tools/key-deregister.ts +9 -0
  63. package/payload/platform/plugins/epc/mcp/src/tools/key-list.ts +10 -0
  64. package/payload/platform/plugins/epc/mcp/src/tools/key-register.ts +44 -0
  65. package/payload/platform/plugins/epc/mcp/src/tools/lookup.ts +43 -0
  66. package/payload/platform/plugins/epc/mcp/tsconfig.json +20 -0
  67. package/payload/platform/plugins/epc/mcp/vitest.config.ts +8 -0
  68. package/payload/platform/plugins/preval/.claude-plugin/plugin.json +1 -1
  69. package/payload/platform/plugins/preval/PLUGIN.md +25 -17
  70. package/payload/platform/plugins/preval/skills/property-preval/SKILL.md +107 -39
  71. package/payload/platform/plugins/preval/skills/property-preval/references/render.py +449 -84
  72. package/payload/platform/plugins/preval/skills/property-preval/references/template-inputs.schema.json +126 -80
  73. package/payload/platform/plugins/preval/skills/property-preval/references/template.html +183 -124
  74. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  75. package/payload/platform/services/claude-session-manager/dist/http-server.js +6 -1
  76. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  77. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +7 -0
  78. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  79. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +8 -0
  80. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  81. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  82. package/payload/platform/templates/agents/admin/SOUL.md +1 -1
  83. package/payload/premium-plugins/real-agent/BUNDLE.md +4 -2
  84. package/payload/premium-plugins/real-agent/agents/valuer.md +1 -1
  85. package/payload/premium-plugins/real-agent/plugins/.claude-plugin/marketplace.json +5 -0
  86. package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/SKILL.md +26 -3
  87. package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/registers.md +1 -1
  88. package/payload/premium-plugins/real-agent/plugins/epc/.claude-plugin/plugin.json +17 -0
  89. package/payload/premium-plugins/real-agent/plugins/epc/PLUGIN.md +85 -0
  90. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/index.d.ts +2 -0
  91. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/index.d.ts.map +1 -0
  92. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/index.js +117 -0
  93. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/index.js.map +1 -0
  94. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/crypto.d.ts +3 -0
  95. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/crypto.d.ts.map +1 -0
  96. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/crypto.js +72 -0
  97. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/crypto.js.map +1 -0
  98. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/epc-api.d.ts +60 -0
  99. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/epc-api.d.ts.map +1 -0
  100. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/epc-api.js +181 -0
  101. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/epc-api.js.map +1 -0
  102. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/file-crypto.d.ts +3 -0
  103. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/file-crypto.d.ts.map +1 -0
  104. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/file-crypto.js +49 -0
  105. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/file-crypto.js.map +1 -0
  106. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/key-store.d.ts +15 -0
  107. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/key-store.d.ts.map +1 -0
  108. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/key-store.js +130 -0
  109. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/key-store.js.map +1 -0
  110. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/neo4j.d.ts +5 -0
  111. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/neo4j.d.ts.map +1 -0
  112. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/neo4j.js +38 -0
  113. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/neo4j.js.map +1 -0
  114. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-deregister.d.ts +4 -0
  115. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-deregister.d.ts.map +1 -0
  116. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-deregister.js +9 -0
  117. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-deregister.js.map +1 -0
  118. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-list.d.ts +4 -0
  119. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-list.d.ts.map +1 -0
  120. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-list.js +10 -0
  121. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-list.js.map +1 -0
  122. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-register.d.ts +5 -0
  123. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-register.d.ts.map +1 -0
  124. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-register.js +36 -0
  125. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-register.js.map +1 -0
  126. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/lookup.d.ts +10 -0
  127. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/lookup.d.ts.map +1 -0
  128. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/lookup.js +30 -0
  129. package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/lookup.js.map +1 -0
  130. package/payload/premium-plugins/real-agent/plugins/epc/mcp/package-lock.json +2566 -0
  131. package/payload/premium-plugins/real-agent/plugins/epc/mcp/package.json +21 -0
  132. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/__tests__/epc-api.test.ts +251 -0
  133. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/__tests__/key-roundtrip.test.ts +113 -0
  134. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/__tests__/lookup.test.ts +181 -0
  135. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/__tests__/schema-parity.test.ts +54 -0
  136. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/index.ts +156 -0
  137. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/crypto.ts +79 -0
  138. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/epc-api.ts +241 -0
  139. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/file-crypto.ts +55 -0
  140. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/key-store.ts +172 -0
  141. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/neo4j.ts +47 -0
  142. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/tools/key-deregister.ts +9 -0
  143. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/tools/key-list.ts +10 -0
  144. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/tools/key-register.ts +44 -0
  145. package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/tools/lookup.ts +43 -0
  146. package/payload/premium-plugins/real-agent/plugins/epc/mcp/tsconfig.json +20 -0
  147. package/payload/premium-plugins/real-agent/plugins/epc/mcp/vitest.config.ts +8 -0
  148. package/payload/premium-plugins/real-agent/plugins/preval/.claude-plugin/plugin.json +1 -1
  149. package/payload/premium-plugins/real-agent/plugins/preval/PLUGIN.md +25 -17
  150. package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/SKILL.md +107 -39
  151. package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/render.py +449 -84
  152. package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template-inputs.schema.json +126 -80
  153. package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template.html +183 -124
  154. package/payload/server/public/assets/{admin-Bp-BjBCX.js → admin-DhN3G8W7.js} +1 -1
  155. package/payload/server/public/assets/{data-BGUAGVkV.js → data-B2ZVXOcE.js} +1 -1
  156. package/payload/server/public/assets/{graph-MvYxZOBF.js → graph-BAMGPHrK.js} +1 -1
  157. package/payload/server/public/assets/{graph-labels-D865qb3K.js → graph-labels-D9eBbvxo.js} +1 -1
  158. package/payload/server/public/assets/{page-C2b1nlOc.js → page-CV27Al6Z.js} +1 -1
  159. package/payload/server/public/assets/{page--hOVRrgN.js → page-DjdVMWCz.js} +1 -1
  160. package/payload/server/public/data.html +3 -3
  161. package/payload/server/public/graph.html +3 -3
  162. package/payload/server/public/index.html +4 -4
  163. package/payload/server/server.js +3 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.60",
3
+ "version": "0.1.62",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -107,7 +107,7 @@ Tools are available via the `admin` MCP server.
107
107
  - `hooks/pre-tool-use.sh` — enforces admin agent write boundaries
108
108
  - `hooks/playwright-file-guard.sh` — rewrites file:// URLs to a backgrounded loopback http.server before Playwright sees them
109
109
  - `hooks/webfetch-preflight.mjs` — short-circuits WebFetch on JS-SPA shells with a structured `WEBFETCH_CANNOT_READ_JS_SPA` error so the agent surfaces a loud failure to the owner instead of paying the 60s extraction timeout. Fail-open on any internal error.
110
- - `hooks/turn-completed-graph-write.sh` — Stop hook fired once per completed admin-agent turn. Gates on `MAXY_SESSION_ROLE=admin` + `MAXY_IS_RECORDER!=1` so it never recurses into the recorder PTY or fires on public sessions. **Task 134 redesign** — the recorder is spawned via the same route Sidebar uses. ONE POST to `POST /api/admin/claude-sessions`, body carries `{specialist:'database-operator', initialMessage:'<instruction>\n\n<last-turn-text>', originatingSessionId:<op>}` — no synthetic `senderId: 'turn-recorder'` marker. The Hono wrapper bypasses cookie auth on this exact method+path when the request originates from `127.0.0.1` (same trust boundary the claude-session-manager itself relies on), looks up the operator's senderId from the manager's `/<originatingSessionId>/meta`, and forwards a Sidebar-shape spawn body. The `/recorder-spawn` sibling route is gone. The hook reads its UI port from `MAXY_UI_INTERNAL_PORT` (stamped on the manager systemd unit) — no fallback; absence emits `[turn-recorder] spawn-failed reason=missing-env env=MAXY_UI_INTERNAL_PORT` to stderr instead of silently 19199-ing. The manager's `POST /:id/input` wraps multi-line `text` in DEC bracketed-paste markers (`\e[200~ ... \e[201~\r`) so the TUI submits it as one turn both Sidebar's multi-line first messages and the recorder's multi-line instruction depend on that branch. The recorder-auto-archive subscriber stops the recorder PTY as soon as its JSONL contains an assistant message with `stop_reason === "end_turn"`. **Task 129** — every emit goes through `POST /api/admin/log-ingest` so the lines land in `server.log` keyed by the operator session id. The chain is `trigger` → `spawn-request` → `spawn-success` → `input-delivered` → `tool-call` × N → `tool-result` × N → `write-complete` → `auto-archive`; each gated-off path emits one `trigger-skipped reason=<role-not-admin|is-recorder|empty-stdin|missing-transcript|last-turn-empty>` line. The spawn body carries `originatingSessionId` as a structural top-level field so downstream loggers read the operator id without parsing prose. Failure modes — `spawn-failed`, `tool-surface-missing`, `input-failed`, `write-empty`, `auto-archive reason=stale-recorder` — each emit one named line; absence is itself a defect.
110
+ - `hooks/turn-completed-graph-write.sh` — Stop hook fired once per completed admin-agent turn. Gates on `MAXY_SESSION_ROLE=admin` + `MAXY_IS_RECORDER!=1` so it never recurses into the recorder PTY or fires on public sessions. **Task 134 redesign** — the recorder is spawned via the same route Sidebar uses. ONE POST to `POST /api/admin/claude-sessions`, body carries `{specialist:'database-operator', initialMessage:'<instruction>\n\n<last-turn-text>', originatingSessionId:<op>}` — no synthetic `senderId: 'turn-recorder'` marker. The Hono wrapper bypasses cookie auth on this exact method+path when the request originates from `127.0.0.1` (same trust boundary the claude-session-manager itself relies on), looks up the operator's senderId from the manager's `/<originatingSessionId>/meta`, and forwards a Sidebar-shape spawn body. The `/recorder-spawn` sibling route is gone. The hook reads its UI port from `MAXY_UI_INTERNAL_PORT` (stamped on the manager systemd unit) — no fallback; absence emits `[turn-recorder] spawn-failed reason=missing-env env=MAXY_UI_INTERNAL_PORT` to stderr instead of silently 19199-ing. **Task 153** `initialMessage` rides on the `/spawn` body as a trailing positional argv to `claude`, so the database-operator session's JSONL first `role=user` line equals `"update the graph with any new or missing information or intent\n\n<last-turn-text>"` verbatim. No separate `POST /:id/input` call, no bracketed-paste, no keystroke injection. The recorder-auto-archive subscriber stops the recorder PTY as soon as its JSONL contains an assistant message with `stop_reason === "end_turn"`. **Task 129** — every emit goes through `POST /api/admin/log-ingest` so the lines land in `server.log` keyed by the operator session id. The chain is `trigger` → `spawn-request` → `spawn-success` → `input-delivered` → `tool-call` × N → `tool-result` × N → `write-complete` → `auto-archive`; each gated-off path emits one `trigger-skipped reason=<role-not-admin|is-recorder|empty-stdin|missing-transcript|last-turn-empty>` line. The spawn body carries `originatingSessionId` as a structural top-level field so downstream loggers read the operator id without parsing prose. Failure modes — `spawn-failed`, `tool-surface-missing`, `input-failed`, `write-empty`, `auto-archive reason=stale-recorder` — each emit one named line; absence is itself a defect.
111
111
 
112
112
  ## Session identifiers (Task 135)
113
113
 
@@ -27,8 +27,8 @@ Do not silently nest the output under a brand workspace just because one is pres
27
27
 
28
28
  ```bash
29
29
  mkdir -p <output_dir>/web
30
- cp ~/.claude/plugins/real-estate-brochure/skills/property-brochure/references/template.html <output_dir>/brochure.html
31
- cp ~/.claude/plugins/real-estate-brochure/skills/property-brochure/references/index.html <output_dir>/web/index.html
30
+ cp ~/.claude/plugins/brochures/skills/property-brochure/references/template.html <output_dir>/brochure.html
31
+ cp ~/.claude/plugins/brochures/skills/property-brochure/references/index.html <output_dir>/web/index.html
32
32
  ```
33
33
 
34
34
  `<output_dir>` is whatever the **Output location** section above resolves to — typically `./output/` in the caller's CWD.
@@ -91,16 +91,39 @@ unsubstituted=$(grep -c '{{' "$output_dir/brochure.html" "$output_dir/web/index.
91
91
 
92
92
  - **Property photographs** — minimum 16, ideally 25+
93
93
  - **Property name and address** (see `property-extract → Property name vs address`)
94
- - **Floor plan image**, **EPC rating**
94
+ - **Floor plan image** — MUST be `Read` before any floor-area, per-floor breakdown, or room-dimension substitution. The image carries measured ground / first / outbuilding totals; the listing's prose description does not.
95
+ - **EPC rating** — verified from the EPC chart image; if not legible, ask the operator.
95
96
  - **Property walkthrough transcript or written description**
96
97
  - **Seller brief** at `<property_dir>/seller-brief.md` — non-empty answers to all 20 questions in `make-brochure/references/seller-brief-template.md`. Stop and ask if missing; do not synthesise from the listing description.
97
98
  - **Agent details** — from `property.json -> agent` (no built-in default). Logos follow the colour-of-the-art naming convention: `<brand>-logo-light.png` (for dark surfaces — cover/back) and `<brand>-logo-dark.png` (for light surfaces — TOC).
98
99
  - **Page orientation** — `landscape` (default) or `portrait`
99
100
  - **DESIGN doc** (optional) — controls register choice; never replaces the template structure
100
101
 
102
+ ## Pre-substitution verification gate
103
+
104
+ Before any `{{ token }}` is substituted, walk every field whose primary source is a file already on disk or one direct question to the operator. **Read the source or ask. Never silently default to `TBC`, "approx.", or an empty string when the answer was a `Read` or a one-line question away.**
105
+
106
+ The recurring failure pattern is trusting a derived summary (a WebFetch markdown digest, a listing's prose description) over the primary source already in `<property_dir>/raw/`. The fix is to consult the primary source before the substitution dict is filled — not after the brochure renders. This rule is the factual counterpart to the contact-sheet rule: that one says "verify each photo by pixel content, not filename"; this one says "verify each fact by primary source, not by derived summary."
107
+
108
+ ### Fields that need active verification
109
+
110
+ | Field | Primary source — Read or ask, never paraphrase |
111
+ |---|---|
112
+ | `floor_area`, `mi_floor_area`, per-floor breakdown, `floorplan_lead`, `floorplan_sub`, landing-page floor-plan rows | The floor plan image. Floor plans are almost always under 2000 px on the longest edge — safe to `Read` directly. Take ground, first, garage, outbuilding totals from the image. **Never substitute from the listing's prose ("3,000 sq ft", "spacious")**. |
113
+ | Room dimensions in chapter copy (principal bedroom, kitchen, sitting room) and en-suite / bathroom layout | Same floor plan. If the listing prose says "principal bedroom with en-suite" but the plan shows no adjacent shower room, the plan wins. |
114
+ | `video_url`, `video_short_url` | Inspect the live listing page directly (browser MCP, or a second targeted WebFetch asking specifically "is there a video tour, give the URL"). A WebFetch summary that omits the field is not evidence the field is absent. If still unclear, ask the operator. |
115
+ | `mi_council_tax`, `mi_broadband`, `mi_mobile`, `mi_tenure`, `mi_year_built`, `mi_uprn`, `mi_title_number`, `mi_flood_*`, `mi_local_authority`, `mi_easements`, `mi_covenants`, `mi_rights_of_way` | These come from a Sprift KFB report, the seller, or the agent. If not supplied, prompt the operator with one consolidated question listing every unknown — do not silently fill with `TBC`. |
116
+ | `epc_rating` | The EPC chart image. If the rating isn't legible, prompt. |
117
+ | `agent.phone`, `agent.email`, brand logo, brand name | From `property.json -> agent` and the brand pack. If either is absent, prompt — never invent a default. |
118
+
119
+ ### Operator confirmation prompt
120
+
121
+ When any field above is unresolved after consulting its primary source, **stop before substitution** and present the operator with one consolidated prompt listing every unknown by name, the primary source already consulted, and the candidate value (if any). Operator answers in one pass; substitution then proceeds from confirmed values. **Never render a brochure with fields silently coerced to `TBC`, an approximation, or a default. A `TBC` shipped to a buyer must have been confirmed as a `TBC` by the operator, not chosen by the agent.**
122
+
101
123
  ## Hard rules (load-bearing — do not violate)
102
124
 
103
125
  - **Never `Read` an image whose longest edge exceeds 2000px** — it can drop the session. Measure with `sips -g pixelWidth -g pixelHeight <file>` first.
126
+ - **Never substitute a floor-area value without first `Read`ing the floor plan image.** Listing-prose figures and round-number guesses are silent factual defects.
104
127
  - **Never assign an image to a slot from filename alone.** Verify by pixel content via a contact-sheet montage.
105
128
  - **Never synthesise a location map** from postcode, OSM call, or AI artwork. Extract the agent's screenshot or omit. ([references/structure.md → Location map](references/structure.md))
106
129
  - **Page count must be even** (16 default; 12 if the property is too small). 14 is forbidden.
@@ -31,7 +31,7 @@ The signal is the user's wording, not the property. Don't infer premium from a h
31
31
  - `<brand>/properties/<other-listing>/output/brochure.html` — a sibling property's rendered brochure
32
32
  - A previous brochure that was hand-edited after the last template release
33
33
  - A "brand brochure" or "marketing PDF" supplied by the agency
34
- - An older version of `template.html` cached anywhere outside `~/.claude/plugins/real-estate-brochure/skills/property-brochure/references/`
34
+ - An older version of `template.html` cached anywhere outside `~/.claude/plugins/brochures/skills/property-brochure/references/`
35
35
 
36
36
  Sibling brochures contain property-specific copy edits, hand-tuned image positions, ad-hoc CSS overrides, and drift from the canonical layout. Starting from one bakes that drift into the new brochure and silently diverges every property's structure over time. The DESIGN doc supplies design **tokens** (fonts, palette, voice) — it does not authorise reaching for an existing brochure as a starting point.
37
37
 
@@ -75,13 +75,13 @@ grep '\[client-acquire\]' ~/.${brand}/logs/claude-agent-stream-*.log
75
75
 
76
76
  ## Spawn-with-`initialMessage` wrapper
77
77
 
78
- The Hono route `POST /api/admin/claude-sessions` at [`platform/ui/server/routes/admin/claude-sessions.ts`](../../../ui/server/routes/admin/claude-sessions.ts) is the single canonical entry point for any caller — UI click handler or programmatic — that needs to open an admin PTY session with a first user prompt. Direct calls to the session-manager's `/spawn` and `/:id/input` are not allowed; the wrapper owns the spawn-then-input ordering, the per-spawn enrichment (`aboutOwner`, `dormantPlugins`, `activePlugins`, `specialistDomains`, `tunnelUrl`), and the `senderId` resolution from the admin session cookie. The session manager binds to `127.0.0.1` only so the wrapper is the sole authorised caller; any ad-hoc spawn that bypasses it duplicates this contract and is a bug.
78
+ The Hono route `POST /api/admin/claude-sessions` at [`platform/ui/server/routes/admin/claude-sessions.ts`](../../../ui/server/routes/admin/claude-sessions.ts) is the single canonical entry point for any caller — UI click handler or programmatic — that needs to open an admin PTY session with a first user prompt. Direct calls to the session-manager's `/spawn` are not allowed; the wrapper owns the per-spawn enrichment (`aboutOwner`, `dormantPlugins`, `activePlugins`, `specialistDomains`, `tunnelUrl`) and the `senderId` resolution from the admin session cookie. The session manager binds to `127.0.0.1` only so the wrapper is the sole authorised caller; any ad-hoc spawn that bypasses it duplicates this contract and is a bug.
79
79
 
80
80
  **Body schema.** `{channel?, permissionMode?, initialMessage?, specialist?, model?}` for the cookie path (Sidebar — `senderId` resolved from the admin session cookie, `hidden: false`). The Stop-hook loopback caller adds `adminSessionId` and uses three overrides: `specialist: 'database-operator'`, `model: 'haiku'`, plus `initialMessage`. The wrapper rejects nothing structurally on the cookie path — string fields are coerced or defaulted (`channel` → `'browser'`, `permissionMode` → undefined, `initialMessage` → null if empty/whitespace). The loopback path refuses with `400 admin-session-id-required` if `adminSessionId` is missing or non-UUID, and `404 admin-session-not-found` if the manager has no StoredSession for it.
81
81
 
82
82
  **Auth surfaces.** Two: `cookie` (Sidebar — `requireAdminSession` validates `session_key` and resolves `senderId` from the in-memory session store) and `loopback` (Stop hook — `requireAdminSessionOrLoopback` skips cookie auth when method is `POST`, path is exactly `/api/admin/claude-sessions`, and the TCP peer is `127.0.0.1`/`::1`; the handler then resolves the operator's `senderId` from `GET {managerBase}/<adminSessionId>/meta`). Both the manager and the UI server bind to `127.0.0.1` only for internal traffic, so the trust boundary is reachable only from a process running on the device. Task 147 — one route, one body shape (the loopback variant differs only by overrides), no `/recorder-spawn` sibling, no XFF disambiguation.
83
83
 
84
- **Forwarded endpoints.** Two upstream calls per spawn-with-message: `POST {managerBase}/spawn` (enriched body) returns `{sessionId}`; when `initialMessage` is set, the wrapper then issues `POST {managerBase}/<sessionId>/input { text: <initialMessage> }` synchronously so the PTY has its first turn stimulus before the operator types. The HTTP response streams the spawn upstream body straight through.
84
+ **Forwarded endpoints.** One upstream call per spawn-with-message: `POST {managerBase}/spawn` (enriched body, with `initialMessage` inlined when set). The manager appends `initialMessage` as the trailing positional argv to `claude` so the CLI processes it as the session's first user turn at PTY startup the JSONL first `role=user` line equals `initialMessage` verbatim. No follow-up `POST {managerBase}/<sessionId>/input` call, no bracketed-paste, no keystroke injection. The HTTP response streams the spawn upstream body straight through. (Task 153.)
85
85
 
86
86
  **Callers.** `Sidebar.tsx`'s "+ New session" click handler (sends no `initialMessage` — Task 146 made Claude Code's own label canonical, so no UI-side stimulus is injected); the turn-recorder hook (loopback path — see "Turn-recorder" below); future programmatic spawns. Every caller routes through this wrapper. The on-the-wire signal that the contract held is the `[claude-session-manager:wrapper] spawn-request-in surface=<cookie|loopback>` log line followed by `forward-spawn-done`.
87
87
 
@@ -494,7 +494,7 @@ A boot gate refuses to start the manager when any admin-allowlisted tool `mcp__<
494
494
 
495
495
  **Per-spawn signals (server.log).** Every spawn emits `pty-spawn-mcp-config servers=<N> tools=<M> bytes=<B> path=<…>` once, plus one `pty-spawn-agents-dir role=<admin|public> path=<…>` per added directory. The diagnostic one-liner is `grep -E 'pty-spawn-mcp-config|pty-spawn-agents-dir|mcp-config-allowlist-coverage|boot-failed reason=mcp-allowlist' ~/.<brand>/logs/server.log | tail -50`.
496
496
 
497
- **Programmatic spawn entry point.** Every admin PTY spawn that needs a first user prompt — UI click, turn-recorder hook, future automation — routes through the single wrapper at [`platform/ui/server/routes/admin/claude-sessions.ts`](../../../ui/server/routes/admin/claude-sessions.ts). The wrapper owns the spawn → input ordering, the per-spawn enrichment (owner profile, dormant/active plugins, specialist domains, tunnel URL), and the `senderId` resolution; it forwards to the session manager's `POST /spawn` then `POST /<sessionId>/input` on `127.0.0.1`. No caller composes those two manager endpoints directly. The recorder branch sets `senderId: 'turn-recorder'`. See `admin-session.md` "Spawn-with-initialMessage wrapper" for the body schema and caller list.
497
+ **Programmatic spawn entry point.** Every admin PTY spawn that needs a first user prompt — UI click, turn-recorder hook, future automation — routes through the single wrapper at [`platform/ui/server/routes/admin/claude-sessions.ts`](../../../ui/server/routes/admin/claude-sessions.ts). The wrapper owns the per-spawn enrichment (owner profile, dormant/active plugins, specialist domains, tunnel URL) and the `senderId` resolution; it forwards a single `POST /spawn` to the session manager on `127.0.0.1`, with `initialMessage` inlined on that body. The manager appends `initialMessage` as the trailing positional argv to `claude`, so the CLI processes it as the session's first user turn at PTY startup — no separate `POST /<sessionId>/input` call, no bracketed-paste. (Task 153.) See `admin-session.md` "Spawn-with-initialMessage wrapper" for the body schema and caller list.
498
498
 
499
499
  **Recorder auto-archive (lifecycle, not user-initiated).** The session manager's `attachRecorderAutoArchive` ([`platform/services/claude-session-manager/src/http-server.ts:178`](../../../services/claude-session-manager/src/http-server.ts)) wires every spawn whose `senderId === 'turn-recorder'` to a JSONL watcher: as soon as the recorder's JSONL contains `"stop_reason":"end_turn"`, the manager calls `stopSession`, the PTY exits, the PID file is removed, and `fs-watcher.ts:275-297` demotes the row to `state: 'archived'`. This is the lifecycle archive path — the row stays in place, the JSONL stays on disk, no directory move. It is structurally distinct from the user-initiated `POST /api/admin/claude-sessions/:id/archive` route, which actually `mv`s the JSONL between `<slugDir>` and `<slugDir>/archive/`; that path is the operator pruning their visible session list, not the recorder's per-turn cleanup.
500
500
 
@@ -73,7 +73,7 @@ Brand decides which premium plugins ship. Maxy installs ship only `platform/plug
73
73
  | Plugin | Type | What it does | Public agent |
74
74
  |--------|------|-------------|-------------|
75
75
  | `teaching` | Skills | Interactive tutoring, lesson planning, and study pack generation from your knowledge base | Yes — all 3 skills serve students and parents |
76
- | `real-agent` | Bundle (12 sub-plugins) | UK estate agency skills — sales, listings, vendor management, buyer management, lead generation, coaching, business operations, teaching, Loop CRM (five value pillars: auto-respond, viewing lifecycle, pipeline mining, listings prospecting, maintenance & preferences), PropertyData market analytics (valuation, sold prices, £/sqft baselines, £/sqft growth, demand-rent, area risk, planning precedent, UPRN matching, property-type distribution), property brochures, social-share image cards, A4 market reports, and single-address preval packs (full UK address → 4-page A4 PDF covering valuation, area, and demand). 3 specialist roles (negotiator, valuer, compliance) | 4 sub-plugins (estate-sales, buyers, estate-coaching, estate-teaching) |
76
+ | `real-agent` | Bundle (13 sub-plugins) | UK estate agency skills — sales, listings, vendor management, buyer management, lead generation, coaching, business operations, teaching, Loop CRM (five value pillars: auto-respond, viewing lifecycle, pipeline mining, listings prospecting, maintenance & preferences), PropertyData market analytics (valuation, sold prices, £/sqft baselines, £/sqft growth, demand-rent, area risk, planning precedent, UPRN matching, property-type distribution), gov.uk EPC floor-area lookup, property brochures, social-share image cards, A4 market reports, and single-address preval packs (full UK address → 4-page A4 PDF covering valuation, area, and demand). 3 specialist roles (negotiator, valuer, compliance) | 4 sub-plugins (estate-sales, buyers, estate-coaching, estate-teaching) |
77
77
  | `writer-craft` | Skills + Agent | Manuscript review and writing craft — story architecture, reader engagement, prose craft, editorial practice, and multi-level review | No — writing craft serves the author |
78
78
 
79
79
  **How it works:** Every boot {{productName}} delivers the brand's premium plugins from staging into `platform/plugins/` and stamps `enabledPlugins` against what is actually on disk. No conversation needed — the brand's full set is active from the first turn after install. Updates and reinstalls re-deliver from staging.
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "real-agent-epc",
3
+ "description": "gov.uk EPC register — by-address and by-UPRN lookup of the most recent Energy Performance Certificate per matched property. Returns total floor area in m² and sqft (m² × 10.7639), current and potential energy ratings, lodgement date, and UPRN. One encrypted credential per account.",
4
+ "version": "0.1.0",
5
+ "author": {
6
+ "name": "Rubytech LLC"
7
+ },
8
+ "mcpServers": {
9
+ "real-agent-epc": {
10
+ "type": "stdio",
11
+ "command": "node",
12
+ "args": [
13
+ "${CLAUDE_PLUGIN_ROOT}/mcp/dist/index.js"
14
+ ]
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: real-agent-epc
3
+ description: "gov.uk EPC register — by-address and by-UPRN lookup of the most recent Energy Performance Certificate per matched property. Returns total floor area in m² and sqft (m² × 10.7639), current and potential energy ratings, lodgement date, and UPRN. One encrypted credential per account."
4
+ tools:
5
+ - name: epc-key-register
6
+ publicAllowlist: false
7
+ - name: epc-key-deregister
8
+ publicAllowlist: false
9
+ - name: epc-key-list
10
+ publicAllowlist: false
11
+ - name: epc-lookup-by-address
12
+ publicAllowlist: false
13
+ - name: epc-lookup-by-uprn
14
+ publicAllowlist: false
15
+ always: false
16
+ metadata: {"platform":{"optional":true,"embed":["admin"]}}
17
+ mcp:
18
+ command: node
19
+ args:
20
+ - ${PLATFORM_ROOT}/lib/mcp-spawn-tee/dist/index.js
21
+ - ${PREMIUM_PLUGINS_ROOT}/real-agent/plugins/epc/mcp/dist/index.js
22
+ env:
23
+ MCP_SPAWN_TEE_NAME: epc
24
+ LOG_DIR: ${LOG_DIR}
25
+ PLATFORM_ROOT: ${PLATFORM_ROOT}
26
+ ACCOUNT_ID: ${ACCOUNT_ID}
27
+ SESSION_ID: ${SESSION_ID}
28
+ ---
29
+
30
+ # Real Agent — EPC register
31
+
32
+ Local stdio MCP that proxies the gov.uk Energy Performance Certificate register at `epc.opendatacommunities.org`. Closes the by-address floor-area gap PropertyData does not cover: the EPC register holds `total-floor-area` in m² for every England & Wales property certified since ~2008, which lets preval multiply subject sqft × postcode £/sqft band into a £ range for the actual address.
33
+
34
+ ## Capabilities
35
+
36
+ - **Key management** — register, deregister, and list a single EPC credential per account. AES-256-GCM encrypted at rest in Neo4j under the `EpcApiKey` label.
37
+ - **By-address lookup** — full address (+ optional postcode) → most-recent EPC per matched property. Returns m², sqft, current and potential ratings, lodgement date, UPRN.
38
+ - **By-UPRN lookup** — known UPRN → same shape, one property.
39
+
40
+ ## Credential format
41
+
42
+ The EPC register uses HTTP Basic auth where the credential is the **email** of the account that generated the API key, then a colon, then the **API key**: `email:api-key`. Both halves are required. The credential is supplied as one string to `epc-key-register` so the surface mirrors property-data exactly; the MCP stores the colon-joined value and base64-encodes it for every request.
43
+
44
+ ## Security
45
+
46
+ The EPC credential is encrypted at rest with AES-256-GCM. The encryption-key file lives at `~/<configDir>/.epc-encryption-key` (platform mode) or `~/.claude/.realagent-epc-encryption-key` (standalone mode), both with mode `0600`. Never include the credential in responses; identify the key by registration timestamp only. If the operator asks for the credential, explain that it is encrypted and cannot be retrieved — they must re-register from the EPC dashboard.
47
+
48
+ ## Standalone mode (`~/.claude/` spawn)
49
+
50
+ The same `mcp/dist/index.js` boots in two environments, selected by env flag:
51
+
52
+ | Env var | Platform mode | Standalone mode |
53
+ |---|---|---|
54
+ | `EPC_STANDALONE` | unset | `"1"` |
55
+ | `ACCOUNT_ID` | platform tenant id | absent (defaults to `"local"`) |
56
+ | `NEO4J_URI` etc. | required | unused |
57
+ | `EPC_API_KEY` | unused | optional one-shot bypass (skips the store entirely) |
58
+
59
+ In standalone mode the encrypted credential lives at `~/.claude/.realagent-epc-key.enc`.
60
+
61
+ ## When to use these tools
62
+
63
+ ### Pillar — Subject-property floor area for preval valuation
64
+
65
+ **Triggers** — operator says "sqft for", "floor area of", "epc for", "what's the energy rating of", or a preval skill needs subject-property sqft to multiply against `property-data-prices-per-sqf`:
66
+ - `epc-lookup-by-address` — when only the address is known
67
+ - `epc-lookup-by-uprn` — when `property-data-address-match-uprn` already resolved the UPRN
68
+
69
+ ### Key prerequisite
70
+
71
+ Every lookup tool requires a registered credential. If `epc-key-list` returns nothing for the account, surface the registration prompt before attempting any lookup — every downstream tool returns `key-not-registered` otherwise.
72
+
73
+ ## Tool error envelope
74
+
75
+ Every tool returns either the parsed result as JSON text or an `isError: true` envelope with deterministic text shape:
76
+
77
+ - `Error: key-not-registered. Register an EPC API key with epc-key-register before calling this tool.`
78
+ - `Error: http-<status> from EPC register. <upstream-message>`
79
+ - `Error: bad-response from EPC register (non-JSON, length=<n>).`
80
+
81
+ Zero matches are **not** an error: lookup tools return `{"status":"ok","matches":[]}` when the EPC register has no certificate on file for the address or UPRN. The upstream 404 is still logged with `status=http-404`.
82
+
83
+ ## Free but rate-limited
84
+
85
+ The EPC register API is free (no credits) and email-verified — there is no per-call cost. The upstream throttle is generous for one-off subject-property lookups but is not designed for postcode-wide fan-out. The MCP performs one lookup per call; consumers should not loop directly across hundreds of addresses.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,117 @@
1
+ // Logging-tee was a platform-wrapper concern: the platform's mcp-spawn-tee
2
+ // pipes our stderr where it wants. In standalone mode (Claude Code native
3
+ // spawn) the host owns the stderr pipe. Either way, we just write to stderr.
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+ import { keyRegister } from "./tools/key-register.js";
8
+ import { keyDeregister } from "./tools/key-deregister.js";
9
+ import { keyList } from "./tools/key-list.js";
10
+ import { epcLookupByAddress, epcLookupByUprn } from "./tools/lookup.js";
11
+ import { closeDriver } from "./lib/neo4j.js";
12
+ import { KeyNotRegisteredError, EpcError, BadResponseError, } from "./lib/epc-api.js";
13
+ import { selectKeyStore } from "./lib/key-store.js";
14
+ const accountId = process.env.ACCOUNT_ID ?? "local";
15
+ const server = new McpServer({
16
+ name: "maxy-epc",
17
+ version: "0.1.0",
18
+ });
19
+ const store = selectKeyStore();
20
+ console.error(`[epc] server started, account=${accountId} backend=${store.backendName} path=${store.backendPath}`);
21
+ function errorEnvelope(err) {
22
+ let text;
23
+ if (err instanceof KeyNotRegisteredError) {
24
+ text =
25
+ "Error: key-not-registered. Register an EPC API key with epc-key-register before calling this tool.";
26
+ }
27
+ else if (err instanceof EpcError) {
28
+ text = `Error: http-${err.status} from EPC register. ${err.upstreamMessage}`;
29
+ }
30
+ else if (err instanceof BadResponseError) {
31
+ text = `Error: bad-response from EPC register (non-JSON, length=${err.length}).`;
32
+ }
33
+ else {
34
+ text = `Error: ${err instanceof Error ? err.message : String(err)}`;
35
+ }
36
+ return { content: [{ type: "text", text }], isError: true };
37
+ }
38
+ // ─────────────────────────────────────────────────────────────────
39
+ // Key management
40
+ // ─────────────────────────────────────────────────────────────────
41
+ server.tool("epc-key-register", "Register the account's EPC register credential. The credential is the email that generated the API key on the EPC dashboard, a colon, then the API key (format: email:api-key). Validates against the EPC register, encrypts with AES-256-GCM, and stores in Neo4j. One credential per account; re-registering overwrites.", {
42
+ apiKey: z
43
+ .string()
44
+ .min(8)
45
+ .describe("EPC credential in the form email:api-key. The email is the account that generated the key at epc.opendatacommunities.org."),
46
+ }, async ({ apiKey }) => {
47
+ try {
48
+ await keyRegister({ apiKey, accountId });
49
+ return {
50
+ content: [
51
+ { type: "text", text: "EPC API key registered successfully." },
52
+ ],
53
+ };
54
+ }
55
+ catch (err) {
56
+ return errorEnvelope(err);
57
+ }
58
+ });
59
+ server.tool("epc-key-deregister", "Remove the account's EPC API credential. The encrypted credential is permanently deleted from Neo4j.", {}, async () => {
60
+ try {
61
+ await keyDeregister({ accountId });
62
+ return {
63
+ content: [{ type: "text", text: "EPC API key deregistered." }],
64
+ };
65
+ }
66
+ catch (err) {
67
+ return errorEnvelope(err);
68
+ }
69
+ });
70
+ server.tool("epc-key-list", "Show whether an EPC API credential is registered for this account. Never reveals the credential value.", {}, async () => {
71
+ try {
72
+ const text = await keyList({ accountId });
73
+ return { content: [{ type: "text", text }] };
74
+ }
75
+ catch (err) {
76
+ return errorEnvelope(err);
77
+ }
78
+ });
79
+ // ─────────────────────────────────────────────────────────────────
80
+ // Lookup
81
+ // ─────────────────────────────────────────────────────────────────
82
+ server.tool("epc-lookup-by-address", "Look up the most recent Energy Performance Certificate per matched property for a UK address. Returns total_floor_area_m2, total_floor_area_sqft (m² × 10.7639), current and potential energy ratings, lodgement date, address, and UPRN. Multiple matches surface as an array. Zero matches return an empty array with status ok. Use to derive subject-property sqft for preval valuation.", {
83
+ address: z.string().min(2).describe("UK address line one (street and house number / name)"),
84
+ postcode: z
85
+ .string()
86
+ .optional()
87
+ .describe("Full UK postcode (optional but recommended to narrow matches)"),
88
+ }, async ({ address, postcode }) => {
89
+ try {
90
+ const text = await epcLookupByAddress({ accountId, address, postcode });
91
+ return { content: [{ type: "text", text }] };
92
+ }
93
+ catch (err) {
94
+ return errorEnvelope(err);
95
+ }
96
+ });
97
+ server.tool("epc-lookup-by-uprn", "Look up the most recent Energy Performance Certificate for a specific UPRN. Returns the same shape as epc-lookup-by-address. Use when the property's UPRN is already known from property-data-address-match-uprn or Land Registry.", {
98
+ uprn: z.string().min(1).describe("UK Unique Property Reference Number"),
99
+ }, async ({ uprn }) => {
100
+ try {
101
+ const text = await epcLookupByUprn({ accountId, uprn });
102
+ return { content: [{ type: "text", text }] };
103
+ }
104
+ catch (err) {
105
+ return errorEnvelope(err);
106
+ }
107
+ });
108
+ // ─────────────────────────────────────────────────────────────────
109
+ // Lifecycle
110
+ // ─────────────────────────────────────────────────────────────────
111
+ process.on("SIGINT", async () => {
112
+ await closeDriver();
113
+ process.exit(0);
114
+ });
115
+ const transport = new StdioServerTransport();
116
+ await server.connect(transport);
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function encrypt(plaintext: string): string;
2
+ export declare function decrypt(encryptedValue: string): string;
3
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/lib/crypto.ts"],"names":[],"mappings":"AAgDA,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED,wBAAgB,OAAO,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAoBtD"}
@@ -0,0 +1,72 @@
1
+ import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
2
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { homedir } from "node:os";
5
+ const ALGORITHM = "aes-256-gcm";
6
+ const KEY_LENGTH = 32;
7
+ const IV_LENGTH = 12;
8
+ const AUTH_TAG_LENGTH = 16;
9
+ const KEY_FILE_NAME = ".epc-encryption-key";
10
+ function resolveConfigDir() {
11
+ try {
12
+ const platformRoot = process.env.PLATFORM_ROOT;
13
+ if (platformRoot) {
14
+ const brandPath = resolve(platformRoot, "config/brand.json");
15
+ const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
16
+ if (brand.configDir)
17
+ return brand.configDir;
18
+ }
19
+ }
20
+ catch {
21
+ // Fall back to default
22
+ }
23
+ return ".maxy";
24
+ }
25
+ function keyFilePath() {
26
+ return resolve(homedir(), resolveConfigDir(), KEY_FILE_NAME);
27
+ }
28
+ function loadOrGenerateKey() {
29
+ const path = keyFilePath();
30
+ if (existsSync(path)) {
31
+ const hex = readFileSync(path, "utf-8").trim();
32
+ const buf = Buffer.from(hex, "hex");
33
+ if (buf.length !== KEY_LENGTH) {
34
+ throw new Error(`Encryption key at ${path} is ${buf.length} bytes, expected ${KEY_LENGTH}. ` +
35
+ `Delete the file and re-register the EPC API key.`);
36
+ }
37
+ return buf;
38
+ }
39
+ const key = randomBytes(KEY_LENGTH);
40
+ writeFileSync(path, key.toString("hex"), { mode: 0o600 });
41
+ console.error(`[epc] encryption key generated at ${path}`);
42
+ return key;
43
+ }
44
+ export function encrypt(plaintext) {
45
+ const key = loadOrGenerateKey();
46
+ const iv = randomBytes(IV_LENGTH);
47
+ const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
48
+ let encrypted = cipher.update(plaintext, "utf-8", "hex");
49
+ encrypted += cipher.final("hex");
50
+ const authTag = cipher.getAuthTag().toString("hex");
51
+ return `${iv.toString("hex")}:${encrypted}:${authTag}`;
52
+ }
53
+ export function decrypt(encryptedValue) {
54
+ const parts = encryptedValue.split(":");
55
+ if (parts.length !== 3) {
56
+ throw new Error("Malformed encrypted value — expected iv:ciphertext:authTag");
57
+ }
58
+ const [ivHex, ciphertextHex, authTagHex] = parts;
59
+ const path = keyFilePath();
60
+ if (!existsSync(path)) {
61
+ throw new Error(`Encryption key not found at ${path}. The stored EPC API key is unrecoverable. Re-register.`);
62
+ }
63
+ const key = loadOrGenerateKey();
64
+ const iv = Buffer.from(ivHex, "hex");
65
+ const authTag = Buffer.from(authTagHex, "hex");
66
+ const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
67
+ decipher.setAuthTag(authTag);
68
+ let decrypted = decipher.update(ciphertextHex, "hex", "utf-8");
69
+ decrypted += decipher.final("utf-8");
70
+ return decrypted;
71
+ }
72
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/lib/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ export declare const BASE_URL = "https://epc.opendatacommunities.org/api/v1";
2
+ export declare const REQUEST_TIMEOUT_MS = 15000;
3
+ export declare const M2_TO_SQFT = 10.7639;
4
+ export declare class KeyNotRegisteredError extends Error {
5
+ constructor();
6
+ }
7
+ export declare class EpcError extends Error {
8
+ readonly status: number;
9
+ readonly endpoint: string;
10
+ readonly upstreamMessage: string;
11
+ constructor(status: number, endpoint: string, upstreamMessage: string);
12
+ }
13
+ export declare class BadResponseError extends Error {
14
+ readonly endpoint: string;
15
+ readonly length: number;
16
+ constructor(endpoint: string, length: number);
17
+ }
18
+ export interface EpcKeyMeta {
19
+ createdAt: string;
20
+ updatedAt: string;
21
+ }
22
+ export declare function loadAccountKey(accountId: string): Promise<EpcKeyMeta | null>;
23
+ export declare function resolveAccountApiKey(accountId: string): Promise<string>;
24
+ export interface EpcRow {
25
+ "lmk-key"?: string;
26
+ address1?: string;
27
+ address2?: string;
28
+ address3?: string;
29
+ posttown?: string;
30
+ postcode?: string;
31
+ uprn?: string;
32
+ "current-energy-rating"?: string;
33
+ "potential-energy-rating"?: string;
34
+ "lodgement-date"?: string;
35
+ "total-floor-area"?: string;
36
+ [k: string]: unknown;
37
+ }
38
+ export interface EpcSearchOptions {
39
+ toolName: string;
40
+ accountId: string;
41
+ identifier: string;
42
+ params: Record<string, string | number | undefined>;
43
+ }
44
+ interface CallResult {
45
+ status: number;
46
+ rows: EpcRow[];
47
+ }
48
+ export declare function epcSearch(opts: EpcSearchOptions): Promise<CallResult>;
49
+ export interface EpcMatch {
50
+ total_floor_area_m2: number | null;
51
+ total_floor_area_sqft: number | null;
52
+ current_energy_rating: string | null;
53
+ potential_energy_rating: string | null;
54
+ lodgement_date: string | null;
55
+ address: string;
56
+ uprn: string | null;
57
+ }
58
+ export declare function groupAndPickMostRecent(rows: EpcRow[]): EpcMatch[];
59
+ export {};
60
+ //# sourceMappingURL=epc-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"epc-api.d.ts","sourceRoot":"","sources":["../../src/lib/epc-api.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,QAAQ,+CAA+C,CAAC;AACrE,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,eAAO,MAAM,UAAU,UAAU,CAAC;AAElC,qBAAa,qBAAsB,SAAQ,KAAK;;CAK/C;AAED,qBAAa,QAAS,SAAQ,KAAK;aAEf,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,MAAM;aAChB,eAAe,EAAE,MAAM;gBAFvB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM;CAK1C;AAED,qBAAa,gBAAiB,SAAQ,KAAK;aACb,QAAQ,EAAE,MAAM;aAAkB,MAAM,EAAE,MAAM;gBAAhD,QAAQ,EAAE,MAAM,EAAkB,MAAM,EAAE,MAAM;CAI7E;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAIlF;AAED,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM7E;AAMD,MAAM,WAAW,MAAM;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAGlB,UAAU,EAAE,MAAM,CAAC;IAEnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;CACrD;AAED,UAAU,UAAU;IAGlB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiE3E;AAED,MAAM,WAAW,QAAQ;IACvB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAsBD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,CAuCjE"}