@rubytech/create-maxy-code 0.1.58 → 0.1.60
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/hooks/__tests__/turn-completed-graph-write.test.sh +30 -19
- package/payload/platform/plugins/admin/hooks/turn-completed-graph-write.sh +35 -90
- package/payload/platform/plugins/docs/references/admin-session.md +17 -16
- package/payload/platform/plugins/docs/references/platform.md +3 -3
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/loop/PLUGIN.md +13 -1
- package/payload/platform/plugins/loop/mcp/dist/index.js +8 -7
- package/payload/platform/plugins/loop/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/loop/mcp/dist/lib/file-crypto.d.ts +3 -0
- package/payload/platform/plugins/loop/mcp/dist/lib/file-crypto.d.ts.map +1 -0
- package/payload/platform/plugins/loop/mcp/dist/lib/file-crypto.js +48 -0
- package/payload/platform/plugins/loop/mcp/dist/lib/file-crypto.js.map +1 -0
- package/payload/platform/plugins/loop/mcp/dist/lib/key-store.d.ts +22 -0
- package/payload/platform/plugins/loop/mcp/dist/lib/key-store.d.ts.map +1 -0
- package/payload/platform/plugins/loop/mcp/dist/lib/key-store.js +165 -0
- package/payload/platform/plugins/loop/mcp/dist/lib/key-store.js.map +1 -0
- package/payload/platform/plugins/loop/mcp/dist/lib/loop-api.d.ts +1 -1
- package/payload/platform/plugins/loop/mcp/dist/lib/loop-api.d.ts.map +1 -1
- package/payload/platform/plugins/loop/mcp/dist/lib/loop-api.js +4 -39
- package/payload/platform/plugins/loop/mcp/dist/lib/loop-api.js.map +1 -1
- package/payload/platform/plugins/loop/mcp/dist/tools/key-deregister.d.ts.map +1 -1
- package/payload/platform/plugins/loop/mcp/dist/tools/key-deregister.js +5 -15
- package/payload/platform/plugins/loop/mcp/dist/tools/key-deregister.js.map +1 -1
- package/payload/platform/plugins/loop/mcp/dist/tools/key-register.d.ts.map +1 -1
- package/payload/platform/plugins/loop/mcp/dist/tools/key-register.js +11 -28
- package/payload/platform/plugins/loop/mcp/dist/tools/key-register.js.map +1 -1
- package/payload/platform/plugins/loop/mcp/src/index.ts +10 -7
- package/payload/platform/plugins/loop/mcp/src/lib/file-crypto.ts +54 -0
- package/payload/platform/plugins/loop/mcp/src/lib/key-store.ts +221 -0
- package/payload/platform/plugins/loop/mcp/src/lib/loop-api.ts +5 -42
- package/payload/platform/plugins/loop/mcp/src/tools/key-deregister.ts +5 -20
- package/payload/platform/plugins/loop/mcp/src/tools/key-register.ts +14 -39
- package/payload/platform/plugins/preval/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/preval/PLUGIN.md +39 -0
- package/payload/platform/plugins/preval/skills/property-preval/SKILL.md +78 -0
- package/payload/platform/plugins/preval/skills/property-preval/references/render.py +265 -0
- package/payload/platform/plugins/preval/skills/property-preval/references/template-inputs.schema.json +143 -0
- package/payload/platform/plugins/preval/skills/property-preval/references/template.html +253 -0
- package/payload/platform/plugins/property-data/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/property-data/PLUGIN.md +60 -2
- package/payload/platform/plugins/property-data/mcp/dist/index.js +95 -12
- package/payload/platform/plugins/property-data/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/lib/file-crypto.d.ts +3 -0
- package/payload/platform/plugins/property-data/mcp/dist/lib/file-crypto.d.ts.map +1 -0
- package/payload/platform/plugins/property-data/mcp/dist/lib/file-crypto.js +49 -0
- package/payload/platform/plugins/property-data/mcp/dist/lib/file-crypto.js.map +1 -0
- package/payload/platform/plugins/property-data/mcp/dist/lib/key-store.d.ts +15 -0
- package/payload/platform/plugins/property-data/mcp/dist/lib/key-store.d.ts.map +1 -0
- package/payload/platform/plugins/property-data/mcp/dist/lib/key-store.js +135 -0
- package/payload/platform/plugins/property-data/mcp/dist/lib/key-store.js.map +1 -0
- package/payload/platform/plugins/property-data/mcp/dist/lib/propertydata-api.d.ts +0 -1
- package/payload/platform/plugins/property-data/mcp/dist/lib/propertydata-api.d.ts.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/lib/propertydata-api.js +11 -22
- package/payload/platform/plugins/property-data/mcp/dist/lib/propertydata-api.js.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.d.ts +10 -0
- package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.d.ts.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.js +15 -2
- package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.js.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/tools/key-deregister.d.ts.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/tools/key-deregister.js +5 -15
- package/payload/platform/plugins/property-data/mcp/dist/tools/key-deregister.js.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/tools/key-register.d.ts.map +1 -1
- package/payload/platform/plugins/property-data/mcp/dist/tools/key-register.js +30 -23
- package/payload/platform/plugins/property-data/mcp/dist/tools/key-register.js.map +1 -1
- package/payload/platform/plugins/property-data/mcp/src/__tests__/endpoints.test.ts +252 -0
- package/payload/platform/plugins/property-data/mcp/src/__tests__/schema-parity.test.ts +16 -2
- package/payload/platform/plugins/property-data/mcp/src/index.ts +171 -11
- package/payload/platform/plugins/property-data/mcp/src/lib/file-crypto.ts +55 -0
- package/payload/platform/plugins/property-data/mcp/src/lib/key-store.ts +181 -0
- package/payload/platform/plugins/property-data/mcp/src/lib/propertydata-api.ts +9 -24
- package/payload/platform/plugins/property-data/mcp/src/tools/endpoints.ts +22 -2
- package/payload/platform/plugins/property-data/mcp/src/tools/key-deregister.ts +5 -17
- package/payload/platform/plugins/property-data/mcp/src/tools/key-register.ts +29 -33
- package/payload/platform/plugins/property-data/skills/property-data/SKILL.md +19 -0
- 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 +25 -260
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts +3 -7
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js +6 -32
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +6 -24
- 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 +18 -91
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/reaper.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/reaper.js +0 -11
- package/payload/platform/services/claude-session-manager/dist/reaper.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts +0 -21
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-store.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 +5 -3
- package/payload/premium-plugins/real-agent/agents/negotiator.md +1 -1
- 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/loop/PLUGIN.md +13 -1
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/index.js +8 -7
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/file-crypto.d.ts +3 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/file-crypto.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/file-crypto.js +48 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/file-crypto.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/key-store.d.ts +22 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/key-store.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/key-store.js +165 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/key-store.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/loop-api.d.ts +1 -1
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/loop-api.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/loop-api.js +4 -39
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/lib/loop-api.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/key-deregister.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/key-deregister.js +5 -15
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/key-deregister.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/key-register.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/key-register.js +11 -28
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/key-register.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/index.ts +10 -7
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/lib/file-crypto.ts +54 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/lib/key-store.ts +221 -0
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/lib/loop-api.ts +5 -42
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/tools/key-deregister.ts +5 -20
- package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/tools/key-register.ts +14 -39
- package/payload/premium-plugins/real-agent/plugins/preval/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/real-agent/plugins/preval/PLUGIN.md +39 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/SKILL.md +78 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/render.py +265 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template-inputs.schema.json +143 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template.html +253 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/.claude-plugin/plugin.json +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/PLUGIN.md +60 -2
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/index.js +95 -12
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/file-crypto.d.ts +3 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/file-crypto.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/file-crypto.js +49 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/file-crypto.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/key-store.d.ts +15 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/key-store.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/key-store.js +135 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/key-store.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/propertydata-api.d.ts +0 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/propertydata-api.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/propertydata-api.js +11 -22
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/propertydata-api.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.d.ts +10 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.js +15 -2
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-deregister.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-deregister.js +5 -15
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-deregister.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-register.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-register.js +30 -23
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-register.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/__tests__/endpoints.test.ts +252 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/__tests__/schema-parity.test.ts +16 -2
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/index.ts +171 -11
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/lib/file-crypto.ts +55 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/lib/key-store.ts +181 -0
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/lib/propertydata-api.ts +9 -24
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/endpoints.ts +22 -2
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/key-deregister.ts +5 -17
- package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/key-register.ts +29 -33
- package/payload/premium-plugins/real-agent/plugins/property-data/skills/property-data/SKILL.md +19 -0
- package/payload/server/public/assets/{admin-2ZJmzJ_g.js → admin-Bp-BjBCX.js} +51 -51
- package/payload/server/public/assets/{data-DlmoKxmN.js → data-BGUAGVkV.js} +1 -1
- package/payload/server/public/assets/{graph-BVvcFSEQ.js → graph-MvYxZOBF.js} +1 -1
- package/payload/server/public/assets/{graph-labels-cCvgu38Y.js → graph-labels-D865qb3K.js} +1 -1
- package/payload/server/public/assets/{page-BnAxPkmg.js → page--hOVRrgN.js} +1 -1
- package/payload/server/public/assets/{page-BnaN3u86.js → page-C2b1nlOc.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 +18 -83
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/dist/index.d.ts +0 -51
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/dist/index.d.ts.map +0 -1
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/dist/index.js +0 -196
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/dist/index.js.map +0 -1
- package/payload/premium-plugins/real-agent/lib/mcp-stderr-tee/package.json +0 -7
|
@@ -0,0 +1,181 @@
|
|
|
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
|
+
|
|
8
|
+
// Storage abstraction so the same MCP body runs against Neo4j (platform mode)
|
|
9
|
+
// or an on-disk encrypted file (standalone mode, e.g. spawned by Claude Code
|
|
10
|
+
// from ~/.claude/). Selected by the PROPERTY_DATA_STANDALONE env flag at
|
|
11
|
+
// boot. See .docs/realagent-mcps-standalone-mode.md for the contract.
|
|
12
|
+
|
|
13
|
+
export interface StoredKey {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface KeyStore {
|
|
20
|
+
readonly backendName: string;
|
|
21
|
+
readonly backendPath: string;
|
|
22
|
+
load(accountId: string): Promise<StoredKey | null>;
|
|
23
|
+
save(accountId: string, apiKey: string): Promise<void>;
|
|
24
|
+
delete(accountId: string): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class Neo4jKeyStore implements KeyStore {
|
|
28
|
+
readonly backendName = "neo4j";
|
|
29
|
+
readonly backendPath = process.env.NEO4J_URI ?? "<unset>";
|
|
30
|
+
|
|
31
|
+
async load(accountId: string): Promise<StoredKey | null> {
|
|
32
|
+
const session = getSession();
|
|
33
|
+
try {
|
|
34
|
+
const result = await session.run(
|
|
35
|
+
`MATCH (k:PropertyDataKey {accountId: $accountId})
|
|
36
|
+
RETURN k.encryptedKey AS encryptedKey,
|
|
37
|
+
k.createdAt AS createdAt,
|
|
38
|
+
k.updatedAt AS updatedAt`,
|
|
39
|
+
{ accountId },
|
|
40
|
+
);
|
|
41
|
+
if (result.records.length === 0) return null;
|
|
42
|
+
const r = result.records[0];
|
|
43
|
+
return {
|
|
44
|
+
apiKey: decrypt(r.get("encryptedKey")),
|
|
45
|
+
createdAt: r.get("createdAt") ?? "",
|
|
46
|
+
updatedAt: r.get("updatedAt") ?? "",
|
|
47
|
+
};
|
|
48
|
+
} finally {
|
|
49
|
+
await session.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async save(accountId: string, apiKey: string): Promise<void> {
|
|
54
|
+
const encryptedKey = encrypt(apiKey);
|
|
55
|
+
const now = new Date().toISOString();
|
|
56
|
+
const session = getSession();
|
|
57
|
+
try {
|
|
58
|
+
await session.run(
|
|
59
|
+
`CREATE INDEX property_data_key_account IF NOT EXISTS
|
|
60
|
+
FOR (k:PropertyDataKey) ON (k.accountId)`,
|
|
61
|
+
{},
|
|
62
|
+
);
|
|
63
|
+
await session.run(
|
|
64
|
+
`MERGE (k:PropertyDataKey {accountId: $accountId})
|
|
65
|
+
ON CREATE SET k.encryptedKey = $encryptedKey, k.createdAt = $now, k.updatedAt = $now
|
|
66
|
+
ON MATCH SET k.encryptedKey = $encryptedKey, k.updatedAt = $now`,
|
|
67
|
+
{ accountId, encryptedKey, now },
|
|
68
|
+
);
|
|
69
|
+
} finally {
|
|
70
|
+
await session.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async delete(accountId: string): Promise<boolean> {
|
|
75
|
+
const session = getSession();
|
|
76
|
+
try {
|
|
77
|
+
const result = await session.run(
|
|
78
|
+
`MATCH (k:PropertyDataKey {accountId: $accountId})
|
|
79
|
+
DELETE k
|
|
80
|
+
RETURN count(k) AS deleted`,
|
|
81
|
+
{ accountId },
|
|
82
|
+
);
|
|
83
|
+
const deleted = result.records[0]?.get("deleted")?.toNumber?.() ?? 0;
|
|
84
|
+
return deleted > 0;
|
|
85
|
+
} finally {
|
|
86
|
+
await session.close();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// FileKeyStore — single key per accountId stored as an AES-256-GCM-encrypted
|
|
92
|
+
// JSON blob. The file holds a map { [accountId]: { encryptedKey, createdAt,
|
|
93
|
+
// updatedAt } } so it parallels the Neo4j shape, but in standalone mode the
|
|
94
|
+
// accountId is "local" (only one).
|
|
95
|
+
|
|
96
|
+
interface FileEnvelope {
|
|
97
|
+
[accountId: string]: { encryptedKey: string; createdAt: string; updatedAt: string };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const LABEL = "property-data";
|
|
101
|
+
|
|
102
|
+
class FileKeyStore implements KeyStore {
|
|
103
|
+
readonly backendName = "file";
|
|
104
|
+
readonly backendPath: string;
|
|
105
|
+
private readonly storePath: string;
|
|
106
|
+
private readonly keyFilePath: string;
|
|
107
|
+
|
|
108
|
+
constructor() {
|
|
109
|
+
const home = homedir();
|
|
110
|
+
this.storePath = resolve(home, ".claude/.realagent-property-data-key.enc");
|
|
111
|
+
this.keyFilePath = resolve(home, ".claude/.realagent-property-data-encryption-key");
|
|
112
|
+
this.backendPath = this.storePath;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private ensureDir(): void {
|
|
116
|
+
mkdirSync(dirname(this.storePath), { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private readAll(): FileEnvelope {
|
|
120
|
+
if (!existsSync(this.storePath)) return {};
|
|
121
|
+
const blob = readFileSync(this.storePath, "utf-8").trim();
|
|
122
|
+
if (!blob) return {};
|
|
123
|
+
const json = decryptWith(blob, this.keyFilePath, LABEL);
|
|
124
|
+
return JSON.parse(json) as FileEnvelope;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private writeAll(env: FileEnvelope): void {
|
|
128
|
+
this.ensureDir();
|
|
129
|
+
const blob = encryptWith(JSON.stringify(env), this.keyFilePath, LABEL);
|
|
130
|
+
writeFileSync(this.storePath, blob, { mode: 0o600 });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async load(accountId: string): Promise<StoredKey | null> {
|
|
134
|
+
const env = this.readAll();
|
|
135
|
+
const entry = env[accountId];
|
|
136
|
+
if (!entry) return null;
|
|
137
|
+
// The encryptedKey field is double-encrypted: the file blob is decrypted
|
|
138
|
+
// above, the inner field is the original AES-GCM-wrapped key. Keep the
|
|
139
|
+
// inner layer so an unauthenticated read of decrypted JSON still doesn't
|
|
140
|
+
// expose the API key directly in process memory until decryptWith runs.
|
|
141
|
+
return {
|
|
142
|
+
apiKey: decryptWith(entry.encryptedKey, this.keyFilePath, LABEL),
|
|
143
|
+
createdAt: entry.createdAt,
|
|
144
|
+
updatedAt: entry.updatedAt,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async save(accountId: string, apiKey: string): Promise<void> {
|
|
149
|
+
const env = this.readAll();
|
|
150
|
+
const now = new Date().toISOString();
|
|
151
|
+
const encryptedKey = encryptWith(apiKey, this.keyFilePath, LABEL);
|
|
152
|
+
const prev = env[accountId];
|
|
153
|
+
env[accountId] = {
|
|
154
|
+
encryptedKey,
|
|
155
|
+
createdAt: prev?.createdAt ?? now,
|
|
156
|
+
updatedAt: now,
|
|
157
|
+
};
|
|
158
|
+
this.writeAll(env);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async delete(accountId: string): Promise<boolean> {
|
|
162
|
+
const env = this.readAll();
|
|
163
|
+
if (!(accountId in env)) return false;
|
|
164
|
+
delete env[accountId];
|
|
165
|
+
this.writeAll(env);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let cached: KeyStore | null = null;
|
|
171
|
+
|
|
172
|
+
export function selectKeyStore(): KeyStore {
|
|
173
|
+
if (cached) return cached;
|
|
174
|
+
cached = process.env.PROPERTY_DATA_STANDALONE === "1" ? new FileKeyStore() : new Neo4jKeyStore();
|
|
175
|
+
return cached;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Test seam — used by unit tests to reset the cached store between cases.
|
|
179
|
+
export function _resetKeyStoreForTests(): void {
|
|
180
|
+
cached = null;
|
|
181
|
+
}
|
package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/lib/propertydata-api.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { decrypt } from "./crypto.js";
|
|
1
|
+
import { selectKeyStore } from "./key-store.js";
|
|
3
2
|
|
|
4
3
|
// PropertyData API V1 client. Every endpoint is a GET against
|
|
5
4
|
// https://api.propertydata.co.uk/<endpoint> with `key=<api-key>` plus
|
|
@@ -42,37 +41,23 @@ export class BadResponseError extends Error {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
export interface PropertyDataKey {
|
|
45
|
-
encryptedKey: string;
|
|
46
44
|
createdAt: string;
|
|
47
45
|
updatedAt: string;
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
export async function loadAccountKey(accountId: string): Promise<PropertyDataKey | null> {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`MATCH (k:PropertyDataKey {accountId: $accountId})
|
|
55
|
-
RETURN k.encryptedKey AS encryptedKey,
|
|
56
|
-
k.createdAt AS createdAt,
|
|
57
|
-
k.updatedAt AS updatedAt`,
|
|
58
|
-
{ accountId },
|
|
59
|
-
);
|
|
60
|
-
if (result.records.length === 0) return null;
|
|
61
|
-
const r = result.records[0];
|
|
62
|
-
return {
|
|
63
|
-
encryptedKey: r.get("encryptedKey"),
|
|
64
|
-
createdAt: r.get("createdAt") ?? "",
|
|
65
|
-
updatedAt: r.get("updatedAt") ?? "",
|
|
66
|
-
};
|
|
67
|
-
} finally {
|
|
68
|
-
await session.close();
|
|
69
|
-
}
|
|
49
|
+
const stored = await selectKeyStore().load(accountId);
|
|
50
|
+
if (!stored) return null;
|
|
51
|
+
return { createdAt: stored.createdAt, updatedAt: stored.updatedAt };
|
|
70
52
|
}
|
|
71
53
|
|
|
72
54
|
export async function resolveAccountApiKey(accountId: string): Promise<string> {
|
|
73
|
-
|
|
55
|
+
// Bypass for one-shot CLI runs: explicit env var beats stored key.
|
|
56
|
+
const envKey = process.env.PROPERTY_DATA_API_KEY;
|
|
57
|
+
if (envKey && envKey.trim()) return envKey.trim();
|
|
58
|
+
const stored = await selectKeyStore().load(accountId);
|
|
74
59
|
if (!stored) throw new KeyNotRegisteredError();
|
|
75
|
-
return
|
|
60
|
+
return stored.apiKey;
|
|
76
61
|
}
|
|
77
62
|
|
|
78
63
|
export interface CallOptions {
|
|
@@ -19,9 +19,29 @@ function call(toolName: string, endpoint: string, args: Args): Promise<string> {
|
|
|
19
19
|
export const valuationSale = (a: Args) => call("property-data-valuation-sale", "valuation-sale", a);
|
|
20
20
|
export const valuationRent = (a: Args) => call("property-data-valuation-rent", "valuation-rent", a);
|
|
21
21
|
export const prices = (a: Args) => call("property-data-prices", "prices", a);
|
|
22
|
-
export const pricesRent = (a: Args) => call("property-data-prices-rent", "
|
|
22
|
+
export const pricesRent = (a: Args) => call("property-data-prices-rent", "rents", a);
|
|
23
23
|
export const soldPrices = (a: Args) => call("property-data-sold-prices", "sold-prices", a);
|
|
24
24
|
export const demand = (a: Args) => call("property-data-demand", "demand", a);
|
|
25
25
|
export const yields = (a: Args) => call("property-data-yields", "yields", a);
|
|
26
26
|
export const growth = (a: Args) => call("property-data-growth", "growth", a);
|
|
27
|
-
export const nationalHpi = (a: Args) => call("property-data-national-hpi", "national-
|
|
27
|
+
export const nationalHpi = (a: Args) => call("property-data-national-hpi", "national-data", a);
|
|
28
|
+
|
|
29
|
+
// Task 144 — 10 additional read endpoints (£/sqft, area risk,
|
|
30
|
+
// planning precedent, UPRN, demand-rent, property-type mix).
|
|
31
|
+
// All inherit the same observability + error contract as above.
|
|
32
|
+
|
|
33
|
+
export const pricesPerSqf = (a: Args) =>
|
|
34
|
+
call("property-data-prices-per-sqf", "prices-per-sqf", a);
|
|
35
|
+
export const soldPricesPerSqf = (a: Args) =>
|
|
36
|
+
call("property-data-sold-prices-per-sqf", "sold-prices-per-sqf", a);
|
|
37
|
+
export const crime = (a: Args) => call("property-data-crime", "crime", a);
|
|
38
|
+
export const floodRisk = (a: Args) => call("property-data-flood-risk", "flood-risk", a);
|
|
39
|
+
export const councilTax = (a: Args) => call("property-data-council-tax", "council-tax", a);
|
|
40
|
+
export const planningApplications = (a: Args) =>
|
|
41
|
+
call("property-data-planning-applications", "planning-applications", a);
|
|
42
|
+
export const addressMatchUprn = (a: Args) =>
|
|
43
|
+
call("property-data-address-match-uprn", "address-match-uprn", a);
|
|
44
|
+
export const growthPsf = (a: Args) => call("property-data-growth-psf", "growth-psf", a);
|
|
45
|
+
export const demandRent = (a: Args) => call("property-data-demand-rent", "demand-rent", a);
|
|
46
|
+
export const propertyTypes = (a: Args) =>
|
|
47
|
+
call("property-data-property-types", "property-types", a);
|
package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/key-deregister.ts
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { selectKeyStore } from "../lib/key-store.js";
|
|
2
2
|
|
|
3
3
|
export async function keyDeregister(params: { accountId: string }): Promise<void> {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const result = await session.run(
|
|
8
|
-
`MATCH (k:PropertyDataKey {accountId: $accountId})
|
|
9
|
-
DELETE k
|
|
10
|
-
RETURN count(k) AS deleted`,
|
|
11
|
-
{ accountId },
|
|
12
|
-
);
|
|
13
|
-
const deleted = result.records[0]?.get("deleted")?.toNumber?.() ?? 0;
|
|
14
|
-
if (deleted === 0) {
|
|
15
|
-
throw new Error("No PropertyData API key is registered for this account.");
|
|
16
|
-
}
|
|
17
|
-
} finally {
|
|
18
|
-
await session.close();
|
|
4
|
+
const deleted = await selectKeyStore().delete(params.accountId);
|
|
5
|
+
if (!deleted) {
|
|
6
|
+
throw new Error("No PropertyData API key is registered for this account.");
|
|
19
7
|
}
|
|
20
|
-
console.error(`[property-data] key-op op=deregister account=${accountId}`);
|
|
8
|
+
console.error(`[property-data] key-op op=deregister account=${params.accountId}`);
|
|
21
9
|
}
|
package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/key-register.ts
CHANGED
|
@@ -1,27 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { encrypt } from "../lib/crypto.js";
|
|
1
|
+
import { selectKeyStore } from "../lib/key-store.js";
|
|
3
2
|
import { PropertyDataError } from "../lib/propertydata-api.js";
|
|
4
3
|
|
|
5
4
|
const BASE_URL = "https://api.propertydata.co.uk";
|
|
6
5
|
const REQUEST_TIMEOUT_MS = 15_000;
|
|
7
6
|
|
|
8
|
-
// Validate
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// key
|
|
12
|
-
//
|
|
7
|
+
// Validate the key against PropertyData's /valuation-sale endpoint with no
|
|
8
|
+
// inputs — a valid key returns HTTP 400 + body code 701 "Missing input:
|
|
9
|
+
// property_type" (proof the key was accepted), without consuming a credit.
|
|
10
|
+
// An invalid key returns auth failure (401/403) or a different upstream code.
|
|
11
|
+
// Evidence: probing `/national-hpi` returns code X01 "Invalid API endpoint"
|
|
12
|
+
// — that path does not exist on PropertyData; `/valuation-sale` does.
|
|
13
|
+
|
|
13
14
|
async function validateApiKey(apiKey: string): Promise<void> {
|
|
14
|
-
|
|
15
|
+
// Probe /valuation-sale with one input. Evidence-backed envelopes:
|
|
16
|
+
// invalid key → {code:"X03", message:"Invalid input: key"}
|
|
17
|
+
// valid key → {code:"701", message:"Missing input: property_type"} (no credit)
|
|
18
|
+
// PropertyData does not charge for missing-input errors, so this is a free probe.
|
|
19
|
+
const url = `${BASE_URL}/valuation-sale?key=${encodeURIComponent(apiKey)}&postcode=SW1A1AA`;
|
|
15
20
|
const response = await fetch(url, {
|
|
16
21
|
headers: { Accept: "application/json" },
|
|
17
22
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
18
23
|
});
|
|
24
|
+
const body = await response.text().catch(() => "");
|
|
19
25
|
if (response.status === 401 || response.status === 403) {
|
|
20
26
|
throw new Error(`PropertyData rejected the API key (HTTP ${response.status}).`);
|
|
21
27
|
}
|
|
28
|
+
let parsed: { status?: string; code?: string; message?: string } = {};
|
|
29
|
+
try { parsed = JSON.parse(body); } catch { /* fall through */ }
|
|
30
|
+
if (parsed.code === "X03") {
|
|
31
|
+
throw new Error(`PropertyData rejected the API key (code=X03 ${parsed.message ?? "Invalid input: key"}).`);
|
|
32
|
+
}
|
|
33
|
+
// 701 (missing-input) or status:"success" both prove the key was authenticated.
|
|
34
|
+
// Any other envelope: surface raw so the operator sees the upstream error.
|
|
35
|
+
if (parsed.status === "success" || parsed.code === "701") return;
|
|
36
|
+
if (parsed.status === "error") {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`PropertyData validate probe returned unexpected envelope (code=${parsed.code ?? "?"} message=${parsed.message ?? "?"}).`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
22
41
|
if (!response.ok) {
|
|
23
|
-
|
|
24
|
-
throw new PropertyDataError(response.status, "national-hpi", body.slice(0, 200));
|
|
42
|
+
throw new PropertyDataError(response.status, "valuation-sale", body.slice(0, 200));
|
|
25
43
|
}
|
|
26
44
|
}
|
|
27
45
|
|
|
@@ -30,29 +48,7 @@ export async function keyRegister(params: {
|
|
|
30
48
|
accountId: string;
|
|
31
49
|
}): Promise<void> {
|
|
32
50
|
const { apiKey, accountId } = params;
|
|
33
|
-
|
|
34
51
|
await validateApiKey(apiKey);
|
|
35
|
-
|
|
36
|
-
const encryptedKey = encrypt(apiKey);
|
|
37
|
-
const now = new Date().toISOString();
|
|
38
|
-
|
|
39
|
-
const session = getSession();
|
|
40
|
-
try {
|
|
41
|
-
await session.run(
|
|
42
|
-
`CREATE INDEX property_data_key_account IF NOT EXISTS
|
|
43
|
-
FOR (k:PropertyDataKey) ON (k.accountId)`,
|
|
44
|
-
{},
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
await session.run(
|
|
48
|
-
`MERGE (k:PropertyDataKey {accountId: $accountId})
|
|
49
|
-
ON CREATE SET k.encryptedKey = $encryptedKey, k.createdAt = $now, k.updatedAt = $now
|
|
50
|
-
ON MATCH SET k.encryptedKey = $encryptedKey, k.updatedAt = $now`,
|
|
51
|
-
{ accountId, encryptedKey, now },
|
|
52
|
-
);
|
|
53
|
-
} finally {
|
|
54
|
-
await session.close();
|
|
55
|
-
}
|
|
56
|
-
|
|
52
|
+
await selectKeyStore().save(accountId, apiKey);
|
|
57
53
|
console.error(`[property-data] key-op op=register account=${accountId}`);
|
|
58
54
|
}
|
package/payload/premium-plugins/real-agent/plugins/property-data/skills/property-data/SKILL.md
CHANGED
|
@@ -18,6 +18,15 @@ Trigger phrases:
|
|
|
18
18
|
- "what's it worth — [postcode], [type], [beds]"
|
|
19
19
|
- "sold prices on [street|postcode]"
|
|
20
20
|
- "growth in [postcode]"
|
|
21
|
+
- "£/sqft in [postcode]" / "price per square foot in [postcode]"
|
|
22
|
+
- "£/sqft growth in [postcode]"
|
|
23
|
+
- "crime in [postcode|town]"
|
|
24
|
+
- "flood risk for [postcode]"
|
|
25
|
+
- "council tax band for [postcode]"
|
|
26
|
+
- "planning applications near [postcode]"
|
|
27
|
+
- "what's the UPRN of [address]"
|
|
28
|
+
- "rental demand in [postcode]"
|
|
29
|
+
- "property-type mix in [postcode]"
|
|
21
30
|
|
|
22
31
|
## Inputs required
|
|
23
32
|
|
|
@@ -39,6 +48,16 @@ Trigger phrases:
|
|
|
39
48
|
| `property-data-yields` | Gross rental yield | postcode, bedrooms |
|
|
40
49
|
| `property-data-growth` | Short and long-term price growth | postcode |
|
|
41
50
|
| `property-data-national-hpi` | UK national HPI series | (none) |
|
|
51
|
+
| `property-data-prices-per-sqf` | Asking £/sqft distribution | postcode (type, bedrooms optional) |
|
|
52
|
+
| `property-data-sold-prices-per-sqf` | Achieved £/sqft distribution | postcode (type, bedrooms, max_age optional) |
|
|
53
|
+
| `property-data-growth-psf` | 7-year £/sqft series with YoY% | postcode (type optional) |
|
|
54
|
+
| `property-data-crime` | Crime rate + breakdown | postcode, or location, or town |
|
|
55
|
+
| `property-data-flood-risk` | River + sea flood rating | full postcode |
|
|
56
|
+
| `property-data-council-tax` | Council + A–H band charges | full postcode |
|
|
57
|
+
| `property-data-planning-applications` | Planning precedent (2 credits/call) | postcode (decision_rating, category, max_age_decision, results optional) |
|
|
58
|
+
| `property-data-address-match-uprn` | Address → UPRN (10 credits/call) | address |
|
|
59
|
+
| `property-data-demand-rent` | Rental demand signals | postcode (bedrooms, type optional) |
|
|
60
|
+
| `property-data-property-types` | Output-area type distribution | postcode |
|
|
42
61
|
|
|
43
62
|
## Output
|
|
44
63
|
|