estatehelm 1.0.21 → 1.0.22
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/README.md +13 -9
- package/dist/index.js +213 -201
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,17 +68,21 @@ Remove credentials and revoke device access.
|
|
|
68
68
|
Start the MCP server.
|
|
69
69
|
|
|
70
70
|
Options:
|
|
71
|
-
- `-m, --mode <mode>` - Privacy mode: `
|
|
71
|
+
- `-m, --mode <mode>` - Privacy mode: `safe` (default) or `full`
|
|
72
|
+
- `-t, --token <token>` - MCP access token (or set ESTATEHELM_MCP_TOKEN env var)
|
|
72
73
|
- `--poll <interval>` - Background sync interval (e.g., `15m`)
|
|
73
74
|
|
|
74
75
|
### `estatehelm status`
|
|
75
76
|
|
|
76
77
|
Show current login status and cache information.
|
|
77
78
|
|
|
78
|
-
### `estatehelm sync`
|
|
79
|
+
### `estatehelm sync [options]`
|
|
79
80
|
|
|
80
81
|
Force sync cache from server.
|
|
81
82
|
|
|
83
|
+
Options:
|
|
84
|
+
- `-t, --token <token>` - MCP access token (or set ESTATEHELM_MCP_TOKEN env var)
|
|
85
|
+
|
|
82
86
|
### `estatehelm cache clear`
|
|
83
87
|
|
|
84
88
|
Clear local cache (keeps credentials).
|
|
@@ -100,20 +104,20 @@ Get configuration value(s).
|
|
|
100
104
|
|
|
101
105
|
## Privacy Modes
|
|
102
106
|
|
|
103
|
-
###
|
|
107
|
+
### Safe Mode (default)
|
|
104
108
|
|
|
105
|
-
|
|
109
|
+
Sensitive fields (passwords, account numbers, etc.) are redacted. Use this when sharing your screen or when uncertain.
|
|
106
110
|
|
|
107
111
|
```bash
|
|
108
|
-
estatehelm mcp --mode
|
|
112
|
+
estatehelm mcp --mode safe
|
|
109
113
|
```
|
|
110
114
|
|
|
111
|
-
###
|
|
115
|
+
### Full Mode
|
|
112
116
|
|
|
113
|
-
|
|
117
|
+
All data is returned as-is. Use this when you're alone and want full access.
|
|
114
118
|
|
|
115
119
|
```bash
|
|
116
|
-
estatehelm mcp --mode
|
|
120
|
+
estatehelm mcp --mode full
|
|
117
121
|
```
|
|
118
122
|
|
|
119
123
|
Redacted fields by entity type:
|
|
@@ -202,7 +206,7 @@ The device was revoked from EstateHelm settings. Run `estatehelm login` to re-re
|
|
|
202
206
|
|
|
203
207
|
### Cache issues
|
|
204
208
|
|
|
205
|
-
Try clearing the cache: `estatehelm cache clear`, then `estatehelm sync`.
|
|
209
|
+
Try clearing the cache: `estatehelm cache clear`, then `estatehelm sync --token YOUR_TOKEN`.
|
|
206
210
|
|
|
207
211
|
## License
|
|
208
212
|
|
package/dist/index.js
CHANGED
|
@@ -744,195 +744,6 @@ async function decryptEntity(householdKey, encrypted, options = {}) {
|
|
|
744
744
|
}
|
|
745
745
|
}
|
|
746
746
|
|
|
747
|
-
// ../encryption/src/entityKeyMapping.ts
|
|
748
|
-
var ENTITY_KEY_TYPE_MAP = {
|
|
749
|
-
// General household items
|
|
750
|
-
"property": "general",
|
|
751
|
-
"maintenance_task": "task",
|
|
752
|
-
"pet": "general",
|
|
753
|
-
"vehicle": "general",
|
|
754
|
-
"device": "general",
|
|
755
|
-
"valuable": "general",
|
|
756
|
-
"valuables": "general",
|
|
757
|
-
// Route alias
|
|
758
|
-
"access_code": "access_code",
|
|
759
|
-
"contact": "general",
|
|
760
|
-
"service": "general",
|
|
761
|
-
"document": "general",
|
|
762
|
-
"travel": "general",
|
|
763
|
-
"resident": "general",
|
|
764
|
-
"home_improvement": "general",
|
|
765
|
-
// Property improvements
|
|
766
|
-
"vehicle_maintenance": "general",
|
|
767
|
-
// Vehicle maintenance history
|
|
768
|
-
"vehicle_service_visit": "general",
|
|
769
|
-
// Vehicle service visits
|
|
770
|
-
"pet_vet_visit": "general",
|
|
771
|
-
// Pet vet visits (like vehicle_service_visit for vehicles)
|
|
772
|
-
"pet_health": "general",
|
|
773
|
-
// Pet health records (simple single records)
|
|
774
|
-
"military_record": "general",
|
|
775
|
-
// Military service records
|
|
776
|
-
"education_record": "general",
|
|
777
|
-
// Education records (diplomas, transcripts, etc.)
|
|
778
|
-
"credential": "general",
|
|
779
|
-
// Credentials (professional licenses, government IDs, etc.)
|
|
780
|
-
"credentials": "general",
|
|
781
|
-
// Route alias
|
|
782
|
-
"membership_record": "general",
|
|
783
|
-
// Membership records (airline, hotel, retail loyalty programs)
|
|
784
|
-
// Health records (sensitive - requires health key)
|
|
785
|
-
"health_record": "health",
|
|
786
|
-
// Financial
|
|
787
|
-
"bank_account": "financial",
|
|
788
|
-
"investment": "financial",
|
|
789
|
-
"tax_document": "financial",
|
|
790
|
-
"tax_year": "financial",
|
|
791
|
-
"taxes": "financial",
|
|
792
|
-
// Route alias
|
|
793
|
-
"financial_account": "financial",
|
|
794
|
-
"financial": "financial",
|
|
795
|
-
// Route alias
|
|
796
|
-
// Legal (owner-only)
|
|
797
|
-
"legal": "legal",
|
|
798
|
-
// Insurance (both names for compatibility)
|
|
799
|
-
"insurance": "general",
|
|
800
|
-
"insurance_policy": "general",
|
|
801
|
-
"subscription": "subscription",
|
|
802
|
-
// Passwords (shared household credentials)
|
|
803
|
-
"password": "password",
|
|
804
|
-
// Identity (Personal Vault)
|
|
805
|
-
"identity": "identity",
|
|
806
|
-
// Calendar Connections (Personal Vault - user's OAuth tokens for calendar sync)
|
|
807
|
-
"calendar_connection": "identity",
|
|
808
|
-
// Continuity (Personal Vault - shared messages for beneficiaries)
|
|
809
|
-
"continuity": "continuity",
|
|
810
|
-
// Emergency (household-scoped emergency info, encrypted with general key so all members can decrypt)
|
|
811
|
-
"emergency": "general"
|
|
812
|
-
};
|
|
813
|
-
function getKeyTypeForEntity(entityType) {
|
|
814
|
-
const keyType = ENTITY_KEY_TYPE_MAP[entityType];
|
|
815
|
-
if (!keyType) {
|
|
816
|
-
throw new Error(
|
|
817
|
-
`No key type mapping found for entity type: ${entityType}. Please add it to ENTITY_KEY_TYPE_MAP in entityKeyMapping.ts`
|
|
818
|
-
);
|
|
819
|
-
}
|
|
820
|
-
return keyType;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// ../encryption/src/recoveryKey.ts
|
|
824
|
-
var RECOVERY_KEY_SIZE = 16;
|
|
825
|
-
var GROUP_SIZE = 4;
|
|
826
|
-
var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
827
|
-
function encodeBase32(bytes) {
|
|
828
|
-
let bits = "";
|
|
829
|
-
for (const byte of bytes) {
|
|
830
|
-
bits += byte.toString(2).padStart(8, "0");
|
|
831
|
-
}
|
|
832
|
-
while (bits.length % 5 !== 0) {
|
|
833
|
-
bits += "0";
|
|
834
|
-
}
|
|
835
|
-
let result = "";
|
|
836
|
-
for (let i = 0; i < bits.length; i += 5) {
|
|
837
|
-
const chunk = bits.substring(i, i + 5);
|
|
838
|
-
const index = parseInt(chunk, 2);
|
|
839
|
-
result += BASE32_ALPHABET[index];
|
|
840
|
-
}
|
|
841
|
-
return result;
|
|
842
|
-
}
|
|
843
|
-
function decodeBase32(base32) {
|
|
844
|
-
const cleaned = base32.toUpperCase().replace(/[^A-Z2-7]/g, "");
|
|
845
|
-
let bits = "";
|
|
846
|
-
for (const char of cleaned) {
|
|
847
|
-
const index = BASE32_ALPHABET.indexOf(char);
|
|
848
|
-
if (index === -1) {
|
|
849
|
-
throw new Error(`Invalid base32 character: ${char}`);
|
|
850
|
-
}
|
|
851
|
-
bits += index.toString(2).padStart(5, "0");
|
|
852
|
-
}
|
|
853
|
-
const bytes = [];
|
|
854
|
-
for (let i = 0; i < bits.length - bits.length % 8; i += 8) {
|
|
855
|
-
const byte = parseInt(bits.substring(i, i + 8), 2);
|
|
856
|
-
bytes.push(byte);
|
|
857
|
-
}
|
|
858
|
-
return new Uint8Array(bytes);
|
|
859
|
-
}
|
|
860
|
-
function formatRecoveryKey(base32) {
|
|
861
|
-
const groups = [];
|
|
862
|
-
for (let i = 0; i < base32.length; i += GROUP_SIZE) {
|
|
863
|
-
groups.push(base32.substring(i, i + GROUP_SIZE));
|
|
864
|
-
}
|
|
865
|
-
return groups.join("-");
|
|
866
|
-
}
|
|
867
|
-
function unformatRecoveryKey(formatted) {
|
|
868
|
-
return formatted.toUpperCase().replace(/[^A-Z2-7]/g, "");
|
|
869
|
-
}
|
|
870
|
-
function validateRecoveryKey(recoveryKey) {
|
|
871
|
-
try {
|
|
872
|
-
const unformatted = unformatRecoveryKey(recoveryKey);
|
|
873
|
-
if (unformatted.length < 20 || unformatted.length > 30) {
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
876
|
-
for (const char of unformatted) {
|
|
877
|
-
if (!BASE32_ALPHABET.includes(char)) {
|
|
878
|
-
return false;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
const bytes = decodeBase32(unformatted);
|
|
882
|
-
return bytes.length >= RECOVERY_KEY_SIZE - 2 && bytes.length <= RECOVERY_KEY_SIZE + 2;
|
|
883
|
-
} catch {
|
|
884
|
-
return false;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
function parseRecoveryKey(recoveryKey) {
|
|
888
|
-
if (!validateRecoveryKey(recoveryKey)) {
|
|
889
|
-
throw new Error("Invalid recovery key format");
|
|
890
|
-
}
|
|
891
|
-
const unformatted = unformatRecoveryKey(recoveryKey);
|
|
892
|
-
const bytes = decodeBase32(unformatted);
|
|
893
|
-
const normalizedBytes = new Uint8Array(RECOVERY_KEY_SIZE);
|
|
894
|
-
normalizedBytes.set(bytes.slice(0, RECOVERY_KEY_SIZE));
|
|
895
|
-
const base32 = encodeBase32(normalizedBytes);
|
|
896
|
-
const formatted = formatRecoveryKey(base32);
|
|
897
|
-
const base64 = base64Encode(normalizedBytes);
|
|
898
|
-
return {
|
|
899
|
-
bytes: normalizedBytes,
|
|
900
|
-
formatted,
|
|
901
|
-
base64
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
async function deriveWrapKey(recoveryKeyBytes, serverWrapSecret, info = "hearthcoo-wrap-key-v1") {
|
|
905
|
-
if (recoveryKeyBytes.length !== RECOVERY_KEY_SIZE) {
|
|
906
|
-
throw new Error(`Recovery key must be ${RECOVERY_KEY_SIZE} bytes`);
|
|
907
|
-
}
|
|
908
|
-
if (serverWrapSecret.length !== 32) {
|
|
909
|
-
throw new Error("Server wrap secret must be 32 bytes");
|
|
910
|
-
}
|
|
911
|
-
const recoveryKeyMaterial = await crypto.subtle.importKey(
|
|
912
|
-
"raw",
|
|
913
|
-
recoveryKeyBytes,
|
|
914
|
-
"HKDF",
|
|
915
|
-
false,
|
|
916
|
-
["deriveKey"]
|
|
917
|
-
);
|
|
918
|
-
const wrapKey = await crypto.subtle.deriveKey(
|
|
919
|
-
{
|
|
920
|
-
name: "HKDF",
|
|
921
|
-
hash: "SHA-256",
|
|
922
|
-
salt: serverWrapSecret,
|
|
923
|
-
info: new TextEncoder().encode(info)
|
|
924
|
-
},
|
|
925
|
-
recoveryKeyMaterial,
|
|
926
|
-
{
|
|
927
|
-
name: "AES-GCM",
|
|
928
|
-
length: 256
|
|
929
|
-
},
|
|
930
|
-
false,
|
|
931
|
-
["encrypt", "decrypt"]
|
|
932
|
-
);
|
|
933
|
-
return wrapKey;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
747
|
// ../types/src/contacts.ts
|
|
937
748
|
function getContactDisplayName(contact, fallback = "") {
|
|
938
749
|
const personName = [contact.first_name, contact.last_name].filter(Boolean).join(" ");
|
|
@@ -946,6 +757,76 @@ function getContactDisplayName(contact, fallback = "") {
|
|
|
946
757
|
// ../types/src/keys.ts
|
|
947
758
|
var DEFAULT_KEY_BUNDLE_ALG = "ECDH-P-521";
|
|
948
759
|
|
|
760
|
+
// ../types/src/entities.ts
|
|
761
|
+
var ENTITIES = {
|
|
762
|
+
// ===== General household items =====
|
|
763
|
+
property: { keyType: "general" },
|
|
764
|
+
pet: { keyType: "general" },
|
|
765
|
+
vehicle: { keyType: "general" },
|
|
766
|
+
contact: { keyType: "general" },
|
|
767
|
+
device: { keyType: "general" },
|
|
768
|
+
valuable: { keyType: "general" },
|
|
769
|
+
resident: { keyType: "general" },
|
|
770
|
+
service: { keyType: "general" },
|
|
771
|
+
home_improvement: { keyType: "general" },
|
|
772
|
+
vehicle_maintenance: { keyType: "general" },
|
|
773
|
+
vehicle_service_visit: { keyType: "general" },
|
|
774
|
+
pet_vet_visit: { keyType: "general" },
|
|
775
|
+
military_record: { keyType: "general" },
|
|
776
|
+
education_record: { keyType: "general" },
|
|
777
|
+
credential: { keyType: "general" },
|
|
778
|
+
membership_record: { keyType: "general" },
|
|
779
|
+
maintenance_task: { keyType: "task" },
|
|
780
|
+
// ===== Access codes =====
|
|
781
|
+
access_code: { keyType: "access_code" },
|
|
782
|
+
// ===== Insurance =====
|
|
783
|
+
insurance_policy: { keyType: "general" },
|
|
784
|
+
// ===== Subscriptions =====
|
|
785
|
+
subscription: { keyType: "subscription" },
|
|
786
|
+
// ===== Financial (sensitive) =====
|
|
787
|
+
financial_account: { keyType: "financial" },
|
|
788
|
+
tax_year: { keyType: "financial" },
|
|
789
|
+
// ===== Health (sensitive) =====
|
|
790
|
+
health_record: { keyType: "health" },
|
|
791
|
+
// ===== Legal (owner-only) =====
|
|
792
|
+
legal_document: { keyType: "legal" },
|
|
793
|
+
// ===== Personal vault =====
|
|
794
|
+
digital_identity: { keyType: "identity" },
|
|
795
|
+
calendar_connection: { keyType: "identity" },
|
|
796
|
+
// ===== Shared passwords =====
|
|
797
|
+
password: { keyType: "password", hasSchema: false },
|
|
798
|
+
// ===== Internal/legacy types (no schemas) =====
|
|
799
|
+
emergency: { keyType: "general", hasSchema: false },
|
|
800
|
+
continuity: { keyType: "continuity", hasSchema: false },
|
|
801
|
+
document: { keyType: "general", hasSchema: false },
|
|
802
|
+
travel: { keyType: "general", hasSchema: false },
|
|
803
|
+
pet_health: { keyType: "general", hasSchema: false },
|
|
804
|
+
bank_account: { keyType: "financial", hasSchema: false },
|
|
805
|
+
investment: { keyType: "financial", hasSchema: false },
|
|
806
|
+
tax_document: { keyType: "financial", hasSchema: false }
|
|
807
|
+
};
|
|
808
|
+
var ENTITY_ALIASES = {
|
|
809
|
+
valuables: "valuable",
|
|
810
|
+
credentials: "credential",
|
|
811
|
+
taxes: "tax_year",
|
|
812
|
+
financial: "financial_account",
|
|
813
|
+
insurance: "insurance_policy",
|
|
814
|
+
legal: "legal_document",
|
|
815
|
+
identity: "digital_identity"
|
|
816
|
+
};
|
|
817
|
+
var ENTITY_TYPES = Object.keys(ENTITIES);
|
|
818
|
+
var SCHEMA_ENTITY_TYPES = Object.entries(ENTITIES).filter(([_, config]) => config.hasSchema !== false).map(([type]) => type);
|
|
819
|
+
function getKeyTypeForEntity(entityType) {
|
|
820
|
+
const resolvedType = entityType in ENTITY_ALIASES ? ENTITY_ALIASES[entityType] : entityType;
|
|
821
|
+
const config = ENTITIES[resolvedType];
|
|
822
|
+
if (!config) {
|
|
823
|
+
throw new Error(
|
|
824
|
+
`Unknown entity type: ${entityType}. Please add it to ENTITIES in entities.ts`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
return config.keyType;
|
|
828
|
+
}
|
|
829
|
+
|
|
949
830
|
// ../types/src/options.ts
|
|
950
831
|
var PERSONAL_LEGAL_DOCUMENT_TYPE_OPTIONS = [
|
|
951
832
|
{ label: "Birth Certificate", value: "birth_certificate" },
|
|
@@ -1209,6 +1090,131 @@ var COSTARICA_NAV_COLORS = stripeColors([
|
|
|
1209
1090
|
// Blue stripe
|
|
1210
1091
|
]);
|
|
1211
1092
|
|
|
1093
|
+
// ../encryption/src/entityKeyMapping.ts
|
|
1094
|
+
var ENTITY_KEY_TYPE_MAP = {
|
|
1095
|
+
// Build from ENTITIES
|
|
1096
|
+
...Object.fromEntries(
|
|
1097
|
+
Object.entries(ENTITIES).map(([type, config]) => [type, config.keyType])
|
|
1098
|
+
),
|
|
1099
|
+
// Add aliases
|
|
1100
|
+
...Object.fromEntries(
|
|
1101
|
+
Object.entries(ENTITY_ALIASES).map(([alias, target]) => [alias, ENTITIES[target].keyType])
|
|
1102
|
+
)
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
// ../encryption/src/recoveryKey.ts
|
|
1106
|
+
var RECOVERY_KEY_SIZE = 16;
|
|
1107
|
+
var GROUP_SIZE = 4;
|
|
1108
|
+
var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
1109
|
+
function encodeBase32(bytes) {
|
|
1110
|
+
let bits = "";
|
|
1111
|
+
for (const byte of bytes) {
|
|
1112
|
+
bits += byte.toString(2).padStart(8, "0");
|
|
1113
|
+
}
|
|
1114
|
+
while (bits.length % 5 !== 0) {
|
|
1115
|
+
bits += "0";
|
|
1116
|
+
}
|
|
1117
|
+
let result = "";
|
|
1118
|
+
for (let i = 0; i < bits.length; i += 5) {
|
|
1119
|
+
const chunk = bits.substring(i, i + 5);
|
|
1120
|
+
const index = parseInt(chunk, 2);
|
|
1121
|
+
result += BASE32_ALPHABET[index];
|
|
1122
|
+
}
|
|
1123
|
+
return result;
|
|
1124
|
+
}
|
|
1125
|
+
function decodeBase32(base32) {
|
|
1126
|
+
const cleaned = base32.toUpperCase().replace(/[^A-Z2-7]/g, "");
|
|
1127
|
+
let bits = "";
|
|
1128
|
+
for (const char of cleaned) {
|
|
1129
|
+
const index = BASE32_ALPHABET.indexOf(char);
|
|
1130
|
+
if (index === -1) {
|
|
1131
|
+
throw new Error(`Invalid base32 character: ${char}`);
|
|
1132
|
+
}
|
|
1133
|
+
bits += index.toString(2).padStart(5, "0");
|
|
1134
|
+
}
|
|
1135
|
+
const bytes = [];
|
|
1136
|
+
for (let i = 0; i < bits.length - bits.length % 8; i += 8) {
|
|
1137
|
+
const byte = parseInt(bits.substring(i, i + 8), 2);
|
|
1138
|
+
bytes.push(byte);
|
|
1139
|
+
}
|
|
1140
|
+
return new Uint8Array(bytes);
|
|
1141
|
+
}
|
|
1142
|
+
function formatRecoveryKey(base32) {
|
|
1143
|
+
const groups = [];
|
|
1144
|
+
for (let i = 0; i < base32.length; i += GROUP_SIZE) {
|
|
1145
|
+
groups.push(base32.substring(i, i + GROUP_SIZE));
|
|
1146
|
+
}
|
|
1147
|
+
return groups.join("-");
|
|
1148
|
+
}
|
|
1149
|
+
function unformatRecoveryKey(formatted) {
|
|
1150
|
+
return formatted.toUpperCase().replace(/[^A-Z2-7]/g, "");
|
|
1151
|
+
}
|
|
1152
|
+
function validateRecoveryKey(recoveryKey) {
|
|
1153
|
+
try {
|
|
1154
|
+
const unformatted = unformatRecoveryKey(recoveryKey);
|
|
1155
|
+
if (unformatted.length < 20 || unformatted.length > 30) {
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
for (const char of unformatted) {
|
|
1159
|
+
if (!BASE32_ALPHABET.includes(char)) {
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const bytes = decodeBase32(unformatted);
|
|
1164
|
+
return bytes.length >= RECOVERY_KEY_SIZE - 2 && bytes.length <= RECOVERY_KEY_SIZE + 2;
|
|
1165
|
+
} catch {
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function parseRecoveryKey(recoveryKey) {
|
|
1170
|
+
if (!validateRecoveryKey(recoveryKey)) {
|
|
1171
|
+
throw new Error("Invalid recovery key format");
|
|
1172
|
+
}
|
|
1173
|
+
const unformatted = unformatRecoveryKey(recoveryKey);
|
|
1174
|
+
const bytes = decodeBase32(unformatted);
|
|
1175
|
+
const normalizedBytes = new Uint8Array(RECOVERY_KEY_SIZE);
|
|
1176
|
+
normalizedBytes.set(bytes.slice(0, RECOVERY_KEY_SIZE));
|
|
1177
|
+
const base32 = encodeBase32(normalizedBytes);
|
|
1178
|
+
const formatted = formatRecoveryKey(base32);
|
|
1179
|
+
const base64 = base64Encode(normalizedBytes);
|
|
1180
|
+
return {
|
|
1181
|
+
bytes: normalizedBytes,
|
|
1182
|
+
formatted,
|
|
1183
|
+
base64
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
async function deriveWrapKey(recoveryKeyBytes, serverWrapSecret, info = "hearthcoo-wrap-key-v1") {
|
|
1187
|
+
if (recoveryKeyBytes.length !== RECOVERY_KEY_SIZE) {
|
|
1188
|
+
throw new Error(`Recovery key must be ${RECOVERY_KEY_SIZE} bytes`);
|
|
1189
|
+
}
|
|
1190
|
+
if (serverWrapSecret.length !== 32) {
|
|
1191
|
+
throw new Error("Server wrap secret must be 32 bytes");
|
|
1192
|
+
}
|
|
1193
|
+
const recoveryKeyMaterial = await crypto.subtle.importKey(
|
|
1194
|
+
"raw",
|
|
1195
|
+
recoveryKeyBytes,
|
|
1196
|
+
"HKDF",
|
|
1197
|
+
false,
|
|
1198
|
+
["deriveKey"]
|
|
1199
|
+
);
|
|
1200
|
+
const wrapKey = await crypto.subtle.deriveKey(
|
|
1201
|
+
{
|
|
1202
|
+
name: "HKDF",
|
|
1203
|
+
hash: "SHA-256",
|
|
1204
|
+
salt: serverWrapSecret,
|
|
1205
|
+
info: new TextEncoder().encode(info)
|
|
1206
|
+
},
|
|
1207
|
+
recoveryKeyMaterial,
|
|
1208
|
+
{
|
|
1209
|
+
name: "AES-GCM",
|
|
1210
|
+
length: 256
|
|
1211
|
+
},
|
|
1212
|
+
false,
|
|
1213
|
+
["encrypt", "decrypt"]
|
|
1214
|
+
);
|
|
1215
|
+
return wrapKey;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1212
1218
|
// ../encryption/src/keyBundle.ts
|
|
1213
1219
|
async function decryptPrivateKeyWithWrapKey(encryptedPrivateKey, wrapKey) {
|
|
1214
1220
|
const packed = base64Decode(encryptedPrivateKey);
|
|
@@ -1889,7 +1895,7 @@ async function getPrivateKey(mcpToken) {
|
|
|
1889
1895
|
// src/server.ts
|
|
1890
1896
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
1891
1897
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
1892
|
-
var
|
|
1898
|
+
var import_types44 = require("@modelcontextprotocol/sdk/types.js");
|
|
1893
1899
|
|
|
1894
1900
|
// ../cache-sqlite/src/sqliteStore.ts
|
|
1895
1901
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
@@ -4844,7 +4850,7 @@ function enrichEntities(entities, entityType) {
|
|
|
4844
4850
|
}
|
|
4845
4851
|
|
|
4846
4852
|
// src/resources/entities.ts
|
|
4847
|
-
var
|
|
4853
|
+
var ENTITY_TYPES2 = Object.keys(ENTITY_KEY_TYPE_MAP).filter(
|
|
4848
4854
|
(t) => !ROUTE_ALIASES.has(t)
|
|
4849
4855
|
);
|
|
4850
4856
|
async function getEntities(householdId, entityType, privateKey, privacyMode) {
|
|
@@ -5096,7 +5102,7 @@ async function startServer(mode, mcpToken) {
|
|
|
5096
5102
|
}
|
|
5097
5103
|
}
|
|
5098
5104
|
);
|
|
5099
|
-
server.setRequestHandler(
|
|
5105
|
+
server.setRequestHandler(import_types44.ListResourcesRequestSchema, async () => {
|
|
5100
5106
|
const households = await listHouseholds();
|
|
5101
5107
|
const resources = [
|
|
5102
5108
|
{
|
|
@@ -5113,7 +5119,7 @@ async function startServer(mode, mcpToken) {
|
|
|
5113
5119
|
description: `Household: ${household.name}`,
|
|
5114
5120
|
mimeType: "application/json"
|
|
5115
5121
|
});
|
|
5116
|
-
for (const type of
|
|
5122
|
+
for (const type of ENTITY_TYPES2) {
|
|
5117
5123
|
resources.push({
|
|
5118
5124
|
uri: `estatehelm://households/${household.id}/${type}`,
|
|
5119
5125
|
name: `${household.name} - ${formatEntityType(type)}`,
|
|
@@ -5124,7 +5130,7 @@ async function startServer(mode, mcpToken) {
|
|
|
5124
5130
|
}
|
|
5125
5131
|
return { resources };
|
|
5126
5132
|
});
|
|
5127
|
-
server.setRequestHandler(
|
|
5133
|
+
server.setRequestHandler(import_types44.ReadResourceRequestSchema, async (request) => {
|
|
5128
5134
|
const uri = request.params.uri;
|
|
5129
5135
|
const parsed = parseResourceUri(uri);
|
|
5130
5136
|
if (!parsed) {
|
|
@@ -5153,7 +5159,7 @@ async function startServer(mode, mcpToken) {
|
|
|
5153
5159
|
]
|
|
5154
5160
|
};
|
|
5155
5161
|
});
|
|
5156
|
-
server.setRequestHandler(
|
|
5162
|
+
server.setRequestHandler(import_types44.ListToolsRequestSchema, async () => {
|
|
5157
5163
|
return {
|
|
5158
5164
|
tools: [
|
|
5159
5165
|
{
|
|
@@ -5246,7 +5252,7 @@ async function startServer(mode, mcpToken) {
|
|
|
5246
5252
|
]
|
|
5247
5253
|
};
|
|
5248
5254
|
});
|
|
5249
|
-
server.setRequestHandler(
|
|
5255
|
+
server.setRequestHandler(import_types44.CallToolRequestSchema, async (request) => {
|
|
5250
5256
|
const { name, arguments: args } = request.params;
|
|
5251
5257
|
trackToolUse(name);
|
|
5252
5258
|
switch (name) {
|
|
@@ -5296,7 +5302,7 @@ async function startServer(mode, mcpToken) {
|
|
|
5296
5302
|
throw new Error(`Unknown tool: ${name}`);
|
|
5297
5303
|
}
|
|
5298
5304
|
});
|
|
5299
|
-
server.setRequestHandler(
|
|
5305
|
+
server.setRequestHandler(import_types44.ListPromptsRequestSchema, async () => {
|
|
5300
5306
|
return {
|
|
5301
5307
|
prompts: [
|
|
5302
5308
|
{
|
|
@@ -5329,7 +5335,7 @@ async function startServer(mode, mcpToken) {
|
|
|
5329
5335
|
]
|
|
5330
5336
|
};
|
|
5331
5337
|
});
|
|
5332
|
-
server.setRequestHandler(
|
|
5338
|
+
server.setRequestHandler(import_types44.GetPromptRequestSchema, async (request) => {
|
|
5333
5339
|
const { name, arguments: args } = request.params;
|
|
5334
5340
|
switch (name) {
|
|
5335
5341
|
case "household_summary": {
|
|
@@ -5485,17 +5491,23 @@ program.command("status").description("Show current login status and cache info"
|
|
|
5485
5491
|
process.exit(1);
|
|
5486
5492
|
}
|
|
5487
5493
|
});
|
|
5488
|
-
program.command("sync").description("Force sync cache from server").action(async () => {
|
|
5494
|
+
program.command("sync").description("Force sync cache from server").option("-t, --token <token>", "MCP access token (or set ESTATEHELM_MCP_TOKEN env var)").action(async (options) => {
|
|
5489
5495
|
try {
|
|
5496
|
+
const token = options.token || process.env.ESTATEHELM_MCP_TOKEN;
|
|
5497
|
+
if (!token) {
|
|
5498
|
+
console.error("Access token required.");
|
|
5499
|
+
console.error("Use --token flag or set ESTATEHELM_MCP_TOKEN environment variable.");
|
|
5500
|
+
process.exit(1);
|
|
5501
|
+
}
|
|
5490
5502
|
console.log("Syncing...");
|
|
5491
5503
|
const client = await getAuthenticatedClient();
|
|
5492
5504
|
if (!client) {
|
|
5493
5505
|
console.error("Not logged in. Run: estatehelm login");
|
|
5494
5506
|
process.exit(1);
|
|
5495
5507
|
}
|
|
5496
|
-
const privateKey = await getPrivateKey();
|
|
5508
|
+
const privateKey = await getPrivateKey(token);
|
|
5497
5509
|
if (!privateKey) {
|
|
5498
|
-
console.error("Failed to load encryption keys.
|
|
5510
|
+
console.error("Failed to load encryption keys. Check your MCP token or run: estatehelm login");
|
|
5499
5511
|
process.exit(1);
|
|
5500
5512
|
}
|
|
5501
5513
|
await initCache();
|