estatehelm 1.0.8 → 1.0.9

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/dist/index.js CHANGED
@@ -933,6 +933,16 @@ async function deriveWrapKey(recoveryKeyBytes, serverWrapSecret, info = "hearthc
933
933
  return wrapKey;
934
934
  }
935
935
 
936
+ // ../types/src/contacts.ts
937
+ function getContactDisplayName(contact, fallback = "") {
938
+ const personName = [contact.first_name, contact.last_name].filter(Boolean).join(" ");
939
+ const companyName = contact.company_name || "";
940
+ if (personName && companyName) {
941
+ return `${personName} / ${companyName}`;
942
+ }
943
+ return personName || companyName || fallback;
944
+ }
945
+
936
946
  // ../types/src/keys.ts
937
947
  var DEFAULT_KEY_BUNDLE_ALG = "ECDH-P-521";
938
948
 
@@ -1725,45 +1735,7 @@ async function getPrivateKey() {
1725
1735
  // src/server.ts
1726
1736
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
1727
1737
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
1728
- var import_types5 = require("@modelcontextprotocol/sdk/types.js");
1729
-
1730
- // src/filter.ts
1731
- var REDACTION_RULES = {
1732
- // Password entries
1733
- password: ["password", "notes"],
1734
- // Identity documents
1735
- identity: ["password", "recoveryKey", "securityAnswers"],
1736
- // Financial accounts
1737
- bank_account: ["accountNumber", "routingNumber"],
1738
- investment: ["accountNumber"],
1739
- // Access codes
1740
- access_code: ["code", "pin"],
1741
- // Credentials (show last 4 of document number)
1742
- credential: ["documentNumber"]
1743
- };
1744
- var PARTIAL_REDACTION_FIELDS = ["documentNumber", "accountNumber"];
1745
- var REDACTED = "[REDACTED]";
1746
- function redactEntity(entity, entityType, mode) {
1747
- if (mode === "full") {
1748
- return entity;
1749
- }
1750
- const fieldsToRedact = REDACTION_RULES[entityType] || [];
1751
- if (fieldsToRedact.length === 0) {
1752
- return entity;
1753
- }
1754
- const redacted = { ...entity };
1755
- for (const field of fieldsToRedact) {
1756
- if (field in redacted && redacted[field] != null) {
1757
- const value = redacted[field];
1758
- if (PARTIAL_REDACTION_FIELDS.includes(field) && typeof value === "string" && value.length > 4) {
1759
- redacted[field] = `****${value.slice(-4)}`;
1760
- } else {
1761
- redacted[field] = REDACTED;
1762
- }
1763
- }
1764
- }
1765
- return redacted;
1766
- }
1738
+ var import_types42 = require("@modelcontextprotocol/sdk/types.js");
1767
1739
 
1768
1740
  // ../cache-sqlite/src/sqliteStore.ts
1769
1741
  var import_better_sqlite3 = __toESM(require("better-sqlite3"));
@@ -2478,194 +2450,2623 @@ async function downloadAndDecryptFile(client, householdId, fileId, entityId, ent
2478
2450
  mimeType
2479
2451
  );
2480
2452
  return {
2481
- data: decrypted.data,
2482
- dataBase64: base64Encode(decrypted.data),
2453
+ bytes: decrypted.bytes,
2454
+ dataBase64: base64Encode(decrypted.bytes),
2483
2455
  mimeType: decrypted.mimeType,
2484
2456
  fileName: fileInfo.fileName
2485
2457
  };
2486
2458
  }
2487
2459
 
