@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.
Files changed (53) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +94 -0
  3. package/dist/adapter-client.d.ts +68 -0
  4. package/dist/adapter-client.d.ts.map +1 -0
  5. package/dist/adapter-client.js +149 -0
  6. package/dist/adapter-client.js.map +1 -0
  7. package/dist/credential-store.d.ts +71 -0
  8. package/dist/credential-store.d.ts.map +1 -0
  9. package/dist/credential-store.js +143 -0
  10. package/dist/credential-store.js.map +1 -0
  11. package/dist/errors.d.ts +55 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +160 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +28 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/plugin-config.d.ts +37 -0
  20. package/dist/plugin-config.d.ts.map +1 -0
  21. package/dist/plugin-config.js +63 -0
  22. package/dist/plugin-config.js.map +1 -0
  23. package/dist/thumbnails.d.ts +31 -0
  24. package/dist/thumbnails.d.ts.map +1 -0
  25. package/dist/thumbnails.js +32 -0
  26. package/dist/thumbnails.js.map +1 -0
  27. package/dist/tool-result.d.ts +19 -0
  28. package/dist/tool-result.d.ts.map +1 -0
  29. package/dist/tool-result.js +18 -0
  30. package/dist/tool-result.js.map +1 -0
  31. package/dist/tools/search-stays.d.ts +45 -0
  32. package/dist/tools/search-stays.d.ts.map +1 -0
  33. package/dist/tools/search-stays.js +191 -0
  34. package/dist/tools/search-stays.js.map +1 -0
  35. package/dist/tools/stayfinder-signup.d.ts +38 -0
  36. package/dist/tools/stayfinder-signup.d.ts.map +1 -0
  37. package/dist/tools/stayfinder-signup.js +102 -0
  38. package/dist/tools/stayfinder-signup.js.map +1 -0
  39. package/dist/tools/stayfinder-verify.d.ts +26 -0
  40. package/dist/tools/stayfinder-verify.d.ts.map +1 -0
  41. package/dist/tools/stayfinder-verify.js +124 -0
  42. package/dist/tools/stayfinder-verify.js.map +1 -0
  43. package/dist/types.d.ts +193 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +17 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/validation.d.ts +51 -0
  48. package/dist/validation.d.ts.map +1 -0
  49. package/dist/validation.js +174 -0
  50. package/dist/validation.js.map +1 -0
  51. package/openclaw.plugin.json +41 -0
  52. package/package.json +87 -0
  53. package/skills/lodging-search/SKILL.md +235 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Tiny helper that builds the AgentToolResult shape the OpenClaw runtime
