@rubytech/create-maxy-code 0.1.45 → 0.1.49

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.
Files changed (158) hide show
  1. package/dist/__tests__/premium-mcp-discover.test.js +85 -0
  2. package/dist/index.js +25 -16
  3. package/dist/lib/premium-mcp-discover.js +34 -0
  4. package/package.json +1 -1
  5. package/payload/platform/lib/admins-write/dist/index.d.ts +0 -1
  6. package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -1
  7. package/payload/platform/lib/admins-write/dist/index.js +2 -5
  8. package/payload/platform/lib/admins-write/dist/index.js.map +1 -1
  9. package/payload/platform/lib/admins-write/src/index.ts +2 -8
  10. package/payload/platform/lib/graph-mcp/dist/index.js +1 -4
  11. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
  12. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts +7 -6
  13. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts.map +1 -1
  14. package/payload/platform/lib/graph-mcp/dist/schema-cache.js +7 -6
  15. package/payload/platform/lib/graph-mcp/dist/schema-cache.js.map +1 -1
  16. package/payload/platform/lib/graph-mcp/src/index.ts +1 -4
  17. package/payload/platform/lib/graph-mcp/src/schema-cache.ts +7 -6
  18. package/payload/platform/lib/graph-search/dist/index.d.ts +5 -5
  19. package/payload/platform/lib/graph-search/dist/index.js +4 -4
  20. package/payload/platform/lib/graph-search/src/__tests__/fulltext-coverage.test.ts +6 -7
  21. package/payload/platform/lib/graph-search/src/index.ts +5 -5
  22. package/payload/platform/lib/graph-trash/dist/index.d.ts +8 -12
  23. package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -1
  24. package/payload/platform/lib/graph-trash/dist/index.js +8 -12
  25. package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
  26. package/payload/platform/lib/graph-trash/src/index.ts +8 -12
  27. package/payload/platform/lib/graph-write/dist/index.d.ts +8 -9
  28. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  29. package/payload/platform/lib/graph-write/dist/index.js +9 -10
  30. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  31. package/payload/platform/lib/graph-write/src/index.ts +9 -10
  32. package/payload/platform/lib/mcp-eager/dist/index.d.ts +4 -4
  33. package/payload/platform/lib/mcp-eager/dist/index.js +4 -4
  34. package/payload/platform/lib/mcp-eager/src/index.ts +4 -4
  35. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  36. package/payload/platform/plugins/admin/hooks/__tests__/turn-completed-graph-write.test.sh +56 -38
  37. package/payload/platform/plugins/admin/hooks/turn-completed-graph-write.sh +42 -40
  38. package/payload/platform/plugins/cloudflare/scripts/__tests__/tunnel-ingress.test.ts +2 -2
  39. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +7 -9
  40. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +10 -12
  41. package/payload/platform/plugins/cloudflare/scripts/tunnel-ingress.ts +3 -3
  42. package/payload/platform/plugins/docs/references/deployment.md +2 -14
  43. package/payload/platform/plugins/docs/references/platform.md +2 -2
  44. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  45. package/payload/platform/plugins/docs/references/troubleshooting.md +5 -5
  46. package/payload/platform/plugins/loop/PLUGIN.md +67 -12
  47. package/payload/platform/plugins/loop/mcp/dist/index.js +42 -16
  48. package/payload/platform/plugins/loop/mcp/dist/index.js.map +1 -1
  49. package/payload/platform/plugins/loop/mcp/dist/tools/auto-responder.d.ts +12 -0
  50. package/payload/platform/plugins/loop/mcp/dist/tools/auto-responder.d.ts.map +1 -0
  51. package/payload/platform/plugins/loop/mcp/dist/tools/auto-responder.js +22 -0
  52. package/payload/platform/plugins/loop/mcp/dist/tools/auto-responder.js.map +1 -0
  53. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts +0 -7
  54. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts.map +1 -1
  55. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-enquiry.js +4 -38
  56. package/payload/platform/plugins/loop/mcp/dist/tools/marketing-enquiry.js.map +1 -1
  57. package/payload/platform/plugins/loop/mcp/dist/tools/property-intent.d.ts +33 -0
  58. package/payload/platform/plugins/loop/mcp/dist/tools/property-intent.d.ts.map +1 -0
  59. package/payload/platform/plugins/loop/mcp/dist/tools/property-intent.js +19 -0
  60. package/payload/platform/plugins/loop/mcp/dist/tools/property-intent.js.map +1 -0
  61. package/payload/platform/plugins/loop/mcp/dist/tools/property-listed.d.ts +0 -1
  62. package/payload/platform/plugins/loop/mcp/dist/tools/property-listed.d.ts.map +1 -1
  63. package/payload/platform/plugins/loop/mcp/dist/tools/property-listed.js +1 -7
  64. package/payload/platform/plugins/loop/mcp/dist/tools/property-listed.js.map +1 -1
  65. package/payload/platform/plugins/loop/mcp/dist/tools/property-sold.d.ts +9 -0
  66. package/payload/platform/plugins/loop/mcp/dist/tools/property-sold.d.ts.map +1 -0
  67. package/payload/platform/plugins/loop/mcp/dist/tools/property-sold.js +19 -0
  68. package/payload/platform/plugins/loop/mcp/dist/tools/property-sold.js.map +1 -0
  69. package/payload/platform/plugins/loop/mcp/src/__tests__/new-tool-shape.test.ts +99 -0
  70. package/payload/platform/plugins/loop/mcp/src/__tests__/swagger-write-coverage.test.ts +12 -11
  71. package/payload/platform/plugins/loop/mcp/src/index.ts +68 -17
  72. package/payload/platform/plugins/loop/mcp/src/tools/auto-responder.ts +80 -0
  73. package/payload/platform/plugins/loop/mcp/src/tools/marketing-enquiry.ts +14 -100
  74. package/payload/platform/plugins/loop/mcp/src/tools/property-intent.ts +84 -0
  75. package/payload/platform/plugins/loop/mcp/src/tools/property-listed.ts +1 -8
  76. package/payload/platform/plugins/loop/mcp/src/tools/property-sold.ts +67 -0
  77. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +4 -3
  78. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +1 -1
  79. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -1
  80. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +7 -12
  81. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -1
  82. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-not-applicable.test.js +2 -2
  83. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js +4 -4
  84. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js.map +1 -1
  85. package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.d.ts.map +1 -1
  86. package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.js +8 -9
  87. package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.js.map +1 -1
  88. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.d.ts +2 -2
  89. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.js +2 -2
  90. package/payload/platform/scripts/admin-persist-audit.ts +4 -10
  91. package/payload/platform/scripts/conversation-id-allowlist.txt +0 -1
  92. package/payload/platform/scripts/lib/resolve-account-dir.sh +0 -2
  93. package/payload/platform/scripts/resume-tunnel.sh +2 -9
  94. package/payload/platform/scripts/seed-neo4j.sh +26 -104
  95. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  96. package/payload/platform/services/claude-session-manager/dist/http-server.js +53 -7
  97. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  98. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +0 -5
  99. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  100. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +13 -17
  101. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  102. package/payload/platform/templates/agents/admin/IDENTITY.md +10 -2
  103. package/payload/platform/templates/agents/admin/SOUL.md +23 -1
  104. package/payload/premium-plugins/real-agent/agents/buyer-enquiry/SOUL.md +10 -0
  105. package/payload/premium-plugins/real-agent/agents/compliance.md +1 -1
  106. package/payload/premium-plugins/real-agent/agents/negotiator.md +1 -1
  107. package/payload/premium-plugins/real-agent/agents/valuer.md +1 -1
  108. package/payload/premium-plugins/real-agent/plugins/loop/PLUGIN.md +67 -12
  109. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/index.js +42 -16
  110. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/index.js.map +1 -1
  111. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/auto-responder.d.ts +12 -0
  112. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/auto-responder.d.ts.map +1 -0
  113. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/auto-responder.js +22 -0
  114. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/auto-responder.js.map +1 -0
  115. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts +0 -7
  116. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts.map +1 -1
  117. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/marketing-enquiry.js +4 -38
  118. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/marketing-enquiry.js.map +1 -1
  119. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-intent.d.ts +33 -0
  120. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-intent.d.ts.map +1 -0
  121. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-intent.js +19 -0
  122. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-intent.js.map +1 -0
  123. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-listed.d.ts +0 -1
  124. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-listed.d.ts.map +1 -1
  125. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-listed.js +1 -7
  126. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-listed.js.map +1 -1
  127. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-sold.d.ts +9 -0
  128. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-sold.d.ts.map +1 -0
  129. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-sold.js +19 -0
  130. package/payload/premium-plugins/real-agent/plugins/loop/mcp/dist/tools/property-sold.js.map +1 -0
  131. package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/__tests__/new-tool-shape.test.ts +99 -0
  132. package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/__tests__/swagger-write-coverage.test.ts +12 -11
  133. package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/index.ts +68 -17
  134. package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/tools/auto-responder.ts +80 -0
  135. package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/tools/marketing-enquiry.ts +14 -100
  136. package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/tools/property-intent.ts +84 -0
  137. package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/tools/property-listed.ts +1 -8
  138. package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/tools/property-sold.ts +67 -0
  139. package/payload/server/public/assets/admin-DBuCWSPS.js +216 -0
  140. package/payload/server/public/assets/{data-BxpOR0eM.js → data-BeblX4m4.js} +1 -1
  141. package/payload/server/public/assets/{graph-CrmxRSeb.js → graph-ZuBiYril.js} +1 -1
  142. package/payload/server/public/assets/graph-labels-CnbJR6cp.js +1 -0
  143. package/payload/server/public/assets/page-DDyny08O.js +50 -0
  144. package/payload/server/public/assets/page-nAY64ID7.js +1 -0
  145. package/payload/server/public/data.html +3 -3
  146. package/payload/server/public/graph.html +3 -3
  147. package/payload/server/public/index.html +4 -4
  148. package/payload/server/server.js +9 -46
  149. package/payload/platform/plugins/docs/references/migration-guide.md +0 -90
  150. package/payload/platform/plugins/loop/mcp/src/tools/property-request.ts +0 -42
  151. package/payload/platform/scripts/component-knowledgedoc-backfill.ts +0 -214
  152. package/payload/platform/scripts/migrate-import.sh +0 -437
  153. package/payload/platform/scripts/taskmaster-export.sh +0 -388
  154. package/payload/premium-plugins/real-agent/plugins/loop/mcp/src/tools/property-request.ts +0 -42
  155. package/payload/server/public/assets/admin-DVZcLuYu.js +0 -216
  156. package/payload/server/public/assets/graph-labels-C8A6BmtP.js +0 -1
  157. package/payload/server/public/assets/page-D5wAeFU0.js +0 -50
  158. package/payload/server/public/assets/page-DR5NU6S0.js +0 -1
