@framers/agentos-ext-trulia-search 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/manifest.json +4 -4
- package/package.json +19 -7
- package/src/TruliaSearchTool.ts +0 -135
- package/src/index.ts +0 -20
- package/src/types.ts +0 -36
- package/test/TruliaSearchTool.spec.ts +0 -62
- package/tsconfig.json +0 -17
package/README.md
CHANGED
package/manifest.json
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"$schema": "https://agentos.sh/schemas/extension-manifest-v1.json",
|
|
3
3
|
"id": "com.framers.tools.trulia-search",
|
|
4
4
|
"name": "Trulia Property Search",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.2",
|
|
6
6
|
"description": "Search real estate properties on Trulia with RapidAPI or Firecrawl scrape fallback.",
|
|
7
|
-
"author": { "name": "
|
|
8
|
-
"license": "
|
|
7
|
+
"author": { "name": "Frame", "url": "https://frame.dev" },
|
|
8
|
+
"license": "Apache-2.0",
|
|
9
9
|
"keywords": ["trulia", "real-estate", "property", "search", "housing"],
|
|
10
|
-
"agentosVersion": "
|
|
10
|
+
"agentosVersion": ">=0.10.40",
|
|
11
11
|
"categories": ["tool", "research"],
|
|
12
12
|
"platforms": [],
|
|
13
13
|
"sdk": "none",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@framers/agentos-ext-trulia-search",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Search real estate properties on Trulia by location, price, bedrooms, and property type",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@framers/agentos": "
|
|
17
|
+
"@framers/agentos": ">=0.10.40"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"agentos",
|
|
@@ -24,22 +24,34 @@
|
|
|
24
24
|
"real-estate",
|
|
25
25
|
"property-search"
|
|
26
26
|
],
|
|
27
|
-
"license": "
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
33
|
-
"url": "git+https://github.com/
|
|
33
|
+
"url": "git+https://github.com/framerslab/agentos-extensions.git",
|
|
34
34
|
"directory": "registry/curated/research/trulia-search"
|
|
35
35
|
},
|
|
36
|
-
"homepage": "https://github.com/
|
|
36
|
+
"homepage": "https://github.com/framerslab/agentos-extensions/tree/master/registry/curated/research/trulia-search#readme",
|
|
37
37
|
"bugs": {
|
|
38
|
-
"url": "https://github.com/
|
|
38
|
+
"url": "https://github.com/framerslab/agentos-extensions/issues"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"manifest.json",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@framers/agentos": "^0.13.40",
|
|
47
|
+
"@types/node": "^20.12.12",
|
|
48
|
+
"rimraf": "^5.0.7",
|
|
49
|
+
"typescript": "^5.4.5",
|
|
50
|
+
"vitest": "^3.2.7"
|
|
39
51
|
},
|
|
40
52
|
"scripts": {
|
|
41
53
|
"build": "tsc",
|
|
42
|
-
"test": "vitest run",
|
|
54
|
+
"test": "vitest run --passWithNoTests",
|
|
43
55
|
"clean": "rimraf dist"
|
|
44
56
|
}
|
|
45
57
|
}
|
package/src/TruliaSearchTool.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Trulia property search tool.
|
|
4
|
-
*
|
|
5
|
-
* Uses RapidAPI Trulia endpoint when available, falls back to
|
|
6
|
-
* Firecrawl scrape of trulia.com search results.
|
|
7
|
-
*
|
|
8
|
-
* @module agentos-ext-trulia-search/TruliaSearchTool
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { TruliaSearchInput, TruliaSearchOutput, TruliaListing } from './types.js';
|
|
12
|
-
|
|
13
|
-
/** ITool implementation for Trulia real estate search. */
|
|
14
|
-
export class TruliaSearchTool {
|
|
15
|
-
readonly id = 'trulia_search';
|
|
16
|
-
readonly name = 'trulia_search';
|
|
17
|
-
readonly displayName = 'Trulia Property Search';
|
|
18
|
-
readonly description = 'Search for real estate properties by location, price, bedrooms, and property type.';
|
|
19
|
-
readonly parameters = {
|
|
20
|
-
type: 'object' as const,
|
|
21
|
-
properties: {
|
|
22
|
-
location: { type: 'string', description: 'City, state or zip code (e.g., "Austin, TX")' },
|
|
23
|
-
propertyType: { type: 'string', enum: ['house', 'apartment', 'condo', 'townhouse', 'land'], description: 'Property type filter' },
|
|
24
|
-
minPrice: { type: 'number', description: 'Minimum price' },
|
|
25
|
-
maxPrice: { type: 'number', description: 'Maximum price' },
|
|
26
|
-
bedrooms: { type: 'number', description: 'Minimum bedrooms' },
|
|
27
|
-
bathrooms: { type: 'number', description: 'Minimum bathrooms' },
|
|
28
|
-
maxResults: { type: 'number', description: 'Maximum results (default: 20)' },
|
|
29
|
-
},
|
|
30
|
-
required: ['location'],
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
private truliaRapidApiKey?: string;
|
|
34
|
-
private firecrawlApiKey?: string;
|
|
35
|
-
|
|
36
|
-
constructor(config?: { truliaRapidApiKey?: string; firecrawlApiKey?: string }) {
|
|
37
|
-
this.truliaRapidApiKey = config?.truliaRapidApiKey ?? process.env.TRULIA_RAPIDAPI_KEY;
|
|
38
|
-
this.firecrawlApiKey = config?.firecrawlApiKey ?? process.env.FIRECRAWL_API_KEY;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async execute(input: TruliaSearchInput): Promise<TruliaSearchOutput> {
|
|
42
|
-
const maxResults = input.maxResults ?? 20;
|
|
43
|
-
|
|
44
|
-
if (this.truliaRapidApiKey) {
|
|
45
|
-
return this.searchViaRapidApi(input, maxResults);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (this.firecrawlApiKey) {
|
|
49
|
-
return this.searchViaFirecrawlScrape(input, maxResults);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return { listings: [], totalResults: 0, location: input.location };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Search via RapidAPI Trulia endpoint. */
|
|
56
|
-
private async searchViaRapidApi(input: TruliaSearchInput, maxResults: number): Promise<TruliaSearchOutput> {
|
|
57
|
-
const params = new URLSearchParams({ location: input.location });
|
|
58
|
-
if (input.propertyType) params.set('type', input.propertyType);
|
|
59
|
-
if (input.minPrice) params.set('minPrice', String(input.minPrice));
|
|
60
|
-
if (input.maxPrice) params.set('maxPrice', String(input.maxPrice));
|
|
61
|
-
if (input.bedrooms) params.set('beds', String(input.bedrooms));
|
|
62
|
-
if (input.bathrooms) params.set('baths', String(input.bathrooms));
|
|
63
|
-
params.set('limit', String(maxResults));
|
|
64
|
-
|
|
65
|
-
const res = await fetch(`https://trulia-com4.p.rapidapi.com/properties/search?${params}`, {
|
|
66
|
-
headers: {
|
|
67
|
-
'x-rapidapi-key': this.truliaRapidApiKey!,
|
|
68
|
-
'x-rapidapi-host': 'trulia-com4.p.rapidapi.com',
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
if (!res.ok) throw new Error(`Trulia RapidAPI error: ${res.status}`);
|
|
73
|
-
|
|
74
|
-
const data = (await res.json()) as {
|
|
75
|
-
data?: Array<{
|
|
76
|
-
address?: string; price?: number; bedrooms?: number; bathrooms?: number;
|
|
77
|
-
sqft?: number; url?: string; photo?: string; listedDate?: string; propertyType?: string;
|
|
78
|
-
}>;
|
|
79
|
-
total?: number;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const listings: TruliaListing[] = (data.data ?? []).map((p) => ({
|
|
83
|
-
address: p.address ?? '',
|
|
84
|
-
price: p.price ?? 0,
|
|
85
|
-
beds: p.bedrooms ?? 0,
|
|
86
|
-
baths: p.bathrooms ?? 0,
|
|
87
|
-
sqft: p.sqft ?? 0,
|
|
88
|
-
url: p.url ?? '',
|
|
89
|
-
imageUrl: p.photo,
|
|
90
|
-
listingDate: p.listedDate ?? '',
|
|
91
|
-
propertyType: p.propertyType ?? 'house',
|
|
92
|
-
}));
|
|
93
|
-
|
|
94
|
-
return { listings, totalResults: data.total ?? listings.length, location: input.location };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Fallback: scrape trulia.com via Firecrawl. */
|
|
98
|
-
private async searchViaFirecrawlScrape(input: TruliaSearchInput, maxResults: number): Promise<TruliaSearchOutput> {
|
|
99
|
-
const locationSlug = input.location.toLowerCase().replace(/[,\s]+/g, '-').replace(/-+/g, '-');
|
|
100
|
-
const url = `https://www.trulia.com/${locationSlug}/`;
|
|
101
|
-
|
|
102
|
-
const res = await fetch('https://api.firecrawl.dev/v1/scrape', {
|
|
103
|
-
method: 'POST',
|
|
104
|
-
headers: {
|
|
105
|
-
'Content-Type': 'application/json',
|
|
106
|
-
'Authorization': `Bearer ${this.firecrawlApiKey}`,
|
|
107
|
-
},
|
|
108
|
-
body: JSON.stringify({ url, formats: ['markdown'] }),
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (!res.ok) return { listings: [], totalResults: 0, location: input.location };
|
|
112
|
-
|
|
113
|
-
const data = (await res.json()) as { data?: { markdown?: string } };
|
|
114
|
-
const markdown = data.data?.markdown ?? '';
|
|
115
|
-
|
|
116
|
-
const listings: TruliaListing[] = [];
|
|
117
|
-
const pricePattern = /\$[\d,]+/g;
|
|
118
|
-
const prices = markdown.match(pricePattern) ?? [];
|
|
119
|
-
|
|
120
|
-
for (let i = 0; i < Math.min(prices.length, maxResults); i++) {
|
|
121
|
-
listings.push({
|
|
122
|
-
address: `Listing ${i + 1} in ${input.location}`,
|
|
123
|
-
price: parseInt(prices[i].replace(/[$,]/g, ''), 10),
|
|
124
|
-
beds: input.bedrooms ?? 0,
|
|
125
|
-
baths: input.bathrooms ?? 0,
|
|
126
|
-
sqft: 0,
|
|
127
|
-
url,
|
|
128
|
-
listingDate: new Date().toISOString(),
|
|
129
|
-
propertyType: input.propertyType ?? 'house',
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return { listings, totalResults: listings.length, location: input.location };
|
|
134
|
-
}
|
|
135
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Extension pack factory for Trulia property search.
|
|
4
|
-
* @module agentos-ext-trulia-search
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { TruliaSearchTool } from './TruliaSearchTool.js';
|
|
8
|
-
|
|
9
|
-
export { TruliaSearchTool } from './TruliaSearchTool.js';
|
|
10
|
-
export type { TruliaSearchInput, TruliaSearchOutput, TruliaListing } from './types.js';
|
|
11
|
-
|
|
12
|
-
/** Create the Trulia search extension pack. */
|
|
13
|
-
export function createExtensionPack(ctx?: { config?: { truliaRapidApiKey?: string; firecrawlApiKey?: string } }) {
|
|
14
|
-
const tool = new TruliaSearchTool(ctx?.config);
|
|
15
|
-
return {
|
|
16
|
-
name: '@framers/agentos-ext-trulia-search',
|
|
17
|
-
version: '0.1.0',
|
|
18
|
-
descriptors: [{ id: 'trulia_search', kind: 'tool' as const, priority: 50, payload: tool }],
|
|
19
|
-
};
|
|
20
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Types for the Trulia real estate search extension.
|
|
4
|
-
* @module agentos-ext-trulia-search/types
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/** Input for trulia_search tool. */
|
|
8
|
-
export interface TruliaSearchInput {
|
|
9
|
-
location: string;
|
|
10
|
-
propertyType?: 'house' | 'apartment' | 'condo' | 'townhouse' | 'land';
|
|
11
|
-
minPrice?: number;
|
|
12
|
-
maxPrice?: number;
|
|
13
|
-
bedrooms?: number;
|
|
14
|
-
bathrooms?: number;
|
|
15
|
-
maxResults?: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** A single property listing. */
|
|
19
|
-
export interface TruliaListing {
|
|
20
|
-
address: string;
|
|
21
|
-
price: number;
|
|
22
|
-
beds: number;
|
|
23
|
-
baths: number;
|
|
24
|
-
sqft: number;
|
|
25
|
-
url: string;
|
|
26
|
-
imageUrl?: string;
|
|
27
|
-
listingDate: string;
|
|
28
|
-
propertyType: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Output of trulia_search. */
|
|
32
|
-
export interface TruliaSearchOutput {
|
|
33
|
-
listings: TruliaListing[];
|
|
34
|
-
totalResults: number;
|
|
35
|
-
location: string;
|
|
36
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
-
import { TruliaSearchTool } from '../src/TruliaSearchTool.js';
|
|
4
|
-
|
|
5
|
-
describe('TruliaSearchTool', () => {
|
|
6
|
-
const originalFetch = globalThis.fetch;
|
|
7
|
-
|
|
8
|
-
afterEach(() => {
|
|
9
|
-
globalThis.fetch = originalFetch;
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('has correct tool metadata', () => {
|
|
13
|
-
const tool = new TruliaSearchTool();
|
|
14
|
-
expect(tool.id).toBe('trulia_search');
|
|
15
|
-
expect(tool.name).toBe('trulia_search');
|
|
16
|
-
expect(tool.parameters.required).toContain('location');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('returns empty listings when no API keys configured', async () => {
|
|
20
|
-
const tool = new TruliaSearchTool({ truliaRapidApiKey: undefined, firecrawlApiKey: undefined });
|
|
21
|
-
const result = await tool.execute({ location: 'Austin, TX' });
|
|
22
|
-
expect(result.listings).toEqual([]);
|
|
23
|
-
expect(result.location).toBe('Austin, TX');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('searches via RapidAPI when key is provided', async () => {
|
|
27
|
-
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
28
|
-
ok: true,
|
|
29
|
-
json: () => Promise.resolve({
|
|
30
|
-
data: [
|
|
31
|
-
{ address: '123 Main St', price: 350000, bedrooms: 3, bathrooms: 2, sqft: 1500, url: 'https://trulia.com/p/1', propertyType: 'house' },
|
|
32
|
-
{ address: '456 Oak Ave', price: 420000, bedrooms: 4, bathrooms: 3, sqft: 2200, url: 'https://trulia.com/p/2', propertyType: 'house' },
|
|
33
|
-
],
|
|
34
|
-
total: 2,
|
|
35
|
-
}),
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const tool = new TruliaSearchTool({ truliaRapidApiKey: 'test-key' });
|
|
39
|
-
const result = await tool.execute({ location: 'Austin, TX', bedrooms: 3 });
|
|
40
|
-
|
|
41
|
-
expect(result.listings.length).toBe(2);
|
|
42
|
-
expect(result.listings[0].address).toBe('123 Main St');
|
|
43
|
-
expect(result.listings[0].price).toBe(350000);
|
|
44
|
-
expect(result.totalResults).toBe(2);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('falls back to Firecrawl scrape when no RapidAPI key', async () => {
|
|
48
|
-
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
49
|
-
ok: true,
|
|
50
|
-
json: () => Promise.resolve({
|
|
51
|
-
data: { markdown: 'Property 1 $250,000 3 bed\nProperty 2 $350,000 4 bed\nProperty 3 $450,000 5 bed' },
|
|
52
|
-
}),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const tool = new TruliaSearchTool({ firecrawlApiKey: 'fc-key' });
|
|
56
|
-
const result = await tool.execute({ location: 'Austin, TX', maxResults: 2 });
|
|
57
|
-
|
|
58
|
-
expect(result.listings.length).toBe(2);
|
|
59
|
-
expect(result.listings[0].price).toBe(250000);
|
|
60
|
-
expect(result.listings[1].price).toBe(350000);
|
|
61
|
-
});
|
|
62
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"declarationMap": true,
|
|
8
|
-
"sourceMap": true,
|
|
9
|
-
"outDir": "dist",
|
|
10
|
-
"rootDir": "src",
|
|
11
|
-
"strict": true,
|
|
12
|
-
"esModuleInterop": true,
|
|
13
|
-
"skipLibCheck": true
|
|
14
|
-
},
|
|
15
|
-
"include": ["src"],
|
|
16
|
-
"exclude": ["dist", "test", "node_modules"]
|
|
17
|
-
}
|