3
+ * expects. Inlined here instead of importing from `openclaw/plugin-sdk/core`
4
+ * because the SDK's bundled JS re-exports through a hashed chunk file
5
+ * (`common-B7pbdYUb.js`) that doesn't resolve cleanly as a named import
6
+ * from `openclaw/plugin-sdk/core` at test time. The function is 5 lines;
7
+ * owning it avoids a fragile import.
8
+ */
9
+ export function toolTextResult(text, details) {
10
+ return {
11
+ content: [{ type: 'text', text }],
12
+ details,
13
+ };
14
+ }
15
+ export function toolJsonResult(payload) {
16
+ return toolTextResult(JSON.stringify(payload, null, 2), payload);
17
+ }
18
+ //# sourceMappingURL=tool-result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result.js","sourceRoot":"","sources":["../src/tool-result.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,MAAM,UAAU,cAAc,CAAI,IAAY,EAAE,OAAU;IACxD,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACjC,OAAO;KACR,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAI,OAAU;IAC1C,OAAO,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `search_stays` — the main lodging search tool.
3
+ *
4
+ * This is the tool the agent reaches for when the user asks about hotels,
5
+ * vacation rentals, or "places to stay." The bundled SKILL.md tells the
6
+ * model to prefer it over web_search / web_fetch / browser for any
7
+ * lodging query.
8
+ *
9
+ * The tool:
10
+ * 1. Reads the credential file for the bearer token
11
+ * 2. Validates the request parameters locally (dates, filters, etc.)
12
+ * BEFORE the HTTP call so we never burn an adapter call (or a rate-
13
+ * limit slot) on something the adapter would also reject
14
+ * 3. Calls POST /v1/search/stays on the StayFinder service
15
+ * 4. On success: returns the full adapter response as JSON (the model
16
+ * needs the full result list to present options to the user), plus
17
+ * a trailing usage hint reminding the model to share redirect_links
18
+ * 5. On error: throws with a model-facing message from formatErrorForModel
19
+ *
20
+ * The `unauthorized` and `token_expired` error codes get special treatment:
21
+ * the model reads them and triggers the bootstrap flow (signup → verify)
22
+ * as described in the SKILL.md walkthroughs.
23
+ */
24
+ export declare const SearchStaysParamsSchema: import("@sinclair/typebox").TObject<{
25
+ destination: import("@sinclair/typebox").TString;
26
+ check_in: import("@sinclair/typebox").TString;
27
+ check_out: import("@sinclair/typebox").TString;
28
+ adults: import("@sinclair/typebox").TInteger;
29
+ children_ages: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TInteger>>;
30
+ lodging_type: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"hotel">, import("@sinclair/typebox").TLiteral<"vacation_rental">, import("@sinclair/typebox").TLiteral<"any">]>>;
31
+ filters: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
32
+ pet_friendly: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
33
+ free_cancellation: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
34
+ min_star_rating: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
35
+ max_star_rating: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
36
+ price_min: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
37
+ price_max: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
38
+ }>>;
39
+ sort: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"recommended">, import("@sinclair/typebox").TLiteral<"price_asc">, import("@sinclair/typebox").TLiteral<"price_desc">, import("@sinclair/typebox").TLiteral<"rating_desc">, import("@sinclair/typebox").TLiteral<"distance">]>>;
40
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
41
+ intent: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
42
+ hotel_name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
43
+ }>;
44
+ export declare function createSearchStaysTool(api: any): any;
45
+ //# sourceMappingURL=search-stays.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-stays.d.ts","sourceRoot":"","sources":["../../src/tools/search-stays.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAeH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;EAgHnC,CAAC;AA4BF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CA4EnD"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * `search_stays` — the main lodging search tool.
3
+ *
4
+ * This is the tool the agent reaches for when the user asks about hotels,
5
+ * vacation rentals, or "places to stay." The bundled SKILL.md tells the
6
+ * model to prefer it over web_search / web_fetch / browser for any
7
+ * lodging query.
8
+ *
9
+ * The tool:
10
+ * 1. Reads the credential file for the bearer token
11
+ * 2. Validates the request parameters locally (dates, filters, etc.)
12
+ * BEFORE the HTTP call so we never burn an adapter call (or a rate-
13
+ * limit slot) on something the adapter would also reject
14
+ * 3. Calls POST /v1/search/stays on the StayFinder service
15
+ * 4. On success: returns the full adapter response as JSON (the model
16
+ * needs the full result list to present options to the user), plus
17
+ * a trailing usage hint reminding the model to share redirect_links
18
+ * 5. On error: throws with a model-facing message from formatErrorForModel
19
+ *
20
+ * The `unauthorized` and `token_expired` error codes get special treatment:
21
+ * the model reads them and triggers the bootstrap flow (signup → verify)
22
+ * as described in the SKILL.md walkthroughs.
23
+ */
24
+ import { Type } from '@sinclair/typebox';
25
+ import { AdapterClient } from '../adapter-client.js';
26
+ import { readCredential } from '../credential-store.js';
27
+ import { AdapterError, formatErrorForModel } from '../errors.js';
28
+ import { readPluginConfig } from '../plugin-config.js';
29
+ import { upgradeThumbnailUrl } from '../thumbnails.js';
30
+ import { toolTextResult } from '../tool-result.js';
31
+ import { validateSearchStaysRequest } from '../validation.js';
32
+ const TOOL_NAME = 'search_stays';
33
+ export const SearchStaysParamsSchema = Type.Object({
34
+ destination: Type.String({
35
+ minLength: 2,
36
+ maxLength: 200,
37
+ description: "Free-text destination. Examples: 'Manhattan Lower East Side', 'Maui', 'Paris 5th arrondissement'. " +
38
+ 'The service resolves this to a destination ID.',
39
+ }),
40
+ check_in: Type.String({
41
+ pattern: '^\\d{4}-\\d{2}-\\d{2}$',
42
+ description: 'Check-in date in YYYY-MM-DD format. Must be today or later, within the next 500 days.',
43
+ }),
44
+ check_out: Type.String({
45
+ pattern: '^\\d{4}-\\d{2}-\\d{2}$',
46
+ description: 'Check-out date in YYYY-MM-DD format. Must be after check_in and within 30 days of check_in.',
47
+ }),
48
+ adults: Type.Integer({
49
+ minimum: 1,
50
+ maximum: 8,
51
+ description: 'Number of adult guests.',
52
+ }),
53
+ children_ages: Type.Optional(Type.Array(Type.Integer({ minimum: 0, maximum: 17 }), {
54
+ minItems: 0,
55
+ maxItems: 6,
56
+ description: 'Ages of accompanying children at time of stay. Omit or empty array if none.',
57
+ })),
58
+ lodging_type: Type.Optional(Type.Union([
59
+ Type.Literal('hotel'),
60
+ Type.Literal('vacation_rental'),
61
+ Type.Literal('any'),
62
+ ], {
63
+ default: 'any',
64
+ description: "Filter by lodging type. 'any' returns both hotels and vacation rentals.",
65
+ })),
66
+ filters: Type.Optional(Type.Object({
67
+ pet_friendly: Type.Optional(Type.Boolean()),
68
+ free_cancellation: Type.Optional(Type.Boolean()),
69
+ min_star_rating: Type.Optional(Type.Number({ minimum: 1, maximum: 5 })),
70
+ max_star_rating: Type.Optional(Type.Number({ minimum: 1, maximum: 5 })),
71
+ price_min: Type.Optional(Type.Number({ minimum: 0 })),
72
+ price_max: Type.Optional(Type.Number({ minimum: 0 })),
73
+ }, {
74
+ description: 'Optional filters. All are AND-combined. Price filters are per-night in the response currency.',
75
+ })),
76
+ sort: Type.Optional(Type.Union([
77
+ Type.Literal('recommended'),
78
+ Type.Literal('price_asc'),
79
+ Type.Literal('price_desc'),
80
+ Type.Literal('rating_desc'),
81
+ Type.Literal('distance'),
82
+ ], { default: 'recommended', description: 'Sort order for results.' })),
83
+ limit: Type.Optional(Type.Integer({
84
+ minimum: 1,
85
+ maximum: 25,
86
+ default: 10,
87
+ description: 'Maximum number of results to return. Use smaller values (5-10) for chat responses, larger (15-25) for detailed comparison.',
88
+ })),
89
+ intent: Type.Optional(Type.String({
90
+ maxLength: 280,
91
+ description: "Optional one-sentence description of the trip's purpose, vibe, or constraints — " +
92
+ "examples: 'romantic anniversary weekend', 'business trip with early meetings', " +
93
+ "'family vacation with two kids under 10', 'wedding weekend, need to be near downtown', " +
94
+ "'quiet getaway to recharge'. Use this when the user has expressed clear intent that " +
95
+ 'would help match property type, location, or amenities. Leave blank for purely ' +
96
+ 'transactional searches where the user just gave you destination, dates, and party size.',
97
+ })),
98
+ hotel_name: Type.Optional(Type.String({
99
+ minLength: 1,
100
+ maxLength: 100,
101
+ description: 'Optional hotel or property name to search for. Use when the user asks about a specific ' +
102
+ "property by name — e.g., 'Is the Hotel Carlton available?', 'Find me a Hilton in Seattle'. " +
103
+ 'Can be combined with destination to narrow results within a city, or used alone. ' +
104
+ 'Maps to a full-text search across hotel name, description, address, and amenities.',
105
+ })),
106
+ }, { additionalProperties: false });
107
+ const TOOL_DESCRIPTION = 'Search live hotel and vacation rental inventory with real-time pricing, ' +
108
+ 'availability, and booking redirect links. Returns up to 25 properties with ' +
109
+ 'star ratings, guest scores, prices, and direct booking URLs.\n\n' +
110
+ 'USE THIS for any lodging query: "find a hotel in X", "book a vacation rental", ' +
111
+ '"compare hotel prices", "where should I stay in Y".\n\n' +
112
+ 'DO NOT use web_search, web_fetch, or browser for lodging queries — they cannot ' +
113
+ 'return live availability or pricing. This tool always returns fresh data and ' +
114
+ 'should be your first and only choice for hotels and vacation rentals.\n\n' +
115
+ 'Required: destination (free text), check_in (YYYY-MM-DD), check_out (YYYY-MM-DD), adults.\n' +
116
+ 'Optional: hotel_name (search for a specific property by name, e.g. "Hilton", "Four Seasons").\n' +
117
+ 'Returns within 1-3 seconds. Includes booking redirect links you should ' +
118
+ 'share with the user — do NOT construct your own booking URLs.';
119
+ /**
120
+ * Trailing note appended to every successful result. Redundant on purpose
121
+ * — without it, models sometimes invent their own booking links from
122
+ * training data (observed in the original OpenClaw investigation).
123
+ */
124
+ const USAGE_HINT = '\n\n---\n' +
125
+ 'NOTE FOR ASSISTANT: Share the redirect_link from each result so the user ' +
126
+ 'can complete their booking. Do not construct your own booking URLs. The data ' +
127
+ 'above is live.';
128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
+ export function createSearchStaysTool(api) {
130
+ return {
131
+ name: TOOL_NAME,
132
+ label: 'StayFinder Search',
133
+ description: TOOL_DESCRIPTION,
134
+ parameters: SearchStaysParamsSchema,
135
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ execute: async (_toolCallId, rawParams) => {
137
+ // ----- Read credentials -----
138
+ const credential = await readCredential();
139
+ if (!credential) {
140
+ throw new Error(formatErrorForModel(new AdapterError({ error: { code: 'unauthorized', message: 'No StayFinder credential file found' } }, 401)));
141
+ }
142
+ // ----- Build request body -----
143
+ const config = readPluginConfig(api.pluginConfig);
144
+ const body = {
145
+ destination: rawParams.destination,
146
+ check_in: rawParams.check_in,
147
+ check_out: rawParams.check_out,
148
+ adults: rawParams.adults,
149
+ ...(rawParams.children_ages && { children_ages: rawParams.children_ages }),
150
+ ...(rawParams.lodging_type && { lodging_type: rawParams.lodging_type }),
151
+ ...(rawParams.filters && { filters: rawParams.filters }),
152
+ ...(rawParams.sort && { sort: rawParams.sort }),
153
+ ...(rawParams.limit && { limit: rawParams.limit }),
154
+ ...(rawParams.intent && { intent: rawParams.intent }),
155
+ ...(rawParams.hotel_name && { hotel_name: rawParams.hotel_name }),
156
+ ...(config.default_pos_country && { pos_country: config.default_pos_country }),
157
+ ...(config.default_currency && { currency: config.default_currency }),
158
+ };
159
+ // ----- Pre-HTTP validation -----
160
+ const validationError = validateSearchStaysRequest(body);
161
+ if (validationError)
162
+ throw new Error(validationError);
163
+ // ----- Call the adapter -----
164
+ const client = new AdapterClient({
165
+ config,
166
+ apiToken: credential.api_token,
167
+ pluginVersion: api.version ?? '0.0.0',
168
+ });
169
+ let response;
170
+ try {
171
+ response = await client.searchStays(body);
172
+ }
173
+ catch (err) {
174
+ if (err instanceof AdapterError) {
175
+ throw new Error(formatErrorForModel(err));
176
+ }
177
+ throw err;
178
+ }
179
+ // ----- Upgrade thumbnail URLs to card-sized resolution -----
180
+ for (const property of response.results) {
181
+ if (property.thumbnail_url) {
182
+ property.thumbnail_url = upgradeThumbnailUrl(property.thumbnail_url);
183
+ }
184
+ }
185
+ // ----- Return result with usage hint -----
186
+ const json = JSON.stringify(response, null, 2);
187
+ return toolTextResult(json + USAGE_HINT, response);
188
+ },
189
+ };
190
+ }
191
+ //# sourceMappingURL=search-stays.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-stays.js","sourceRoot":"","sources":["../../src/tools/search-stays.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `stayfinder_signup` bootstrap tool.
3
+ *
4
+ * Drives the first step of the StayFinder onboarding flow: takes an email
5
+ * address from the agent, fires `POST /v1/signup` against the service,
6
+ * and returns a friendly "check your inbox for a 6-digit code" message.
7
+ *
8
+ * This tool is unauthenticated — it does NOT read or use a credential
9
+ * file. The agent calls it both for first-time setup (after `unauthorized`)
10
+ * and for re-authentication (after `token_expired`). In the re-auth case,
11
+ * the agent should pass the email it already has from the cached
12
+ * credential record, NOT prompt the user for it again — the bundled
13
+ * SKILL.md tells the model how to do this.
14
+ *
15
+ * The companion tool `stayfinder_verify` accepts the 6-digit code the
16
+ * user pastes back and writes the resulting token to disk. The agent
17
+ * always calls them as a pair: signup → user reads email → verify.
18
+ */
19
+ /**
20
+ * TypeBox schema for the tool's parameters. The model reads the
21
+ * description fields when deciding how to call the tool, so they're
22
+ * tuned for clarity and to nudge the model toward the right behavior
23
+ * (especially for re-auth — see the email field's description).
24
+ */
25
+ export declare const StayFinderSignupParamsSchema: import("@sinclair/typebox").TObject<{
26
+ email: import("@sinclair/typebox").TString;
27
+ }>;
28
+ /**
29
+ * Factory that builds the AnyAgentTool object the SDK's `api.registerTool`
30
+ * accepts. Takes the plugin api so the tool can read pluginConfig and the
31
+ * plugin version at execute time.
32
+ *
33
+ * Returns an AnyAgentTool-shaped object directly rather than reaching for
34
+ * a typed import — the SDK ships .js without .d.ts for the helper, so
35
+ * we type-erase at the boundary.
36
+ */
37
+ export declare function createStayFinderSignupTool(api: any): any;
38
+ //# sourceMappingURL=stayfinder-signup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stayfinder-signup.d.ts","sourceRoot":"","sources":["../../src/tools/stayfinder-signup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAYH;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B;;EAaxC,CAAC;AAWF;;;;;;;;GAQG;AAEH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CA+CxD"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `stayfinder_signup` bootstrap tool.
3
+ *
4
+ * Drives the first step of the StayFinder onboarding flow: takes an email
5
+ * address from the agent, fires `POST /v1/signup` against the service,
6
+ * and returns a friendly "check your inbox for a 6-digit code" message.
7
+ *
8
+ * This tool is unauthenticated — it does NOT read or use a credential
9
+ * file. The agent calls it both for first-time setup (after `unauthorized`)
10
+ * and for re-authentication (after `token_expired`). In the re-auth case,
11
+ * the agent should pass the email it already has from the cached
12
+ * credential record, NOT prompt the user for it again — the bundled
13
+ * SKILL.md tells the model how to do this.
14
+ *
15
+ * The companion tool `stayfinder_verify` accepts the 6-digit code the
16
+ * user pastes back and writes the resulting token to disk. The agent
17
+ * always calls them as a pair: signup → user reads email → verify.
18
+ */
19
+ import { Type } from '@sinclair/typebox';
20
+ import { AdapterClient } from '../adapter-client.js';
21
+ import { toolTextResult } from '../tool-result.js';
22
+ import { AdapterError, formatErrorForModel } from '../errors.js';
23
+ import { readPluginConfig } from '../plugin-config.js';
24
+ import { validateEmailLoose } from '../validation.js';
25
+ const TOOL_NAME = 'stayfinder_signup';
26
+ /**
27
+ * TypeBox schema for the tool's parameters. The model reads the
28
+ * description fields when deciding how to call the tool, so they're
29
+ * tuned for clarity and to nudge the model toward the right behavior
30
+ * (especially for re-auth — see the email field's description).
31
+ */
32
+ export const StayFinderSignupParamsSchema = Type.Object({
33
+ email: Type.String({
34
+ minLength: 5,
35
+ maxLength: 254,
36
+ description: "The user's email address. A 6-digit verification code will be sent here. " +
37
+ 'For first-time setup, ask the user for their email and pass it. ' +
38
+ 'For RE-AUTHENTICATION (after a `token_expired` error), pass the email from the cached credential record — ' +
39
+ 'do NOT ask the user for their email again, it is already known.',
40
+ }),
41
+ }, { additionalProperties: false });
42
+ const TOOL_DESCRIPTION = 'Start the StayFinder signup flow. Sends a 6-digit verification code to the user\'s email. ' +
43
+ 'Use this when (a) the user wants to set up StayFinder for the first time, OR ' +
44
+ '(b) search_stays returns `unauthorized` (plugin isn\'t configured), OR ' +
45
+ '(c) search_stays returns `token_expired` (the previous token aged out from inactivity — re-auth with the cached email). ' +
46
+ 'After calling this, tell the user a 6-digit code is on its way and ask them to paste the digits back into the chat. ' +
47
+ 'Then call stayfinder_verify with the email and the code. ' +
48
+ 'For case (c) above, call this WITHOUT asking the user for their email first — the email is in the credential record.';
49
+ /**
50
+ * Factory that builds the AnyAgentTool object the SDK's `api.registerTool`
51
+ * accepts. Takes the plugin api so the tool can read pluginConfig and the
52
+ * plugin version at execute time.
53
+ *
54
+ * Returns an AnyAgentTool-shaped object directly rather than reaching for
55
+ * a typed import — the SDK ships .js without .d.ts for the helper, so
56
+ * we type-erase at the boundary.
57
+ */
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ export function createStayFinderSignupTool(api) {
60
+ return {
61
+ name: TOOL_NAME,
62
+ label: 'StayFinder Signup',
63
+ description: TOOL_DESCRIPTION,
64
+ parameters: StayFinderSignupParamsSchema,
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ execute: async (_toolCallId, rawParams) => {
67
+ const email = typeof rawParams?.email === 'string' ? rawParams.email.trim() : '';
68
+ // Local validation first — fail fast on obvious garbage so we don't
69
+ // burn an HTTP call (and a per-IP rate-limit slot on the adapter).
70
+ const emailError = validateEmailLoose(email);
71
+ if (emailError) {
72
+ throw new Error(emailError);
73
+ }
74
+ const config = readPluginConfig(api.pluginConfig);
75
+ const client = new AdapterClient({
76
+ config,
77
+ // No apiToken — signup is unauthenticated
78
+ pluginVersion: api.version ?? '0.0.0',
79
+ });
80
+ let response;
81
+ try {
82
+ response = await client.signup(email);
83
+ }
84
+ catch (err) {
85
+ if (err instanceof AdapterError) {
86
+ throw new Error(formatErrorForModel(err));
87
+ }
88
+ // Network / timeout / unexpected — let the underlying message through
89
+ throw err;
90
+ }
91
+ const message = `Sent! Check ${email} for a 6-digit code from StayFinder. ` +
92
+ `Ask the user to paste the digits back here, then call stayfinder_verify with the email and code. ` +
93
+ `The code expires in ${Math.round(response.expires_in_seconds / 60)} minutes.`;
94
+ return toolTextResult(message, {
95
+ status: response.status,
96
+ email,
97
+ expires_in_seconds: response.expires_in_seconds,
98
+ });
99
+ },
100
+ };
101
+ }
102
+ //# sourceMappingURL=stayfinder-signup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stayfinder-signup.js","sourceRoot":"","sources":["../../src/tools/stayfinder-signup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,SAAS,GAAG,mBAAmB,CAAC;AAEtC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC,MAAM,CACrD;IACE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;QACjB,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,GAAG;QACd,WAAW,EACT,2EAA2E;YAC3E,kEAAkE;YAClE,4GAA4G;YAC5G,iEAAiE;KACpE,CAAC;CACH,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAChC,CAAC;AAEF,MAAM,gBAAgB,GACpB,4FAA4F;IAC5F,+EAA+E;IAC/E,yEAAyE;IACzE,0HAA0H;IAC1H,sHAAsH;IACtH,2DAA2D;IAC3D,sHAAsH,CAAC;AAEzH;;;;;;;;GAQG;AACH,8DAA8D;AAC9D,MAAM,UAAU,0BAA0B,CAAC,GAAQ;IACjD,OAAO;QACL,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,gBAAgB;QAC7B,UAAU,EAAE,4BAA4B;QACxC,8DAA8D;QAC9D,OAAO,EAAE,KAAK,EAAE,WAAmB,EAAE,SAAc,EAAE,EAAE;YACrD,MAAM,KAAK,GAAG,OAAO,SAAS,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAEjF,oEAAoE;YACpE,mEAAmE;YACnE,MAAM,UAAU,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC7C,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC;YAC9B,CAAC;YAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAClD,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC;gBAC/B,MAAM;gBACN,0CAA0C;gBAC1C,aAAa,EAAE,GAAG,CAAC,OAAO,IAAI,OAAO;aACtC,CAAC,CAAC;YAEH,IAAI,QAAQ,CAAC;YACb,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACxC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;oBAChC,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC5C,CAAC;gBACD,sEAAsE;gBACtE,MAAM,GAAG,CAAC;YACZ,CAAC;YAED,MAAM,OAAO,GACX,eAAe,KAAK,uCAAuC;gBAC3D,mGAAmG;gBACnG,uBAAuB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,kBAAkB,GAAG,EAAE,CAAC,WAAW,CAAC;YAEjF,OAAO,cAAc,CAAC,OAAO,EAAE;gBAC7B,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,KAAK;gBACL,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;aAChD,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `stayfinder_verify` bootstrap tool.
3
+ *
4
+ * Exchanges the 6-digit code the user pasted back into the chat for an
5
+ * opaque API token, then writes it to the credential store. The user
6
+ * never sees the token — this tool handles it end-to-end.
7
+ *
8
+ * Always called as the second step after `stayfinder_signup`. The model
9
+ * provides both the email (same one it passed to signup) and the code
10
+ * (the 6 digits the user pasted). The tool:
11
+ *
12
+ * 1. Sanitizes the code (strips whitespace / dashes / stray chars)
13
+ * 2. Calls POST /v1/signup/verify with {email, code}
14
+ * 3. On success: writes the token + tenant metadata to the credential
15
+ * store at ~/.openclaw/credentials/stayfinder.json, then returns a
16
+ * friendly "you're set up" message
17
+ * 4. On error: throws with a model-facing message from formatErrorForModel
18
+ * so the agent knows the concrete next step (retry, send a fresh code,
19
+ * etc.)
20
+ */
21
+ export declare const StayFinderVerifyParamsSchema: import("@sinclair/typebox").TObject<{
22
+ email: import("@sinclair/typebox").TString;
23
+ code: import("@sinclair/typebox").TString;
24
+ }>;
25
+ export declare function createStayFinderVerifyTool(api: any): any;
26
+ //# sourceMappingURL=stayfinder-verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stayfinder-verify.d.ts","sourceRoot":"","sources":["../../src/tools/stayfinder-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAcH,eAAO,MAAM,4BAA4B;;;EAgBxC,CAAC;AAWF,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CA2FxD"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * `stayfinder_verify` bootstrap tool.
3
+ *
4
+ * Exchanges the 6-digit code the user pasted back into the chat for an
5
+ * opaque API token, then writes it to the credential store. The user
6
+ * never sees the token — this tool handles it end-to-end.
7
+ *
8
+ * Always called as the second step after `stayfinder_signup`. The model
9
+ * provides both the email (same one it passed to signup) and the code
10
+ * (the 6 digits the user pasted). The tool:
11
+ *
12
+ * 1. Sanitizes the code (strips whitespace / dashes / stray chars)
13
+ * 2. Calls POST /v1/signup/verify with {email, code}
14
+ * 3. On success: writes the token + tenant metadata to the credential
15
+ * store at ~/.openclaw/credentials/stayfinder.json, then returns a
16
+ * friendly "you're set up" message
17
+ * 4. On error: throws with a model-facing message from formatErrorForModel
18
+ * so the agent knows the concrete next step (retry, send a fresh code,
19
+ * etc.)
20
+ */
21
+ import { Type } from '@sinclair/typebox';
22
+ import { AdapterClient } from '../adapter-client.js';
23
+ import { writeCredential } from '../credential-store.js';
24
+ import { AdapterError, formatErrorForModel } from '../errors.js';
25
+ import { readPluginConfig } from '../plugin-config.js';
26
+ import { toolTextResult } from '../tool-result.js';
27
+ import { sanitizeAndValidateCode, validateEmailLoose } from '../validation.js';
28
+ const TOOL_NAME = 'stayfinder_verify';
29
+ export const StayFinderVerifyParamsSchema = Type.Object({
30
+ email: Type.String({
31
+ minLength: 5,
32
+ maxLength: 254,
33
+ description: 'The same email address used when calling stayfinder_signup.',
34
+ }),
35
+ code: Type.String({
36
+ minLength: 1,
37
+ maxLength: 20,
38
+ description: 'The 6-digit verification code from the email. ' +
39
+ 'Pass exactly 6 decimal digits. Stray spaces or dashes are OK — the plugin strips them.',
40
+ }),
41
+ }, { additionalProperties: false });
42
+ const TOOL_DESCRIPTION = 'Submit the 6-digit verification code the user received in their email. ' +
43
+ 'This exchanges the code for an API token, which the plugin saves to its credential store automatically. ' +
44
+ 'Use this after stayfinder_signup, once the user has pasted their 6-digit code into the chat. ' +
45
+ 'The user only ever pastes the code (six digits). They do not — and cannot — paste a token; ' +
46
+ 'the token is written directly to the credential store and never shown to the user. ' +
47
+ 'Required: email (the same address used in stayfinder_signup), code (six digits).';
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ export function createStayFinderVerifyTool(api) {
50
+ return {
51
+ name: TOOL_NAME,
52
+ label: 'StayFinder Verify',
53
+ description: TOOL_DESCRIPTION,
54
+ parameters: StayFinderVerifyParamsSchema,
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ execute: async (_toolCallId, rawParams) => {
57
+ const email = typeof rawParams?.email === 'string' ? rawParams.email.trim() : '';
58
+ const rawCode = typeof rawParams?.code === 'string' ? rawParams.code : '';
59
+ // ----- Local validation (fail before HTTP) -----
60
+ const emailError = validateEmailLoose(email);
61
+ if (emailError)
62
+ throw new Error(emailError);
63
+ const cleanedCode = sanitizeAndValidateCode(rawCode);
64
+ if (cleanedCode === null) {
65
+ throw new Error('That doesn\'t look like a 6-digit code. ' +
66
+ 'The code is six numbers (like 473829) from the StayFinder email. ' +
67
+ 'Ask the user to copy just the digits.');
68
+ }
69
+ // ----- Call the adapter -----
70
+ const config = readPluginConfig(api.pluginConfig);
71
+ const client = new AdapterClient({
72
+ config,
73
+ // No apiToken — verify is unauthenticated
74
+ pluginVersion: api.version ?? '0.0.0',
75
+ });
76
+ let response;
77
+ try {
78
+ response = await client.signupVerify(email, cleanedCode);
79
+ }
80
+ catch (err) {
81
+ if (err instanceof AdapterError) {
82
+ throw new Error(formatErrorForModel(err));
83
+ }
84
+ throw err;
85
+ }
86
+ // ----- Write credentials to disk -----
87
+ const credential = {
88
+ api_token: response.token,
89
+ saved_at: new Date().toISOString(),
90
+ tenant_id: response.tenant_id,
91
+ email,
92
+ token_kind: response.token_kind,
93
+ expires_at: response.expires_at,
94
+ };
95
+ try {
96
+ await writeCredential(credential);
97
+ }
98
+ catch (err) {
99
+ throw new Error(`Got the token but failed to save it: ${err.message}. ` +
100
+ 'Check file permissions on ~/.openclaw/credentials/ and try again.');
101
+ }
102
+ // ----- Drop the token from memory (defense in depth) -----
103
+ // `response.token` is still in scope but goes out of scope when
104
+ // this function returns. We could set `response.token = ''` to
105
+ // zero it early, but that would require a mutable binding which
106
+ // makes the code less clear. The GC will collect it shortly.
107
+ // ----- Return success message -----
108
+ const expiryNote = response.token_kind === 'ephemeral' && response.expires_at
109
+ ? ` Your access will stay active as long as you keep using it (re-auth after about a week of inactivity).`
110
+ : '';
111
+ const message = `Got it! Your StayFinder access is active. ` +
112
+ `${response.quota_per_hour} searches per hour.${expiryNote} ` +
113
+ `You can now use search_stays to find hotels and vacation rentals.`;
114
+ return toolTextResult(message, {
115
+ status: 'verified',
116
+ tenant_id: response.tenant_id,
117
+ token_kind: response.token_kind,
118
+ expires_at: response.expires_at,
119
+ quota_per_hour: response.quota_per_hour,
120
+ });
121
+ },
122
+ };
123
+ }
124
+ //# sourceMappingURL=stayfinder-verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stayfinder-verify.js","sourceRoot":"","sources":["../../src/tools/stayfinder-verify.ts"],"names":[],"mappings":"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"}