@@ -1,113 +1,27 @@
1
- import { loopGet, loopPost, loopPut, withTeamKey } from "../lib/loop-api.js";
1
+ import { loopPost, withTeamKey } from "../lib/loop-api.js";
2
2
 
3
- type EnquiryAction = "seller-enquiry" | "autoresponder-get" | "autoresponder-answers" | "autoresponder-details" | "autoresponder-refer";
3
+ // Loop API V2: POST /marketing/enquiries/seller
4
+ // Submit a seller-side marketing enquiry. Auto-responder endpoints used to
5
+ // live in this tool but were split into `loop-auto-responder` for intent
6
+ // fidelity (Task 103).
4
7
 
5
- // ─── Loop API V2: Marketing Enquiry Endpoints ──────────────────
6
- // POST /marketing/enquiries/seller — submit seller enquiry
7
- // GET /marketing/enquiries/auto-responder/{id}/{key} — read enquiry
8
- // PUT .../auto-responder/{id}/answers/{key} — submit answers
9
- // PUT .../auto-responder/{id}/details/{key} — update details
10
- // PUT .../auto-responder/{id}/refer/{key} — refer enquiry
11
- // Note: auto-responder uses personCode + key (UUID), not personId
12
- // ────────────────────────────────────────────────────────────────
13
8
  interface LoopBooleanResponse {
14
9
  success?: boolean;
15
10
  [key: string]: unknown;
16
11
  }