2488
- // src/server.ts
2489
- async function startServer(mode) {
2490
- const config = loadConfig();
2491
- const privacyMode = mode || config.defaultMode;
2492
- console.error(`[MCP] Starting server in ${privacyMode} mode`);
2493
- const client = await getAuthenticatedClient();
2494
- if (!client) {
2495
- console.error("[MCP] Not logged in. Run: estatehelm login");
2496
- process.exit(1);
2497
- }
2498
- const privateKey = await getPrivateKey();
2499
- if (!privateKey) {
2500
- console.error("[MCP] Failed to load encryption keys. Run: estatehelm login");
2501
- process.exit(1);
2460
+ // src/resources/households.ts
2461
+ async function listHouseholds() {
2462
+ return getHouseholds();
2463
+ }
2464
+ async function getHousehold(householdId) {
2465
+ const households = await getHouseholds();
2466
+ return households.find((h) => h.id === householdId) || null;
2467
+ }
2468
+ async function getHouseholdSummary(householdId, privateKey, getDecryptedEntities2) {
2469
+ const households = await getHouseholds();
2470
+ const household = households.find((h) => h.id === householdId);
2471
+ if (!household) {
2472
+ return null;
2502
2473
  }
2503
- await initCache();
2504
- console.error("[MCP] Checking for updates...");
2505
- const synced = await syncIfNeeded(client, privateKey);
2506
- if (synced) {
2507
- console.error("[MCP] Cache updated");
2508
- } else {
2509
- console.error("[MCP] Cache is up to date");
2474
+ const entityTypes = [
2475
+ "pet",
2476
+ "property",
2477
+ "vehicle",
2478
+ "contact",
2479
+ "insurance",
2480
+ "bank_account",
2481
+ "investment",
2482
+ "subscription",
2483
+ "maintenance_task",
2484
+ "password",
2485
+ "access_code"
2486
+ ];
2487
+ const counts = {};
2488
+ for (const type of entityTypes) {
2489
+ const entities = await getDecryptedEntities2(householdId, type, privateKey);
2490
+ counts[type] = entities.length;
2510
2491
  }
2511
- const server = new import_server.Server(
2512
- {
2513
- name: "estatehelm",
2514
- version: "1.0.0"
2492
+ return {
2493
+ household: {
2494
+ id: household.id,
2495
+ name: household.name
2515
2496
  },
2516
- {
2517
- capabilities: {
2518
- resources: {},
2519
- tools: {},
2520
- prompts: {}
2497
+ counts,
2498
+ totalEntities: Object.values(counts).reduce((a, b) => a + b, 0)
2499
+ };
2500
+ }
2501
+
2502
+ // src/filter.ts
2503
+ var REDACTION_RULES = {
2504
+ // Password entries
2505
+ password: ["password", "notes"],
2506
+ // Identity documents
2507
+ identity: ["password", "recovery_key", "security_answers"],
2508
+ // Financial accounts
2509
+ bank_account: ["account_number", "routing_number"],
2510
+ investment: ["account_number"],
2511
+ // Access codes
2512
+ access_code: ["code", "pin"],
2513
+ // Credentials (show last 4 of document number)
2514
+ credential: ["document_number"]
2515
+ };
2516
+ var PARTIAL_REDACTION_FIELDS = ["document_number", "account_number"];
2517
+ var REDACTED = "[REDACTED]";
2518
+ function redactEntity(entity, entityType, mode) {
2519
+ if (mode === "full") {
2520
+ return entity;
2521
+ }
2522
+ const fieldsToRedact = REDACTION_RULES[entityType] || [];
2523
+ if (fieldsToRedact.length === 0) {
2524
+ return entity;
2525
+ }
2526
+ const redacted = { ...entity };
2527
+ for (const field of fieldsToRedact) {
2528
+ if (field in redacted && redacted[field] != null) {
2529
+ const value = redacted[field];
2530
+ if (PARTIAL_REDACTION_FIELDS.includes(field) && typeof value === "string" && value.length > 4) {
2531
+ redacted[field] = `****${value.slice(-4)}`;
2532
+ } else {
2533
+ redacted[field] = REDACTED;
2521
2534
  }
2522
2535
  }
2523
- );
2524
- server.setRequestHandler(import_types5.ListResourcesRequestSchema, async () => {
2525
- const households = await getHouseholds();
2526
- const resources = [
2527
- {
2528
- uri: "estatehelm://households",
2529
- name: "All Households",
2530
- description: "List of all households you have access to",
2531
- mimeType: "application/json"
2532
- }
2533
- ];
2534
- for (const household of households) {
2535
- resources.push({
2536
- uri: `estatehelm://households/${household.id}`,
2537
- name: household.name,
2538
- description: `Household: ${household.name}`,
2539
- mimeType: "application/json"
2540
- });
2541
- const entityTypes = [
2542
- "pet",
2543
- "property",
2544
- "vehicle",
2545
- "contact",
2546
- "insurance",
2547
- "bank_account",
2548
- "investment",
2549
- "subscription",
2550
- "maintenance_task",
2551
- "password",
2552
- "access_code",
2553
- "document",
2554
- "medical",
2555
- "prescription",
2556
- "credential",
2557
- "utility"
2558
- ];
2559
- for (const type of entityTypes) {
2560
- resources.push({
2561
- uri: `estatehelm://households/${household.id}/${type}`,
2562
- name: `${household.name} - ${formatEntityType(type)}`,
2563
- description: `${formatEntityType(type)} in ${household.name}`,
2564
- mimeType: "application/json"
2565
- });
2566
- }
2536
+ }
2537
+ return redacted;
2538
+ }
2539
+
2540
+ // ../list-data/src/computedFields.ts
2541
+ function getExpiryStatus(days) {
2542
+ if (days < 0) return "expired";
2543
+ if (days <= 30) return "expiring_soon";
2544
+ return "ok";
2545
+ }
2546
+
2547
+ // ../utils/src/dateHelpers.ts
2548
+ function calculateDetailedAge(dateOfBirth) {
2549
+ if (!dateOfBirth) return null;
2550
+ const birth = new Date(dateOfBirth);
2551
+ const today = /* @__PURE__ */ new Date();
2552
+ let years = today.getFullYear() - birth.getFullYear();
2553
+ let months = today.getMonth() - birth.getMonth();
2554
+ if (today.getDate() < birth.getDate()) {
2555
+ months--;
2556
+ }
2557
+ if (months < 0) {
2558
+ years--;
2559
+ months += 12;
2560
+ }
2561
+ const fractionalYears = years + months / 12;
2562
+ return { years, months, fractionalYears };
2563
+ }
2564
+ function calculateCatHumanYears(catAge) {
2565
+ if (catAge <= 0) return 0;
2566
+ if (catAge < 1) return Math.round(catAge * 15);
2567
+ if (catAge < 2) return Math.round(15 + (catAge - 1) * 9);
2568
+ return Math.round(24 + (catAge - 2) * 4);
2569
+ }
2570
+ function calculateDogHumanYears(dogAge, size = "medium") {
2571
+ if (dogAge <= 0) return 0;
2572
+ if (dogAge < 1) return Math.round(dogAge * 15);
2573
+ if (dogAge < 2) return Math.round(15 + (dogAge - 1) * 9);
2574
+ const yearlyRate = size === "small" ? 4 : size === "large" ? 6 : 5;
2575
+ return Math.round(24 + (dogAge - 2) * yearlyRate);
2576
+ }
2577
+ function parseDateLocal(dateStr) {
2578
+ const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})/);
2579
+ if (match) {
2580
+ const [, year, month, day] = match;
2581
+ return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
2582
+ }
2583
+ return new Date(dateStr);
2584
+ }
2585
+ function daysUntil(dateStr) {
2586
+ const targetDate = parseDateLocal(dateStr);
2587
+ const today = /* @__PURE__ */ new Date();
2588
+ today.setHours(0, 0, 0, 0);
2589
+ const diffTime = targetDate.getTime() - today.getTime();
2590
+ return Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
2591
+ }
2592
+
2593
+ // ../utils/src/search/searchEngine.ts
2594
+ var DEFAULT_OPTIONS = {
2595
+ minQueryLength: 1,
2596
+ maxPerGroup: 5,
2597
+ maxTotal: 25,
2598
+ includeTypes: [],
2599
+ excludeTypes: []
2600
+ };
2601
+ function scoreMatch(query, text) {
2602
+ if (!query || !text) return 0;
2603
+ const queryLower = query.toLowerCase().trim();
2604
+ const textLower = text.toLowerCase();
2605
+ if (queryLower.length === 0) return 0;
2606
+ if (textLower === queryLower) {
2607
+ return 100;
2608
+ }
2609
+ if (textLower.startsWith(queryLower)) {
2610
+ return 75;
2611
+ }
2612
+ const words = textLower.split(/\s+/);
2613
+ for (const word of words) {
2614
+ if (word.startsWith(queryLower)) {
2615
+ return 50;
2567
2616
  }
2568
- return { resources };
2569
- });
2570
- server.setRequestHandler(import_types5.ReadResourceRequestSchema, async (request) => {
2571
- const uri = request.params.uri;
2572
- const parsed = parseResourceUri(uri);
2573
- if (!parsed) {
2574
- throw new Error(`Invalid resource URI: ${uri}`);
2617
+ }
2618
+ if (textLower.includes(queryLower)) {
2619
+ return 25;
2620
+ }
2621
+ return 0;
2622
+ }
2623
+ function scoreEntity(entity, query) {
2624
+ const matchedFields = [];
2625
+ let bestScore = 0;
2626
+ const nameScore = scoreMatch(query, entity.name);
2627
+ if (nameScore > 0) {
2628
+ matchedFields.push("name");
2629
+ bestScore = Math.max(bestScore, nameScore);
2630
+ }
2631
+ if (entity.subtitle) {
2632
+ const subtitleScore = scoreMatch(query, entity.subtitle);
2633
+ if (subtitleScore > 0) {
2634
+ matchedFields.push("subtitle");
2635
+ bestScore = Math.max(bestScore, subtitleScore * 0.9);
2575
2636
  }
2576
- let content;
2577
- if (parsed.type === "households" && !parsed.householdId) {
2578
- content = await getHouseholds();
2579
- } else if (parsed.type === "households" && parsed.householdId && !parsed.entityType) {
2580
- const households = await getHouseholds();
2581
- content = households.find((h) => h.id === parsed.householdId);
2582
- if (!content) {
2583
- throw new Error(`Household not found: ${parsed.householdId}`);
2637
+ }
2638
+ if (entity.keywords && entity.keywords.length > 0) {
2639
+ for (const keyword of entity.keywords) {
2640
+ const keywordScore = scoreMatch(query, keyword);
2641
+ if (keywordScore > 0) {
2642
+ if (!matchedFields.includes("keywords")) {
2643
+ matchedFields.push("keywords");
2644
+ }
2645
+ bestScore = Math.max(bestScore, keywordScore * 0.8);
2584
2646
  }
2585
- } else if (parsed.householdId && parsed.entityType) {
2586
- const entities = await getDecryptedEntities(parsed.householdId, parsed.entityType, privateKey);
2587
- content = entities.map((e) => redactEntity(e, parsed.entityType, privacyMode));
2588
- } else {
2589
- throw new Error(`Unsupported resource: ${uri}`);
2590
2647
  }
2591
- return {
2592
- contents: [
2593
- {
2594
- uri,
2595
- mimeType: "application/json",
2596
- text: JSON.stringify(content, null, 2)
2597
- }
2598
- ]
2599
- };
2648
+ }
2649
+ return { score: bestScore, matchedFields };
2650
+ }
2651
+ function searchEntities(entities, query, options) {
2652
+ const opts = { ...DEFAULT_OPTIONS, ...options };
2653
+ const trimmedQuery = query.trim();
2654
+ if (trimmedQuery.length < opts.minQueryLength) {
2655
+ return [];
2656
+ }
2657
+ let filteredEntities = entities;
2658
+ if (opts.includeTypes && opts.includeTypes.length > 0) {
2659
+ filteredEntities = filteredEntities.filter(
2660
+ (e) => opts.includeTypes.includes(e.entityType)
2661
+ );
2662
+ }
2663
+ if (opts.excludeTypes && opts.excludeTypes.length > 0) {
2664
+ filteredEntities = filteredEntities.filter(
2665
+ (e) => !opts.excludeTypes.includes(e.entityType)
2666
+ );
2667
+ }
2668
+ if (trimmedQuery.length === 0) {
2669
+ const results2 = filteredEntities.map((entity) => ({
2670
+ entity,
2671
+ score: 1,
2672
+ // Default score for unfiltered results
2673
+ matchedFields: []
2674
+ }));
2675
+ results2.sort((a, b) => (a.entity?.name || "").localeCompare(b.entity?.name || ""));
2676
+ return results2.slice(0, opts.maxTotal);
2677
+ }
2678
+ const results = [];
2679
+ for (const entity of filteredEntities) {
2680
+ const { score, matchedFields } = scoreEntity(entity, trimmedQuery);
2681
+ if (score > 0) {
2682
+ results.push({ entity, score, matchedFields });
2683
+ }
2684
+ }
2685
+ results.sort((a, b) => {
2686
+ if (b.score !== a.score) {
2687
+ return b.score - a.score;
2688
+ }
2689
+ return (a.entity?.name || "").localeCompare(b.entity?.name || "");
2600
2690
  });
2601
- server.setRequestHandler(import_types5.ListToolsRequestSchema, async () => {
2602
- return {
2603
- tools: [
2604
- {
2605
- name: "search_entities",
2606
- description: "Search across all entities in EstateHelm",
2607
- inputSchema: {
2608
- type: "object",
2609
- properties: {
2610
- query: {
2611
- type: "string",
2612
- description: "Search query"
2613
- },
2614
- householdId: {
2615
- type: "string",
2616
- description: "Optional: Limit search to a specific household"
2617
- },
2618
- entityType: {
2619
- type: "string",
2620
- description: "Optional: Limit search to a specific entity type"
2621
- }
2622
- },
2623
- required: ["query"]
2624
- }
2625
- },
2626
- {
2627
- name: "get_household_summary",
2628
- description: "Get a summary of a household including counts and key dates",
2629
- inputSchema: {
2630
- type: "object",
2631
- properties: {
2632
- householdId: {
2633
- type: "string",
2634
- description: "The household ID"
2635
- }
2636
- },
2637
- required: ["householdId"]
2638
- }
2639
- },
2640
- {
2641
- name: "get_expiring_items",
2642
- description: "Get items expiring within a given number of days",
2643
- inputSchema: {
2644
- type: "object",
2645
- properties: {
2646
- days: {
2647
- type: "number",
2648
- description: "Number of days to look ahead (default: 30)"
2649
- },
2650
- householdId: {
2651
- type: "string",
2652
- description: "Optional: Limit to a specific household"
2653
- }
2654
- }
2655
- }
2656
- },
2657
- {
2658
- name: "refresh",
2659
- description: "Force refresh of cached data from the server",
2660
- inputSchema: {
2661
- type: "object",
2662
- properties: {}
2663
- }
2664
- },
2665
- {
2666
- name: "get_file",
2667
- description: "Download and decrypt a file attachment. Returns base64-encoded file data that can be saved to disk.",
2668
- inputSchema: {
2691
+ return results.slice(0, opts.maxTotal);
2692
+ }
2693
+
2694
+ // ../utils/src/search/entityIndexer.ts
2695
+ function formatLabel(value) {
2696
+ if (!value) return "";
2697
+ return value.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2698
+ }
2699
+ function getVehicleName(vehicle) {
2700
+ if (vehicle.name) return vehicle.name;
2701
+ const parts = [vehicle.year, vehicle.make, vehicle.model].filter(Boolean);
2702
+ return parts.length > 0 ? parts.join(" ") : "Vehicle";
2703
+ }
2704
+ function getContactName(contact) {
2705
+ const fullName = [contact.first_name, contact.last_name].filter(Boolean).join(" ");
2706
+ if (fullName) return fullName;
2707
+ if (contact.company_name) return contact.company_name;
2708
+ return "Contact";
2709
+ }
2710
+ function getSubscriptionName(sub) {
2711
+ return sub.custom_name || formatLabel(sub.provider) || "Subscription";
2712
+ }
2713
+ function getServiceName(service) {
2714
+ return service.service_name || service.name || "Service";
2715
+ }
2716
+ function getPropertyName(property) {
2717
+ if (property.name) return property.name;
2718
+ if (property.street_address) {
2719
+ return property.city ? `${property.street_address}, ${property.city}` : property.street_address;
2720
+ }
2721
+ return "Property";
2722
+ }
2723
+ function getPetName(pet) {
2724
+ return pet.name || "Pet";
2725
+ }
2726
+ function getAccessCodeName(code) {
2727
+ return code.name || code.description || "Access Code";
2728
+ }
2729
+ function getFinancialAccountName(account) {
2730
+ return account.name || account.account_name || account.nickname || (account.institution ? `${account.institution} Account` : "Financial Account");
2731
+ }
2732
+ function getInsuranceName(policy) {
2733
+ return policy.name || policy.policy_name || policy.provider || formatLabel(policy.type) || "Insurance";
2734
+ }
2735
+ function getValuableName(valuable) {
2736
+ return valuable.name || valuable.item_name || valuable.description || "Valuable";
2737
+ }
2738
+ function getMaintenanceTaskName(task) {
2739
+ return task.name || task.title || task.task_name || "Maintenance Task";
2740
+ }
2741
+ function getDeviceName(device) {
2742
+ return device.name || device.device_name || "Device";
2743
+ }
2744
+ function getPetVetVisitName(visit) {
2745
+ return visit.name || visit.clinic_name || visit.reason || "Vet Visit";
2746
+ }
2747
+ function getPetHealthName(record) {
2748
+ return record.name || (record.record_type ? formatLabel(record.record_type) : "Health Record");
2749
+ }
2750
+ function getVehicleMaintenanceName(record) {
2751
+ return record.name || record.service_type || record.description || "Maintenance Record";
2752
+ }
2753
+ function getVehicleServiceVisitName(visit) {
2754
+ return visit.name || visit.shop_name || visit.description || "Service Visit";
2755
+ }
2756
+ function getEntityDisplayName(entityType, data) {
2757
+ switch (entityType) {
2758
+ case "property":
2759
+ return getPropertyName(data);
2760
+ case "vehicle":
2761
+ return getVehicleName(data);
2762
+ case "pet":
2763
+ return getPetName(data);
2764
+ case "contact":
2765
+ return getContactName(data);
2766
+ case "subscription":
2767
+ return getSubscriptionName(data);
2768
+ case "service":
2769
+ return getServiceName(data);
2770
+ case "insurance":
2771
+ return getInsuranceName(data);
2772
+ case "valuable":
2773
+ return getValuableName(data);
2774
+ case "financial_account":
2775
+ return getFinancialAccountName(data);
2776
+ case "access_code":
2777
+ return getAccessCodeName(data);
2778
+ case "maintenance_task":
2779
+ return getMaintenanceTaskName(data);
2780
+ case "device":
2781
+ return getDeviceName(data);
2782
+ case "pet_vet_visit":
2783
+ return getPetVetVisitName(data);
2784
+ case "pet_health":
2785
+ return getPetHealthName(data);
2786
+ case "vehicle_maintenance":
2787
+ return getVehicleMaintenanceName(data);
2788
+ case "vehicle_service_visit":
2789
+ return getVehicleServiceVisitName(data);
2790
+ case "home_improvement":
2791
+ return data.name || data.title || data.project_name || "Home Improvement";
2792
+ case "credential":
2793
+ return data.name || data.title || "Credential";
2794
+ case "password":
2795
+ return data.name || data.site_name || data.title || "Password";
2796
+ case "legal":
2797
+ return data.name || data.title || "Legal Document";
2798
+ case "military_record":
2799
+ return data.name || data.title || "Military Record";
2800
+ case "education_record":
2801
+ return data.name || data.institution || data.title || "Education Record";
2802
+ case "travel":
2803
+ return data.name || data.document_type || "Travel Document";
2804
+ case "identity":
2805
+ return data.name || data.document_type || "Identity Document";
2806
+ case "document":
2807
+ return data.name || data.title || data.file_name || "Document";
2808
+ default:
2809
+ return data.name || data.title || entityType;
2810
+ }
2811
+ }
2812
+ function indexProperty(property) {
2813
+ const subtitle = property.city && property.state ? `${property.city}, ${property.state}` : property.street_address || "Property";
2814
+ return {
2815
+ id: property.id,
2816
+ entityType: "property",
2817
+ name: property.name,
2818
+ subtitle,
2819
+ // Synonyms: house, home for natural language searches
2820
+ keywords: [property.street_address, property.city, property.state, "house", "home"].filter(Boolean),
2821
+ icon: "Home",
2822
+ route: `/property?id=${property.id}`,
2823
+ routeParams: { id: property.id }
2824
+ };
2825
+ }
2826
+ function indexVehicle(vehicle) {
2827
+ const subtitle = formatLabel(vehicle.vehicle_type) || "Vehicle";
2828
+ const synonyms = [];
2829
+ if (vehicle.vehicle_type === "car" || vehicle.vehicle_type === "sedan" || vehicle.vehicle_type === "suv" || vehicle.vehicle_type === "truck") {
2830
+ synonyms.push("car", "auto", "automobile");
2831
+ }
2832
+ if (vehicle.vehicle_type === "motorcycle") {
2833
+ synonyms.push("bike", "motorbike");
2834
+ }
2835
+ if (vehicle.vehicle_type === "boat") {
2836
+ synonyms.push("watercraft");
2837
+ }
2838
+ const keywords = [vehicle.make, vehicle.model, vehicle.license_plate, ...synonyms].filter(Boolean);
2839
+ return {
2840
+ id: vehicle.id,
2841
+ entityType: "vehicle",
2842
+ name: getVehicleName(vehicle),
2843
+ subtitle,
2844
+ keywords,
2845
+ icon: "Car",
2846
+ route: `/vehicle?id=${vehicle.id}`,
2847
+ routeParams: { id: vehicle.id }
2848
+ };
2849
+ }
2850
+ function indexPet(pet) {
2851
+ const speciesLabel = formatLabel(pet.species);
2852
+ const subtitle = pet.breed ? `${speciesLabel} - ${pet.breed}` : speciesLabel || "Pet";
2853
+ const synonyms = [];
2854
+ if (pet.species === "dog" || pet.species === "canine") {
2855
+ synonyms.push("dog", "puppy", "pup");
2856
+ } else if (pet.species === "cat" || pet.species === "feline") {
2857
+ synonyms.push("cat", "kitty", "kitten");
2858
+ } else if (pet.species === "bird") {
2859
+ synonyms.push("bird", "parrot");
2860
+ } else if (pet.species === "fish") {
2861
+ synonyms.push("fish", "aquarium");
2862
+ }
2863
+ return {
2864
+ id: pet.id,
2865
+ entityType: "pet",
2866
+ name: pet.name,
2867
+ subtitle,
2868
+ keywords: [pet.breed, pet.species, ...synonyms].filter(Boolean),
2869
+ icon: "PawPrint",
2870
+ route: `/pet?id=${pet.id}`,
2871
+ routeParams: { id: pet.id }
2872
+ };
2873
+ }
2874
+ function indexContact(contact, linkedEntities) {
2875
+ const subtitle = contact.specialty ? `${formatLabel(contact.type)} - ${contact.specialty}` : formatLabel(contact.type) || "Contact";
2876
+ const linkedNames = linkedEntities?.map((e) => e.name) || [];
2877
+ const linkedRoles = linkedEntities?.map((e) => formatLabel(e.role)) || [];
2878
+ return {
2879
+ id: contact.id,
2880
+ entityType: "contact",
2881
+ name: getContactName(contact),
2882
+ subtitle,
2883
+ keywords: [
2884
+ contact.company_name,
2885
+ contact.first_name,
2886
+ contact.last_name,
2887
+ contact.specialty,
2888
+ ...linkedNames,
2889
+ ...linkedRoles
2890
+ ].filter(Boolean),
2891
+ icon: "Phone",
2892
+ route: `/contact?id=${contact.id}`,
2893
+ routeParams: { id: contact.id }
2894
+ };
2895
+ }
2896
+ function indexSubscription(sub) {
2897
+ const subtitle = formatLabel(sub.category) || "Subscription";
2898
+ const displayName = getSubscriptionName(sub);
2899
+ const keywords = [];
2900
+ if (sub.provider) {
2901
+ keywords.push(sub.provider);
2902
+ const formatted = formatLabel(sub.provider);
2903
+ if (formatted !== sub.provider) {
2904
+ keywords.push(formatted);
2905
+ }
2906
+ }
2907
+ if (sub.custom_name) {
2908
+ keywords.push(sub.custom_name);
2909
+ }
2910
+ if (sub.category) {
2911
+ keywords.push(sub.category);
2912
+ }
2913
+ const formattedProvider = formatLabel(sub.provider);
2914
+ const searchableName = sub.provider && formattedProvider !== displayName ? `${displayName} (${formattedProvider})` : displayName;
2915
+ return {
2916
+ id: sub.id,
2917
+ entityType: "subscription",
2918
+ name: searchableName,
2919
+ subtitle,
2920
+ keywords,
2921
+ icon: "CreditCard",
2922
+ route: `/subscription?id=${sub.id}`,
2923
+ routeParams: { id: sub.id }
2924
+ };
2925
+ }
2926
+ function indexService(service) {
2927
+ const subtitle = formatLabel(service.service_type) || "Service";
2928
+ return {
2929
+ id: service.id,
2930
+ entityType: "service",
2931
+ name: getServiceName(service),
2932
+ subtitle,
2933
+ keywords: [service.provider, service.service_type].filter(Boolean),
2934
+ icon: "Wrench",
2935
+ route: `/service?id=${service.id}`,
2936
+ routeParams: { id: service.id }
2937
+ };
2938
+ }
2939
+ function indexInsurance(policy, coveredAssetNames) {
2940
+ const subtitle = formatLabel(policy.type) || "Insurance";
2941
+ const keywords = [policy.policy_number, policy.type, ...coveredAssetNames || []].filter(Boolean);
2942
+ return {
2943
+ id: policy.id,
2944
+ entityType: "insurance",
2945
+ name: policy.provider || "Insurance Policy",
2946
+ subtitle,
2947
+ keywords,
2948
+ icon: "Shield",
2949
+ route: `/insurance?id=${policy.id}`,
2950
+ routeParams: { id: policy.id }
2951
+ };
2952
+ }
2953
+ function indexValuable(valuable) {
2954
+ const categoryLabel = valuable.category === "other" && valuable.other_category ? valuable.other_category : formatLabel(valuable.category);
2955
+ const subtitle = categoryLabel || "Valuable";
2956
+ return {
2957
+ id: valuable.id,
2958
+ entityType: "valuable",
2959
+ name: valuable.name,
2960
+ subtitle,
2961
+ keywords: [valuable.category, valuable.other_category].filter(Boolean),
2962
+ icon: "Gem",
2963
+ route: `/valuables?id=${valuable.id}`,
2964
+ routeParams: { id: valuable.id }
2965
+ };
2966
+ }
2967
+ function indexFinancialAccount(account, ownerNames) {
2968
+ const typeLabel = formatLabel(account.account_type) || "Financial Account";
2969
+ let subtitle = typeLabel;
2970
+ if (ownerNames && ownerNames.length > 0) {
2971
+ subtitle = `${typeLabel} \u2022 ${ownerNames.join(", ")}`;
2972
+ }
2973
+ return {
2974
+ id: account.id,
2975
+ entityType: "financial_account",
2976
+ name: account.nickname || account.institution || "Financial Account",
2977
+ subtitle,
2978
+ keywords: [account.institution, account.account_type, ...ownerNames || []].filter(Boolean),
2979
+ icon: "Landmark",
2980
+ route: `/financial?id=${account.id}`,
2981
+ routeParams: { id: account.id }
2982
+ };
2983
+ }
2984
+ function indexCredential(credential, personName) {
2985
+ const subtypeLabel = formatLabel(credential.credential_subtype);
2986
+ const typeLabel = subtypeLabel || formatLabel(credential.credential_type) || "Credential";
2987
+ const subtitle = personName ? `${typeLabel} \u2022 ${personName}` : typeLabel;
2988
+ const synonyms = ["id", "identification"];
2989
+ const subtype = credential.credential_subtype?.toLowerCase() || "";
2990
+ const type = credential.credential_type?.toLowerCase() || "";
2991
+ if (subtype.includes("passport")) {
2992
+ synonyms.push("passport", "travel document");
2993
+ }
2994
+ if (subtype.includes("driver") || subtype.includes("license")) {
2995
+ synonyms.push("license", "drivers license", "DL", "driving license");
2996
+ }
2997
+ if (subtype.includes("ssn") || subtype.includes("social_security")) {
2998
+ synonyms.push("social security", "SSN", "social security number");
2999
+ }
3000
+ if (type.includes("professional") || type.includes("license")) {
3001
+ synonyms.push("license", "certification", "cert");
3002
+ }
3003
+ if (subtype.includes("birth") || subtype.includes("certificate")) {
3004
+ synonyms.push("birth certificate", "certificate");
3005
+ }
3006
+ return {
3007
+ id: credential.id,
3008
+ entityType: "credential",
3009
+ name: credential.name,
3010
+ subtitle,
3011
+ keywords: [credential.credential_type, credential.credential_subtype, credential.issuing_authority, personName, ...synonyms].filter(Boolean),
3012
+ icon: "CreditCard",
3013
+ route: `/credentials?id=${credential.id}`,
3014
+ routeParams: { id: credential.id }
3015
+ };
3016
+ }
3017
+ function indexPetVetVisit(visit, petName) {
3018
+ const procedureNames = visit.procedures?.map((p) => p.name).filter(Boolean).join(", ");
3019
+ let subtitle = petName || "Vet Visit";
3020
+ if (petName && procedureNames) {
3021
+ subtitle = `${petName} \u2022 ${procedureNames}`;
3022
+ } else if (procedureNames) {
3023
+ subtitle = procedureNames;
3024
+ }
3025
+ return {
3026
+ id: visit.id,
3027
+ entityType: "pet_vet_visit",
3028
+ name: visit.name || `${petName || "Pet"} - Vet Visit`,
3029
+ subtitle,
3030
+ keywords: [petName, ...visit.procedures?.map((p) => p.name).filter(Boolean) || []].filter(Boolean),
3031
+ icon: "Stethoscope",
3032
+ route: `/pet?tab=health&id=${visit.id}`,
3033
+ routeParams: { tab: "health", id: visit.id }
3034
+ };
3035
+ }
3036
+ function indexVehicleService(service, vehicleName) {
3037
+ const serviceNames = service.services?.map((s) => s.name).filter(Boolean).join(", ");
3038
+ let subtitle = vehicleName || "Service Record";
3039
+ if (vehicleName && serviceNames) {
3040
+ subtitle = `${vehicleName} \u2022 ${serviceNames}`;
3041
+ } else if (serviceNames) {
3042
+ subtitle = serviceNames;
3043
+ }
3044
+ return {
3045
+ id: service.id,
3046
+ entityType: "vehicle_service",
3047
+ name: service.name || `${vehicleName || "Vehicle"} - Service`,
3048
+ subtitle,
3049
+ keywords: [vehicleName, ...service.services?.map((s) => s.name).filter(Boolean) || []].filter(Boolean),
3050
+ icon: "FileText",
3051
+ route: `/vehicle-maintenance?id=${service.id}`,
3052
+ routeParams: { id: service.id }
3053
+ };
3054
+ }
3055
+ function indexMaintenanceTask(task, assetName) {
3056
+ const subtitle = assetName ? `${formatLabel(task.category)} - ${assetName}` : formatLabel(task.category) || "Maintenance";
3057
+ return {
3058
+ id: task.id,
3059
+ entityType: "maintenance_task",
3060
+ name: task.name || task.title || "Maintenance Task",
3061
+ subtitle,
3062
+ keywords: [task.category].filter(Boolean),
3063
+ icon: "ClipboardList",
3064
+ route: `/maintenance?id=${task.id}`,
3065
+ routeParams: { id: task.id }
3066
+ };
3067
+ }
3068
+ function indexLegalDocument(doc, residentNames) {
3069
+ const typeLabel = formatLabel(doc.type) || "Legal Document";
3070
+ let subtitle = typeLabel;
3071
+ if (residentNames && residentNames.length > 0) {
3072
+ subtitle = `${typeLabel} \u2022 ${residentNames.join(", ")}`;
3073
+ }
3074
+ const synonyms = ["doc", "document", "legal", "paperwork"];
3075
+ return {
3076
+ id: doc.id,
3077
+ entityType: "legal_document",
3078
+ name: doc.name,
3079
+ subtitle,
3080
+ keywords: [doc.type, ...residentNames || [], ...synonyms].filter(Boolean),
3081
+ icon: "FileText",
3082
+ route: `/legal?id=${doc.id}`,
3083
+ routeParams: { id: doc.id }
3084
+ };
3085
+ }
3086
+ function indexAccessCode(code, propertyName) {
3087
+ const subtitle = propertyName ? `${formatLabel(code.code_type)} - ${propertyName}` : formatLabel(code.code_type) || "Access Code";
3088
+ return {
3089
+ id: code.id,
3090
+ entityType: "access_code",
3091
+ name: code.name,
3092
+ subtitle,
3093
+ keywords: [code.code_type].filter(Boolean),
3094
+ icon: "Key",
3095
+ route: `/property?tab=access&id=${code.id}`,
3096
+ routeParams: { tab: "access", id: code.id }
3097
+ };
3098
+ }
3099
+ function indexDevice(device) {
3100
+ const subtitle = device.brand ? `${formatLabel(device.device_type)} - ${device.brand}` : formatLabel(device.device_type) || "Device";
3101
+ return {
3102
+ id: device.id,
3103
+ entityType: "device",
3104
+ name: device.name,
3105
+ subtitle,
3106
+ keywords: [device.device_type, device.brand, device.model].filter(Boolean),
3107
+ icon: "Wifi",
3108
+ route: `/device?id=${device.id}`,
3109
+ routeParams: { id: device.id }
3110
+ };
3111
+ }
3112
+ function indexPerson(person) {
3113
+ const subtitle = formatLabel(person.relationship_type) || formatLabel(person.person_type) || "Person";
3114
+ return {
3115
+ id: person.id,
3116
+ entityType: "person",
3117
+ name: person.name,
3118
+ subtitle,
3119
+ keywords: [person.person_type, person.relationship_type].filter(Boolean),
3120
+ icon: "User",
3121
+ route: `/people?id=${person.id}`,
3122
+ routeParams: { id: person.id }
3123
+ };
3124
+ }
3125
+ function indexHealthRecord(record, ownerName) {
3126
+ const subtitle = ownerName ? `${formatLabel(record.record_type)} - ${ownerName}` : formatLabel(record.record_type) || "Health Record";
3127
+ return {
3128
+ id: record.id,
3129
+ entityType: "health_record",
3130
+ name: record.name,
3131
+ subtitle,
3132
+ keywords: [record.record_type, record.provider].filter(Boolean),
3133
+ icon: "Heart",
3134
+ route: `/records?type=health_record&id=${record.id}`,
3135
+ routeParams: { id: record.id }
3136
+ };
3137
+ }
3138
+ function indexEducationRecord(record, personName) {
3139
+ const subtitle = personName ? `${formatLabel(record.record_type)} - ${personName}` : record.institution || formatLabel(record.record_type) || "Education Record";
3140
+ return {
3141
+ id: record.id,
3142
+ entityType: "education_record",
3143
+ name: record.name,
3144
+ subtitle,
3145
+ keywords: [record.record_type, record.institution, record.level, record.field_of_study].filter(Boolean),
3146
+ icon: "GraduationCap",
3147
+ route: `/records?type=education_record&id=${record.id}`,
3148
+ routeParams: { id: record.id }
3149
+ };
3150
+ }
3151
+ function indexMilitaryRecord(record, personName) {
3152
+ const branchLabel = formatLabel(record.branch);
3153
+ const subtitle = personName ? `${branchLabel || formatLabel(record.record_type)} - ${personName}` : branchLabel || formatLabel(record.record_type) || "Military Record";
3154
+ return {
3155
+ id: record.id,
3156
+ entityType: "military_record",
3157
+ name: record.name,
3158
+ subtitle,
3159
+ keywords: [record.record_type, record.branch, record.rank].filter(Boolean),
3160
+ icon: "Medal",
3161
+ route: `/records?type=military_record&id=${record.id}`,
3162
+ routeParams: { id: record.id }
3163
+ };
3164
+ }
3165
+ function indexHomeImprovement(improvement, propertyName) {
3166
+ const subtitle = propertyName ? `${formatLabel(improvement.improvement_type)} - ${propertyName}` : formatLabel(improvement.improvement_type) || "Home Improvement";
3167
+ return {
3168
+ id: improvement.id,
3169
+ entityType: "home_improvement",
3170
+ name: improvement.name,
3171
+ subtitle,
3172
+ keywords: [improvement.improvement_type].filter(Boolean),
3173
+ icon: "Hammer",
3174
+ route: `/home-improvement/${improvement.id}`,
3175
+ routeParams: { id: improvement.id }
3176
+ };
3177
+ }
3178
+ function indexTaxYear(taxYear, filerNames) {
3179
+ let subtitle = taxYear.country || "Tax Year";
3180
+ if (filerNames && filerNames.length > 0) {
3181
+ subtitle = filerNames.join(", ");
3182
+ }
3183
+ return {
3184
+ id: taxYear.id,
3185
+ entityType: "tax_year",
3186
+ name: `${taxYear.year} Tax Year`,
3187
+ subtitle,
3188
+ keywords: [taxYear.country, String(taxYear.year), ...filerNames || []].filter(Boolean),
3189
+ icon: "Receipt",
3190
+ route: `/taxes?id=${taxYear.id}`,
3191
+ routeParams: { id: taxYear.id }
3192
+ };
3193
+ }
3194
+ function indexMembershipRecord(record, personName) {
3195
+ const typeLabel = formatLabel(record.membership_type);
3196
+ let subtitle = typeLabel || "Membership";
3197
+ if (personName) {
3198
+ subtitle = record.level ? `${typeLabel} \u2022 ${record.level} \u2022 ${personName}` : `${typeLabel} \u2022 ${personName}`;
3199
+ } else if (record.level) {
3200
+ subtitle = `${typeLabel} \u2022 ${record.level}`;
3201
+ }
3202
+ return {
3203
+ id: record.id,
3204
+ entityType: "membership_record",
3205
+ name: record.name,
3206
+ subtitle,
3207
+ keywords: [record.membership_type, record.membership_number, record.level, record.alliance, personName].filter(Boolean),
3208
+ icon: "Ticket",
3209
+ route: `/records?type=membership_record&id=${record.id}`,
3210
+ routeParams: { id: record.id }
3211
+ };
3212
+ }
3213
+ function indexAllEntities(data) {
3214
+ const entities = [];
3215
+ const propertyNames = /* @__PURE__ */ new Map();
3216
+ const vehicleNames = /* @__PURE__ */ new Map();
3217
+ const petNames = /* @__PURE__ */ new Map();
3218
+ const personNames = /* @__PURE__ */ new Map();
3219
+ data.properties?.forEach((p) => propertyNames.set(p.id, p.name));
3220
+ data.vehicles?.forEach((v) => vehicleNames.set(v.id, getVehicleName(v)));
3221
+ data.pets?.forEach((p) => petNames.set(p.id, p.name));
3222
+ data.people?.forEach((p) => personNames.set(p.id, p.name));
3223
+ const contactLinkedEntities = /* @__PURE__ */ new Map();
3224
+ const addContactLink = (contactId, name, role) => {
3225
+ const existing = contactLinkedEntities.get(contactId) || [];
3226
+ existing.push({ name, role });
3227
+ contactLinkedEntities.set(contactId, existing);
3228
+ };
3229
+ data.pets?.forEach((pet) => {
3230
+ pet.contact_relationships?.forEach((rel) => {
3231
+ addContactLink(rel.contact_id, pet.name, rel.role);
3232
+ });
3233
+ });
3234
+ data.vehicles?.forEach((vehicle) => {
3235
+ const name = getVehicleName(vehicle);
3236
+ vehicle.contact_relationships?.forEach((rel) => {
3237
+ addContactLink(rel.contact_id, name, rel.role);
3238
+ });
3239
+ });
3240
+ data.properties?.forEach((property) => {
3241
+ property.contact_relationships?.forEach((rel) => {
3242
+ addContactLink(rel.contact_id, property.name, rel.role);
3243
+ });
3244
+ });
3245
+ data.properties?.forEach((p) => entities.push(indexProperty(p)));
3246
+ data.vehicles?.forEach((v) => entities.push(indexVehicle(v)));
3247
+ data.pets?.forEach((p) => entities.push(indexPet(p)));
3248
+ data.contacts?.forEach((c) => {
3249
+ const linkedEntities = contactLinkedEntities.get(c.id);
3250
+ entities.push(indexContact(c, linkedEntities));
3251
+ });
3252
+ data.subscriptions?.forEach((s) => entities.push(indexSubscription(s)));
3253
+ data.services?.forEach((s) => entities.push(indexService(s)));
3254
+ data.insurancePolicies?.forEach((policy) => {
3255
+ const coveredAssetNames = [];
3256
+ policy.relationships?.forEach((rel) => {
3257
+ if (rel.entity_type === "property") {
3258
+ const name = propertyNames.get(rel.entity_id);
3259
+ if (name) coveredAssetNames.push(name);
3260
+ } else if (rel.entity_type === "vehicle") {
3261
+ const name = vehicleNames.get(rel.entity_id);
3262
+ if (name) coveredAssetNames.push(name);
3263
+ } else if (rel.entity_type === "pet") {
3264
+ const name = petNames.get(rel.entity_id);
3265
+ if (name) coveredAssetNames.push(name);
3266
+ }
3267
+ });
3268
+ if (policy.property_id) {
3269
+ const name = propertyNames.get(policy.property_id);
3270
+ if (name && !coveredAssetNames.includes(name)) coveredAssetNames.push(name);
3271
+ }
3272
+ if (policy.vehicle_id) {
3273
+ const name = vehicleNames.get(policy.vehicle_id);
3274
+ if (name && !coveredAssetNames.includes(name)) coveredAssetNames.push(name);
3275
+ }
3276
+ if (policy.pet_id) {
3277
+ const name = petNames.get(policy.pet_id);
3278
+ if (name && !coveredAssetNames.includes(name)) coveredAssetNames.push(name);
3279
+ }
3280
+ entities.push(indexInsurance(policy, coveredAssetNames.length > 0 ? coveredAssetNames : void 0));
3281
+ });
3282
+ data.valuables?.forEach((v) => entities.push(indexValuable(v)));
3283
+ data.financialAccounts?.forEach((a) => {
3284
+ const ownerNames = a.owner_ids?.map((id) => personNames.get(id)).filter(Boolean);
3285
+ entities.push(indexFinancialAccount(a, ownerNames));
3286
+ });
3287
+ data.credentials?.forEach((c) => {
3288
+ const personName = c.person_id ? personNames.get(c.person_id) : void 0;
3289
+ entities.push(indexCredential(c, personName));
3290
+ });
3291
+ data.petVetVisits?.forEach((v) => {
3292
+ const petName = v.pet_id ? petNames.get(v.pet_id) : void 0;
3293
+ entities.push(indexPetVetVisit(v, petName));
3294
+ });
3295
+ data.vehicleServices?.forEach((s) => {
3296
+ const vehicleName = s.vehicle_id ? vehicleNames.get(s.vehicle_id) : void 0;
3297
+ entities.push(indexVehicleService(s, vehicleName));
3298
+ });
3299
+ data.maintenanceTasks?.forEach((t) => {
3300
+ let assetName;
3301
+ if (t.property_id) assetName = propertyNames.get(t.property_id);
3302
+ else if (t.vehicle_id) assetName = vehicleNames.get(t.vehicle_id);
3303
+ entities.push(indexMaintenanceTask(t, assetName));
3304
+ });
3305
+ data.legalDocuments?.forEach((d) => {
3306
+ const residentNames = d.resident_ids?.map((id) => personNames.get(id)).filter(Boolean);
3307
+ entities.push(indexLegalDocument(d, residentNames));
3308
+ });
3309
+ data.accessCodes?.forEach((c) => {
3310
+ const propertyName = c.property_id ? propertyNames.get(c.property_id) : void 0;
3311
+ entities.push(indexAccessCode(c, propertyName));
3312
+ });
3313
+ data.devices?.forEach((d) => entities.push(indexDevice(d)));
3314
+ data.people?.forEach((p) => entities.push(indexPerson(p)));
3315
+ data.healthRecords?.forEach((r) => {
3316
+ let ownerName;
3317
+ if (r.person_id) ownerName = personNames.get(r.person_id);
3318
+ else if (r.pet_id) ownerName = petNames.get(r.pet_id);
3319
+ entities.push(indexHealthRecord(r, ownerName));
3320
+ });
3321
+ data.educationRecords?.forEach((r) => {
3322
+ const personName = r.person_id ? personNames.get(r.person_id) : void 0;
3323
+ entities.push(indexEducationRecord(r, personName));
3324
+ });
3325
+ data.militaryRecords?.forEach((r) => {
3326
+ const personName = r.person_id ? personNames.get(r.person_id) : void 0;
3327
+ entities.push(indexMilitaryRecord(r, personName));
3328
+ });
3329
+ data.membershipRecords?.forEach((r) => {
3330
+ const personName = r.person_id ? personNames.get(r.person_id) : void 0;
3331
+ entities.push(indexMembershipRecord(r, personName));
3332
+ });
3333
+ data.homeImprovements?.forEach((i) => {
3334
+ const propertyName = i.property_id ? propertyNames.get(i.property_id) : void 0;
3335
+ entities.push(indexHomeImprovement(i, propertyName));
3336
+ });
3337
+ data.taxYears?.forEach((t) => {
3338
+ const filerNames = t.filer_ids?.map((id) => personNames.get(id)).filter(Boolean);
3339
+ entities.push(indexTaxYear(t, filerNames));
3340
+ });
3341
+ return entities;
3342
+ }
3343
+
3344
+ // ../list-data/src/adapters/contactAdapter.ts
3345
+ function getContactComputedFields(contact) {
3346
+ const computed = {
3347
+ displayName: getContactDisplayName(contact)
3348
+ };
3349
+ if (contact.type) {
3350
+ computed.formattedType = contact.type.charAt(0).toUpperCase() + contact.type.slice(1).replace(/_/g, " ");
3351
+ }
3352
+ return computed;
3353
+ }
3354
+
3355
+ // ../list-data/src/adapters/insuranceAdapter.ts
3356
+ function getInsuranceComputedFields(policy) {
3357
+ const computed = {
3358
+ displayName: policy.provider || "Insurance Policy"
3359
+ };
3360
+ if (policy.expiration_date) {
3361
+ computed.daysUntilExpiry = daysUntil(policy.expiration_date);
3362
+ computed.expiryStatus = getExpiryStatus(computed.daysUntilExpiry);
3363
+ }
3364
+ if (policy.type) {
3365
+ computed.formattedType = policy.type.charAt(0).toUpperCase() + policy.type.slice(1);
3366
+ }
3367
+ return computed;
3368
+ }
3369
+
3370
+ // ../list-data/src/adapters/petAdapter.ts
3371
+ function getPetComputedFields(pet) {
3372
+ const computed = {
3373
+ displayName: pet.name
3374
+ };
3375
+ const detailedAge = calculateDetailedAge(pet.date_of_birth);
3376
+ if (detailedAge !== null && !pet.memorialized_at) {
3377
+ if (detailedAge.years < 1) {
3378
+ const months = detailedAge.months || 1;
3379
+ computed.age = `${months} month${months !== 1 ? "s" : ""}`;
3380
+ } else {
3381
+ computed.age = `${detailedAge.years} year${detailedAge.years !== 1 ? "s" : ""}`;
3382
+ }
3383
+ if (pet.species === "cat") {
3384
+ computed.humanYears = calculateCatHumanYears(detailedAge.fractionalYears);
3385
+ } else if (pet.species === "dog") {
3386
+ let size = "medium";
3387
+ const weightHistory = pet.weight_history || [];
3388
+ const latestWeight = weightHistory.length > 0 ? [...weightHistory].sort((a, b) => b.date.localeCompare(a.date))[0] : null;
3389
+ const weightInLbs = latestWeight ? latestWeight.unit === "kg" ? latestWeight.weight * 2.20462 : latestWeight.weight : pet.weight_lbs;
3390
+ if (weightInLbs) {
3391
+ if (weightInLbs < 20) size = "small";
3392
+ else if (weightInLbs > 50) size = "large";
3393
+ }
3394
+ computed.humanYears = calculateDogHumanYears(detailedAge.fractionalYears, size);
3395
+ }
3396
+ }
3397
+ if (pet.species) {
3398
+ computed.formattedType = pet.species.charAt(0).toUpperCase() + pet.species.slice(1).replace(/_/g, " ");
3399
+ }
3400
+ return computed;
3401
+ }
3402
+
3403
+ // ../list-data/src/adapters/vehicleAdapter.ts
3404
+ function getVehicleComputedFields(vehicle) {
3405
+ const parts = [vehicle.year, vehicle.make, vehicle.model].filter(Boolean);
3406
+ const displayName = parts.length > 0 ? parts.join(" ") : vehicle.name || "Vehicle";
3407
+ const computed = {
3408
+ displayName
3409
+ };
3410
+ if (vehicle.registration_expiration) {
3411
+ const monthNum = parseInt(vehicle.registration_expiration, 10);
3412
+ if (monthNum >= 1 && monthNum <= 12) {
3413
+ const now = /* @__PURE__ */ new Date();
3414
+ const currentYear = now.getFullYear();
3415
+ const currentMonth = now.getMonth() + 1;
3416
+ let targetYear = currentYear;
3417
+ if (monthNum < currentMonth) {
3418
+ targetYear = currentYear + 1;
3419
+ }
3420
+ const expirationDate = new Date(targetYear, monthNum, 0);
3421
+ const daysUntil2 = Math.ceil((expirationDate.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24));
3422
+ computed.daysUntilExpiry = daysUntil2;
3423
+ if (daysUntil2 < 0) {
3424
+ computed.expiryStatus = "expired";
3425
+ } else if (daysUntil2 <= 30) {
3426
+ computed.expiryStatus = "expiring_soon";
3427
+ } else {
3428
+ computed.expiryStatus = "ok";
3429
+ }
3430
+ }
3431
+ }
3432
+ const vehicleType = vehicle.vehicle_type || vehicle.type;
3433
+ if (vehicleType) {
3434
+ computed.formattedType = vehicleType.charAt(0).toUpperCase() + vehicleType.slice(1);
3435
+ }
3436
+ return computed;
3437
+ }
3438
+
3439
+ // ../list-data/src/data/propertyMaintenanceTemplates.json
3440
+ var propertyMaintenanceTemplates_default = [
3441
+ {
3442
+ id: "smoke-detector-maintenance",
3443
+ name: "Smoke & CO Detector Maintenance",
3444
+ description: "Monthly: Test all detectors. Annually: Replace batteries",
3445
+ category: "safety",
3446
+ applicableTo: "property",
3447
+ conditions: {},
3448
+ defaultFrequency: {
3449
+ type: "recurring",
3450
+ interval: "monthly"
3451
+ },
3452
+ priority: "critical",
3453
+ skillLevel: "diy_easy",
3454
+ estimatedTimeMinutes: 15,
3455
+ whyImportant: "Life-saving devices must be functional - test monthly, replace batteries yearly"
3456
+ },
3457
+ {
3458
+ id: "hvac-filter-replacement",
3459
+ name: "Replace HVAC Air Filter",
3460
+ description: "Change air filter in heating/cooling system to maintain efficiency",
3461
+ category: "hvac",
3462
+ applicableTo: "property",
3463
+ conditions: {
3464
+ hasAC: true
3465
+ },
3466
+ defaultFrequency: {
3467
+ type: "recurring",
3468
+ interval: "monthly"
3469
+ },
3470
+ priority: "important",
3471
+ skillLevel: "diy_easy",
3472
+ estimatedTimeMinutes: 10,
3473
+ whyImportant: "Dirty filters reduce efficiency by up to 15% and worsen air quality"
3474
+ },
3475
+ {
3476
+ id: "furnace-inspection",
3477
+ name: "Furnace Inspection & Service",
3478
+ description: "Professional inspection and cleaning of gas furnace",
3479
+ category: "hvac",
3480
+ applicableTo: "property",
3481
+ conditions: {
3482
+ hasGasFurnace: true
3483
+ },
3484
+ defaultFrequency: {
3485
+ type: "seasonal",
3486
+ season: "fall",
3487
+ monthsNorthern: [9],
3488
+ monthsSouthern: [3]
3489
+ },
3490
+ priority: "important",
3491
+ skillLevel: "professional",
3492
+ estimatedTimeMinutes: 90,
3493
+ whyImportant: "Ensures safe operation and prevents carbon monoxide leaks"
3494
+ },
3495
+ {
3496
+ id: "ac-service",
3497
+ name: "Air Conditioning Service",
3498
+ description: "Professional AC inspection and cleaning",
3499
+ category: "hvac",
3500
+ applicableTo: "property",
3501
+ conditions: {
3502
+ hasAC: true
3503
+ },
3504
+ defaultFrequency: {
3505
+ type: "seasonal",
3506
+ season: "spring",
3507
+ monthsNorthern: [4, 5],
3508
+ monthsSouthern: [10, 11]
3509
+ },
3510
+ priority: "important",
3511
+ skillLevel: "professional",
3512
+ estimatedTimeMinutes: 90,
3513
+ whyImportant: "Maintains efficiency and prevents breakdowns in summer heat"
3514
+ },
3515
+ {
3516
+ id: "gutter-cleaning-fall",
3517
+ name: "Clean Gutters (Fall)",
3518
+ description: "Remove leaves and debris from gutters and downspouts",
3519
+ category: "exterior",
3520
+ applicableTo: "property",
3521
+ conditions: {
3522
+ hasGutters: true,
3523
+ propertyType: ["single_family", "townhouse"]
3524
+ },
3525
+ defaultFrequency: {
3526
+ type: "seasonal",
3527
+ season: "fall",
3528
+ monthsNorthern: [10, 11],
3529
+ monthsSouthern: [4, 5]
3530
+ },
3531
+ priority: "important",
3532
+ skillLevel: "diy_moderate",
3533
+ estimatedTimeMinutes: 120,
3534
+ whyImportant: "Clogged gutters can cause water damage to foundation and roof"
3535
+ },
3536
+ {
3537
+ id: "gutter-cleaning-spring",
3538
+ name: "Clean Gutters (Spring)",
3539
+ description: "Remove debris accumulated over winter",
3540
+ category: "exterior",
3541
+ applicableTo: "property",
3542
+ conditions: {
3543
+ hasGutters: true,
3544
+ propertyType: ["single_family", "townhouse"]
3545
+ },
3546
+ defaultFrequency: {
3547
+ type: "seasonal",
3548
+ season: "spring",
3549
+ monthsNorthern: [4, 5],
3550
+ monthsSouthern: [10, 11]
3551
+ },
3552
+ priority: "important",
3553
+ skillLevel: "diy_moderate",
3554
+ estimatedTimeMinutes: 120,
3555
+ whyImportant: "Spring runoff needs clear gutters to prevent water damage"
3556
+ },
3557
+ {
3558
+ id: "roof-moss-removal",
3559
+ name: "Remove Roof Moss",
3560
+ description: "Clean moss and debris from roof shingles",
3561
+ category: "roof",
3562
+ applicableTo: "property",
3563
+ conditions: {
3564
+ propertyType: ["single_family", "townhouse"],
3565
+ climate: ["marine", "humid_subtropical", "humid_continental"]
3566
+ },
3567
+ defaultFrequency: {
3568
+ type: "recurring",
3569
+ interval: "yearly"
3570
+ },
3571
+ priority: "important",
3572
+ skillLevel: "professional",
3573
+ estimatedTimeMinutes: 240,
3574
+ whyImportant: "Moss damages shingles and reduces roof lifespan significantly"
3575
+ },
3576
+ {
3577
+ id: "roof-inspection",
3578
+ name: "Inspect Roof",
3579
+ description: "Check for damaged shingles, leaks, and wear",
3580
+ category: "roof",
3581
+ applicableTo: "property",
3582
+ conditions: {
3583
+ propertyType: ["single_family", "townhouse"]
3584
+ },
3585
+ defaultFrequency: {
3586
+ type: "recurring",
3587
+ interval: "biannually"
3588
+ },
3589
+ priority: "important",
3590
+ skillLevel: "professional",
3591
+ estimatedTimeMinutes: 60,
3592
+ whyImportant: "Early detection of roof damage prevents expensive interior water damage"
3593
+ },
3594
+ {
3595
+ id: "pool-chemistry-check",
3596
+ name: "Check Pool Chemistry",
3597
+ description: "Test and balance pH, chlorine, and alkalinity levels",
3598
+ category: "pool",
3599
+ applicableTo: "property",
3600
+ conditions: {
3601
+ hasPool: true
3602
+ },
3603
+ defaultFrequency: {
3604
+ type: "recurring",
3605
+ interval: "weekly"
3606
+ },
3607
+ priority: "important",
3608
+ skillLevel: "diy_easy",
3609
+ estimatedTimeMinutes: 30,
3610
+ whyImportant: "Prevents algae growth and keeps water safe for swimming"
3611
+ },
3612
+ {
3613
+ id: "pool-filter-cleaning",
3614
+ name: "Clean Pool Filter",
3615
+ description: "Backwash or clean pool filter system",
3616
+ category: "pool",
3617
+ applicableTo: "property",
3618
+ conditions: {
3619
+ hasPool: true
3620
+ },
3621
+ defaultFrequency: {
3622
+ type: "recurring",
3623
+ interval: "monthly"
3624
+ },
3625
+ priority: "important",
3626
+ skillLevel: "diy_moderate",
3627
+ estimatedTimeMinutes: 45,
3628
+ whyImportant: "Keeps water circulation efficient and clear"
3629
+ },
3630
+ {
3631
+ id: "pool-winterization",
3632
+ name: "Winterize Pool",
3633
+ description: "Close pool for winter: drain, add antifreeze, install cover",
3634
+ category: "pool",
3635
+ applicableTo: "property",
3636
+ conditions: {
3637
+ hasPool: true,
3638
+ winterSeverity: ["moderate", "severe"]
3639
+ },
3640
+ defaultFrequency: {
3641
+ type: "seasonal",
3642
+ season: "fall",
3643
+ monthsNorthern: [10],
3644
+ monthsSouthern: [4]
3645
+ },
3646
+ priority: "critical",
3647
+ skillLevel: "professional",
3648
+ estimatedTimeMinutes: 180,
3649
+ whyImportant: "Prevents freeze damage to pool equipment and plumbing"
3650
+ },
3651
+ {
3652
+ id: "pool-opening",
3653
+ name: "Open Pool for Summer",
3654
+ description: "Remove cover, refill, balance chemicals, start equipment",
3655
+ category: "pool",
3656
+ applicableTo: "property",
3657
+ conditions: {
3658
+ hasPool: true,
3659
+ winterSeverity: ["moderate", "severe"]
3660
+ },
3661
+ defaultFrequency: {
3662
+ type: "seasonal",
3663
+ season: "spring",
3664
+ monthsNorthern: [4, 5],
3665
+ monthsSouthern: [10, 11]
3666
+ },
3667
+ priority: "important",
3668
+ skillLevel: "professional",
3669
+ estimatedTimeMinutes: 180,
3670
+ whyImportant: "Proper opening prevents damage and ensures safe swimming"
3671
+ },
3672
+ {
3673
+ id: "sprinkler-system-blowout",
3674
+ name: "Blow Out Sprinkler System",
3675
+ description: "Clear water from sprinkler lines to prevent freeze damage",
3676
+ category: "landscaping",
3677
+ applicableTo: "property",
3678
+ conditions: {
3679
+ hasSprinklerSystem: true,
3680
+ winterSeverity: ["moderate", "severe"]
3681
+ },
3682
+ defaultFrequency: {
3683
+ type: "seasonal",
3684
+ season: "fall",
3685
+ monthsNorthern: [10],
3686
+ monthsSouthern: [4]
3687
+ },
3688
+ priority: "critical",
3689
+ skillLevel: "professional",
3690
+ estimatedTimeMinutes: 60,
3691
+ whyImportant: "Frozen water in lines can cause $1000+ in repairs"
3692
+ },
3693
+ {
3694
+ id: "sprinkler-system-startup",
3695
+ name: "Start Up Sprinkler System",
3696
+ description: "Turn on system, check for leaks, adjust spray patterns",
3697
+ category: "landscaping",
3698
+ applicableTo: "property",
3699
+ conditions: {
3700
+ hasSprinklerSystem: true,
3701
+ winterSeverity: ["moderate", "severe"]
3702
+ },
3703
+ defaultFrequency: {
3704
+ type: "seasonal",
3705
+ season: "spring",
3706
+ monthsNorthern: [4, 5],
3707
+ monthsSouthern: [10, 11]
3708
+ },
3709
+ priority: "important",
3710
+ skillLevel: "diy_moderate",
3711
+ estimatedTimeMinutes: 90,
3712
+ whyImportant: "Catch leaks early before they waste water and money"
3713
+ },
3714
+ {
3715
+ id: "chimney-cleaning-inspection",
3716
+ name: "Chimney Cleaning & Inspection",
3717
+ description: "Remove creosote buildup and inspect for damage",
3718
+ category: "safety",
3719
+ applicableTo: "property",
3720
+ conditions: {
3721
+ hasFireplace: true
3722
+ },
3723
+ defaultFrequency: {
3724
+ type: "recurring",
3725
+ interval: "yearly"
3726
+ },
3727
+ priority: "important",
3728
+ skillLevel: "professional",
3729
+ estimatedTimeMinutes: 120,
3730
+ whyImportant: "Prevents chimney fires caused by creosote buildup"
3731
+ },
3732
+ {
3733
+ id: "septic-tank-pumping",
3734
+ name: "Pump Septic Tank",
3735
+ description: "Professional septic tank pumping and inspection",
3736
+ category: "plumbing",
3737
+ applicableTo: "property",
3738
+ conditions: {
3739
+ propertyType: ["single_family"],
3740
+ hasSepticSystem: true
3741
+ },
3742
+ defaultFrequency: {
3743
+ type: "recurring",
3744
+ customMonths: 36
3745
+ },
3746
+ priority: "critical",
3747
+ skillLevel: "professional",
3748
+ estimatedTimeMinutes: 120,
3749
+ whyImportant: "Prevents backup and expensive drain field replacement ($10,000+)"
3750
+ },
3751
+ {
3752
+ id: "water-heater-flush",
3753
+ name: "Flush Water Heater",
3754
+ description: "Drain sediment from water heater tank",
3755
+ category: "plumbing",
3756
+ applicableTo: "property",
3757
+ conditions: {},
3758
+ defaultFrequency: {
3759
+ type: "recurring",
3760
+ interval: "yearly"
3761
+ },
3762
+ priority: "routine",
3763
+ skillLevel: "diy_moderate",
3764
+ estimatedTimeMinutes: 60,
3765
+ whyImportant: "Removes sediment buildup that reduces efficiency and lifespan"
3766
+ },
3767
+ {
3768
+ id: "deck-staining",
3769
+ name: "Stain/Seal Deck",
3770
+ description: "Apply protective stain or sealant to wood deck",
3771
+ category: "exterior",
3772
+ applicableTo: "property",
3773
+ conditions: {
3774
+ hasDeck: true,
3775
+ deckMaterial: ["wood"]
3776
+ },
3777
+ defaultFrequency: {
3778
+ type: "recurring",
3779
+ customMonths: 24
3780
+ },
3781
+ priority: "important",
3782
+ skillLevel: "diy_moderate",
3783
+ estimatedTimeMinutes: 480,
3784
+ whyImportant: "Protects wood from rot and extends deck life by years"
3785
+ },
3786
+ {
3787
+ id: "driveway-sealing",
3788
+ name: "Seal Asphalt Driveway",
3789
+ description: "Apply sealcoat to protect asphalt surface",
3790
+ category: "exterior",
3791
+ applicableTo: "property",
3792
+ conditions: {
3793
+ hasDriveway: true,
3794
+ drivewayMaterial: ["asphalt"]
3795
+ },
3796
+ defaultFrequency: {
3797
+ type: "recurring",
3798
+ customMonths: 24
3799
+ },
3800
+ priority: "routine",
3801
+ skillLevel: "diy_moderate",
3802
+ estimatedTimeMinutes: 360,
3803
+ whyImportant: "Prevents cracks and extends driveway life significantly"
3804
+ },
3805
+ {
3806
+ id: "power-wash-exterior",
3807
+ name: "Power Wash Home Exterior",
3808
+ description: "Clean siding, walkways, and deck to remove dirt and mildew",
3809
+ category: "exterior",
3810
+ applicableTo: "property",
3811
+ conditions: {
3812
+ propertyType: ["single_family", "townhouse"]
3813
+ },
3814
+ defaultFrequency: {
3815
+ type: "recurring",
3816
+ interval: "yearly"
3817
+ },
3818
+ priority: "routine",
3819
+ skillLevel: "diy_moderate",
3820
+ estimatedTimeMinutes: 240,
3821
+ whyImportant: "Prevents mildew damage and maintains curb appeal"
3822
+ },
3823
+ {
3824
+ id: "window-caulking",
3825
+ name: "Inspect & Recaulk Windows",
3826
+ description: "Check window caulking and reapply where needed",
3827
+ category: "exterior",
3828
+ applicableTo: "property",
3829
+ conditions: {
3830
+ propertyType: ["single_family", "townhouse"]
3831
+ },
3832
+ defaultFrequency: {
3833
+ type: "recurring",
3834
+ customMonths: 24
3835
+ },
3836
+ priority: "routine",
3837
+ skillLevel: "diy_moderate",
3838
+ estimatedTimeMinutes: 180,
3839
+ whyImportant: "Prevents water infiltration and improves energy efficiency"
3840
+ },
3841
+ {
3842
+ id: "dryer-vent-cleaning",
3843
+ name: "Clean Dryer Vent",
3844
+ description: "Remove lint buildup from dryer vent ductwork",
3845
+ category: "appliances",
3846
+ applicableTo: "property",
3847
+ conditions: {},
3848
+ defaultFrequency: {
3849
+ type: "recurring",
3850
+ interval: "yearly"
3851
+ },
3852
+ priority: "important",
3853
+ skillLevel: "diy_moderate",
3854
+ estimatedTimeMinutes: 45,
3855
+ whyImportant: "Prevents fire hazard and improves dryer efficiency"
3856
+ },
3857
+ {
3858
+ id: "refrigerator-coil-cleaning",
3859
+ name: "Clean Refrigerator Coils",
3860
+ description: "Vacuum dust from refrigerator condenser coils",
3861
+ category: "appliances",
3862
+ applicableTo: "property",
3863
+ conditions: {},
3864
+ defaultFrequency: {
3865
+ type: "recurring",
3866
+ interval: "biannually"
3867
+ },
3868
+ priority: "routine",
3869
+ skillLevel: "diy_easy",
3870
+ estimatedTimeMinutes: 30,
3871
+ whyImportant: "Improves efficiency and extends refrigerator lifespan"
3872
+ },
3873
+ {
3874
+ id: "lawn-mowing",
3875
+ name: "Mow Lawn",
3876
+ description: "Cut grass to maintain healthy lawn appearance",
3877
+ category: "landscaping",
3878
+ applicableTo: "property",
3879
+ conditions: {
3880
+ propertyType: ["single_family", "townhouse"],
3881
+ lawnSize: ["small", "medium"]
3882
+ },
3883
+ defaultFrequency: {
3884
+ type: "recurring",
3885
+ interval: "weekly"
3886
+ },
3887
+ priority: "routine",
3888
+ skillLevel: "diy_easy",
3889
+ estimatedTimeMinutes: 60,
3890
+ whyImportant: "Keeps lawn healthy and property looking well-maintained"
3891
+ },
3892
+ {
3893
+ id: "lawn-fertilizing-spring",
3894
+ name: "Fertilize Lawn (Spring)",
3895
+ description: "Apply spring fertilizer to promote growth",
3896
+ category: "landscaping",
3897
+ applicableTo: "property",
3898
+ conditions: {
3899
+ propertyType: ["single_family", "townhouse"],
3900
+ lawnSize: ["small", "medium"]
3901
+ },
3902
+ defaultFrequency: {
3903
+ type: "seasonal",
3904
+ season: "spring",
3905
+ monthsNorthern: [4, 5],
3906
+ monthsSouthern: [10, 11]
3907
+ },
3908
+ priority: "routine",
3909
+ skillLevel: "diy_easy",
3910
+ estimatedTimeMinutes: 90,
3911
+ whyImportant: "Promotes healthy growth and green color"
3912
+ },
3913
+ {
3914
+ id: "lawn-fertilizing-fall",
3915
+ name: "Fertilize Lawn (Fall)",
3916
+ description: "Apply fall fertilizer to strengthen roots",
3917
+ category: "landscaping",
3918
+ applicableTo: "property",
3919
+ conditions: {
3920
+ propertyType: ["single_family", "townhouse"],
3921
+ lawnSize: ["small", "medium"]
3922
+ },
3923
+ defaultFrequency: {
3924
+ type: "seasonal",
3925
+ season: "fall",
3926
+ monthsNorthern: [9, 10],
3927
+ monthsSouthern: [3, 4]
3928
+ },
3929
+ priority: "routine",
3930
+ skillLevel: "diy_easy",
3931
+ estimatedTimeMinutes: 90,
3932
+ whyImportant: "Strengthens roots for winter and early spring growth"
3933
+ },
3934
+ {
3935
+ id: "lawn-aeration",
3936
+ name: "Aerate Lawn",
3937
+ description: "Core aerate lawn to improve water and nutrient absorption",
3938
+ category: "landscaping",
3939
+ applicableTo: "property",
3940
+ conditions: {
3941
+ propertyType: ["single_family", "townhouse"],
3942
+ lawnSize: ["medium"]
3943
+ },
3944
+ defaultFrequency: {
3945
+ type: "recurring",
3946
+ interval: "yearly"
3947
+ },
3948
+ priority: "routine",
3949
+ skillLevel: "diy_moderate",
3950
+ estimatedTimeMinutes: 120,
3951
+ whyImportant: "Reduces soil compaction and promotes healthier grass"
3952
+ },
3953
+ {
3954
+ id: "tree-trimming",
3955
+ name: "Trim Trees & Shrubs",
3956
+ description: "Prune dead branches and shape trees/shrubs",
3957
+ category: "landscaping",
3958
+ applicableTo: "property",
3959
+ conditions: {
3960
+ propertyType: ["single_family", "townhouse"],
3961
+ treeCount: ["few"]
3962
+ },
3963
+ defaultFrequency: {
3964
+ type: "recurring",
3965
+ interval: "yearly"
3966
+ },
3967
+ priority: "routine",
3968
+ skillLevel: "professional",
3969
+ estimatedTimeMinutes: 180,
3970
+ whyImportant: "Prevents damage from falling branches and promotes healthy growth"
3971
+ },
3972
+ {
3973
+ id: "well-pump-testing",
3974
+ name: "Test Well Pump",
3975
+ description: "Check well pump operation and water quality",
3976
+ category: "plumbing",
3977
+ applicableTo: "property",
3978
+ conditions: {
3979
+ hasWell: true
3980
+ },
3981
+ defaultFrequency: {
3982
+ type: "recurring",
3983
+ interval: "yearly"
3984
+ },
3985
+ priority: "important",
3986
+ skillLevel: "professional",
3987
+ estimatedTimeMinutes: 90,
3988
+ whyImportant: "Catches pump issues early before complete failure"
3989
+ },
3990
+ {
3991
+ id: "basement-sump-pump-test",
3992
+ name: "Test Sump Pump",
3993
+ description: "Pour water into sump pit to ensure pump activates properly",
3994
+ category: "plumbing",
3995
+ applicableTo: "property",
3996
+ conditions: {
3997
+ hasBasement: true
3998
+ },
3999
+ defaultFrequency: {
4000
+ type: "recurring",
4001
+ interval: "quarterly"
4002
+ },
4003
+ priority: "important",
4004
+ skillLevel: "diy_easy",
4005
+ estimatedTimeMinutes: 15,
4006
+ whyImportant: "Prevents basement flooding during heavy rain"
4007
+ },
4008
+ {
4009
+ id: "solar-panel-cleaning",
4010
+ name: "Clean Solar Panels",
4011
+ description: "Remove dirt, pollen, bird droppings, and debris from solar panels",
4012
+ category: "solar",
4013
+ applicableTo: "property",
4014
+ conditions: {
4015
+ hasSolarPanels: true
4016
+ },
4017
+ defaultFrequency: {
4018
+ type: "recurring",
4019
+ interval: "yearly"
4020
+ },
4021
+ priority: "important",
4022
+ skillLevel: "diy_moderate",
4023
+ estimatedTimeMinutes: 60,
4024
+ whyImportant: "Dirty panels can reduce energy output by 20-25%"
4025
+ },
4026
+ {
4027
+ id: "solar-panel-visual-inspection",
4028
+ name: "Inspect Solar Panels",
4029
+ description: "Check panels for cracks, hotspots, discoloration, loose connections, and debris buildup",
4030
+ category: "solar",
4031
+ applicableTo: "property",
4032
+ conditions: {
4033
+ hasSolarPanels: true
4034
+ },
4035
+ defaultFrequency: {
4036
+ type: "recurring",
4037
+ interval: "biannually"
4038
+ },
4039
+ priority: "important",
4040
+ skillLevel: "diy_easy",
4041
+ estimatedTimeMinutes: 30,
4042
+ whyImportant: "Early detection of damage prevents costly repairs and output loss"
4043
+ },
4044
+ {
4045
+ id: "solar-inverter-inspection",
4046
+ name: "Check Solar Inverter",
4047
+ description: "Verify inverter is functioning properly, check status lights and error codes",
4048
+ category: "solar",
4049
+ applicableTo: "property",
4050
+ conditions: {
4051
+ hasSolarPanels: true
4052
+ },
4053
+ defaultFrequency: {
4054
+ type: "recurring",
4055
+ interval: "monthly"
4056
+ },
4057
+ priority: "important",
4058
+ skillLevel: "diy_easy",
4059
+ estimatedTimeMinutes: 10,
4060
+ whyImportant: "Inverter failure stops all energy production - catch issues early"
4061
+ },
4062
+ {
4063
+ id: "solar-system-professional-inspection",
4064
+ name: "Professional Solar System Inspection",
4065
+ description: "Full system inspection including wiring, connections, mounting, and performance analysis",
4066
+ category: "solar",
4067
+ applicableTo: "property",
4068
+ conditions: {
4069
+ hasSolarPanels: true
4070
+ },
4071
+ defaultFrequency: {
4072
+ type: "recurring",
4073
+ customMonths: 24
4074
+ },
4075
+ priority: "important",
4076
+ skillLevel: "professional",
4077
+ estimatedTimeMinutes: 120,
4078
+ whyImportant: "Ensures system operates at peak efficiency and maintains warranty"
4079
+ },
4080
+ {
4081
+ id: "home-battery-visual-inspection",
4082
+ name: "Inspect Home Battery System",
4083
+ description: "Check battery unit for damage, leaks, unusual sounds, and ensure ventilation is clear",
4084
+ category: "solar",
4085
+ applicableTo: "property",
4086
+ conditions: {
4087
+ hasHomeBattery: true
4088
+ },
4089
+ defaultFrequency: {
4090
+ type: "recurring",
4091
+ interval: "quarterly"
4092
+ },
4093
+ priority: "important",
4094
+ skillLevel: "diy_easy",
4095
+ estimatedTimeMinutes: 15,
4096
+ whyImportant: "Early detection of battery issues prevents safety hazards and costly damage"
4097
+ },
4098
+ {
4099
+ id: "home-battery-firmware-update",
4100
+ name: "Check Battery Firmware Updates",
4101
+ description: "Check manufacturer app for firmware updates and apply if available",
4102
+ category: "solar",
4103
+ applicableTo: "property",
4104
+ conditions: {
4105
+ hasHomeBattery: true
4106
+ },
4107
+ defaultFrequency: {
4108
+ type: "recurring",
4109
+ interval: "quarterly"
4110
+ },
4111
+ priority: "routine",
4112
+ skillLevel: "diy_easy",
4113
+ estimatedTimeMinutes: 15,
4114
+ whyImportant: "Updates improve performance, fix bugs, and enhance safety features"
4115
+ },
4116
+ {
4117
+ id: "home-battery-professional-inspection",
4118
+ name: "Professional Battery System Inspection",
4119
+ description: "Full inspection of battery connections, performance testing, and state of health analysis",
4120
+ category: "solar",
4121
+ applicableTo: "property",
4122
+ conditions: {
4123
+ hasHomeBattery: true
4124
+ },
4125
+ defaultFrequency: {
4126
+ type: "recurring",
4127
+ interval: "yearly"
4128
+ },
4129
+ priority: "important",
4130
+ skillLevel: "professional",
4131
+ estimatedTimeMinutes: 90,
4132
+ whyImportant: "Ensures safe operation and optimal battery health for longevity"
4133
+ },
4134
+ {
4135
+ id: "window-cleaning",
4136
+ name: "Clean Windows",
4137
+ description: "Clean interior and exterior windows for improved visibility and appearance",
4138
+ category: "interior",
4139
+ applicableTo: "property",
4140
+ conditions: {},
4141
+ defaultFrequency: {
4142
+ type: "recurring",
4143
+ interval: "biannually"
4144
+ },
4145
+ priority: "routine",
4146
+ skillLevel: "diy_moderate",
4147
+ estimatedTimeMinutes: 180,
4148
+ whyImportant: "Clean windows improve natural light and curb appeal"
4149
+ },
4150
+ {
4151
+ id: "carpet-cleaning",
4152
+ name: "Carpet Cleaning",
4153
+ description: "Deep clean carpets to remove dirt, stains, and allergens",
4154
+ category: "interior",
4155
+ applicableTo: "property",
4156
+ conditions: {
4157
+ hasCarpets: true
4158
+ },
4159
+ defaultFrequency: {
4160
+ type: "recurring",
4161
+ interval: "yearly"
4162
+ },
4163
+ priority: "routine",
4164
+ skillLevel: "professional",
4165
+ estimatedTimeMinutes: 240,
4166
+ whyImportant: "Extends carpet life, improves air quality, and removes allergens"
4167
+ }
4168
+ ];
4169
+
4170
+ // ../list-data/src/data/vehicleMaintenanceTemplates.json
4171
+ var vehicleMaintenanceTemplates_default = [
4172
+ {
4173
+ id: "oil-change",
4174
+ name: "Oil Change",
4175
+ description: "Change engine oil and oil filter",
4176
+ category: "engine",
4177
+ applicableTo: "vehicle",
4178
+ conditions: {
4179
+ vehicleType: ["car", "rv"],
4180
+ engineType: ["gasoline", "diesel", "hybrid", "plugin_hybrid"]
4181
+ },
4182
+ defaultFrequency: {
4183
+ type: "recurring",
4184
+ interval: "quarterly"
4185
+ },
4186
+ priority: "critical",
4187
+ skillLevel: "diy_moderate",
4188
+ estimatedTimeMinutes: 45,
4189
+ whyImportant: "Regular oil changes prevent engine wear and extend vehicle life. Most critical maintenance task."
4190
+ },
4191
+ {
4192
+ id: "tire-rotation",
4193
+ name: "Tire Rotation",
4194
+ description: "Rotate tires to ensure even wear",
4195
+ category: "tires",
4196
+ applicableTo: "vehicle",
4197
+ conditions: {
4198
+ vehicleType: ["car", "rv"]
4199
+ },
4200
+ defaultFrequency: {
4201
+ type: "recurring",
4202
+ interval: "biannually"
4203
+ },
4204
+ priority: "important",
4205
+ skillLevel: "diy_moderate",
4206
+ estimatedTimeMinutes: 60,
4207
+ whyImportant: "Even tire wear extends tire life by up to 20% and improves safety and fuel efficiency."
4208
+ },
4209
+ {
4210
+ id: "brake-inspection",
4211
+ name: "Brake Inspection",
4212
+ description: "Inspect brake pads, rotors, and fluid",
4213
+ category: "brakes",
4214
+ applicableTo: "vehicle",
4215
+ conditions: {
4216
+ vehicleType: ["car", "motorcycle", "rv"]
4217
+ },
4218
+ defaultFrequency: {
4219
+ type: "recurring",
4220
+ interval: "biannually"
4221
+ },
4222
+ priority: "critical",
4223
+ skillLevel: "professional",
4224
+ estimatedTimeMinutes: 45,
4225
+ whyImportant: "Brake failure is life-threatening. Regular inspection prevents accidents and costly repairs."
4226
+ },
4227
+ {
4228
+ id: "air-filter-replacement",
4229
+ name: "Replace Engine Air Filter",
4230
+ description: "Replace engine air filter for optimal performance",
4231
+ category: "engine",
4232
+ applicableTo: "vehicle",
4233
+ conditions: {
4234
+ vehicleType: ["car", "rv"],
4235
+ engineType: ["gasoline", "diesel", "hybrid", "plugin_hybrid"]
4236
+ },
4237
+ defaultFrequency: {
4238
+ type: "recurring",
4239
+ interval: "yearly"
4240
+ },
4241
+ priority: "routine",
4242
+ skillLevel: "diy_easy",
4243
+ estimatedTimeMinutes: 15,
4244
+ whyImportant: "Clean air filter improves fuel economy by up to 10% and engine performance."
4245
+ },
4246
+ {
4247
+ id: "cabin-air-filter",
4248
+ name: "Replace Cabin Air Filter",
4249
+ description: "Replace cabin air filter for clean interior air",
4250
+ category: "interior",
4251
+ applicableTo: "vehicle",
4252
+ conditions: {
4253
+ vehicleType: ["car", "rv"]
4254
+ },
4255
+ defaultFrequency: {
4256
+ type: "recurring",
4257
+ interval: "yearly"
4258
+ },
4259
+ priority: "routine",
4260
+ skillLevel: "diy_easy",
4261
+ estimatedTimeMinutes: 10,
4262
+ whyImportant: "Filters out pollen, dust, and pollutants for healthier cabin air."
4263
+ },
4264
+ {
4265
+ id: "battery-check",
4266
+ name: "Battery Test",
4267
+ description: "Test battery voltage and terminals, clean corrosion",
4268
+ category: "electrical",
4269
+ applicableTo: "vehicle",
4270
+ conditions: {
4271
+ vehicleType: ["car", "rv"]
4272
+ },
4273
+ defaultFrequency: {
4274
+ type: "recurring",
4275
+ interval: "biannually"
4276
+ },
4277
+ priority: "important",
4278
+ skillLevel: "diy_easy",
4279
+ estimatedTimeMinutes: 20,
4280
+ whyImportant: "Prevents unexpected breakdowns. Most batteries last 3-5 years."
4281
+ },
4282
+ {
4283
+ id: "coolant-flush",
4284
+ name: "Coolant Flush",
4285
+ description: "Drain and replace engine coolant/antifreeze",
4286
+ category: "engine",
4287
+ applicableTo: "vehicle",
4288
+ conditions: {
4289
+ vehicleType: ["car", "rv"],
4290
+ engineType: ["gasoline", "diesel", "hybrid", "plugin_hybrid"]
4291
+ },
4292
+ defaultFrequency: {
4293
+ type: "recurring",
4294
+ interval: "biannually"
4295
+ },
4296
+ priority: "important",
4297
+ skillLevel: "diy_moderate",
4298
+ estimatedTimeMinutes: 90,
4299
+ whyImportant: "Prevents engine overheating and corrosion. Critical for engine longevity."
4300
+ },
4301
+ {
4302
+ id: "transmission-service",
4303
+ name: "Transmission Service",
4304
+ description: "Change transmission fluid and filter",
4305
+ category: "drivetrain",
4306
+ applicableTo: "vehicle",
4307
+ conditions: {
4308
+ vehicleType: ["car", "rv"],
4309
+ transmissionType: ["automatic", "cvt"]
4310
+ },
4311
+ defaultFrequency: {
4312
+ type: "recurring",
4313
+ interval: "biannually"
4314
+ },
4315
+ priority: "important",
4316
+ skillLevel: "professional",
4317
+ estimatedTimeMinutes: 120,
4318
+ whyImportant: "Extends transmission life. Transmission replacement can cost $3,000-$8,000."
4319
+ },
4320
+ {
4321
+ id: "spark-plugs",
4322
+ name: "Replace Spark Plugs",
4323
+ description: "Replace spark plugs for optimal ignition",
4324
+ category: "engine",
4325
+ applicableTo: "vehicle",
4326
+ conditions: {
4327
+ vehicleType: ["car", "rv"],
4328
+ engineType: ["gasoline", "hybrid", "plugin_hybrid"]
4329
+ },
4330
+ defaultFrequency: {
4331
+ type: "recurring",
4332
+ interval: "yearly"
4333
+ },
4334
+ priority: "routine",
4335
+ skillLevel: "diy_moderate",
4336
+ estimatedTimeMinutes: 60,
4337
+ whyImportant: "Improves fuel efficiency, reduces emissions, and ensures smooth engine operation."
4338
+ },
4339
+ {
4340
+ id: "wiper-blades",
4341
+ name: "Replace Wiper Blades",
4342
+ description: "Replace windshield wiper blades",
4343
+ category: "exterior",
4344
+ applicableTo: "vehicle",
4345
+ conditions: {
4346
+ vehicleType: ["car", "rv"]
4347
+ },
4348
+ defaultFrequency: {
4349
+ type: "recurring",
4350
+ interval: "yearly"
4351
+ },
4352
+ priority: "routine",
4353
+ skillLevel: "diy_easy",
4354
+ estimatedTimeMinutes: 10,
4355
+ whyImportant: "Essential for visibility and safety in rain and snow."
4356
+ },
4357
+ {
4358
+ id: "wheel-alignment",
4359
+ name: "Wheel Alignment",
4360
+ description: "Check and adjust wheel alignment",
4361
+ category: "tires",
4362
+ applicableTo: "vehicle",
4363
+ conditions: {
4364
+ vehicleType: ["car"]
4365
+ },
4366
+ defaultFrequency: {
4367
+ type: "recurring",
4368
+ interval: "yearly"
4369
+ },
4370
+ priority: "routine",
4371
+ skillLevel: "professional",
4372
+ estimatedTimeMinutes: 60,
4373
+ whyImportant: "Prevents uneven tire wear and improves handling and fuel economy."
4374
+ },
4375
+ {
4376
+ id: "timing-belt",
4377
+ name: "Timing Belt Replacement",
4378
+ description: "Replace timing belt (if applicable)",
4379
+ category: "engine",
4380
+ applicableTo: "vehicle",
4381
+ conditions: {
4382
+ vehicleType: ["car"],
4383
+ engineType: ["gasoline", "diesel"]
4384
+ },
4385
+ defaultFrequency: {
4386
+ type: "recurring",
4387
+ interval: "yearly"
4388
+ },
4389
+ priority: "critical",
4390
+ skillLevel: "professional",
4391
+ estimatedTimeMinutes: 240,
4392
+ whyImportant: "Timing belt failure can cause catastrophic engine damage. Check your owner's manual for mileage interval (typically 60k-100k miles)."
4393
+ },
4394
+ {
4395
+ id: "differential-service",
4396
+ name: "Differential Service",
4397
+ description: "Change differential fluid",
4398
+ category: "drivetrain",
4399
+ applicableTo: "vehicle",
4400
+ conditions: {
4401
+ vehicleType: ["car"],
4402
+ driveType: ["awd", "4wd", "rwd"]
4403
+ },
4404
+ defaultFrequency: {
4405
+ type: "recurring",
4406
+ interval: "biannually"
4407
+ },
4408
+ priority: "routine",
4409
+ skillLevel: "professional",
4410
+ estimatedTimeMinutes: 90,
4411
+ whyImportant: "Maintains drivetrain efficiency and prevents costly differential failure."
4412
+ },
4413
+ {
4414
+ id: "motorcycle-chain-maintenance",
4415
+ name: "Chain Cleaning & Lubrication",
4416
+ description: "Clean and lubricate motorcycle chain",
4417
+ category: "drivetrain",
4418
+ applicableTo: "vehicle",
4419
+ conditions: {
4420
+ vehicleType: ["motorcycle"]
4421
+ },
4422
+ defaultFrequency: {
4423
+ type: "recurring",
4424
+ interval: "monthly"
4425
+ },
4426
+ priority: "important",
4427
+ skillLevel: "diy_easy",
4428
+ estimatedTimeMinutes: 30,
4429
+ whyImportant: "Prevents chain wear and ensures safe operation. Chain failure can be dangerous."
4430
+ },
4431
+ {
4432
+ id: "bicycle-tune-up",
4433
+ name: "Bicycle Tune-Up",
4434
+ description: "Check brakes, gears, tire pressure, chain lubrication",
4435
+ category: "general",
4436
+ applicableTo: "vehicle",
4437
+ conditions: {
4438
+ vehicleType: ["bicycle"]
4439
+ },
4440
+ defaultFrequency: {
4441
+ type: "recurring",
4442
+ interval: "quarterly"
4443
+ },
4444
+ priority: "important",
4445
+ skillLevel: "diy_easy",
4446
+ estimatedTimeMinutes: 45,
4447
+ whyImportant: "Ensures safe and efficient riding. Prevents accidents and costly repairs."
4448
+ },
4449
+ {
4450
+ id: "ev-battery-check",
4451
+ name: "EV Battery Health Check",
4452
+ description: "Professional battery health diagnostic",
4453
+ category: "electrical",
4454
+ applicableTo: "vehicle",
4455
+ conditions: {
4456
+ vehicleType: ["car"],
4457
+ engineType: ["electric", "plugin_hybrid"]
4458
+ },
4459
+ defaultFrequency: {
4460
+ type: "recurring",
4461
+ interval: "yearly"
4462
+ },
4463
+ priority: "important",
4464
+ skillLevel: "professional",
4465
+ estimatedTimeMinutes: 45,
4466
+ whyImportant: "Monitors battery degradation and ensures warranty compliance."
4467
+ },
4468
+ {
4469
+ id: "winter-tire-swap",
4470
+ name: "Winter Tire Installation",
4471
+ description: "Swap to winter tires for cold weather",
4472
+ category: "tires",
4473
+ applicableTo: "vehicle",
4474
+ conditions: {
4475
+ vehicleType: ["car"],
4476
+ hasWinterTires: true
4477
+ },
4478
+ defaultFrequency: {
4479
+ type: "recurring",
4480
+ interval: "yearly"
4481
+ },
4482
+ priority: "important",
4483
+ skillLevel: "diy_moderate",
4484
+ estimatedTimeMinutes: 90,
4485
+ whyImportant: "Winter tires dramatically improve traction and safety in snow and ice."
4486
+ },
4487
+ {
4488
+ id: "boat-engine-oil",
4489
+ name: "Boat Engine Oil Change",
4490
+ description: "Change engine oil and filter for boat motor",
4491
+ category: "engine",
4492
+ applicableTo: "vehicle",
4493
+ conditions: {
4494
+ vehicleType: ["boat"]
4495
+ },
4496
+ defaultFrequency: {
4497
+ type: "recurring",
4498
+ interval: "yearly"
4499
+ },
4500
+ priority: "critical",
4501
+ skillLevel: "diy_moderate",
4502
+ estimatedTimeMinutes: 60,
4503
+ whyImportant: "Marine engines operate in harsh conditions. Regular oil changes prevent corrosion and extend engine life."
4504
+ },
4505
+ {
4506
+ id: "boat-hull-inspection",
4507
+ name: "Hull Inspection & Cleaning",
4508
+ description: "Inspect hull for damage, clean barnacles and growth",
4509
+ category: "exterior",
4510
+ applicableTo: "vehicle",
4511
+ conditions: {
4512
+ vehicleType: ["boat"]
4513
+ },
4514
+ defaultFrequency: {
4515
+ type: "recurring",
4516
+ interval: "biannually"
4517
+ },
4518
+ priority: "important",
4519
+ skillLevel: "professional",
4520
+ estimatedTimeMinutes: 180,
4521
+ whyImportant: "Hull growth reduces fuel efficiency and speed. Damage can lead to costly repairs or sinking."
4522
+ },
4523
+ {
4524
+ id: "boat-winterization",
4525
+ name: "Winterization",
4526
+ description: "Prepare boat for winter storage: drain water, stabilize fuel, protect engine",
4527
+ category: "general",
4528
+ applicableTo: "vehicle",
4529
+ conditions: {
4530
+ vehicleType: ["boat"]
4531
+ },
4532
+ defaultFrequency: {
4533
+ type: "recurring",
4534
+ interval: "yearly"
4535
+ },
4536
+ priority: "critical",
4537
+ skillLevel: "professional",
4538
+ estimatedTimeMinutes: 240,
4539
+ whyImportant: "Prevents freeze damage to engine and systems. Skipping winterization can cause thousands in repairs."
4540
+ },
4541
+ {
4542
+ id: "boat-battery-maintenance",
4543
+ name: "Battery Check & Charging",
4544
+ description: "Test battery, check terminals, maintain charge",
4545
+ category: "electrical",
4546
+ applicableTo: "vehicle",
4547
+ conditions: {
4548
+ vehicleType: ["boat"]
4549
+ },
4550
+ defaultFrequency: {
4551
+ type: "recurring",
4552
+ interval: "quarterly"
4553
+ },
4554
+ priority: "important",
4555
+ skillLevel: "diy_easy",
4556
+ estimatedTimeMinutes: 30,
4557
+ whyImportant: "Dead battery leaves you stranded on water. Marine batteries need regular charging and maintenance."
4558
+ },
4559
+ {
4560
+ id: "boat-safety-equipment",
4561
+ name: "Safety Equipment Inspection",
4562
+ description: "Check life jackets, fire extinguishers, flares, first aid kit",
4563
+ category: "safety",
4564
+ applicableTo: "vehicle",
4565
+ conditions: {
4566
+ vehicleType: ["boat"]
4567
+ },
4568
+ defaultFrequency: {
4569
+ type: "recurring",
4570
+ interval: "yearly"
4571
+ },
4572
+ priority: "critical",
4573
+ skillLevel: "diy_easy",
4574
+ estimatedTimeMinutes: 45,
4575
+ whyImportant: "Required by law and essential for safety. Expired flares and damaged life jackets can be life-threatening."
4576
+ },
4577
+ {
4578
+ id: "boat-bilge-pump",
4579
+ name: "Bilge Pump Test",
4580
+ description: "Test bilge pump operation and clean intake",
4581
+ category: "safety",
4582
+ applicableTo: "vehicle",
4583
+ conditions: {
4584
+ vehicleType: ["boat"]
4585
+ },
4586
+ defaultFrequency: {
4587
+ type: "recurring",
4588
+ interval: "quarterly"
4589
+ },
4590
+ priority: "critical",
4591
+ skillLevel: "diy_easy",
4592
+ estimatedTimeMinutes: 20,
4593
+ whyImportant: "Bilge pump failure can lead to sinking. Regular testing ensures it works when needed."
4594
+ },
4595
+ {
4596
+ id: "boat-propeller-inspection",
4597
+ name: "Propeller Inspection",
4598
+ description: "Check propeller for damage, dings, fishing line",
4599
+ category: "drivetrain",
4600
+ applicableTo: "vehicle",
4601
+ conditions: {
4602
+ vehicleType: ["boat"]
4603
+ },
4604
+ defaultFrequency: {
4605
+ type: "recurring",
4606
+ interval: "biannually"
4607
+ },
4608
+ priority: "important",
4609
+ skillLevel: "diy_easy",
4610
+ estimatedTimeMinutes: 30,
4611
+ whyImportant: "Damaged props reduce performance and fuel efficiency. Can damage transmission if left unchecked."
4612
+ },
4613
+ {
4614
+ id: "boat-fuel-system",
4615
+ name: "Fuel System Maintenance",
4616
+ description: "Add fuel stabilizer, check fuel lines and water separator",
4617
+ category: "engine",
4618
+ applicableTo: "vehicle",
4619
+ conditions: {
4620
+ vehicleType: ["boat"]
4621
+ },
4622
+ defaultFrequency: {
4623
+ type: "recurring",
4624
+ interval: "yearly"
4625
+ },
4626
+ priority: "important",
4627
+ skillLevel: "diy_moderate",
4628
+ estimatedTimeMinutes: 60,
4629
+ whyImportant: "Marine fuel degrades quickly. Prevents engine damage from bad fuel and water contamination."
4630
+ }
4631
+ ];
4632
+
4633
+ // ../list-data/src/adapters/maintenanceAdapter.ts
4634
+ var ALL_MAINTENANCE_TEMPLATES = [
4635
+ ...propertyMaintenanceTemplates_default,
4636
+ ...vehicleMaintenanceTemplates_default
4637
+ ];
4638
+
4639
+ // ../list-data/src/adapters/subscriptionAdapter.ts
4640
+ function getSubscriptionComputedFields(sub) {
4641
+ const computed = {
4642
+ displayName: sub.name || sub.provider || "Subscription"
4643
+ };
4644
+ if (sub.expiration_date) {
4645
+ computed.daysUntilExpiry = daysUntil(sub.expiration_date);
4646
+ computed.expiryStatus = getExpiryStatus(computed.daysUntilExpiry);
4647
+ }
4648
+ if (sub.category) {
4649
+ computed.formattedType = sub.category.charAt(0).toUpperCase() + sub.category.slice(1).replace(/_/g, " ");
4650
+ }
4651
+ return computed;
4652
+ }
4653
+
4654
+ // ../list-data/src/adapters/credentialAdapter.ts
4655
+ function getCredentialComputedFields(credential) {
4656
+ const computed = {
4657
+ displayName: credential.name || credential.credential_type || "Credential"
4658
+ };
4659
+ if (credential.expiration_date) {
4660
+ computed.daysUntilExpiry = daysUntil(credential.expiration_date);
4661
+ computed.expiryStatus = getExpiryStatus(computed.daysUntilExpiry);
4662
+ }
4663
+ if (credential.credential_type) {
4664
+ computed.formattedType = credential.credential_type.charAt(0).toUpperCase() + credential.credential_type.slice(1).replace(/_/g, " ");
4665
+ }
4666
+ return computed;
4667
+ }
4668
+
4669
+ // src/enrichment.ts
4670
+ function getComputedFields(entity, entityType) {
4671
+ switch (entityType) {
4672
+ case "pet":
4673
+ return getPetComputedFields(entity);
4674
+ case "vehicle":
4675
+ return getVehicleComputedFields(entity);
4676
+ case "insurance":
4677
+ return getInsuranceComputedFields(entity);
4678
+ case "subscription":
4679
+ return getSubscriptionComputedFields(entity);
4680
+ case "contact":
4681
+ return getContactComputedFields(entity);
4682
+ case "credential":
4683
+ return getCredentialComputedFields(entity);
4684
+ default:
4685
+ return {
4686
+ displayName: getEntityDisplayName(entityType, entity)
4687
+ };
4688
+ }
4689
+ }
4690
+ function enrichEntity(entity, entityType) {
4691
+ const computed = getComputedFields(entity, entityType);
4692
+ return { ...entity, _computed: computed };
4693
+ }
4694
+ function enrichEntities(entities, entityType) {
4695
+ return entities.map((entity) => enrichEntity(entity, entityType));
4696
+ }
4697
+
4698
+ // src/resources/entities.ts
4699
+ var ENTITY_TYPES = [
4700
+ "pet",
4701
+ "property",
4702
+ "vehicle",
4703
+ "contact",
4704
+ "insurance",
4705
+ "bank_account",
4706
+ "investment",
4707
+ "subscription",
4708
+ "maintenance_task",
4709
+ "password",
4710
+ "access_code",
4711
+ "document",
4712
+ "medical",
4713
+ "prescription",
4714
+ "credential",
4715
+ "utility"
4716
+ ];
4717
+ async function getEntities(householdId, entityType, privateKey, privacyMode) {
4718
+ const entities = await getDecryptedEntities(householdId, entityType, privateKey);
4719
+ return enrichEntities(entities, entityType).map(
4720
+ (entity) => redactEntity(entity, entityType, privacyMode)
4721
+ );
4722
+ }
4723
+ async function getExpiringItems(days, householdId, privateKey) {
4724
+ const households = await getHouseholds();
4725
+ const searchHouseholds = householdId ? households.filter((h) => h.id === householdId) : households;
4726
+ const now = /* @__PURE__ */ new Date();
4727
+ const cutoff = new Date(now.getTime() + days * 24 * 60 * 60 * 1e3);
4728
+ const expiring = [];
4729
+ for (const household of searchHouseholds) {
4730
+ const insurance = await getDecryptedEntities(household.id, "insurance", privateKey);
4731
+ for (const policy of insurance) {
4732
+ if (policy.expiration_date) {
4733
+ const expires = new Date(policy.expiration_date);
4734
+ if (expires <= cutoff) {
4735
+ expiring.push({
4736
+ householdId: household.id,
4737
+ householdName: household.name,
4738
+ type: "insurance",
4739
+ name: policy.name || policy.provider || policy.policy_number,
4740
+ expiresAt: policy.expiration_date,
4741
+ daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
4742
+ });
4743
+ }
4744
+ }
4745
+ }
4746
+ const vehicles = await getDecryptedEntities(household.id, "vehicle", privateKey);
4747
+ for (const vehicle of vehicles) {
4748
+ const vehicleName = `${vehicle.year || ""} ${vehicle.make || ""} ${vehicle.model || ""}`.trim() || "Vehicle";
4749
+ if (vehicle.registration_expiration) {
4750
+ const expires = new Date(vehicle.registration_expiration);
4751
+ if (expires <= cutoff) {
4752
+ expiring.push({
4753
+ householdId: household.id,
4754
+ householdName: household.name,
4755
+ type: "vehicle_registration",
4756
+ name: vehicleName,
4757
+ expiresAt: vehicle.registration_expiration,
4758
+ daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
4759
+ });
4760
+ }
4761
+ }
4762
+ if (vehicle.tabs_expiration) {
4763
+ const expires = new Date(vehicle.tabs_expiration);
4764
+ if (expires <= cutoff) {
4765
+ expiring.push({
4766
+ householdId: household.id,
4767
+ householdName: household.name,
4768
+ type: "vehicle_tabs",
4769
+ name: vehicleName,
4770
+ expiresAt: vehicle.tabs_expiration,
4771
+ daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
4772
+ });
4773
+ }
4774
+ }
4775
+ }
4776
+ const subscriptions = await getDecryptedEntities(household.id, "subscription", privateKey);
4777
+ for (const sub of subscriptions) {
4778
+ if (sub.expiration_date) {
4779
+ const expires = new Date(sub.expiration_date);
4780
+ if (expires <= cutoff) {
4781
+ expiring.push({
4782
+ householdId: household.id,
4783
+ householdName: household.name,
4784
+ type: "subscription",
4785
+ name: sub.name || sub.custom_name || sub.provider || sub.service_name,
4786
+ expiresAt: sub.expiration_date,
4787
+ daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
4788
+ });
4789
+ }
4790
+ }
4791
+ }
4792
+ const credentials = await getDecryptedEntities(household.id, "credential", privateKey);
4793
+ for (const cred of credentials) {
4794
+ if (cred.expiration_date) {
4795
+ const expires = new Date(cred.expiration_date);
4796
+ if (expires <= cutoff) {
4797
+ expiring.push({
4798
+ householdId: household.id,
4799
+ householdName: household.name,
4800
+ type: "credential",
4801
+ name: cred.name || cred.credential_type,
4802
+ expiresAt: cred.expiration_date,
4803
+ daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
4804
+ });
4805
+ }
4806
+ }
4807
+ }
4808
+ }
4809
+ expiring.sort((a, b) => a.daysUntil - b.daysUntil);
4810
+ return expiring;
4811
+ }
4812
+
4813
+ // src/tools/search.ts
4814
+ async function buildEntitiesData(householdId, privateKey) {
4815
+ const entityTypeMapping = {
4816
+ property: "properties",
4817
+ vehicle: "vehicles",
4818
+ pet: "pets",
4819
+ contact: "contacts",
4820
+ subscription: "subscriptions",
4821
+ service: "services",
4822
+ insurance: "insurancePolicies",
4823
+ valuable: "valuables",
4824
+ bank_account: "financialAccounts",
4825
+ investment: "financialAccounts",
4826
+ credential: "credentials",
4827
+ maintenance_task: "maintenanceTasks",
4828
+ access_code: "accessCodes",
4829
+ device: "devices",
4830
+ person: "people",
4831
+ pet_vet_visit: "petVetVisits",
4832
+ vehicle_service: "vehicleServices",
4833
+ legal: "legalDocuments",
4834
+ health_record: "healthRecords",
4835
+ education_record: "educationRecords",
4836
+ military_record: "militaryRecords",
4837
+ membership_record: "membershipRecords",
4838
+ home_improvement: "homeImprovements",
4839
+ tax_year: "taxYears"
4840
+ };
4841
+ const data = {};
4842
+ const entityTypes = Object.keys(entityTypeMapping);
4843
+ for (const type of entityTypes) {
4844
+ try {
4845
+ const entities = await getDecryptedEntities(householdId, type, privateKey);
4846
+ if (entities.length > 0) {
4847
+ const propName = entityTypeMapping[type];
4848
+ const existing = data[propName] || [];
4849
+ data[propName] = [...existing, ...entities];
4850
+ }
4851
+ } catch {
4852
+ }
4853
+ }
4854
+ return data;
4855
+ }
4856
+ async function executeSearch(params, privateKey, privacyMode) {
4857
+ const { query, householdId, entityType } = params;
4858
+ const households = await getHouseholds();
4859
+ const searchHouseholds = householdId ? households.filter((h) => h.id === householdId) : households;
4860
+ const results = [];
4861
+ for (const household of searchHouseholds) {
4862
+ const entitiesData = await buildEntitiesData(household.id, privateKey);
4863
+ const searchableEntities = indexAllEntities(entitiesData);
4864
+ const searchResults = searchEntities(searchableEntities, query, {
4865
+ maxTotal: 50,
4866
+ includeTypes: entityType ? [entityType] : void 0
4867
+ });
4868
+ for (const result of searchResults) {
4869
+ const type = result.entity.entityType;
4870
+ const entityId = result.entity.id;
4871
+ const entities = await getDecryptedEntities(household.id, type, privateKey);
4872
+ const fullEntity = entities.find((e) => e.id === entityId);
4873
+ if (fullEntity) {
4874
+ const enriched = enrichEntity(fullEntity, type);
4875
+ const redacted = redactEntity(enriched, type, privacyMode);
4876
+ results.push({
4877
+ householdId: household.id,
4878
+ householdName: household.name,
4879
+ entityType: type,
4880
+ entity: redacted,
4881
+ score: result.score,
4882
+ matchedFields: result.matchedFields
4883
+ });
4884
+ }
4885
+ }
4886
+ }
4887
+ results.sort((a, b) => b.score - a.score);
4888
+ return results;
4889
+ }
4890
+
4891
+ // src/tools/files.ts
4892
+ async function executeFileDownload(client, params) {
4893
+ const { fileId, householdId, entityId, entityType } = params;
4894
+ try {
4895
+ const result = await downloadAndDecryptFile(
4896
+ client,
4897
+ householdId,
4898
+ fileId,
4899
+ entityId,
4900
+ entityType
4901
+ );
4902
+ return {
4903
+ success: true,
4904
+ fileName: result.fileName,
4905
+ mimeType: result.mimeType,
4906
+ size: result.bytes.length,
4907
+ data: result.dataBase64
4908
+ };
4909
+ } catch (err) {
4910
+ return {
4911
+ success: false,
4912
+ error: err.message
4913
+ };
4914
+ }
4915
+ }
4916
+
4917
+ // src/server.ts
4918
+ async function startServer(mode) {
4919
+ const config = loadConfig();
4920
+ const privacyMode = mode || config.defaultMode;
4921
+ console.error(`[MCP] Starting server in ${privacyMode} mode`);
4922
+ const client = await getAuthenticatedClient();
4923
+ if (!client) {
4924
+ console.error("[MCP] Not logged in. Run: estatehelm login");
4925
+ process.exit(1);
4926
+ }
4927
+ const privateKey = await getPrivateKey();
4928
+ if (!privateKey) {
4929
+ console.error("[MCP] Failed to load encryption keys. Run: estatehelm login");
4930
+ process.exit(1);
4931
+ }
4932
+ await initCache();
4933
+ console.error("[MCP] Checking for updates...");
4934
+ const synced = await syncIfNeeded(client, privateKey);
4935
+ if (synced) {
4936
+ console.error("[MCP] Cache updated");
4937
+ } else {
4938
+ console.error("[MCP] Cache is up to date");
4939
+ }
4940
+ const server = new import_server.Server(
4941
+ {
4942
+ name: "estatehelm",
4943
+ version: "1.0.0"
4944
+ },
4945
+ {
4946
+ capabilities: {
4947
+ resources: {},
4948
+ tools: {},
4949
+ prompts: {}
4950
+ }
4951
+ }
4952
+ );
4953
+ server.setRequestHandler(import_types42.ListResourcesRequestSchema, async () => {
4954
+ const households = await listHouseholds();
4955
+ const resources = [
4956
+ {
4957
+ uri: "estatehelm://households",
4958
+ name: "All Households",
4959
+ description: "List of all households you have access to",
4960
+ mimeType: "application/json"
4961
+ }
4962
+ ];
4963
+ for (const household of households) {
4964
+ resources.push({
4965
+ uri: `estatehelm://households/${household.id}`,
4966
+ name: household.name,
4967
+ description: `Household: ${household.name}`,
4968
+ mimeType: "application/json"
4969
+ });
4970
+ for (const type of ENTITY_TYPES) {
4971
+ resources.push({
4972
+ uri: `estatehelm://households/${household.id}/${type}`,
4973
+ name: `${household.name} - ${formatEntityType(type)}`,
4974
+ description: `${formatEntityType(type)} in ${household.name}`,
4975
+ mimeType: "application/json"
4976
+ });
4977
+ }
4978
+ }
4979
+ return { resources };
4980
+ });
4981
+ server.setRequestHandler(import_types42.ReadResourceRequestSchema, async (request) => {
4982
+ const uri = request.params.uri;
4983
+ const parsed = parseResourceUri(uri);
4984
+ if (!parsed) {
4985
+ throw new Error(`Invalid resource URI: ${uri}`);
4986
+ }
4987
+ let content;
4988
+ if (parsed.type === "households" && !parsed.householdId) {
4989
+ content = await listHouseholds();
4990
+ } else if (parsed.type === "households" && parsed.householdId && !parsed.entityType) {
4991
+ content = await getHousehold(parsed.householdId);
4992
+ if (!content) {
4993
+ throw new Error(`Household not found: ${parsed.householdId}`);
4994
+ }
4995
+ } else if (parsed.householdId && parsed.entityType) {
4996
+ content = await getEntities(parsed.householdId, parsed.entityType, privateKey, privacyMode);
4997
+ } else {
4998
+ throw new Error(`Unsupported resource: ${uri}`);
4999
+ }
5000
+ return {
5001
+ contents: [
5002
+ {
5003
+ uri,
5004
+ mimeType: "application/json",
5005
+ text: JSON.stringify(content, null, 2)
5006
+ }
5007
+ ]
5008
+ };
5009
+ });
5010
+ server.setRequestHandler(import_types42.ListToolsRequestSchema, async () => {
5011
+ return {
5012
+ tools: [
5013
+ {
5014
+ name: "search_entities",
5015
+ description: "Search across all entities in EstateHelm. Returns results with computed fields (age, days until expiry, etc.)",
5016
+ inputSchema: {
5017
+ type: "object",
5018
+ properties: {
5019
+ query: {
5020
+ type: "string",
5021
+ description: "Search query"
5022
+ },
5023
+ householdId: {
5024
+ type: "string",
5025
+ description: "Optional: Limit search to a specific household"
5026
+ },
5027
+ entityType: {
5028
+ type: "string",
5029
+ description: "Optional: Limit search to a specific entity type"
5030
+ }
5031
+ },
5032
+ required: ["query"]
5033
+ }
5034
+ },
5035
+ {
5036
+ name: "get_household_summary",
5037
+ description: "Get a summary of a household including counts and key dates",
5038
+ inputSchema: {
5039
+ type: "object",
5040
+ properties: {
5041
+ householdId: {
5042
+ type: "string",
5043
+ description: "The household ID"
5044
+ }
5045
+ },
5046
+ required: ["householdId"]
5047
+ }
5048
+ },
5049
+ {
5050
+ name: "get_expiring_items",
5051
+ description: "Get items expiring within a given number of days (insurance, vehicle registration, credentials, subscriptions)",
5052
+ inputSchema: {
5053
+ type: "object",
5054
+ properties: {
5055
+ days: {
5056
+ type: "number",
5057
+ description: "Number of days to look ahead (default: 30)"
5058
+ },
5059
+ householdId: {
5060
+ type: "string",
5061
+ description: "Optional: Limit to a specific household"
5062
+ }
5063
+ }
5064
+ }
5065
+ },
5066
+ {
5067
+ name: "get_file",
5068
+ description: "Download and decrypt a file attachment. Returns base64-encoded file data that can be saved to disk.",
5069
+ inputSchema: {
2669
5070
  type: "object",
2670
5071
  properties: {
2671
5072
  fileId: {
@@ -2687,231 +5088,68 @@ async function startServer(mode) {
2687
5088
  },
2688
5089
  required: ["fileId", "householdId", "entityId", "entityType"]
2689
5090
  }
5091
+ },
5092
+ {
5093
+ name: "refresh",
5094
+ description: "Force refresh of cached data from the server",
5095
+ inputSchema: {
5096
+ type: "object",
5097
+ properties: {}
5098
+ }
2690
5099
  }
2691
5100
  ]
2692
5101
  };
2693
5102
  });
2694
- server.setRequestHandler(import_types5.CallToolRequestSchema, async (request) => {
5103
+ server.setRequestHandler(import_types42.CallToolRequestSchema, async (request) => {
2695
5104
  const { name, arguments: args } = request.params;
2696
5105
  switch (name) {
2697
5106
  case "search_entities": {
2698
- const { query, householdId, entityType } = args;
2699
- const households = await getHouseholds();
2700
- const searchHouseholds = householdId ? households.filter((h) => h.id === householdId) : households;
2701
- const results = [];
2702
- for (const household of searchHouseholds) {
2703
- const entityTypes = entityType ? [entityType] : [
2704
- "pet",
2705
- "property",
2706
- "vehicle",
2707
- "contact",
2708
- "insurance",
2709
- "bank_account",
2710
- "investment",
2711
- "subscription",
2712
- "maintenance_task",
2713
- "password",
2714
- "access_code"
2715
- ];
2716
- for (const type of entityTypes) {
2717
- const entities = await getDecryptedEntities(household.id, type, privateKey);
2718
- const matches = entities.filter((e) => searchEntity(e, query));
2719
- for (const match of matches) {
2720
- results.push({
2721
- householdId: household.id,
2722
- householdName: household.name,
2723
- entityType: type,
2724
- entity: redactEntity(match, type, privacyMode)
2725
- });
2726
- }
2727
- }
2728
- }
5107
+ const results = await executeSearch(
5108
+ args,
5109
+ privateKey,
5110
+ privacyMode
5111
+ );
2729
5112
  return {
2730
- content: [
2731
- {
2732
- type: "text",
2733
- text: JSON.stringify(results, null, 2)
2734
- }
2735
- ]
5113
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
2736
5114
  };
2737
5115
  }
2738
5116
  case "get_household_summary": {
2739
5117
  const { householdId } = args;
2740
- const households = await getHouseholds();
2741
- const household = households.find((h) => h.id === householdId);
2742
- if (!household) {
5118
+ const summary = await getHouseholdSummary(householdId, privateKey, getDecryptedEntities);
5119
+ if (!summary) {
2743
5120
  throw new Error(`Household not found: ${householdId}`);
2744
5121
  }
2745
- const entityTypes = [
2746
- "pet",
2747
- "property",
2748
- "vehicle",
2749
- "contact",
2750
- "insurance",
2751
- "bank_account",
2752
- "investment",
2753
- "subscription",
2754
- "maintenance_task",
2755
- "password",
2756
- "access_code"
2757
- ];
2758
- const counts = {};
2759
- for (const type of entityTypes) {
2760
- const entities = await getDecryptedEntities(householdId, type, privateKey);
2761
- counts[type] = entities.length;
2762
- }
2763
- const summary = {
2764
- household: {
2765
- id: household.id,
2766
- name: household.name
2767
- },
2768
- counts,
2769
- totalEntities: Object.values(counts).reduce((a, b) => a + b, 0)
2770
- };
2771
5122
  return {
2772
- content: [
2773
- {
2774
- type: "text",
2775
- text: JSON.stringify(summary, null, 2)
2776
- }
2777
- ]
5123
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
2778
5124
  };
2779
5125
  }
2780
5126
  case "get_expiring_items": {
2781
5127
  const { days = 30, householdId } = args;
2782
- const households = await getHouseholds();
2783
- const searchHouseholds = householdId ? households.filter((h) => h.id === householdId) : households;
2784
- const now = /* @__PURE__ */ new Date();
2785
- const cutoff = new Date(now.getTime() + days * 24 * 60 * 60 * 1e3);
2786
- const expiring = [];
2787
- for (const household of searchHouseholds) {
2788
- const insurance = await getDecryptedEntities(household.id, "insurance", privateKey);
2789
- for (const policy of insurance) {
2790
- if (policy.expiration_date) {
2791
- const expires = new Date(policy.expiration_date);
2792
- if (expires <= cutoff) {
2793
- expiring.push({
2794
- householdId: household.id,
2795
- householdName: household.name,
2796
- type: "insurance",
2797
- name: policy.name || policy.policy_number,
2798
- expiresAt: policy.expiration_date,
2799
- daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
2800
- });
2801
- }
2802
- }
2803
- }
2804
- const vehicles = await getDecryptedEntities(household.id, "vehicle", privateKey);
2805
- for (const vehicle of vehicles) {
2806
- const vehicleName = `${vehicle.year || ""} ${vehicle.make || ""} ${vehicle.model || ""}`.trim();
2807
- if (vehicle.registration_expiration) {
2808
- const expires = new Date(vehicle.registration_expiration);
2809
- if (expires <= cutoff) {
2810
- expiring.push({
2811
- householdId: household.id,
2812
- householdName: household.name,
2813
- type: "vehicle_registration",
2814
- name: vehicleName,
2815
- expiresAt: vehicle.registration_expiration,
2816
- daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
2817
- });
2818
- }
2819
- }
2820
- if (vehicle.tabs_expiration) {
2821
- const expires = new Date(vehicle.tabs_expiration);
2822
- if (expires <= cutoff) {
2823
- expiring.push({
2824
- householdId: household.id,
2825
- householdName: household.name,
2826
- type: "vehicle_tabs",
2827
- name: vehicleName,
2828
- expiresAt: vehicle.tabs_expiration,
2829
- daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
2830
- });
2831
- }
2832
- }
2833
- }
2834
- const subscriptions = await getDecryptedEntities(household.id, "subscription", privateKey);
2835
- for (const sub of subscriptions) {
2836
- if (sub.expiration_date) {
2837
- const expires = new Date(sub.expiration_date);
2838
- if (expires <= cutoff) {
2839
- expiring.push({
2840
- householdId: household.id,
2841
- householdName: household.name,
2842
- type: "subscription",
2843
- name: sub.name || sub.service_name,
2844
- expiresAt: sub.expiration_date,
2845
- daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
2846
- });
2847
- }
2848
- }
2849
- }
2850
- }
2851
- expiring.sort((a, b) => a.daysUntil - b.daysUntil);
5128
+ const expiring = await getExpiringItems(days, householdId, privateKey);
2852
5129
  return {
2853
- content: [
2854
- {
2855
- type: "text",
2856
- text: JSON.stringify(expiring, null, 2)
2857
- }
2858
- ]
5130
+ content: [{ type: "text", text: JSON.stringify(expiring, null, 2) }]
5131
+ };
5132
+ }
5133
+ case "get_file": {
5134
+ const result = await executeFileDownload(client, args);
5135
+ return {
5136
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
2859
5137
  };
2860
5138
  }
2861
5139
  case "refresh": {
2862
5140
  const synced2 = await syncIfNeeded(client, privateKey, true);
2863
5141
  return {
2864
- content: [
2865
- {
2866
- type: "text",
2867
- text: synced2 ? "Cache refreshed with latest data" : "Cache was already up to date"
2868
- }
2869
- ]
5142
+ content: [{
5143
+ type: "text",
5144
+ text: synced2 ? "Cache refreshed with latest data" : "Cache was already up to date"
5145
+ }]
2870
5146
  };
2871
5147
  }
2872
- case "get_file": {
2873
- const { fileId, householdId, entityId, entityType } = args;
2874
- try {
2875
- const result = await downloadAndDecryptFile(
2876
- client,
2877
- householdId,
2878
- fileId,
2879
- entityId,
2880
- entityType
2881
- );
2882
- return {
2883
- content: [
2884
- {
2885
- type: "text",
2886
- text: JSON.stringify({
2887
- success: true,
2888
- fileName: result.fileName,
2889
- mimeType: result.mimeType,
2890
- size: result.data.length,
2891
- data: result.dataBase64
2892
- }, null, 2)
2893
- }
2894
- ]
2895
- };
2896
- } catch (err) {
2897
- return {
2898
- content: [
2899
- {
2900
- type: "text",
2901
- text: JSON.stringify({
2902
- success: false,
2903
- error: err.message
2904
- }, null, 2)
2905
- }
2906
- ]
2907
- };
2908
- }
2909
- }
2910
5148
  default:
2911
5149
  throw new Error(`Unknown tool: ${name}`);
2912
5150
  }
2913
5151
  });
2914
- server.setRequestHandler(import_types5.ListPromptsRequestSchema, async () => {
5152
+ server.setRequestHandler(import_types42.ListPromptsRequestSchema, async () => {
2915
5153
  return {
2916
5154
  prompts: [
2917
5155
  {
@@ -2944,12 +5182,12 @@ async function startServer(mode) {
2944
5182
  ]
2945
5183
  };
2946
5184
  });
2947
- server.setRequestHandler(import_types5.GetPromptRequestSchema, async (request) => {
5185
+ server.setRequestHandler(import_types42.GetPromptRequestSchema, async (request) => {
2948
5186
  const { name, arguments: args } = request.params;
2949
5187
  switch (name) {
2950
5188
  case "household_summary": {
2951
5189
  const householdId = args?.householdId;
2952
- const households = await getHouseholds();
5190
+ const households = await listHouseholds();
2953
5191
  const household = householdId ? households.find((h) => h.id === householdId) : households[0];
2954
5192
  if (!household) {
2955
5193
  throw new Error("No household found");
@@ -2974,7 +5212,7 @@ async function startServer(mode) {
2974
5212
  role: "user",
2975
5213
  content: {
2976
5214
  type: "text",
2977
- text: `What items are expiring in the next ${days} days? Include insurance policies, vehicle registrations, subscriptions, and any other items with expiration dates.`
5215
+ text: `What items are expiring in the next ${days} days? Include insurance policies, vehicle registrations, credentials (passports, licenses), subscriptions, and any other items with expiration dates.`
2978
5216
  }
2979
5217
  }
2980
5218
  ]
@@ -3010,27 +5248,6 @@ function parseResourceUri(uri) {
3010
5248
  function formatEntityType(type) {
3011
5249
  return type.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3012
5250
  }
3013
- function searchEntity(entity, query) {
3014
- const lowerQuery = query.toLowerCase();
3015
- const searchFields = [
3016
- "name",
3017
- "title",
3018
- "description",
3019
- "notes",
3020
- "make",
3021
- "model",
3022
- "policyNumber",
3023
- "serviceName",
3024
- "username",
3025
- "email"
3026
- ];
3027
- for (const field of searchFields) {
3028
- if (entity[field] && String(entity[field]).toLowerCase().includes(lowerQuery)) {
3029
- return true;
3030
- }
3031
- }
3032
- return false;
3033
- }
3034
5251
 
3035
5252
  // src/index.ts
3036
5253
  var program = new import_commander.Command();