@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.
- package/package.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +1 -1
- package/payload/platform/plugins/brochures/skills/property-brochure/SKILL.md +26 -3
- package/payload/platform/plugins/brochures/skills/property-brochure/references/registers.md +1 -1
- package/payload/platform/plugins/docs/references/admin-session.md +2 -2
- package/payload/platform/plugins/docs/references/internals.md +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/epc/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/epc/PLUGIN.md +85 -0
- package/payload/platform/plugins/epc/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/epc/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/index.js +117 -0
- package/payload/platform/plugins/epc/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/crypto.d.ts +3 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/crypto.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/crypto.js +72 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/crypto.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/epc-api.d.ts +60 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/epc-api.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/epc-api.js +181 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/epc-api.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/file-crypto.d.ts +3 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/file-crypto.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/file-crypto.js +49 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/file-crypto.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/key-store.d.ts +15 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/key-store.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/key-store.js +130 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/key-store.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/neo4j.js +38 -0
- package/payload/platform/plugins/epc/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-deregister.d.ts +4 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-deregister.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-deregister.js +9 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-deregister.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-list.d.ts +4 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-list.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-list.js +10 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-list.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-register.d.ts +5 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-register.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-register.js +36 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/key-register.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/lookup.d.ts +10 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/lookup.d.ts.map +1 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/lookup.js +30 -0
- package/payload/platform/plugins/epc/mcp/dist/tools/lookup.js.map +1 -0
- package/payload/platform/plugins/epc/mcp/package-lock.json +2566 -0
- package/payload/platform/plugins/epc/mcp/package.json +21 -0
- package/payload/platform/plugins/epc/mcp/src/__tests__/epc-api.test.ts +251 -0
- package/payload/platform/plugins/epc/mcp/src/__tests__/key-roundtrip.test.ts +113 -0
- package/payload/platform/plugins/epc/mcp/src/__tests__/lookup.test.ts +181 -0
- package/payload/platform/plugins/epc/mcp/src/__tests__/schema-parity.test.ts +54 -0
- package/payload/platform/plugins/epc/mcp/src/index.ts +156 -0
- package/payload/platform/plugins/epc/mcp/src/lib/crypto.ts +79 -0
- package/payload/platform/plugins/epc/mcp/src/lib/epc-api.ts +241 -0
- package/payload/platform/plugins/epc/mcp/src/lib/file-crypto.ts +55 -0
- package/payload/platform/plugins/epc/mcp/src/lib/key-store.ts +172 -0
- package/payload/platform/plugins/epc/mcp/src/lib/neo4j.ts +47 -0
- package/payload/platform/plugins/epc/mcp/src/tools/key-deregister.ts +9 -0
- package/payload/platform/plugins/epc/mcp/src/tools/key-list.ts +10 -0
- package/payload/platform/plugins/epc/mcp/src/tools/key-register.ts +44 -0
- package/payload/platform/plugins/epc/mcp/src/tools/lookup.ts +43 -0
- package/payload/platform/plugins/epc/mcp/tsconfig.json +20 -0
- package/payload/platform/plugins/epc/mcp/vitest.config.ts +8 -0
- package/payload/platform/plugins/preval/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/preval/PLUGIN.md +25 -17
- package/payload/platform/plugins/preval/skills/property-preval/SKILL.md +107 -39
- package/payload/platform/plugins/preval/skills/property-preval/references/render.py +449 -84
- package/payload/platform/plugins/preval/skills/property-preval/references/template-inputs.schema.json +126 -80
- package/payload/platform/plugins/preval/skills/property-preval/references/template.html +183 -124
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +6 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +7 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +8 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
- package/payload/platform/templates/agents/admin/SOUL.md +1 -1
- package/payload/premium-plugins/real-agent/BUNDLE.md +4 -2
- package/payload/premium-plugins/real-agent/agents/valuer.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/SKILL.md +26 -3
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/registers.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/epc/.claude-plugin/plugin.json +17 -0
- package/payload/premium-plugins/real-agent/plugins/epc/PLUGIN.md +85 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/index.d.ts +2 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/index.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/index.js +117 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/index.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/crypto.d.ts +3 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/crypto.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/crypto.js +72 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/crypto.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/epc-api.d.ts +60 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/epc-api.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/epc-api.js +181 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/epc-api.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/file-crypto.d.ts +3 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/file-crypto.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/file-crypto.js +49 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/file-crypto.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/key-store.d.ts +15 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/key-store.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/key-store.js +130 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/key-store.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/neo4j.js +38 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-deregister.d.ts +4 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-deregister.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-deregister.js +9 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-deregister.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-list.d.ts +4 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-list.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-list.js +10 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-list.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-register.d.ts +5 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-register.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-register.js +36 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/key-register.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/lookup.d.ts +10 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/lookup.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/lookup.js +30 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/dist/tools/lookup.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/package-lock.json +2566 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/package.json +21 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/__tests__/epc-api.test.ts +251 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/__tests__/key-roundtrip.test.ts +113 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/__tests__/lookup.test.ts +181 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/__tests__/schema-parity.test.ts +54 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/index.ts +156 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/crypto.ts +79 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/epc-api.ts +241 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/file-crypto.ts +55 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/key-store.ts +172 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/lib/neo4j.ts +47 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/tools/key-deregister.ts +9 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/tools/key-list.ts +10 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/tools/key-register.ts +44 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/src/tools/lookup.ts +43 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/tsconfig.json +20 -0
- package/payload/premium-plugins/real-agent/plugins/epc/mcp/vitest.config.ts +8 -0
- package/payload/premium-plugins/real-agent/plugins/preval/.claude-plugin/plugin.json +1 -1
- package/payload/premium-plugins/real-agent/plugins/preval/PLUGIN.md +25 -17
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/SKILL.md +107 -39
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/render.py +449 -84
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template-inputs.schema.json +126 -80
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template.html +183 -124
- package/payload/server/public/assets/{admin-Bp-BjBCX.js → admin-DhN3G8W7.js} +1 -1
- package/payload/server/public/assets/{data-BGUAGVkV.js → data-B2ZVXOcE.js} +1 -1
- package/payload/server/public/assets/{graph-MvYxZOBF.js → graph-BAMGPHrK.js} +1 -1
- package/payload/server/public/assets/{graph-labels-D865qb3K.js → graph-labels-D9eBbvxo.js} +1 -1
- package/payload/server/public/assets/{page-C2b1nlOc.js → page-CV27Al6Z.js} +1 -1
- package/payload/server/public/assets/{page--hOVRrgN.js → page-DjdVMWCz.js} +1 -1
- 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/server.js +3 -16
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { selectKeyStore } from "./key-store.js";
|
|
2
|
+
// gov.uk EPC register HTTP client. The register lives at
|
|
3
|
+
// epc.opendatacommunities.org and is reached via Basic auth where the
|
|
4
|
+
// credential is `<email>:<api-key>` (the email is the account that
|
|
5
|
+
// generated the key on the dashboard). We store the credential as a single
|
|
6
|
+
// colon-joined string so the key-management surface mirrors property-data
|
|
7
|
+
// exactly (one `apiKey` field per account).
|
|
8
|
+
//
|
|
9
|
+
// Response shape (domestic/search):
|
|
10
|
+
// { "column-names": [...], "rows": [ { "lmk-key": "...",
|
|
11
|
+
// "address1": "...", "address2": "...", "address3": "...",
|
|
12
|
+
// "posttown": "...", "postcode": "...", "uprn": "...",
|
|
13
|
+
// "current-energy-rating": "C", "potential-energy-rating": "B",
|
|
14
|
+
// "lodgement-date": "2023-05-15", "total-floor-area": "75.0", ... } ] }
|
|
15
|
+
//
|
|
16
|
+
// Multiple EPCs per property appear as separate rows (one per lodgement).
|
|
17
|
+
// Callers group by UPRN (or address fallback) and pick the most-recent
|
|
18
|
+
// row per property.
|
|
19
|
+
export const BASE_URL = "https://epc.opendatacommunities.org/api/v1";
|
|
20
|
+
export const REQUEST_TIMEOUT_MS = 15_000;
|
|
21
|
+
export const M2_TO_SQFT = 10.7639;
|
|
22
|
+
export class KeyNotRegisteredError extends Error {
|
|
23
|
+
constructor() {
|
|
24
|
+
super("key-not-registered");
|
|
25
|
+
this.name = "KeyNotRegisteredError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class EpcError extends Error {
|
|
29
|
+
status;
|
|
30
|
+
endpoint;
|
|
31
|
+
upstreamMessage;
|
|
32
|
+
constructor(status, endpoint, upstreamMessage) {
|
|
33
|
+
super(`http-${status} from EPC endpoint=${endpoint}: ${upstreamMessage}`);
|
|
34
|
+
this.status = status;
|
|
35
|
+
this.endpoint = endpoint;
|
|
36
|
+
this.upstreamMessage = upstreamMessage;
|
|
37
|
+
this.name = "EpcError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class BadResponseError extends Error {
|
|
41
|
+
endpoint;
|
|
42
|
+
length;
|
|
43
|
+
constructor(endpoint, length) {
|
|
44
|
+
super(`bad-response from EPC endpoint=${endpoint} (non-JSON, length=${length})`);
|
|
45
|
+
this.endpoint = endpoint;
|
|
46
|
+
this.length = length;
|
|
47
|
+
this.name = "BadResponseError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function loadAccountKey(accountId) {
|
|
51
|
+
const stored = await selectKeyStore().load(accountId);
|
|
52
|
+
if (!stored)
|
|
53
|
+
return null;
|
|
54
|
+
return { createdAt: stored.createdAt, updatedAt: stored.updatedAt };
|
|
55
|
+
}
|
|
56
|
+
export async function resolveAccountApiKey(accountId) {
|
|
57
|
+
const envKey = process.env.EPC_API_KEY;
|
|
58
|
+
if (envKey && envKey.trim())
|
|
59
|
+
return envKey.trim();
|
|
60
|
+
const stored = await selectKeyStore().load(accountId);
|
|
61
|
+
if (!stored)
|
|
62
|
+
throw new KeyNotRegisteredError();
|
|
63
|
+
return stored.apiKey;
|
|
64
|
+
}
|
|
65
|
+
function basicAuthHeader(credential) {
|
|
66
|
+
return `Basic ${Buffer.from(credential, "utf-8").toString("base64")}`;
|
|
67
|
+
}
|
|
68
|
+
export async function epcSearch(opts) {
|
|
69
|
+
const { toolName, accountId, identifier, params } = opts;
|
|
70
|
+
const credential = await resolveAccountApiKey(accountId);
|
|
71
|
+
const search = new URLSearchParams();
|
|
72
|
+
for (const [k, v] of Object.entries(params)) {
|
|
73
|
+
if (v === undefined || v === null || v === "")
|
|
74
|
+
continue;
|
|
75
|
+
search.set(k, String(v));
|
|
76
|
+
}
|
|
77
|
+
const endpoint = "domestic/search";
|
|
78
|
+
const url = `${BASE_URL}/${endpoint}?${search.toString()}`;
|
|
79
|
+
const start = Date.now();
|
|
80
|
+
const response = await fetch(url, {
|
|
81
|
+
headers: {
|
|
82
|
+
Accept: "application/json",
|
|
83
|
+
Authorization: basicAuthHeader(credential),
|
|
84
|
+
},
|
|
85
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
86
|
+
});
|
|
87
|
+
const ms = Date.now() - start;
|
|
88
|
+
const statusTag = response.ok
|
|
89
|
+
? "ok"
|
|
90
|
+
: response.status === 404
|
|
91
|
+
? "http-404"
|
|
92
|
+
: `http-${Math.floor(response.status / 100)}xx`;
|
|
93
|
+
console.error(`[epc] tool=${toolName} account=${accountId} address="${identifier}" status=${statusTag} ms=${ms}`);
|
|
94
|
+
// 404 is a valid outcome — no EPC on file for the address. Surface to the
|
|
95
|
+
// caller as an empty result set; the wrapper turns it into ok+[].
|
|
96
|
+
if (response.status === 404) {
|
|
97
|
+
return { status: 404, rows: [] };
|
|
98
|
+
}
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const body = await response.text().catch(() => "");
|
|
101
|
+
throw new EpcError(response.status, endpoint, body.slice(0, 200));
|
|
102
|
+
}
|
|
103
|
+
const text = await response.text();
|
|
104
|
+
let parsed;
|
|
105
|
+
try {
|
|
106
|
+
parsed = JSON.parse(text);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw new BadResponseError(endpoint, text.length);
|
|
110
|
+
}
|
|
111
|
+
if (!parsed ||
|
|
112
|
+
typeof parsed !== "object" ||
|
|
113
|
+
Array.isArray(parsed) ||
|
|
114
|
+
!("rows" in parsed)) {
|
|
115
|
+
throw new BadResponseError(endpoint, text.length);
|
|
116
|
+
}
|
|
117
|
+
const rows = parsed.rows;
|
|
118
|
+
if (!Array.isArray(rows)) {
|
|
119
|
+
throw new BadResponseError(endpoint, text.length);
|
|
120
|
+
}
|
|
121
|
+
return { status: response.status, rows: rows };
|
|
122
|
+
}
|
|
123
|
+
// Compose the human-readable address from the upstream row's parts. Empty
|
|
124
|
+
// segments are dropped; segments are uppercase in the API and join with
|
|
125
|
+
// ", ".
|
|
126
|
+
function rowAddress(row) {
|
|
127
|
+
const parts = [row.address1, row.address2, row.address3, row.posttown, row.postcode]
|
|
128
|
+
.map((s) => (typeof s === "string" ? s.trim() : ""))
|
|
129
|
+
.filter((s) => s.length > 0);
|
|
130
|
+
return parts.join(", ");
|
|
131
|
+
}
|
|
132
|
+
function parseFloorArea(row) {
|
|
133
|
+
const raw = row["total-floor-area"];
|
|
134
|
+
if (raw == null || raw === "")
|
|
135
|
+
return null;
|
|
136
|
+
const n = Number(raw);
|
|
137
|
+
return Number.isFinite(n) ? n : null;
|
|
138
|
+
}
|
|
139
|
+
// Group rows by UPRN (or address1+postcode when UPRN is absent), then pick
|
|
140
|
+
// the most-recent EPC per group by lodgement date. Rows with no lodgement
|
|
141
|
+
// date sort last.
|
|
142
|
+
export function groupAndPickMostRecent(rows) {
|
|
143
|
+
const buckets = new Map();
|
|
144
|
+
for (const row of rows) {
|
|
145
|
+
const uprn = typeof row.uprn === "string" ? row.uprn.trim() : "";
|
|
146
|
+
const key = uprn || `${row.address1 ?? ""}|${row.postcode ?? ""}`.toLowerCase();
|
|
147
|
+
const existing = buckets.get(key);
|
|
148
|
+
if (existing)
|
|
149
|
+
existing.push(row);
|
|
150
|
+
else
|
|
151
|
+
buckets.set(key, [row]);
|
|
152
|
+
}
|
|
153
|
+
const out = [];
|
|
154
|
+
for (const bucket of buckets.values()) {
|
|
155
|
+
bucket.sort((a, b) => {
|
|
156
|
+
const da = String(a["lodgement-date"] ?? "");
|
|
157
|
+
const db = String(b["lodgement-date"] ?? "");
|
|
158
|
+
// Most-recent first: ISO dates string-sort correctly.
|
|
159
|
+
return db.localeCompare(da);
|
|
160
|
+
});
|
|
161
|
+
const winner = bucket[0];
|
|
162
|
+
const m2 = parseFloorArea(winner);
|
|
163
|
+
out.push({
|
|
164
|
+
total_floor_area_m2: m2,
|
|
165
|
+
total_floor_area_sqft: m2 == null ? null : Number((m2 * M2_TO_SQFT).toFixed(2)),
|
|
166
|
+
current_energy_rating: typeof winner["current-energy-rating"] === "string"
|
|
167
|
+
? winner["current-energy-rating"]
|
|
168
|
+
: null,
|
|
169
|
+
potential_energy_rating: typeof winner["potential-energy-rating"] === "string"
|
|
170
|
+
? winner["potential-energy-rating"]
|
|
171
|
+
: null,
|
|
172
|
+
lodgement_date: typeof winner["lodgement-date"] === "string"
|
|
173
|
+
? winner["lodgement-date"]
|
|
174
|
+
: null,
|
|
175
|
+
address: rowAddress(winner),
|
|
176
|
+
uprn: typeof winner.uprn === "string" && winner.uprn.length > 0 ? winner.uprn : null,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=epc-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"epc-api.js","sourceRoot":"","sources":["../../src/lib/epc-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-crypto.d.ts","sourceRoot":"","sources":["../../src/lib/file-crypto.ts"],"names":[],"mappings":"AA8BA,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQzF;AAED,wBAAgB,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAc9F"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
3
|
+
// Standalone-mode crypto. Same AES-256-GCM envelope as crypto.ts, but the
|
|
4
|
+
// encryption-key file path is passed in (not derived from PLATFORM_ROOT /
|
|
5
|
+
// brand.json), so it can live under ~/.claude/ when the MCP is spawned by
|
|
6
|
+
// Claude Code natively.
|
|
7
|
+
const ALGORITHM = "aes-256-gcm";
|
|
8
|
+
const KEY_LENGTH = 32;
|
|
9
|
+
const IV_LENGTH = 12;
|
|
10
|
+
const AUTH_TAG_LENGTH = 16;
|
|
11
|
+
function loadOrGenerateKey(keyFilePath, label) {
|
|
12
|
+
if (existsSync(keyFilePath)) {
|
|
13
|
+
const hex = readFileSync(keyFilePath, "utf-8").trim();
|
|
14
|
+
const buf = Buffer.from(hex, "hex");
|
|
15
|
+
if (buf.length !== KEY_LENGTH) {
|
|
16
|
+
throw new Error(`[${label}] encryption key at ${keyFilePath} is ${buf.length} bytes, expected ${KEY_LENGTH}.`);
|
|
17
|
+
}
|
|
18
|
+
return buf;
|
|
19
|
+
}
|
|
20
|
+
const key = randomBytes(KEY_LENGTH);
|
|
21
|
+
writeFileSync(keyFilePath, key.toString("hex"), { mode: 0o600 });
|
|
22
|
+
console.error(`[${label}] encryption key generated at ${keyFilePath}`);
|
|
23
|
+
return key;
|
|
24
|
+
}
|
|
25
|
+
export function encryptWith(plaintext, keyFilePath, label) {
|
|
26
|
+
const key = loadOrGenerateKey(keyFilePath, label);
|
|
27
|
+
const iv = randomBytes(IV_LENGTH);
|
|
28
|
+
const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
29
|
+
let encrypted = cipher.update(plaintext, "utf-8", "hex");
|
|
30
|
+
encrypted += cipher.final("hex");
|
|
31
|
+
const authTag = cipher.getAuthTag().toString("hex");
|
|
32
|
+
return `${iv.toString("hex")}:${encrypted}:${authTag}`;
|
|
33
|
+
}
|
|
34
|
+
export function decryptWith(encryptedValue, keyFilePath, label) {
|
|
35
|
+
const parts = encryptedValue.split(":");
|
|
36
|
+
if (parts.length !== 3) {
|
|
37
|
+
throw new Error(`[${label}] bad encrypted envelope (expected iv:ct:tag)`);
|
|
38
|
+
}
|
|
39
|
+
const key = loadOrGenerateKey(keyFilePath, label);
|
|
40
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
41
|
+
const ciphertext = parts[1];
|
|
42
|
+
const authTag = Buffer.from(parts[2], "hex");
|
|
43
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
44
|
+
decipher.setAuthTag(authTag);
|
|
45
|
+
let plaintext = decipher.update(ciphertext, "hex", "utf-8");
|
|
46
|
+
plaintext += decipher.final("utf-8");
|
|
47
|
+
return plaintext;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=file-crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-crypto.js","sourceRoot":"","sources":["../../src/lib/file-crypto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface StoredKey {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
createdAt: string;
|
|
4
|
+
updatedAt: string;
|
|
5
|
+
}
|
|
6
|
+
export interface KeyStore {
|
|
7
|
+
readonly backendName: string;
|
|
8
|
+
readonly backendPath: string;
|
|
9
|
+
load(accountId: string): Promise<StoredKey | null>;
|
|
10
|
+
save(accountId: string, apiKey: string): Promise<void>;
|
|
11
|
+
delete(accountId: string): Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
export declare function selectKeyStore(): KeyStore;
|
|
14
|
+
export declare function _resetKeyStoreForTests(): void;
|
|
15
|
+
//# sourceMappingURL=key-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-store.d.ts","sourceRoot":"","sources":["../../src/lib/key-store.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7C;AA0ID,wBAAgB,cAAc,IAAI,QAAQ,CAIzC;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { getSession } from "./neo4j.js";
|
|
5
|
+
import { encrypt, decrypt } from "./crypto.js";
|
|
6
|
+
import { encryptWith, decryptWith } from "./file-crypto.js";
|
|
7
|
+
class Neo4jKeyStore {
|
|
8
|
+
backendName = "neo4j";
|
|
9
|
+
backendPath = process.env.NEO4J_URI ?? "<unset>";
|
|
10
|
+
async load(accountId) {
|
|
11
|
+
const session = getSession();
|
|
12
|
+
try {
|
|
13
|
+
const result = await session.run(`MATCH (k:EpcApiKey {accountId: $accountId})
|
|
14
|
+
RETURN k.encryptedKey AS encryptedKey,
|
|
15
|
+
k.createdAt AS createdAt,
|
|
16
|
+
k.updatedAt AS updatedAt`, { accountId });
|
|
17
|
+
if (result.records.length === 0)
|
|
18
|
+
return null;
|
|
19
|
+
const r = result.records[0];
|
|
20
|
+
return {
|
|
21
|
+
apiKey: decrypt(r.get("encryptedKey")),
|
|
22
|
+
createdAt: r.get("createdAt") ?? "",
|
|
23
|
+
updatedAt: r.get("updatedAt") ?? "",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await session.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async save(accountId, apiKey) {
|
|
31
|
+
const encryptedKey = encrypt(apiKey);
|
|
32
|
+
const now = new Date().toISOString();
|
|
33
|
+
const session = getSession();
|
|
34
|
+
try {
|
|
35
|
+
await session.run(`CREATE INDEX epc_api_key_account IF NOT EXISTS
|
|
36
|
+
FOR (k:EpcApiKey) ON (k.accountId)`, {});
|
|
37
|
+
await session.run(`MERGE (k:EpcApiKey {accountId: $accountId})
|
|
38
|
+
ON CREATE SET k.encryptedKey = $encryptedKey, k.createdAt = $now, k.updatedAt = $now
|
|
39
|
+
ON MATCH SET k.encryptedKey = $encryptedKey, k.updatedAt = $now`, { accountId, encryptedKey, now });
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await session.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async delete(accountId) {
|
|
46
|
+
const session = getSession();
|
|
47
|
+
try {
|
|
48
|
+
const result = await session.run(`MATCH (k:EpcApiKey {accountId: $accountId})
|
|
49
|
+
DELETE k
|
|
50
|
+
RETURN count(k) AS deleted`, { accountId });
|
|
51
|
+
const deleted = result.records[0]?.get("deleted")?.toNumber?.() ?? 0;
|
|
52
|
+
return deleted > 0;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await session.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const LABEL = "epc";
|
|
60
|
+
class FileKeyStore {
|
|
61
|
+
backendName = "file";
|
|
62
|
+
backendPath;
|
|
63
|
+
storePath;
|
|
64
|
+
keyFilePath;
|
|
65
|
+
constructor() {
|
|
66
|
+
const home = homedir();
|
|
67
|
+
this.storePath = resolve(home, ".claude/.realagent-epc-key.enc");
|
|
68
|
+
this.keyFilePath = resolve(home, ".claude/.realagent-epc-encryption-key");
|
|
69
|
+
this.backendPath = this.storePath;
|
|
70
|
+
}
|
|
71
|
+
ensureDir() {
|
|
72
|
+
mkdirSync(dirname(this.storePath), { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
readAll() {
|
|
75
|
+
if (!existsSync(this.storePath))
|
|
76
|
+
return {};
|
|
77
|
+
const blob = readFileSync(this.storePath, "utf-8").trim();
|
|
78
|
+
if (!blob)
|
|
79
|
+
return {};
|
|
80
|
+
const json = decryptWith(blob, this.keyFilePath, LABEL);
|
|
81
|
+
return JSON.parse(json);
|
|
82
|
+
}
|
|
83
|
+
writeAll(env) {
|
|
84
|
+
this.ensureDir();
|
|
85
|
+
const blob = encryptWith(JSON.stringify(env), this.keyFilePath, LABEL);
|
|
86
|
+
writeFileSync(this.storePath, blob, { mode: 0o600 });
|
|
87
|
+
}
|
|
88
|
+
async load(accountId) {
|
|
89
|
+
const env = this.readAll();
|
|
90
|
+
const entry = env[accountId];
|
|
91
|
+
if (!entry)
|
|
92
|
+
return null;
|
|
93
|
+
return {
|
|
94
|
+
apiKey: decryptWith(entry.encryptedKey, this.keyFilePath, LABEL),
|
|
95
|
+
createdAt: entry.createdAt,
|
|
96
|
+
updatedAt: entry.updatedAt,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async save(accountId, apiKey) {
|
|
100
|
+
const env = this.readAll();
|
|
101
|
+
const now = new Date().toISOString();
|
|
102
|
+
const encryptedKey = encryptWith(apiKey, this.keyFilePath, LABEL);
|
|
103
|
+
const prev = env[accountId];
|
|
104
|
+
env[accountId] = {
|
|
105
|
+
encryptedKey,
|
|
106
|
+
createdAt: prev?.createdAt ?? now,
|
|
107
|
+
updatedAt: now,
|
|
108
|
+
};
|
|
109
|
+
this.writeAll(env);
|
|
110
|
+
}
|
|
111
|
+
async delete(accountId) {
|
|
112
|
+
const env = this.readAll();
|
|
113
|
+
if (!(accountId in env))
|
|
114
|
+
return false;
|
|
115
|
+
delete env[accountId];
|
|
116
|
+
this.writeAll(env);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
let cached = null;
|
|
121
|
+
export function selectKeyStore() {
|
|
122
|
+
if (cached)
|
|
123
|
+
return cached;
|
|
124
|
+
cached = process.env.EPC_STANDALONE === "1" ? new FileKeyStore() : new Neo4jKeyStore();
|
|
125
|
+
return cached;
|
|
126
|
+
}
|
|
127
|
+
export function _resetKeyStoreForTests() {
|
|
128
|
+
cached = null;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=key-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-store.js","sourceRoot":"","sources":["../../src/lib/key-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neo4j.d.ts","sourceRoot":"","sources":["../../src/lib/neo4j.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAqBtD,wBAAgB,SAAS,IAAI,MAAM,CAclC;AAED,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAKjD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import neo4j from "neo4j-driver";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
let driver = null;
|
|
5
|
+
function readPassword() {
|
|
6
|
+
if (process.env.NEO4J_PASSWORD)
|
|
7
|
+
return process.env.NEO4J_PASSWORD;
|
|
8
|
+
const passwordFile = resolve(process.env.PLATFORM_ROOT ?? resolve(import.meta.dirname, "../../../.."), "config/.neo4j-password");
|
|
9
|
+
try {
|
|
10
|
+
return readFileSync(passwordFile, "utf-8").trim();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
throw new Error(`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function getDriver() {
|
|
17
|
+
if (!driver) {
|
|
18
|
+
const uri = process.env.NEO4J_URI;
|
|
19
|
+
if (!uri) {
|
|
20
|
+
throw new Error("[epc] NEO4J_URI unset — refusing to default to bolt://localhost:7687");
|
|
21
|
+
}
|
|
22
|
+
const user = process.env.NEO4J_USER ?? "neo4j";
|
|
23
|
+
const password = readPassword();
|
|
24
|
+
console.error(`[epc] resolved neo4j_uri=${uri}`);
|
|
25
|
+
driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
|
|
26
|
+
}
|
|
27
|
+
return driver;
|
|
28
|
+
}
|
|
29
|
+
export function getSession() {
|
|
30
|
+
return getDriver().session();
|
|
31
|
+
}
|
|
32
|
+
export async function closeDriver() {
|
|
33
|
+
if (driver) {
|
|
34
|
+
await driver.close();
|
|
35
|
+
driver = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=neo4j.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neo4j.js","sourceRoot":"","sources":["../../src/lib/neo4j.ts"],"names":[],"mappings":"AAAA,OAAO,KAA0B,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,IAAI,MAAM,GAAkB,IAAI,CAAC;AAEjC,SAAS,YAAY;IACnB,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAClE,MAAM,YAAY,GAAG,OAAO,CAC1B,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,EACxE,wBAAwB,CACzB,CAAC;IACF,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,yCAAyC,YAAY,gCAAgC,CACtF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;QAC/C,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,4BAA4B,GAAG,EAAE,CAAC,CAAC;QACjD,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-deregister.d.ts","sourceRoot":"","sources":["../../src/tools/key-deregister.ts"],"names":[],"mappings":"AAEA,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { selectKeyStore } from "../lib/key-store.js";
|
|
2
|
+
export async function keyDeregister(params) {
|
|
3
|
+
const deleted = await selectKeyStore().delete(params.accountId);
|
|
4
|
+
if (!deleted) {
|
|
5
|
+
throw new Error("No EPC API key is registered for this account.");
|
|
6
|
+
}
|
|
7
|
+
console.error(`[epc] key-op op=deregister account=${params.accountId}`);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=key-deregister.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-deregister.js","sourceRoot":"","sources":["../../src/tools/key-deregister.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAA6B;IAC/D,MAAM,OAAO,GAAG,MAAM,cAAc,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAChE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,sCAAsC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-list.d.ts","sourceRoot":"","sources":["../../src/tools/key-list.ts"],"names":[],"mappings":"AAEA,wBAAsB,OAAO,CAAC,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAO5E"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { loadAccountKey } from "../lib/epc-api.js";
|
|
2
|
+
export async function keyList(params) {
|
|
3
|
+
const key = await loadAccountKey(params.accountId);
|
|
4
|
+
console.error(`[epc] key-op op=list account=${params.accountId}`);
|
|
5
|
+
if (!key) {
|
|
6
|
+
return "No EPC API key registered. Use epc-key-register to add one.";
|
|
7
|
+
}
|
|
8
|
+
return `EPC API key registered (created: ${key.createdAt}, updated: ${key.updatedAt}). Key value is encrypted and cannot be retrieved.`;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=key-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-list.js","sourceRoot":"","sources":["../../src/tools/key-list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,MAA6B;IACzD,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACnD,OAAO,CAAC,KAAK,CAAC,gCAAgC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,6DAA6D,CAAC;IACvE,CAAC;IACD,OAAO,oCAAoC,GAAG,CAAC,SAAS,cAAc,GAAG,CAAC,SAAS,oDAAoD,CAAC;AAC1I,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-register.d.ts","sourceRoot":"","sources":["../../src/tools/key-register.ts"],"names":[],"mappings":"AA8BA,wBAAsB,WAAW,CAAC,MAAM,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { selectKeyStore } from "../lib/key-store.js";
|
|
2
|
+
import { BASE_URL, REQUEST_TIMEOUT_MS, EpcError } from "../lib/epc-api.js";
|
|
3
|
+
// Probe the EPC register with the supplied credential against
|
|
4
|
+
// /domestic/search constrained to a tiny page so we do not pull a real
|
|
5
|
+
// payload. A valid credential returns HTTP 200 (rows may be empty for a
|
|
6
|
+
// nonsense postcode, which is fine — authentication is what we test).
|
|
7
|
+
// An invalid credential returns HTTP 401 Unauthorized.
|
|
8
|
+
async function validateApiKey(credential) {
|
|
9
|
+
const url = `${BASE_URL}/domestic/search?postcode=SW1A1AA&size=1`;
|
|
10
|
+
const response = await fetch(url, {
|
|
11
|
+
headers: {
|
|
12
|
+
Accept: "application/json",
|
|
13
|
+
Authorization: `Basic ${Buffer.from(credential, "utf-8").toString("base64")}`,
|
|
14
|
+
},
|
|
15
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
16
|
+
});
|
|
17
|
+
if (response.status === 401 || response.status === 403) {
|
|
18
|
+
throw new Error(`EPC register rejected the credential (HTTP ${response.status}). Expected format is email:api-key.`);
|
|
19
|
+
}
|
|
20
|
+
// 200, 404 (no rows for that postcode), and 200-with-rows all prove the
|
|
21
|
+
// credential was accepted. Anything else surfaces as the upstream code.
|
|
22
|
+
if (response.status === 200 || response.status === 404)
|
|
23
|
+
return;
|
|
24
|
+
const body = await response.text().catch(() => "");
|
|
25
|
+
throw new EpcError(response.status, "domestic/search", body.slice(0, 200));
|
|
26
|
+
}
|
|
27
|
+
export async function keyRegister(params) {
|
|
28
|
+
const { apiKey, accountId } = params;
|
|
29
|
+
if (!apiKey.includes(":")) {
|
|
30
|
+
throw new Error("EPC credential must be in the form email:api-key (email of the account that generated the key, then a colon, then the API key).");
|
|
31
|
+
}
|
|
32
|
+
await validateApiKey(apiKey);
|
|
33
|
+
await selectKeyStore().save(accountId, apiKey);
|
|
34
|
+
console.error(`[epc] key-op op=register account=${accountId}`);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=key-register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-register.js","sourceRoot":"","sources":["../../src/tools/key-register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE3E,8DAA8D;AAC9D,uEAAuE;AACvE,wEAAwE;AACxE,sEAAsE;AACtE,uDAAuD;AAEvD,KAAK,UAAU,cAAc,CAAC,UAAkB;IAC9C,MAAM,GAAG,GAAG,GAAG,QAAQ,0CAA0C,CAAC;IAClE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,OAAO,EAAE;YACP,MAAM,EAAE,kBAAkB;YAC1B,aAAa,EAAE,SAAS,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;SAC9E;QACD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC;KAChD,CAAC,CAAC;IACH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CACb,8CAA8C,QAAQ,CAAC,MAAM,sCAAsC,CACpG,CAAC;IACJ,CAAC;IACD,wEAAwE;IACxE,wEAAwE;IACxE,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO;IAC/D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IACnD,MAAM,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAGjC;IACC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,iIAAiI,CAClI,CAAC;IACJ,CAAC;IACD,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,cAAc,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC/C,OAAO,CAAC,KAAK,CAAC,oCAAoC,SAAS,EAAE,CAAC,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function epcLookupByAddress(args: {
|
|
2
|
+
accountId: string;
|
|
3
|
+
address: string;
|
|
4
|
+
postcode?: string;
|
|
5
|
+
}): Promise<string>;
|
|
6
|
+
export declare function epcLookupByUprn(args: {
|
|
7
|
+
accountId: string;
|
|
8
|
+
uprn: string;
|
|
9
|
+
}): Promise<string>;
|
|
10
|
+
//# sourceMappingURL=lookup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../../src/tools/lookup.ts"],"names":[],"mappings":"AAOA,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAalB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CAalB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { epcSearch, groupAndPickMostRecent } from "../lib/epc-api.js";
|
|
2
|
+
export async function epcLookupByAddress(args) {
|
|
3
|
+
const { accountId, address, postcode } = args;
|
|
4
|
+
const { rows } = await epcSearch({
|
|
5
|
+
toolName: "epc-lookup-by-address",
|
|
6
|
+
accountId,
|
|
7
|
+
identifier: address,
|
|
8
|
+
params: { address, postcode, size: 25 },
|
|
9
|
+
});
|
|
10
|
+
const result = {
|
|
11
|
+
status: "ok",
|
|
12
|
+
matches: groupAndPickMostRecent(rows),
|
|
13
|
+
};
|
|
14
|
+
return JSON.stringify(result, null, 2);
|
|
15
|
+
}
|
|
16
|
+
export async function epcLookupByUprn(args) {
|
|
17
|
+
const { accountId, uprn } = args;
|
|
18
|
+
const { rows } = await epcSearch({
|
|
19
|
+
toolName: "epc-lookup-by-uprn",
|
|
20
|
+
accountId,
|
|
21
|
+
identifier: uprn,
|
|
22
|
+
params: { uprn, size: 25 },
|
|
23
|
+
});
|
|
24
|
+
const result = {
|
|
25
|
+
status: "ok",
|
|
26
|
+
matches: groupAndPickMostRecent(rows),
|
|
27
|
+
};
|
|
28
|
+
return JSON.stringify(result, null, 2);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=lookup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lookup.js","sourceRoot":"","sources":["../../src/tools/lookup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAY,MAAM,mBAAmB,CAAC;AAOhF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAIxC;IACC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAC9C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,SAAS,CAAC;QAC/B,QAAQ,EAAE,uBAAuB;QACjC,SAAS;QACT,UAAU,EAAE,OAAO;QACnB,MAAM,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE;KACxC,CAAC,CAAC;IACH,MAAM,MAAM,GAAiB;QAC3B,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE,sBAAsB,CAAC,IAAI,CAAC;KACtC,CAAC;IACF,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAGrC;IACC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,SAAS,CAAC;QAC/B,QAAQ,EAAE,oBAAoB;QAC9B,SAAS;QACT,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;KAC3B,CAAC,CAAC;IACH,MAAM,MAAM,GAAiB;QAC3B,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE,sBAAsB,CAAC,IAAI,CAAC;KACtC,CAAC;IACF,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC"}
|