@riverintel/stayfinder-plugin 0.2.0
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/LICENSE +201 -0
- package/README.md +94 -0
- package/dist/adapter-client.d.ts +68 -0
- package/dist/adapter-client.d.ts.map +1 -0
- package/dist/adapter-client.js +149 -0
- package/dist/adapter-client.js.map +1 -0
- package/dist/credential-store.d.ts +71 -0
- package/dist/credential-store.d.ts.map +1 -0
- package/dist/credential-store.js +143 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/errors.d.ts +55 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +160 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-config.d.ts +37 -0
- package/dist/plugin-config.d.ts.map +1 -0
- package/dist/plugin-config.js +63 -0
- package/dist/plugin-config.js.map +1 -0
- package/dist/thumbnails.d.ts +31 -0
- package/dist/thumbnails.d.ts.map +1 -0
- package/dist/thumbnails.js +32 -0
- package/dist/thumbnails.js.map +1 -0
- package/dist/tool-result.d.ts +19 -0
- package/dist/tool-result.d.ts.map +1 -0
- package/dist/tool-result.js +18 -0
- package/dist/tool-result.js.map +1 -0
- package/dist/tools/search-stays.d.ts +45 -0
- package/dist/tools/search-stays.d.ts.map +1 -0
- package/dist/tools/search-stays.js +191 -0
- package/dist/tools/search-stays.js.map +1 -0
- package/dist/tools/stayfinder-signup.d.ts +38 -0
- package/dist/tools/stayfinder-signup.d.ts.map +1 -0
- package/dist/tools/stayfinder-signup.js +102 -0
- package/dist/tools/stayfinder-signup.js.map +1 -0
- package/dist/tools/stayfinder-verify.d.ts +26 -0
- package/dist/tools/stayfinder-verify.d.ts.map +1 -0
- package/dist/tools/stayfinder-verify.js +124 -0
- package/dist/tools/stayfinder-verify.js.map +1 -0
- package/dist/types.d.ts +193 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +51 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +174 -0
- package/dist/validation.js.map +1 -0
- package/openclaw.plugin.json +41 -0
- package/package.json +87 -0
- package/skills/lodging-search/SKILL.md +235 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared TypeScript types for the StayFinder plugin.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the public adapter API surface (the request/response shapes
|
|
5
|
+
* for `POST /v1/search/stays`, `POST /v1/signup`, `POST /v1/signup/verify`,
|
|
6
|
+
* `GET /v1/tenant/me`) and the on-disk credential store shape.
|
|
7
|
+
*
|
|
8
|
+
* They are deliberately a SUBSET of what the adapter returns — we only type
|
|
9
|
+
* the fields the plugin actually reads. The adapter is free to add new
|
|
10
|
+
* fields without breaking us; missing optional fields parse fine; unexpected
|
|
11
|
+
* extra fields are silently ignored at runtime.
|
|
12
|
+
*
|
|
13
|
+
* The single source of truth for the wire format is the adapter spec at
|
|
14
|
+
* docs/expedia-adapter-cloudrun-spec.md §7 (in the private operations repo).
|
|
15
|
+
*/
|
|
16
|
+
/** ISO-8601 timestamp string, e.g. "2026-04-15T22:32:39.436Z". */
|
|
17
|
+
export type IsoTimestamp = string;
|
|
18
|
+
/** YYYY-MM-DD date string. */
|
|
19
|
+
export type IsoDate = string;
|
|
20
|
+
export interface StayFinderPluginConfig {
|
|
21
|
+
/** Base URL of the StayFinder service. Defaults to the public hosted endpoint. */
|
|
22
|
+
adapter_url: string;
|
|
23
|
+
/** ISO-3166-1 alpha-2 country code for point-of-sale. Defaults to 'US'. */
|
|
24
|
+
default_pos_country: string;
|
|
25
|
+
/** ISO-4217 currency code. If unset, the adapter falls back to the POS default. */
|
|
26
|
+
default_currency?: string;
|
|
27
|
+
/** HTTP timeout for adapter calls, in milliseconds. Defaults to 10000. */
|
|
28
|
+
request_timeout_ms: number;
|
|
29
|
+
}
|
|
30
|
+
export type LodgingType = 'hotel' | 'vacation_rental' | 'any';
|
|
31
|
+
export type SortOrder = 'recommended' | 'price_asc' | 'price_desc' | 'rating_desc' | 'distance';
|
|
32
|
+
export interface SearchStaysFilters {
|
|
33
|
+
pet_friendly?: boolean;
|
|
34
|
+
free_cancellation?: boolean;
|
|
35
|
+
min_star_rating?: number;
|
|
36
|
+
max_star_rating?: number;
|
|
37
|
+
price_min?: number;
|
|
38
|
+
price_max?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface SearchStaysRequest {
|
|
41
|
+
destination: string;
|
|
42
|
+
check_in: IsoDate;
|
|
43
|
+
check_out: IsoDate;
|
|
44
|
+
adults: number;
|
|
45
|
+
children_ages?: number[];
|
|
46
|
+
lodging_type?: LodgingType;
|
|
47
|
+
filters?: SearchStaysFilters;
|
|
48
|
+
sort?: SortOrder;
|
|
49
|
+
limit?: number;
|
|
50
|
+
pos_country?: string;
|
|
51
|
+
currency?: string;
|
|
52
|
+
intent?: string;
|
|
53
|
+
hotel_name?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Free-text fields that come back wrapped in
|
|
57
|
+
* <<<EXTERNAL_UNTRUSTED_CONTENT id="..."> ... <<<END_EXTERNAL_UNTRUSTED_CONTENT id="...">
|
|
58
|
+
* sentinels by the adapter. Typed as string here; the wrapping is plain
|
|
59
|
+
* text inside the string and the model is expected to recognize it.
|
|
60
|
+
*/
|
|
61
|
+
export type WrappedString = string;
|
|
62
|
+
export interface SearchStaysProperty {
|
|
63
|
+
property_id: string;
|
|
64
|
+
name: WrappedString;
|
|
65
|
+
property_type?: string;
|
|
66
|
+
neighborhood?: WrappedString;
|
|
67
|
+
star_rating: number | null;
|
|
68
|
+
guest_rating?: {
|
|
69
|
+
score: number | null;
|
|
70
|
+
scale: number;
|
|
71
|
+
review_count: number;
|
|
72
|
+
};
|
|
73
|
+
price: {
|
|
74
|
+
amount_per_night: number;
|
|
75
|
+
amount_total: number;
|
|
76
|
+
currency: string;
|
|
77
|
+
taxes_included: boolean;
|
|
78
|
+
fees_estimate?: number;
|
|
79
|
+
};
|
|
80
|
+
free_cancellation?: boolean;
|
|
81
|
+
pet_friendly?: boolean;
|
|
82
|
+
descriptions?: {
|
|
83
|
+
location?: WrappedString;
|
|
84
|
+
hotel?: WrappedString;
|
|
85
|
+
room?: WrappedString;
|
|
86
|
+
};
|
|
87
|
+
distance?: {
|
|
88
|
+
value: number;
|
|
89
|
+
unit: 'km' | 'mi';
|
|
90
|
+
};
|
|
91
|
+
thumbnail_url?: string;
|
|
92
|
+
redirect_link: string;
|
|
93
|
+
geo?: {
|
|
94
|
+
lat: number;
|
|
95
|
+
lng: number;
|
|
96
|
+
obfuscated?: boolean;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export interface SearchStaysResponse {
|
|
100
|
+
request_id: string;
|
|
101
|
+
trace_id?: string;
|
|
102
|
+
cached: boolean;
|
|
103
|
+
cached_at: IsoTimestamp;
|
|
104
|
+
cache_ttl_seconds: number;
|
|
105
|
+
brand?: 'expedia' | 'vrbo';
|
|
106
|
+
resolved_destination?: {
|
|
107
|
+
id: string;
|
|
108
|
+
label: string;
|
|
109
|
+
type: string;
|
|
110
|
+
};
|
|
111
|
+
check_in: IsoDate;
|
|
112
|
+
check_out: IsoDate;
|
|
113
|
+
nights: number;
|
|
114
|
+
party: {
|
|
115
|
+
adults: number;
|
|
116
|
+
children: number;
|
|
117
|
+
};
|
|
118
|
+
currency: string;
|
|
119
|
+
result_count: number;
|
|
120
|
+
total_available?: number | null;
|
|
121
|
+
warnings: Array<{
|
|
122
|
+
code: string;
|
|
123
|
+
message: string;
|
|
124
|
+
}>;
|
|
125
|
+
results: SearchStaysProperty[];
|
|
126
|
+
}
|
|
127
|
+
export interface SignupResponse {
|
|
128
|
+
status: 'verification_sent';
|
|
129
|
+
message: string;
|
|
130
|
+
expires_in_seconds: number;
|
|
131
|
+
}
|
|
132
|
+
export interface SignupVerifyResponse {
|
|
133
|
+
status: 'verified';
|
|
134
|
+
tenant_id: string;
|
|
135
|
+
/** Plaintext API token — returned ONCE; never request twice. */
|
|
136
|
+
token: string;
|
|
137
|
+
token_kind: 'ephemeral' | 'persistent';
|
|
138
|
+
expires_at: IsoTimestamp | null;
|
|
139
|
+
quota_per_hour: number;
|
|
140
|
+
default_pos: string;
|
|
141
|
+
}
|
|
142
|
+
export interface TenantMeResponse {
|
|
143
|
+
tenant_id: string;
|
|
144
|
+
name: string | null;
|
|
145
|
+
email: string | null;
|
|
146
|
+
quota: {
|
|
147
|
+
limit_per_hour: number;
|
|
148
|
+
remaining: number;
|
|
149
|
+
reset_at: IsoTimestamp;
|
|
150
|
+
};
|
|
151
|
+
token: {
|
|
152
|
+
kind: 'ephemeral' | 'persistent';
|
|
153
|
+
expires_at: IsoTimestamp | null;
|
|
154
|
+
};
|
|
155
|
+
default_pos: string;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Every known adapter error code, in one place. The plugin maps each one
|
|
159
|
+
* to a model-facing message in errors.ts. Unknown codes still parse fine
|
|
160
|
+
* (the AdapterErrorEnvelope.code field is `string`, not this union) — the
|
|
161
|
+
* union is for exhaustive switch coverage in the mapper.
|
|
162
|
+
*/
|
|
163
|
+
export type AdapterErrorCode = 'invalid_request' | 'missing_field' | 'invalid_email' | 'disposable_email' | 'code_invalid' | 'code_expired' | 'code_attempts_exceeded' | 'unauthorized' | 'token_expired' | 'tenant_suspended' | 'tenant_quota_exceeded' | 'global_quota_exceeded' | 'signup_rate_limited' | 'destination_not_found' | 'destination_ambiguous' | 'expedia_upstream_error' | 'upstream_timeout' | 'upstream_unavailable' | 'internal_error';
|
|
164
|
+
export interface AdapterErrorEnvelope {
|
|
165
|
+
error: {
|
|
166
|
+
code: string;
|
|
167
|
+
message: string;
|
|
168
|
+
request_id?: string;
|
|
169
|
+
trace_id?: string;
|
|
170
|
+
retry_after_seconds?: number;
|
|
171
|
+
attempts_remaining?: number;
|
|
172
|
+
expires_at?: string | null;
|
|
173
|
+
upstream_status?: number | null;
|
|
174
|
+
transaction_id?: string;
|
|
175
|
+
pos_country?: string;
|
|
176
|
+
details?: Record<string, unknown>;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export interface CredentialFile {
|
|
180
|
+
/** Plaintext API token, "oct_..." prefix. */
|
|
181
|
+
api_token: string;
|
|
182
|
+
/** ISO-8601 timestamp when the file was last written. */
|
|
183
|
+
saved_at: IsoTimestamp;
|
|
184
|
+
/** Tenant ID the token belongs to. */
|
|
185
|
+
tenant_id: string;
|
|
186
|
+
/** Email the tenant was registered under (used for re-auth). */
|
|
187
|
+
email: string;
|
|
188
|
+
/** Token kind: ephemeral tokens slide; persistent ones don't. */
|
|
189
|
+
token_kind: 'ephemeral' | 'persistent';
|
|
190
|
+
/** Token's current expires_at; null for persistent tokens. */
|
|
191
|
+
expires_at: IsoTimestamp | null;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared TypeScript types for the StayFinder plugin.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the public adapter API surface (the request/response shapes
|
|
5
|
+
* for `POST /v1/search/stays`, `POST /v1/signup`, `POST /v1/signup/verify`,
|
|
6
|
+
* `GET /v1/tenant/me`) and the on-disk credential store shape.
|
|
7
|
+
*
|
|
8
|
+
* They are deliberately a SUBSET of what the adapter returns — we only type
|
|
9
|
+
* the fields the plugin actually reads. The adapter is free to add new
|
|
10
|
+
* fields without breaking us; missing optional fields parse fine; unexpected
|
|
11
|
+
* extra fields are silently ignored at runtime.
|
|
12
|
+
*
|
|
13
|
+
* The single source of truth for the wire format is the adapter spec at
|
|
14
|
+
* docs/expedia-adapter-cloudrun-spec.md §7 (in the private operations repo).
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-HTTP validation for `search_stays` parameters.
|
|
3
|
+
*
|
|
4
|
+
* The TypeBox schema in `tools/search-stays.ts` catches type and range
|
|
5
|
+
* errors that the runtime can enforce automatically (string vs number,
|
|
6
|
+
* minLength, maximum, enum membership, etc.). Anything that requires
|
|
7
|
+
* cross-field comparison or runtime computation lives here.
|
|
8
|
+
*
|
|
9
|
+
* Returning a non-null string from any of these functions means the
|
|
10
|
+
* tool's `execute` should throw with that string as the user-facing
|
|
11
|
+
* message instead of making the HTTP call. The string is phrased for
|
|
12
|
+
* the model — concrete next action, plain English, no error codes.
|
|
13
|
+
*
|
|
14
|
+
* The spec is explicit that error messages should give the model an
|
|
15
|
+
* actionable hypothesis. "check_out must be after check_in. Did you
|
|
16
|
+
* swap the dates?" is much better than "validation failed".
|
|
17
|
+
*/
|
|
18
|
+
import type { SearchStaysRequest } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Run all post-schema validation checks against a `search_stays` request.
|
|
21
|
+
*
|
|
22
|
+
* Returns null if everything passes; otherwise returns the model-facing
|
|
23
|
+
* error message string. Tools call this in `execute` *before* hitting
|
|
24
|
+
* the HTTP layer so we never burn an adapter call on a request the
|
|
25
|
+
* adapter would also reject.
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateSearchStaysRequest(req: SearchStaysRequest, now?: Date): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Trim and validate an email address with a deliberately-loose check.
|
|
30
|
+
*
|
|
31
|
+
* The plugin doesn't try to be a perfect RFC 5322 validator — that's the
|
|
32
|
+
* adapter's job (it uses the same `email-validator` package and runs the
|
|
33
|
+
* disposable-domain check). All we want here is to reject things that
|
|
34
|
+
* obviously aren't emails before burning an HTTP call.
|
|
35
|
+
*
|
|
36
|
+
* Returns null on success, or a model-facing error string on failure.
|
|
37
|
+
*/
|
|
38
|
+
export declare function validateEmailLoose(raw: string): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Strip whitespace, dashes, dots, and any non-digit characters from a
|
|
41
|
+
* pasted code, then return the result if it's exactly 6 ASCII digits.
|
|
42
|
+
*
|
|
43
|
+
* Returns null when the cleaned input isn't 6 digits — the verify tool
|
|
44
|
+
* uses this to short-circuit "the user pasted a token by mistake" or
|
|
45
|
+
* "the user pasted a phrase" without burning an HTTP call.
|
|
46
|
+
*
|
|
47
|
+
* Defensive cleanup matters because email clients sometimes paste
|
|
48
|
+
* non-breaking spaces or thin spaces around copied text.
|
|
49
|
+
*/
|
|
50
|
+
export declare function sanitizeAndValidateCode(raw: unknown): string | null;
|
|
51
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAwCrD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,kBAAkB,EACvB,GAAG,GAAE,IAAiB,GACrB,MAAM,GAAG,IAAI,CAyFf;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoB7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKnE"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-HTTP validation for `search_stays` parameters.
|
|
3
|
+
*
|
|
4
|
+
* The TypeBox schema in `tools/search-stays.ts` catches type and range
|
|
5
|
+
* errors that the runtime can enforce automatically (string vs number,
|
|
6
|
+
* minLength, maximum, enum membership, etc.). Anything that requires
|
|
7
|
+
* cross-field comparison or runtime computation lives here.
|
|
8
|
+
*
|
|
9
|
+
* Returning a non-null string from any of these functions means the
|
|
10
|
+
* tool's `execute` should throw with that string as the user-facing
|
|
11
|
+
* message instead of making the HTTP call. The string is phrased for
|
|
12
|
+
* the model — concrete next action, plain English, no error codes.
|
|
13
|
+
*
|
|
14
|
+
* The spec is explicit that error messages should give the model an
|
|
15
|
+
* actionable hypothesis. "check_out must be after check_in. Did you
|
|
16
|
+
* swap the dates?" is much better than "validation failed".
|
|
17
|
+
*/
|
|
18
|
+
const MAX_NIGHTS = 30;
|
|
19
|
+
const MAX_DAYS_OUT = 500;
|
|
20
|
+
const ymdRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
21
|
+
/**
|
|
22
|
+
* Parse a YYYY-MM-DD string as UTC midnight. Returns null if the string
|
|
23
|
+
* doesn't match the format or doesn't represent a real date.
|
|
24
|
+
*
|
|
25
|
+
* We use UTC because the adapter (and Expedia) treat the date as
|
|
26
|
+
* calendar-day-in-the-destination, not "wall clock at midnight in the
|
|
27
|
+
* user's local timezone". A user in NYC searching for a hotel in Tokyo
|
|
28
|
+
* for 2026-06-01 means 2026-06-01 in Tokyo, regardless of what time it
|
|
29
|
+
* is on their laptop.
|
|
30
|
+
*/
|
|
31
|
+
const parseYmdUtc = (s) => {
|
|
32
|
+
if (!ymdRegex.test(s))
|
|
33
|
+
return null;
|
|
34
|
+
const ms = Date.parse(`${s}T00:00:00Z`);
|
|
35
|
+
if (!Number.isFinite(ms))
|
|
36
|
+
return null;
|
|
37
|
+
// Reject roll-overs like "2026-02-30" — Date.parse silently rolls them
|
|
38
|
+
// forward, so we round-trip and compare to catch the difference.
|
|
39
|
+
const back = new Date(ms).toISOString().slice(0, 10);
|
|
40
|
+
if (back !== s)
|
|
41
|
+
return null;
|
|
42
|
+
return new Date(ms);
|
|
43
|
+
};
|
|
44
|
+
/** Today, normalized to UTC midnight, as a Date. */
|
|
45
|
+
const todayUtc = () => {
|
|
46
|
+
const d = new Date();
|
|
47
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
48
|
+
return d;
|
|
49
|
+
};
|
|
50
|
+
const daysBetween = (a, b) => {
|
|
51
|
+
const ms = b.getTime() - a.getTime();
|
|
52
|
+
return Math.round(ms / (24 * 60 * 60 * 1000));
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Run all post-schema validation checks against a `search_stays` request.
|
|
56
|
+
*
|
|
57
|
+
* Returns null if everything passes; otherwise returns the model-facing
|
|
58
|
+
* error message string. Tools call this in `execute` *before* hitting
|
|
59
|
+
* the HTTP layer so we never burn an adapter call on a request the
|
|
60
|
+
* adapter would also reject.
|
|
61
|
+
*/
|
|
62
|
+
export function validateSearchStaysRequest(req, now = todayUtc()) {
|
|
63
|
+
// -----------------------------------------------------------------------
|
|
64
|
+
// Date format
|
|
65
|
+
// -----------------------------------------------------------------------
|
|
66
|
+
const checkIn = parseYmdUtc(req.check_in);
|
|
67
|
+
if (!checkIn) {
|
|
68
|
+
return (`check_in is not a valid YYYY-MM-DD date: "${req.check_in}". ` +
|
|
69
|
+
'Ask the user for the date in plain English and re-format it as YYYY-MM-DD.');
|
|
70
|
+
}
|
|
71
|
+
const checkOut = parseYmdUtc(req.check_out);
|
|
72
|
+
if (!checkOut) {
|
|
73
|
+
return (`check_out is not a valid YYYY-MM-DD date: "${req.check_out}". ` +
|
|
74
|
+
'Ask the user for the date in plain English and re-format it as YYYY-MM-DD.');
|
|
75
|
+
}
|
|
76
|
+
// -----------------------------------------------------------------------
|
|
77
|
+
// Date order — by far the most common LLM mistake
|
|
78
|
+
// -----------------------------------------------------------------------
|
|
79
|
+
if (checkOut.getTime() <= checkIn.getTime()) {
|
|
80
|
+
return (`check_out (${req.check_out}) must be after check_in (${req.check_in}). ` +
|
|
81
|
+
'Did you swap the dates? Re-check with the user and call search_stays again.');
|
|
82
|
+
}
|
|
83
|
+
// -----------------------------------------------------------------------
|
|
84
|
+
// check_in not in the past
|
|
85
|
+
// -----------------------------------------------------------------------
|
|
86
|
+
if (checkIn.getTime() < now.getTime()) {
|
|
87
|
+
return (`check_in (${req.check_in}) is in the past. ` +
|
|
88
|
+
'Confirm the dates with the user — Expedia only sells future stays.');
|
|
89
|
+
}
|
|
90
|
+
// -----------------------------------------------------------------------
|
|
91
|
+
// Stay length cap
|
|
92
|
+
// -----------------------------------------------------------------------
|
|
93
|
+
const nights = daysBetween(checkIn, checkOut);
|
|
94
|
+
if (nights > MAX_NIGHTS) {
|
|
95
|
+
return (`Stay length is ${nights} nights, which exceeds the ${MAX_NIGHTS}-night maximum per search. ` +
|
|
96
|
+
'Split the trip into multiple ≤30-night searches and combine the results yourself.');
|
|
97
|
+
}
|
|
98
|
+
// -----------------------------------------------------------------------
|
|
99
|
+
// check_in window cap (Expedia only quotes ~500 days out)
|
|
100
|
+
// -----------------------------------------------------------------------
|
|
101
|
+
const daysOut = daysBetween(now, checkIn);
|
|
102
|
+
if (daysOut > MAX_DAYS_OUT) {
|
|
103
|
+
return (`check_in (${req.check_in}) is ${daysOut} days from now, beyond the ${MAX_DAYS_OUT}-day booking window. ` +
|
|
104
|
+
'Tell the user the destination doesn\'t have inventory open that far in the future yet, and ask if they want to try a date closer to now.');
|
|
105
|
+
}
|
|
106
|
+
// -----------------------------------------------------------------------
|
|
107
|
+
// Filter sanity (only when both ends of a range are present)
|
|
108
|
+
// -----------------------------------------------------------------------
|
|
109
|
+
const filters = req.filters;
|
|
110
|
+
if (filters) {
|
|
111
|
+
if (typeof filters.min_star_rating === 'number' &&
|
|
112
|
+
typeof filters.max_star_rating === 'number' &&
|
|
113
|
+
filters.min_star_rating > filters.max_star_rating) {
|
|
114
|
+
return (`Star rating filter is inverted: min_star_rating ${filters.min_star_rating} > max_star_rating ${filters.max_star_rating}. ` +
|
|
115
|
+
'Did you swap them? Re-call search_stays with the bounds in the right order.');
|
|
116
|
+
}
|
|
117
|
+
if (typeof filters.price_min === 'number' &&
|
|
118
|
+
typeof filters.price_max === 'number' &&
|
|
119
|
+
filters.price_min > filters.price_max) {
|
|
120
|
+
return (`Price filter is inverted: price_min ${filters.price_min} > price_max ${filters.price_max}. ` +
|
|
121
|
+
'Did you swap them? Re-call search_stays with the bounds in the right order.');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Trim and validate an email address with a deliberately-loose check.
|
|
128
|
+
*
|
|
129
|
+
* The plugin doesn't try to be a perfect RFC 5322 validator — that's the
|
|
130
|
+
* adapter's job (it uses the same `email-validator` package and runs the
|
|
131
|
+
* disposable-domain check). All we want here is to reject things that
|
|
132
|
+
* obviously aren't emails before burning an HTTP call.
|
|
133
|
+
*
|
|
134
|
+
* Returns null on success, or a model-facing error string on failure.
|
|
135
|
+
*/
|
|
136
|
+
export function validateEmailLoose(raw) {
|
|
137
|
+
if (typeof raw !== 'string') {
|
|
138
|
+
return 'Email must be a string. Ask the user for their email address.';
|
|
139
|
+
}
|
|
140
|
+
const trimmed = raw.trim();
|
|
141
|
+
if (trimmed.length === 0) {
|
|
142
|
+
return 'Email is empty. Ask the user for their email address.';
|
|
143
|
+
}
|
|
144
|
+
if (trimmed.length > 254) {
|
|
145
|
+
return 'Email is unrealistically long. Ask the user to double-check what they typed.';
|
|
146
|
+
}
|
|
147
|
+
// One @, non-empty local and domain parts, domain has at least one dot,
|
|
148
|
+
// no whitespace anywhere. This matches the adapter's first-pass regex.
|
|
149
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
|
|
150
|
+
return (`"${trimmed}" doesn't look like a valid email address. ` +
|
|
151
|
+
'Ask the user to double-check the spelling.');
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Strip whitespace, dashes, dots, and any non-digit characters from a
|
|
157
|
+
* pasted code, then return the result if it's exactly 6 ASCII digits.
|
|
158
|
+
*
|
|
159
|
+
* Returns null when the cleaned input isn't 6 digits — the verify tool
|
|
160
|
+
* uses this to short-circuit "the user pasted a token by mistake" or
|
|
161
|
+
* "the user pasted a phrase" without burning an HTTP call.
|
|
162
|
+
*
|
|
163
|
+
* Defensive cleanup matters because email clients sometimes paste
|
|
164
|
+
* non-breaking spaces or thin spaces around copied text.
|
|
165
|
+
*/
|
|
166
|
+
export function sanitizeAndValidateCode(raw) {
|
|
167
|
+
if (typeof raw !== 'string')
|
|
168
|
+
return null;
|
|
169
|
+
const cleaned = raw.replace(/[^0-9]/g, '');
|
|
170
|
+
if (cleaned.length !== 6)
|
|
171
|
+
return null;
|
|
172
|
+
return cleaned;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "stayfinder-plugin",
|
|
3
|
+
"name": "StayFinder",
|
|
4
|
+
"description": "Live hotel and vacation rental search via the StayFinder service. Returns real-time pricing, availability, and Expedia booking redirect links — never scrape, never construct booking URLs from training data.",
|
|
5
|
+
"skills": [
|
|
6
|
+
"./skills"
|
|
7
|
+
],
|
|
8
|
+
"contracts": {
|
|
9
|
+
"tools": [
|
|
10
|
+
"search_stays",
|
|
11
|
+
"stayfinder_signup",
|
|
12
|
+
"stayfinder_verify"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"configSchema": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"properties": {
|
|
19
|
+
"adapter_url": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Base URL of the StayFinder service. Defaults to the public hosted endpoint at https://api.stayfinder.riverintel.com if omitted."
|
|
22
|
+
},
|
|
23
|
+
"default_pos_country": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"pattern": "^[A-Z]{2}$",
|
|
26
|
+
"description": "ISO-3166-1 alpha-2 country code for point-of-sale (affects currency and pricing display). Defaults to US."
|
|
27
|
+
},
|
|
28
|
+
"default_currency": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"pattern": "^[A-Z]{3}$",
|
|
31
|
+
"description": "ISO-4217 currency code. Defaults to the POS country's primary currency."
|
|
32
|
+
},
|
|
33
|
+
"request_timeout_ms": {
|
|
34
|
+
"type": "integer",
|
|
35
|
+
"minimum": 1000,
|
|
36
|
+
"maximum": 60000,
|
|
37
|
+
"description": "HTTP timeout for service calls. Defaults to 10000 (10s)."
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@riverintel/stayfinder-plugin",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "OpenClaw plugin for live hotel and vacation rental search via the StayFinder service. Returns real-time pricing, availability, and Expedia booking redirect links — no scraping, no browser automation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/",
|
|
13
|
+
"skills/",
|
|
14
|
+
"openclaw.plugin.json",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"prepublishOnly": "npm run build && npm test"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"openclaw",
|
|
33
|
+
"openclaw-plugin",
|
|
34
|
+
"stayfinder",
|
|
35
|
+
"lodging",
|
|
36
|
+
"hotels",
|
|
37
|
+
"vacation-rental",
|
|
38
|
+
"travel",
|
|
39
|
+
"expedia",
|
|
40
|
+
"vrbo"
|
|
41
|
+
],
|
|
42
|
+
"author": "RiverIntel",
|
|
43
|
+
"license": "Apache-2.0",
|
|
44
|
+
"homepage": "https://github.com/RiverIntel/stayfinder#readme",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/RiverIntel/stayfinder.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/RiverIntel/stayfinder/issues"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=20"
|
|
54
|
+
},
|
|
55
|
+
"openclaw": {
|
|
56
|
+
"extensions": [
|
|
57
|
+
"./dist/index.js"
|
|
58
|
+
],
|
|
59
|
+
"compat": {
|
|
60
|
+
"pluginApi": ">=2026.4.5"
|
|
61
|
+
},
|
|
62
|
+
"build": {
|
|
63
|
+
"openclawVersion": "2026.4.5"
|
|
64
|
+
},
|
|
65
|
+
"install": {
|
|
66
|
+
"npmSpec": "@riverintel/stayfinder-plugin",
|
|
67
|
+
"defaultChoice": "npm",
|
|
68
|
+
"minHostVersion": ">=2026.4.5"
|
|
69
|
+
},
|
|
70
|
+
"release": {
|
|
71
|
+
"publishToClawHub": true,
|
|
72
|
+
"publishToNpm": true
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"openclaw": ">=2026.4.5"
|
|
77
|
+
},
|
|
78
|
+
"dependencies": {
|
|
79
|
+
"@sinclair/typebox": "^0.33.0"
|
|
80
|
+
},
|
|
81
|
+
"devDependencies": {
|
|
82
|
+
"@types/node": "^22.7.5",
|
|
83
|
+
"openclaw": "2026.4.5",
|
|
84
|
+
"typescript": "^5.6.3",
|
|
85
|
+
"vitest": "^4.1.3"
|
|
86
|
+
}
|
|
87
|
+
}
|