@rubytech/create-maxy 1.0.498 → 1.0.500
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +34 -4
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +3 -3
- package/payload/platform/scripts/logs-read.sh +40 -5
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/PLUGIN.md +36 -8
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/index.js +229 -153
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/lib/loop-api.d.ts +19 -1
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/lib/loop-api.d.ts.map +1 -1
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/lib/loop-api.js +99 -3
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/lib/loop-api.js.map +1 -1
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/customer-preferences.d.ts +10 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/customer-preferences.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/customer-preferences.js +24 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/customer-preferences.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/feedback.d.ts +16 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/feedback.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/feedback.js +35 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/feedback.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/key-register.js +1 -1
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/key-register.js.map +1 -1
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-enquiry.d.ts +13 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-enquiry.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-enquiry.js +41 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-enquiry.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match-batch.d.ts +9 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match-batch.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match-batch.js +16 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match-batch.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match-request.d.ts +15 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match-request.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match-request.js +11 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match-request.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match.d.ts +10 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match.js +39 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/marketing-match.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/people-detail.d.ts +9 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/people-detail.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/people-detail.js +33 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/people-detail.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/people-search.d.ts +18 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/people-search.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/people-search.js +59 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/people-search.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-detail.d.ts +10 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-detail.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-detail.js +39 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-detail.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-listed.d.ts +12 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-listed.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-listed.js +28 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-listed.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-request.d.ts +15 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-request.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-request.js +11 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-request.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-search.d.ts +16 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-search.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-search.js +39 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/property-search.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/supplier.d.ts +13 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/supplier.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/supplier.js +49 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/supplier.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/team-availability.d.ts +7 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/team-availability.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/team-availability.js +15 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/team-availability.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-create.d.ts +14 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-create.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-create.js +11 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-create.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-detail.d.ts +9 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-detail.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-detail.js +40 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-detail.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-search.d.ts +13 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-search.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-search.js +34 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-search.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-update.d.ts +14 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-update.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-update.js +18 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/dist/tools/viewing-update.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/index.ts +335 -158
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/lib/loop-api.ts +140 -3
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/customer-preferences.ts +60 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/feedback.ts +80 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/key-register.ts +1 -1
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/marketing-enquiry.ts +105 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/marketing-match-batch.ts +48 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/marketing-match-request.ts +37 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/marketing-match.ts +78 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/people-detail.ts +63 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/people-search.ts +93 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/property-detail.ts +70 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/property-listed.ts +67 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/property-request.ts +37 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/property-search.ts +80 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/supplier.ts +120 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/team-availability.ts +42 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/viewing-create.ts +36 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/viewing-detail.ts +70 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/viewing-search.ts +74 -0
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/viewing-update.ts +48 -0
- package/payload/server/public/assets/{ChatInput-BbMvZgRR.js → ChatInput-Bo9T8v0E.js} +1 -1
- package/payload/server/public/assets/{admin-BSHc9LPS.js → admin-CkpknqK7.js} +60 -60
- package/payload/server/public/assets/{public-DvDzSq3r.js → public-wDhMuZDR.js} +1 -1
- package/payload/server/public/index.html +2 -2
- package/payload/server/public/public.html +2 -2
- package/payload/server/server.js +89 -2
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/feedback-list.ts +0 -54
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/people-list.ts +0 -52
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/properties-list.ts +0 -52
- package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/viewings-list.ts +0 -62
package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/people-search.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateAcrossTeams,
|
|
3
|
+
formatAggregationResult,
|
|
4
|
+
loopGet,
|
|
5
|
+
} from "../lib/loop-api.js";
|
|
6
|
+
|
|
7
|
+
interface LoopPersonSummary {
|
|
8
|
+
id?: number;
|
|
9
|
+
firstName?: string;
|
|
10
|
+
lastName?: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
phone?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type PeopleRole = "buyers" | "sellers" | "renters" | "landlords";
|
|
18
|
+
|
|
19
|
+
export async function peopleSearch(params: {
|
|
20
|
+
accountId: string;
|
|
21
|
+
role?: PeopleRole;
|
|
22
|
+
searchTerm?: string;
|
|
23
|
+
// Role-specific filters (buyers)
|
|
24
|
+
maxPrice?: number;
|
|
25
|
+
minBeds?: number;
|
|
26
|
+
searchAreas?: string;
|
|
27
|
+
propertyTypes?: string;
|
|
28
|
+
// Role-specific filters (sellers/landlords)
|
|
29
|
+
startDate?: string;
|
|
30
|
+
endDate?: string;
|
|
31
|
+
minPrice?: number;
|
|
32
|
+
// Role-specific filters (renters)
|
|
33
|
+
maxRent?: number;
|
|
34
|
+
teamName?: string;
|
|
35
|
+
limit?: number;
|
|
36
|
+
}): Promise<string> {
|
|
37
|
+
const {
|
|
38
|
+
accountId, role, searchTerm,
|
|
39
|
+
maxPrice, minBeds, searchAreas, propertyTypes,
|
|
40
|
+
startDate, endDate, minPrice, maxRent,
|
|
41
|
+
teamName, limit,
|
|
42
|
+
} = params;
|
|
43
|
+
|
|
44
|
+
const result = await aggregateAcrossTeams<LoopPersonSummary>(
|
|
45
|
+
accountId,
|
|
46
|
+
"people",
|
|
47
|
+
"loop-people-search",
|
|
48
|
+
async (apiKey, team) => {
|
|
49
|
+
const qp: string[] = [];
|
|
50
|
+
if (searchTerm) qp.push(`SearchTerm=${encodeURIComponent(searchTerm)}`);
|
|
51
|
+
|
|
52
|
+
if (role === "buyers") {
|
|
53
|
+
if (maxPrice != null) qp.push(`MaxPrice=${maxPrice}`);
|
|
54
|
+
if (minBeds != null) qp.push(`MinBeds=${minBeds}`);
|
|
55
|
+
if (searchAreas) qp.push(`SearchAreas=${encodeURIComponent(searchAreas)}`);
|
|
56
|
+
if (propertyTypes) qp.push(`PropertyTypes=${encodeURIComponent(propertyTypes)}`);
|
|
57
|
+
} else if (role === "sellers" || role === "landlords") {
|
|
58
|
+
if (startDate) qp.push(`StartDate=${encodeURIComponent(startDate)}`);
|
|
59
|
+
if (endDate) qp.push(`EndDate=${encodeURIComponent(endDate)}`);
|
|
60
|
+
if (minPrice != null) qp.push(`MinPrice=${minPrice}`);
|
|
61
|
+
if (maxPrice != null) qp.push(`MaxPrice=${maxPrice}`);
|
|
62
|
+
} else if (role === "renters") {
|
|
63
|
+
if (maxRent != null) qp.push(`MaxRent=${maxRent}`);
|
|
64
|
+
if (minBeds != null) qp.push(`MinBeds=${minBeds}`);
|
|
65
|
+
if (searchAreas) qp.push(`SearchAreas=${encodeURIComponent(searchAreas)}`);
|
|
66
|
+
if (propertyTypes) qp.push(`PropertyTypes=${encodeURIComponent(propertyTypes)}`);
|
|
67
|
+
} else {
|
|
68
|
+
// Generic /people endpoint
|
|
69
|
+
if (startDate) qp.push(`StartDate=${encodeURIComponent(startDate)}`);
|
|
70
|
+
if (endDate) qp.push(`EndDate=${encodeURIComponent(endDate)}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const query = qp.length > 0 ? `?${qp.join("&")}` : "";
|
|
74
|
+
const basePath = role ? `/people/${role}` : "/people";
|
|
75
|
+
const path = `${basePath}${query}`;
|
|
76
|
+
const data = await loopGet<LoopPersonSummary[]>(apiKey, path, "loop-people-search", team);
|
|
77
|
+
return Array.isArray(data) ? data : [];
|
|
78
|
+
},
|
|
79
|
+
{ teamName, limitPerTeam: limit ?? 50, limitTotal: limit ?? 200 }
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const entityName = role ?? "people";
|
|
83
|
+
return formatAggregationResult(
|
|
84
|
+
result,
|
|
85
|
+
(p) => {
|
|
86
|
+
const name = [p.firstName, p.lastName].filter(Boolean).join(" ") || "Unknown";
|
|
87
|
+
const contact = p.email ?? p.phone ?? "";
|
|
88
|
+
const type = p.type ? ` (${p.type})` : "";
|
|
89
|
+
return `- ${name}${contact ? ` <${contact}>` : ""}${type}`;
|
|
90
|
+
},
|
|
91
|
+
entityName
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateAcrossTeams,
|
|
3
|
+
formatAggregationResult,
|
|
4
|
+
loopGet,
|
|
5
|
+
} from "../lib/loop-api.js";
|
|
6
|
+
|
|
7
|
+
interface LoopPropertyDetail {
|
|
8
|
+
id?: number;
|
|
9
|
+
address?: string;
|
|
10
|
+
price?: number;
|
|
11
|
+
status?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
bedrooms?: number;
|
|
14
|
+
bathrooms?: number;
|
|
15
|
+
description?: string;
|
|
16
|
+
receptions?: number;
|
|
17
|
+
tenure?: string;
|
|
18
|
+
councilTax?: string;
|
|
19
|
+
epcRating?: string;
|
|
20
|
+
floorArea?: number;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Department = "sales" | "lettings";
|
|
25
|
+
|
|
26
|
+
export async function propertyDetail(params: {
|
|
27
|
+
accountId: string;
|
|
28
|
+
propertyId: number;
|
|
29
|
+
department: Department;
|
|
30
|
+
previewHash?: number;
|
|
31
|
+
teamName?: string;
|
|
32
|
+
}): Promise<string> {
|
|
33
|
+
const { accountId, propertyId, department, previewHash, teamName } = params;
|
|
34
|
+
|
|
35
|
+
const result = await aggregateAcrossTeams<LoopPropertyDetail>(
|
|
36
|
+
accountId,
|
|
37
|
+
"properties",
|
|
38
|
+
"loop-property-detail",
|
|
39
|
+
async (apiKey, team) => {
|
|
40
|
+
const path = previewHash
|
|
41
|
+
? `/property/residential/${department}/${propertyId}/preview/${previewHash}`
|
|
42
|
+
: `/property/residential/${department}/${propertyId}`;
|
|
43
|
+
const data = await loopGet<LoopPropertyDetail>(apiKey, path, "loop-property-detail", team);
|
|
44
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
45
|
+
return [data];
|
|
46
|
+
}
|
|
47
|
+
return [];
|
|
48
|
+
},
|
|
49
|
+
{ teamName }
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return formatAggregationResult(
|
|
53
|
+
result,
|
|
54
|
+
(p) => {
|
|
55
|
+
const lines = [`**${p.address ?? "Unknown address"}**`];
|
|
56
|
+
if (p.price) lines.push(`Price: £${p.price.toLocaleString("en-GB")}`);
|
|
57
|
+
if (p.status) lines.push(`Status: ${p.status}`);
|
|
58
|
+
if (p.type) lines.push(`Type: ${p.type}`);
|
|
59
|
+
if (p.bedrooms) lines.push(`Bedrooms: ${p.bedrooms}`);
|
|
60
|
+
if (p.bathrooms) lines.push(`Bathrooms: ${p.bathrooms}`);
|
|
61
|
+
if (p.receptions) lines.push(`Receptions: ${p.receptions}`);
|
|
62
|
+
if (p.tenure) lines.push(`Tenure: ${p.tenure}`);
|
|
63
|
+
if (p.councilTax) lines.push(`Council Tax: ${p.councilTax}`);
|
|
64
|
+
if (p.epcRating) lines.push(`EPC: ${p.epcRating}`);
|
|
65
|
+
if (p.description) lines.push(`\n${p.description}`);
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
},
|
|
68
|
+
"property details"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateAcrossTeams,
|
|
3
|
+
formatAggregationResult,
|
|
4
|
+
loopGet,
|
|
5
|
+
} from "../lib/loop-api.js";
|
|
6
|
+
|
|
7
|
+
interface LoopPropertyListing {
|
|
8
|
+
id?: number;
|
|
9
|
+
address?: string;
|
|
10
|
+
price?: number;
|
|
11
|
+
status?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
bedrooms?: number;
|
|
14
|
+
listingUrl?: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Department = "sales" | "lettings" | "both";
|
|
19
|
+
type Channel = "rightmove" | "onTheMarket" | "zoopla" | "website";
|
|
20
|
+
|
|
21
|
+
export async function propertyListed(params: {
|
|
22
|
+
accountId: string;
|
|
23
|
+
channel: Channel;
|
|
24
|
+
department?: Department;
|
|
25
|
+
includeSold?: boolean;
|
|
26
|
+
teamName?: string;
|
|
27
|
+
limit?: number;
|
|
28
|
+
}): Promise<string> {
|
|
29
|
+
const { accountId, channel, department = "both", includeSold = false, teamName, limit } = params;
|
|
30
|
+
|
|
31
|
+
const result = await aggregateAcrossTeams<LoopPropertyListing>(
|
|
32
|
+
accountId,
|
|
33
|
+
"properties",
|
|
34
|
+
"loop-property-listed",
|
|
35
|
+
async (apiKey, team) => {
|
|
36
|
+
const all: LoopPropertyListing[] = [];
|
|
37
|
+
const depts: string[] =
|
|
38
|
+
department === "both" ? ["sales", "lettings"] : [department];
|
|
39
|
+
|
|
40
|
+
for (const dept of depts) {
|
|
41
|
+
const path = `/property/residential/${dept}/listed/${channel}`;
|
|
42
|
+
const data = await loopGet<LoopPropertyListing[]>(apiKey, path, "loop-property-listed", team);
|
|
43
|
+
if (Array.isArray(data)) all.push(...data);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (includeSold) {
|
|
47
|
+
const soldPath = `/property/residential/sold/${channel}`;
|
|
48
|
+
const soldData = await loopGet<LoopPropertyListing[]>(apiKey, soldPath, "loop-property-listed", team);
|
|
49
|
+
if (Array.isArray(soldData)) all.push(...soldData);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return all;
|
|
53
|
+
},
|
|
54
|
+
{ teamName, limitPerTeam: limit ?? 50, limitTotal: limit ?? 200 }
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return formatAggregationResult(
|
|
58
|
+
result,
|
|
59
|
+
(p) => {
|
|
60
|
+
const price = p.price ? ` — £${p.price.toLocaleString("en-GB")}` : "";
|
|
61
|
+
const beds = p.bedrooms ? ` ${p.bedrooms}bed` : "";
|
|
62
|
+
const status = p.status ? ` [${p.status}]` : "";
|
|
63
|
+
return `- ${p.address ?? "Unknown address"}${price}${beds}${status}`;
|
|
64
|
+
},
|
|
65
|
+
`${channel} listings`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { loopPost, withTeamKey } from "../lib/loop-api.js";
|
|
2
|
+
|
|
3
|
+
type Department = "sales" | "lettings";
|
|
4
|
+
type RequestAction = "viewing" | "call-back" | "information";
|
|
5
|
+
|
|
6
|
+
interface LoopBooleanResponse {
|
|
7
|
+
success?: boolean;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function propertyRequest(params: {
|
|
12
|
+
accountId: string;
|
|
13
|
+
teamName: string;
|
|
14
|
+
propertyId: number;
|
|
15
|
+
department: Department;
|
|
16
|
+
action: RequestAction;
|
|
17
|
+
name?: string;
|
|
18
|
+
email?: string;
|
|
19
|
+
phone?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
}): Promise<string> {
|
|
22
|
+
const { accountId, teamName, propertyId, department, action, ...body } = params;
|
|
23
|
+
|
|
24
|
+
const result = await withTeamKey<LoopBooleanResponse>(
|
|
25
|
+
accountId,
|
|
26
|
+
teamName,
|
|
27
|
+
"properties",
|
|
28
|
+
"loop-property-request",
|
|
29
|
+
async (apiKey) => {
|
|
30
|
+
const path = `/property/residential/${department}/${propertyId}/${action}`;
|
|
31
|
+
return loopPost<LoopBooleanResponse>(apiKey, path, body, "loop-property-request", teamName);
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const actionLabel = action === "call-back" ? "callback" : action;
|
|
36
|
+
return `${actionLabel} request submitted for property ${propertyId} (${department}) via team "${teamName}".`;
|
|
37
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateAcrossTeams,
|
|
3
|
+
formatAggregationResult,
|
|
4
|
+
loopGet,
|
|
5
|
+
} from "../lib/loop-api.js";
|
|
6
|
+
|
|
7
|
+
interface LoopPropertySummary {
|
|
8
|
+
id?: number;
|
|
9
|
+
address?: string;
|
|
10
|
+
price?: number;
|
|
11
|
+
status?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
bedrooms?: number;
|
|
14
|
+
bathrooms?: number;
|
|
15
|
+
description?: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Department = "sales" | "lettings" | "both";
|
|
20
|
+
|
|
21
|
+
export async function propertySearch(params: {
|
|
22
|
+
accountId: string;
|
|
23
|
+
department?: Department;
|
|
24
|
+
searchTerm?: string;
|
|
25
|
+
minPrice?: number;
|
|
26
|
+
maxPrice?: number;
|
|
27
|
+
minBedrooms?: number;
|
|
28
|
+
maxBedrooms?: number;
|
|
29
|
+
propertyStatuses?: string;
|
|
30
|
+
propertyTypes?: string;
|
|
31
|
+
teamName?: string;
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): Promise<string> {
|
|
34
|
+
const {
|
|
35
|
+
accountId, department = "both", searchTerm, minPrice, maxPrice,
|
|
36
|
+
minBedrooms, maxBedrooms, propertyStatuses, propertyTypes,
|
|
37
|
+
teamName, limit,
|
|
38
|
+
} = params;
|
|
39
|
+
|
|
40
|
+
const result = await aggregateAcrossTeams<LoopPropertySummary>(
|
|
41
|
+
accountId,
|
|
42
|
+
"properties",
|
|
43
|
+
"loop-property-search",
|
|
44
|
+
async (apiKey, team) => {
|
|
45
|
+
const depts: string[] =
|
|
46
|
+
department === "both" ? ["sales", "lettings"] : [department];
|
|
47
|
+
const all: LoopPropertySummary[] = [];
|
|
48
|
+
|
|
49
|
+
for (const dept of depts) {
|
|
50
|
+
const qp: string[] = [];
|
|
51
|
+
if (searchTerm) qp.push(`SearchTerm=${encodeURIComponent(searchTerm)}`);
|
|
52
|
+
if (minPrice != null) qp.push(`MinPrice=${minPrice}`);
|
|
53
|
+
if (maxPrice != null) qp.push(`MaxPrice=${maxPrice}`);
|
|
54
|
+
if (minBedrooms != null) qp.push(`MinBedrooms=${minBedrooms}`);
|
|
55
|
+
if (maxBedrooms != null) qp.push(`MaxBedrooms=${maxBedrooms}`);
|
|
56
|
+
if (propertyStatuses) qp.push(`PropertyStatuses=${encodeURIComponent(propertyStatuses)}`);
|
|
57
|
+
if (propertyTypes) qp.push(`PropertyTypes=${encodeURIComponent(propertyTypes)}`);
|
|
58
|
+
|
|
59
|
+
const query = qp.length > 0 ? `?${qp.join("&")}` : "";
|
|
60
|
+
const path = `/property/residential/${dept}${query}`;
|
|
61
|
+
const data = await loopGet<LoopPropertySummary[]>(apiKey, path, "loop-property-search", team);
|
|
62
|
+
if (Array.isArray(data)) all.push(...data);
|
|
63
|
+
}
|
|
64
|
+
return all;
|
|
65
|
+
},
|
|
66
|
+
{ teamName, limitPerTeam: limit ?? 50, limitTotal: limit ?? 200 }
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return formatAggregationResult(
|
|
70
|
+
result,
|
|
71
|
+
(p) => {
|
|
72
|
+
const price = p.price ? ` — £${p.price.toLocaleString("en-GB")}` : "";
|
|
73
|
+
const beds = p.bedrooms ? ` ${p.bedrooms}bed` : "";
|
|
74
|
+
const status = p.status ? ` [${p.status}]` : "";
|
|
75
|
+
const propType = p.type ? ` (${p.type})` : "";
|
|
76
|
+
return `- ${p.address ?? "Unknown address"}${price}${beds}${propType}${status}`;
|
|
77
|
+
},
|
|
78
|
+
"properties"
|
|
79
|
+
);
|
|
80
|
+
}
|
package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/supplier.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { loopGet, loopPost, loopPut, withTeamKey } from "../lib/loop-api.js";
|
|
2
|
+
|
|
3
|
+
type SupplierAction =
|
|
4
|
+
| "maintenance-jobs"
|
|
5
|
+
| "maintenance-complete"
|
|
6
|
+
| "maintenance-quotes"
|
|
7
|
+
| "maintenance-submit-quote"
|
|
8
|
+
| "board-jobs"
|
|
9
|
+
| "board-complete";
|
|
10
|
+
|
|
11
|
+
interface LoopSupplierResult {
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface LoopBooleanResponse {
|
|
16
|
+
success?: boolean;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function supplier(params: {
|
|
21
|
+
accountId: string;
|
|
22
|
+
teamName: string;
|
|
23
|
+
action: SupplierAction;
|
|
24
|
+
code: string;
|
|
25
|
+
jobId?: number;
|
|
26
|
+
// For maintenance-submit-quote
|
|
27
|
+
quoteId?: number;
|
|
28
|
+
quoteData?: Record<string, unknown>;
|
|
29
|
+
// For board-complete
|
|
30
|
+
completionData?: Record<string, unknown>;
|
|
31
|
+
}): Promise<string> {
|
|
32
|
+
const { accountId, teamName, action, code } = params;
|
|
33
|
+
|
|
34
|
+
// jobId is required for all actions except maintenance-submit-quote
|
|
35
|
+
const jobId = params.jobId;
|
|
36
|
+
if (action !== "maintenance-submit-quote" && jobId == null) {
|
|
37
|
+
throw new Error(`jobId is required for action "${action}".`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (action === "maintenance-jobs") {
|
|
41
|
+
const result = await withTeamKey<LoopSupplierResult>(
|
|
42
|
+
accountId, teamName, "supplier", "loop-supplier",
|
|
43
|
+
async (apiKey) => {
|
|
44
|
+
return loopGet<LoopSupplierResult>(
|
|
45
|
+
apiKey, `/supplier/maintenance/${encodeURIComponent(code)}/${jobId}/job-list`,
|
|
46
|
+
"loop-supplier", teamName
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
return JSON.stringify(result, null, 2);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (action === "maintenance-complete") {
|
|
54
|
+
await withTeamKey<LoopBooleanResponse>(
|
|
55
|
+
accountId, teamName, "supplier", "loop-supplier",
|
|
56
|
+
async (apiKey) => {
|
|
57
|
+
return loopPost<LoopBooleanResponse>(
|
|
58
|
+
apiKey, `/supplier/maintenance/${encodeURIComponent(code)}/${jobId}/complete`,
|
|
59
|
+
{}, "loop-supplier", teamName
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
return `Maintenance job ${jobId} marked complete via team "${teamName}".`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (action === "maintenance-quotes") {
|
|
67
|
+
const result = await withTeamKey<LoopSupplierResult>(
|
|
68
|
+
accountId, teamName, "supplier", "loop-supplier",
|
|
69
|
+
async (apiKey) => {
|
|
70
|
+
return loopGet<LoopSupplierResult>(
|
|
71
|
+
apiKey, `/supplier/maintenance/${encodeURIComponent(code)}/${jobId}/quote-list`,
|
|
72
|
+
"loop-supplier", teamName
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
return JSON.stringify(result, null, 2);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (action === "maintenance-submit-quote") {
|
|
80
|
+
if (!params.quoteId) throw new Error("quoteId is required for maintenance-submit-quote.");
|
|
81
|
+
await withTeamKey<LoopBooleanResponse>(
|
|
82
|
+
accountId, teamName, "supplier", "loop-supplier",
|
|
83
|
+
async (apiKey) => {
|
|
84
|
+
return loopPut<LoopBooleanResponse>(
|
|
85
|
+
apiKey, `/supplier/maintenance/${encodeURIComponent(code)}/${params.quoteId}/quote-for-job`,
|
|
86
|
+
params.quoteData ?? {}, "loop-supplier", teamName
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
return `Quote ${params.quoteId} submitted via team "${teamName}".`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (action === "board-jobs") {
|
|
94
|
+
const result = await withTeamKey<LoopSupplierResult>(
|
|
95
|
+
accountId, teamName, "supplier", "loop-supplier",
|
|
96
|
+
async (apiKey) => {
|
|
97
|
+
return loopGet<LoopSupplierResult>(
|
|
98
|
+
apiKey, `/supplier/board-contractor/${encodeURIComponent(code)}/${jobId}/job-list`,
|
|
99
|
+
"loop-supplier", teamName
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
return JSON.stringify(result, null, 2);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (action === "board-complete") {
|
|
107
|
+
await withTeamKey<LoopBooleanResponse>(
|
|
108
|
+
accountId, teamName, "supplier", "loop-supplier",
|
|
109
|
+
async (apiKey) => {
|
|
110
|
+
return loopPost<LoopBooleanResponse>(
|
|
111
|
+
apiKey, `/supplier/board-contractor/${encodeURIComponent(code)}/${jobId}/complete`,
|
|
112
|
+
params.completionData ?? {}, "loop-supplier", teamName
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
return `Board contractor job ${jobId} marked complete via team "${teamName}".`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
throw new Error(`Unknown supplier action: ${action}`);
|
|
120
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateAcrossTeams,
|
|
3
|
+
formatAggregationResult,
|
|
4
|
+
loopGet,
|
|
5
|
+
} from "../lib/loop-api.js";
|
|
6
|
+
|
|
7
|
+
interface LoopTimeRange {
|
|
8
|
+
start?: string;
|
|
9
|
+
end?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function teamAvailability(params: {
|
|
14
|
+
accountId: string;
|
|
15
|
+
agentId: string;
|
|
16
|
+
searchDate: string;
|
|
17
|
+
teamName?: string;
|
|
18
|
+
}): Promise<string> {
|
|
19
|
+
const { accountId, agentId, searchDate, teamName } = params;
|
|
20
|
+
|
|
21
|
+
const result = await aggregateAcrossTeams<LoopTimeRange>(
|
|
22
|
+
accountId,
|
|
23
|
+
"team",
|
|
24
|
+
"loop-team-availability",
|
|
25
|
+
async (apiKey, team) => {
|
|
26
|
+
const path = `/team/${encodeURIComponent(agentId)}/availability/${encodeURIComponent(searchDate)}`;
|
|
27
|
+
const data = await loopGet<LoopTimeRange[]>(apiKey, path, "loop-team-availability", team);
|
|
28
|
+
return Array.isArray(data) ? data : [];
|
|
29
|
+
},
|
|
30
|
+
{ teamName }
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return formatAggregationResult(
|
|
34
|
+
result,
|
|
35
|
+
(slot) => {
|
|
36
|
+
const start = slot.start ?? "?";
|
|
37
|
+
const end = slot.end ?? "?";
|
|
38
|
+
return `- ${start} — ${end}`;
|
|
39
|
+
},
|
|
40
|
+
`availability slots for ${searchDate}`
|
|
41
|
+
);
|
|
42
|
+
}
|
package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/viewing-create.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { loopPost, withTeamKey } from "../lib/loop-api.js";
|
|
2
|
+
|
|
3
|
+
type Department = "sales" | "lettings";
|
|
4
|
+
|
|
5
|
+
interface LoopNumberResponse {
|
|
6
|
+
result?: number;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function viewingCreate(params: {
|
|
11
|
+
accountId: string;
|
|
12
|
+
teamName: string;
|
|
13
|
+
department: Department;
|
|
14
|
+
propertyId: number;
|
|
15
|
+
date: string;
|
|
16
|
+
time: string;
|
|
17
|
+
attendeeName: string;
|
|
18
|
+
attendeeEmail?: string;
|
|
19
|
+
attendeePhone?: string;
|
|
20
|
+
}): Promise<string> {
|
|
21
|
+
const { accountId, teamName, department, ...body } = params;
|
|
22
|
+
|
|
23
|
+
const result = await withTeamKey<LoopNumberResponse>(
|
|
24
|
+
accountId,
|
|
25
|
+
teamName,
|
|
26
|
+
"viewings",
|
|
27
|
+
"loop-viewing-create",
|
|
28
|
+
async (apiKey) => {
|
|
29
|
+
const path = `/residential/${department}/viewings`;
|
|
30
|
+
return loopPost<LoopNumberResponse>(apiKey, path, body, "loop-viewing-create", teamName);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const viewingId = result.result ?? "unknown";
|
|
35
|
+
return `Viewing created (ID: ${viewingId}) for property ${params.propertyId} on ${params.date} at ${params.time} via team "${teamName}".`;
|
|
36
|
+
}
|
package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/viewing-detail.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateAcrossTeams,
|
|
3
|
+
formatAggregationResult,
|
|
4
|
+
loopGet,
|
|
5
|
+
} from "../lib/loop-api.js";
|
|
6
|
+
|
|
7
|
+
interface LoopViewingDetail {
|
|
8
|
+
id?: number;
|
|
9
|
+
propertyAddress?: string;
|
|
10
|
+
date?: string;
|
|
11
|
+
time?: string;
|
|
12
|
+
status?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
attendeeName?: string;
|
|
15
|
+
attendeeEmail?: string;
|
|
16
|
+
attendeePhone?: string;
|
|
17
|
+
notes?: string;
|
|
18
|
+
buyerFeedback?: string;
|
|
19
|
+
sellerFeedback?: string;
|
|
20
|
+
renterFeedback?: string;
|
|
21
|
+
landlordFeedback?: string;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Department = "sales" | "lettings";
|
|
26
|
+
|
|
27
|
+
export async function viewingDetail(params: {
|
|
28
|
+
accountId: string;
|
|
29
|
+
viewingId: number;
|
|
30
|
+
department: Department;
|
|
31
|
+
teamName?: string;
|
|
32
|
+
}): Promise<string> {
|
|
33
|
+
const { accountId, viewingId, department, teamName } = params;
|
|
34
|
+
|
|
35
|
+
const result = await aggregateAcrossTeams<LoopViewingDetail>(
|
|
36
|
+
accountId,
|
|
37
|
+
"viewings",
|
|
38
|
+
"loop-viewing-detail",
|
|
39
|
+
async (apiKey, team) => {
|
|
40
|
+
const path = `/residential/${department}/viewings/${viewingId}`;
|
|
41
|
+
const data = await loopGet<LoopViewingDetail>(apiKey, path, "loop-viewing-detail", team);
|
|
42
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
43
|
+
return [data];
|
|
44
|
+
}
|
|
45
|
+
return [];
|
|
46
|
+
},
|
|
47
|
+
{ teamName }
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return formatAggregationResult(
|
|
51
|
+
result,
|
|
52
|
+
(v) => {
|
|
53
|
+
const lines = [`**${v.propertyAddress ?? "Unknown property"}**`];
|
|
54
|
+
const when = [v.date, v.time].filter(Boolean).join(" ");
|
|
55
|
+
if (when) lines.push(`Date/Time: ${when}`);
|
|
56
|
+
if (v.status) lines.push(`Status: ${v.status}`);
|
|
57
|
+
if (v.type) lines.push(`Type: ${v.type}`);
|
|
58
|
+
if (v.attendeeName) lines.push(`Attendee: ${v.attendeeName}`);
|
|
59
|
+
if (v.attendeeEmail) lines.push(`Email: ${v.attendeeEmail}`);
|
|
60
|
+
if (v.attendeePhone) lines.push(`Phone: ${v.attendeePhone}`);
|
|
61
|
+
if (v.notes) lines.push(`Notes: ${v.notes}`);
|
|
62
|
+
if (v.buyerFeedback) lines.push(`Buyer Feedback: ${v.buyerFeedback}`);
|
|
63
|
+
if (v.sellerFeedback) lines.push(`Seller Feedback: ${v.sellerFeedback}`);
|
|
64
|
+
if (v.renterFeedback) lines.push(`Renter Feedback: ${v.renterFeedback}`);
|
|
65
|
+
if (v.landlordFeedback) lines.push(`Landlord Feedback: ${v.landlordFeedback}`);
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
},
|
|
68
|
+
"viewing details"
|
|
69
|
+
);
|
|
70
|
+
}
|
package/payload/premium-plugins/real-agency/plugins/real-agency-loop/mcp/src/tools/viewing-search.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateAcrossTeams,
|
|
3
|
+
formatAggregationResult,
|
|
4
|
+
loopGet,
|
|
5
|
+
} from "../lib/loop-api.js";
|
|
6
|
+
|
|
7
|
+
interface LoopViewingSummary {
|
|
8
|
+
id?: number;
|
|
9
|
+
propertyAddress?: string;
|
|
10
|
+
date?: string;
|
|
11
|
+
time?: string;
|
|
12
|
+
status?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
attendeeName?: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Department = "sales" | "lettings" | "both";
|
|
19
|
+
|
|
20
|
+
export async function viewingSearch(params: {
|
|
21
|
+
accountId: string;
|
|
22
|
+
department?: Department;
|
|
23
|
+
searchTerm?: string;
|
|
24
|
+
appointmentStartDate?: string;
|
|
25
|
+
appointmentEndDate?: string;
|
|
26
|
+
status?: string;
|
|
27
|
+
teamName?: string;
|
|
28
|
+
limit?: number;
|
|
29
|
+
}): Promise<string> {
|
|
30
|
+
const {
|
|
31
|
+
accountId, department = "both", searchTerm,
|
|
32
|
+
appointmentStartDate, appointmentEndDate, status,
|
|
33
|
+
teamName, limit,
|
|
34
|
+
} = params;
|
|
35
|
+
|
|
36
|
+
const result = await aggregateAcrossTeams<LoopViewingSummary>(
|
|
37
|
+
accountId,
|
|
38
|
+
"viewings",
|
|
39
|
+
"loop-viewing-search",
|
|
40
|
+
async (apiKey, team) => {
|
|
41
|
+
const depts: string[] =
|
|
42
|
+
department === "both" ? ["sales", "lettings"] : [department];
|
|
43
|
+
const all: LoopViewingSummary[] = [];
|
|
44
|
+
|
|
45
|
+
for (const dept of depts) {
|
|
46
|
+
const qp: string[] = [];
|
|
47
|
+
if (searchTerm) qp.push(`SearchTerm=${encodeURIComponent(searchTerm)}`);
|
|
48
|
+
if (appointmentStartDate) qp.push(`AppointmentStartDate=${encodeURIComponent(appointmentStartDate)}`);
|
|
49
|
+
if (appointmentEndDate) qp.push(`AppointmentEndDate=${encodeURIComponent(appointmentEndDate)}`);
|
|
50
|
+
if (status) qp.push(`Status=${encodeURIComponent(status)}`);
|
|
51
|
+
|
|
52
|
+
const query = qp.length > 0 ? `?${qp.join("&")}` : "";
|
|
53
|
+
const path = `/residential/${dept}/viewings${query}`;
|
|
54
|
+
const data = await loopGet<LoopViewingSummary[]>(apiKey, path, "loop-viewing-search", team);
|
|
55
|
+
if (Array.isArray(data)) all.push(...data);
|
|
56
|
+
}
|
|
57
|
+
return all;
|
|
58
|
+
},
|
|
59
|
+
{ teamName, limitPerTeam: limit ?? 50, limitTotal: limit ?? 200 }
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return formatAggregationResult(
|
|
63
|
+
result,
|
|
64
|
+
(v) => {
|
|
65
|
+
const addr = v.propertyAddress ?? "Unknown property";
|
|
66
|
+
const when = [v.date, v.time].filter(Boolean).join(" ");
|
|
67
|
+
const viewStatus = v.status ? ` [${v.status}]` : "";
|
|
68
|
+
const attendee = v.attendeeName ? ` — ${v.attendeeName}` : "";
|
|
69
|
+
const viewType = v.type ? ` (${v.type})` : "";
|
|
70
|
+
return `- ${addr} — ${when || "No date"}${viewType}${attendee}${viewStatus}`;
|
|
71
|
+
},
|
|
72
|
+
"viewings"
|
|
73
|
+
);
|
|
74
|
+
}
|