17
12
 
18
- interface LoopAutoResponderDetails {
19
- id?: number;
20
- questions?: unknown[];
21
- enquiryDetails?: unknown;
22
- [key: string]: unknown;
23
- }
24
-
25
13
  export async function marketingEnquiry(params: {
26
14
  accountId: string;
27
15
  teamName: string;
28
- action: EnquiryAction;
29
- // For seller-enquiry
30
16
  sellerEnquiryData?: Record<string, unknown>;
31
- // For autoresponder actions
32
- autoResponderId?: number;
33
- autoResponderKey?: string;
34
- // For autoresponder-answers
35
- answers?: unknown[];
36
- // For autoresponder-details
37
- details?: Record<string, unknown>;
38
17
  }): Promise<string> {
39
- const { accountId, teamName, action } = params;
40
-
41
- if (action === "seller-enquiry") {
42
- await withTeamKey<LoopBooleanResponse>(
43
- accountId, teamName, "marketing", "loop-marketing-enquiry",
44
- async (apiKey) => {
45
- return loopPost<LoopBooleanResponse>(
46
- apiKey, "/marketing/enquiries/seller",
47
- params.sellerEnquiryData ?? {}, "loop-marketing-enquiry", teamName
48
- );
49
- }
50
- );
51
- return `Seller enquiry submitted via team "${teamName}".`;
52
- }
53
-
54
- if (!params.autoResponderId || !params.autoResponderKey) {
55
- throw new Error("autoResponderId and autoResponderKey are required for autoresponder actions.");
56
- }
57
- const id = params.autoResponderId;
58
- const key = params.autoResponderKey;
59
-
60
- if (action === "autoresponder-get") {
61
- const result = await withTeamKey<LoopAutoResponderDetails>(
62
- accountId, teamName, "marketing", "loop-marketing-enquiry",
63
- async (apiKey) => {
64
- return loopGet<LoopAutoResponderDetails>(
65
- apiKey, `/marketing/enquiries/auto-responder/${id}/${key}`,
66
- "loop-marketing-enquiry", teamName
67
- );
68
- }
69
- );
70
- return JSON.stringify(result, null, 2);
71
- }
72
-
73
- if (action === "autoresponder-answers") {
74
- await withTeamKey<LoopBooleanResponse>(
75
- accountId, teamName, "marketing", "loop-marketing-enquiry",
76
- async (apiKey) => {
77
- return loopPut<LoopBooleanResponse>(
78
- apiKey, `/marketing/enquiries/auto-responder/${id}/answers/${key}`,
79
- params.answers ?? [], "loop-marketing-enquiry", teamName
80
- );
81
- }
82
- );
83
- return `Auto-responder answers submitted for enquiry ${id} via team "${teamName}".`;
84
- }
85
-
86
- if (action === "autoresponder-details") {
87
- await withTeamKey<LoopBooleanResponse>(
88
- accountId, teamName, "marketing", "loop-marketing-enquiry",
89
- async (apiKey) => {
90
- return loopPut<LoopBooleanResponse>(
91
- apiKey, `/marketing/enquiries/auto-responder/${id}/details/${key}`,
92
- params.details ?? {}, "loop-marketing-enquiry", teamName
93
- );
94
- }
95
- );
96
- return `Auto-responder details updated for enquiry ${id} via team "${teamName}".`;
97
- }
98
-
99
- if (action === "autoresponder-refer") {
100
- await withTeamKey<LoopBooleanResponse>(
101
- accountId, teamName, "marketing", "loop-marketing-enquiry",
102
- async (apiKey) => {
103
- return loopPut<LoopBooleanResponse>(
104
- apiKey, `/marketing/enquiries/auto-responder/${id}/refer/${key}`,
105
- {}, "loop-marketing-enquiry", teamName
106
- );
107
- }
108
- );
109
- return `Enquiry ${id} referred via team "${teamName}".`;
110
- }
111
-
112
- throw new Error(`Unknown action: ${action}`);
18
+ const { accountId, teamName, sellerEnquiryData } = params;
19
+ await withTeamKey<LoopBooleanResponse>(
20
+ accountId, teamName, "marketing", "loop-marketing-enquiry",
21
+ async (apiKey) => loopPost<LoopBooleanResponse>(
22
+ apiKey, "/marketing/enquiries/seller",
23
+ sellerEnquiryData ?? {}, "loop-marketing-enquiry", teamName
24
+ )
25
+ );
26
+ return `Seller enquiry submitted via team "${teamName}".`;
113
27
  }
@@ -0,0 +1,84 @@
1
+ import { loopPost, withTeamKey } from "../lib/loop-api.js";
2
+
3
+ type Department = "sales" | "lettings";
4
+
5
+ // Loop API V2: distinct operator-intent endpoints (post-Task 103 split).
6
+ // POST /property/residential/{dept}/{id}/viewing — request a viewing
7
+ // POST /property/residential/{dept}/{id}/call-back — request a callback
8
+ // POST /property/residential/{dept}/{id}/information — request more information
9
+ // Each endpoint takes the same {name?, email?, phone?, message?} body but is
10
+ // surfaced as a separate tool so operator intent is preserved on the agent's
11
+ // tool-call surface (replaces the prior bundled loop-property-request).
12
+
13
+ interface LoopBooleanResponse {
14
+ success?: boolean;
15
+ [key: string]: unknown;
16
+ }
17
+
18
+ async function submitIntent(
19
+ toolName: string,
20
+ action: "viewing" | "call-back" | "information",
21
+ params: {
22
+ accountId: string;
23
+ teamName: string;
24
+ propertyId: number;
25
+ department: Department;
26
+ name?: string;
27
+ email?: string;
28
+ phone?: string;
29
+ message?: string;
30
+ },
31
+ successLabel: string
32
+ ): Promise<string> {
33
+ const { accountId, teamName, propertyId, department, ...body } = params;
34
+ await withTeamKey<LoopBooleanResponse>(
35
+ accountId,
36
+ teamName,
37
+ "properties",
38
+ toolName,
39
+ async (apiKey) => {
40
+ const path = `/property/residential/${department}/${propertyId}/${action}`;
41
+ return loopPost<LoopBooleanResponse>(apiKey, path, body, toolName, teamName);
42
+ }
43
+ );
44
+ return `${successLabel} submitted for property ${propertyId} (${department}) via team "${teamName}".`;
45
+ }
46
+
47
+ export async function propertyViewing(params: {
48
+ accountId: string;
49
+ teamName: string;
50
+ propertyId: number;
51
+ department: Department;
52
+ name?: string;
53
+ email?: string;
54
+ phone?: string;
55
+ message?: string;
56
+ }): Promise<string> {
57
+ return submitIntent("loop-property-viewing", "viewing", params, "Viewing request");
58
+ }
59
+
60
+ export async function propertyCallback(params: {
61
+ accountId: string;
62
+ teamName: string;
63
+ propertyId: number;
64
+ department: Department;
65
+ name?: string;
66
+ email?: string;
67
+ phone?: string;
68
+ message?: string;
69
+ }): Promise<string> {
70
+ return submitIntent("loop-property-callback", "call-back", params, "Callback request");
71
+ }
72
+
73
+ export async function propertyInformation(params: {
74
+ accountId: string;
75
+ teamName: string;
76
+ propertyId: number;
77
+ department: Department;
78
+ name?: string;
79
+ email?: string;
80
+ phone?: string;
81
+ message?: string;
82
+ }): Promise<string> {
83
+ return submitIntent("loop-property-information", "information", params, "Information request");
84
+ }
@@ -39,11 +39,10 @@ export async function propertyListed(params: {
39
39
  accountId: string;
40
40
  channel: Channel;
41
41
  department?: Department;
42
- includeSold?: boolean;
43
42
  teamName?: string;
44
43
  limit?: number;
45
44
  }): Promise<string> {
46
- const { accountId, channel, department = "both", includeSold = false, teamName, limit } = params;
45
+ const { accountId, channel, department = "both", teamName, limit } = params;
47
46
 
48
47
  const result = await aggregateAcrossTeams<LoopPropertyListing>(
49
48
  accountId,
@@ -60,12 +59,6 @@ export async function propertyListed(params: {
60
59
  if (Array.isArray(data)) all.push(...data);
61
60
  }
62
61
 
63
- if (includeSold) {
64
- const soldPath = `/property/residential/sold/${channel}`;
65
- const soldData = await loopGet<LoopPropertyListing[]>(apiKey, soldPath, "loop-property-listed", team);
66
- if (Array.isArray(soldData)) all.push(...soldData);
67
- }
68
-
69
62
  return all;
70
63
  },
71
64
  { teamName, limitPerTeam: limit ?? 50, limitTotal: limit ?? 200 }
@@ -0,0 +1,67 @@
1
+ import {
2
+ aggregateAcrossTeams,
3
+ formatAggregationResult,
4
+ loopGet,
5
+ } from "../lib/loop-api.js";
6
+
7
+ // Loop API V2: GET /property/residential/sold/{channel}
8
+ // Distinct from /listed/{channel} — returns exchanged or completed properties
9
+ // (Loop swagger summary: "USE THIS TO POWER WEBSITE SOLD GALLERIES").
10
+ // Use case: canvassing letters built from recent sales in a target postcode.
11
+
12
+ interface LoopPropertyListing {
13
+ listingId?: number;
14
+ propertyId?: number;
15
+ propertyRefId?: string;
16
+ channel?: string;
17
+ propertyAddress?: string;
18
+ displayAddress?: string;
19
+ status?: string;
20
+ propertyType?: string;
21
+ dateLaunched?: string;
22
+ price?: number;
23
+ bedrooms?: number;
24
+ bathrooms?: number;
25
+ receptionRooms?: number;
26
+ shortDescription?: string;
27
+ features?: string[];
28
+ images?: { url: string }[];
29
+ [key: string]: unknown;
30
+ }
31
+
32
+ type Channel = "rightmove" | "onTheMarket" | "zoopla" | "website";
33
+
34
+ export async function propertySold(params: {
35
+ accountId: string;
36
+ channel: Channel;
37
+ teamName?: string;
38
+ limit?: number;
39
+ }): Promise<string> {
40
+ const { accountId, channel, teamName, limit } = params;
41
+
42
+ const result = await aggregateAcrossTeams<LoopPropertyListing>(
43
+ accountId,
44
+ "properties",
45
+ "loop-property-sold",
46
+ async (apiKey, team) => {
47
+ const path = `/property/residential/sold/${channel}`;
48
+ const data = await loopGet<LoopPropertyListing[]>(apiKey, path, "loop-property-sold", team);
49
+ return Array.isArray(data) ? data : [];
50
+ },
51
+ { teamName, limitPerTeam: limit ?? 50, limitTotal: limit ?? 200 }
52
+ );
53
+
54
+ return formatAggregationResult(
55
+ result,
56
+ (p) => {
57
+ const addr = p.displayAddress ?? p.propertyAddress ?? "Unknown address";
58
+ const id = p.propertyId != null ? ` [ID: ${p.propertyId}]` : "";
59
+ const price = p.price ? ` — £${p.price.toLocaleString("en-GB")}` : "";
60
+ const beds = p.bedrooms ? ` ${p.bedrooms}bed` : "";
61
+ const propType = p.propertyType ? ` (${p.propertyType})` : "";
62
+ const status = p.status ? ` [${p.status}]` : "";
63
+ return `- ${addr}${id}${price}${beds}${propType}${status}`;
64
+ },
65
+ `${channel} sold`
66
+ );
67
+ }