composio-scavio 0.1.1 → 0.3.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/dist/index.js CHANGED
@@ -9,15 +9,14 @@ var str = (d) => z.string().describe(d);
9
9
  var ostr = (d) => z.string().optional().describe(d);
10
10
  var onum = (d) => z.number().optional().describe(d);
11
11
  var obool = (d) => z.boolean().optional().describe(d);
12
- function googleSearchParams(i) {
13
- const params = { query: i.query };
14
- if (i.countryCode != null) params.gl = i.countryCode;
15
- if (i.language != null) params.hl = i.language;
16
- if (i.page != null && i.page > 1) params.start = (i.page - 1) * 10;
17
- if (i.device != null) params.device = i.device;
18
- if (i.nfpr != null) params.nfpr = i.nfpr;
19
- return params;
20
- }
12
+ var gl = ostr("Country of the search (ISO 3166-1 alpha-2, e.g. 'us', 'gb', 'de').");
13
+ var hl = ostr("UI language (ISO 639-1, e.g. 'en').");
14
+ var googleDomain = ostr("Regional Google domain, e.g. 'google.co.uk'.");
15
+ var location = ostr("Canonical location name, e.g. 'Austin, Texas, United States'. Encoded server-side.");
16
+ var device = ostr("Device to emulate: 'desktop' or 'mobile'.");
17
+ var currency = ostr("Currency code (ISO 4217, e.g. 'USD').");
18
+ var checkInDate = str("Check-in date (YYYY-MM-DD).");
19
+ var checkOutDate = str("Check-out date (YYYY-MM-DD).");
21
20
  function buildScavioToolkit(options = {}) {
22
21
  const {
23
22
  apiKey,
@@ -27,7 +26,10 @@ function buildScavioToolkit(options = {}) {
27
26
  enableYoutube = true,
28
27
  enableReddit = true,
29
28
  enableTiktok = true,
29
+ enableTiktokShop = true,
30
30
  enableInstagram = true,
31
+ enableX = true,
32
+ enableLinkedin = true,
31
33
  all = false
32
34
  } = options;
33
35
  const client = new Scavio({ apiKey: apiKey ?? process.env.SCAVIO_API_KEY });
@@ -45,16 +47,239 @@ function buildScavioToolkit(options = {}) {
45
47
  tool(
46
48
  "GOOGLE_SEARCH",
47
49
  "Scavio Google Search",
48
- "Search Google for real-time web results (organic_results, ads, and the AI Overview when present). Costs 1 credit.",
50
+ "Search Google for real-time web results. Returns organic_results (each with title, link and snippet) plus knowledge_graph, related_questions, ads and the AI Overview when Google shows one. The body is flat - read organic_results at the top level, not under data. Costs 1 credit.",
49
51
  z.object({
50
- query: str("The search query."),
51
- countryCode: ostr("Two-letter country code, e.g. 'us'."),
52
- language: ostr("Two-letter language code, e.g. 'en'."),
53
- page: onum("Result page number (1-based)."),
54
- device: ostr("Device profile: 'desktop' or 'mobile'."),
55
- nfpr: obool("Disable auto-correction of the query when true.")
52
+ query: str("The search query (1-500 characters)."),
53
+ gl,
54
+ hl,
55
+ google_domain: googleDomain,
56
+ location,
57
+ device,
58
+ start: onum("Result offset, NOT a page number: 0 is page 1, 10 is page 2, up to 990."),
59
+ time_period: ostr("Restrict results to a recent time window: 'last_hour', 'last_day', 'last_week', 'last_month', or 'last_year'."),
60
+ safe: ostr("Turn SafeSearch on with 'active'."),
61
+ nfpr: obool("Disable spelling correction and auto-fixes when true.")
62
+ }),
63
+ (i) => client.google.search(i)
64
+ ),
65
+ tool(
66
+ "GOOGLE_AI_MODE",
67
+ "Scavio Google AI Mode",
68
+ "Ask Google AI Mode a question. Returns Google's generated answer with its cited sources - use this for a synthesized answer and GOOGLE_SEARCH for a ranked result list. Costs 1 credit.",
69
+ z.object({
70
+ query: str("Question or prompt (1-500 characters)."),
71
+ gl,
72
+ hl,
73
+ google_domain: googleDomain,
74
+ location,
75
+ device,
76
+ safe: ostr("Turn SafeSearch on with 'active'.")
77
+ }),
78
+ (i) => client.google.aiMode(i)
79
+ ),
80
+ tool(
81
+ "GOOGLE_MAPS_SEARCH",
82
+ "Scavio Google Maps Search",
83
+ "Search Google Maps for local businesses and places. Returns local results with ratings, review counts, addresses, phone numbers and place ids. Feed a place_id into GOOGLE_MAPS_PLACE or GOOGLE_MAPS_REVIEWS. Costs 1 credit.",
84
+ z.object({
85
+ query: str("Search query, e.g. 'coffee shops in Austin' (1-500 characters)."),
86
+ ll: ostr("Map centre as '@lat,lng,zoomz'; controls which area results come from. Maps localizes by map centre, not gl."),
87
+ start: onum("Result offset; must be a multiple of 20 (0, 20, 40, ...)."),
88
+ gl,
89
+ hl,
90
+ google_domain: googleDomain
91
+ }),
92
+ (i) => client.google.mapsSearch(i)
93
+ ),
94
+ tool(
95
+ "GOOGLE_MAPS_PLACE",
96
+ "Scavio Google Maps Place",
97
+ "Fetch one Google Maps place in full: hours, address, phone, website, rating, price level and photos. Provide place_id or data_cid. Costs 1 credit.",
98
+ z.object({
99
+ place_id: ostr("Place ID (starts with ChIJ). Provide this or data_cid."),
100
+ data_cid: ostr("Numeric CID. Provide this or place_id.")
101
+ }),
102
+ (i) => client.google.mapsPlace(i)
103
+ ),
104
+ tool(
105
+ "GOOGLE_MAPS_REVIEWS",
106
+ "Scavio Google Maps Reviews",
107
+ "List Google Maps reviews for a place. Provide data_id or place_id. Page with next_page_token; each page costs another credit. Costs 1 credit.",
108
+ z.object({
109
+ place_id: ostr("Place ID (starts with ChIJ). Provide this or data_id."),
110
+ data_id: ostr("Data ID in 0xHEX:0xHEX form. Provide this or place_id."),
111
+ num: onum("Reviews per page (1-20)."),
112
+ sort_by: ostr("Review sort order: 'relevance', 'newest', 'highest_rating', or 'lowest_rating'."),
113
+ next_page_token: ostr("Pagination cursor from a prior response."),
114
+ gl,
115
+ hl,
116
+ google_domain: googleDomain
117
+ }),
118
+ (i) => client.google.mapsReviews(i)
119
+ ),
120
+ tool(
121
+ "GOOGLE_SHOPPING",
122
+ "Scavio Google Shopping",
123
+ "Search Google Shopping. Returns product cards with prices, merchants, ratings and catalog ids across retailers - use this for cross-retailer price comparison and AMAZON_SEARCH or WALMART_SEARCH for a single marketplace. Costs 1 credit.",
124
+ z.object({
125
+ query: str("Product search query (1-500 characters)."),
126
+ min_price: onum("Minimum price filter."),
127
+ max_price: onum("Maximum price filter."),
128
+ sort_by: onum("0 = relevance, 1 = price ascending, 2 = price descending."),
129
+ free_shipping: obool("Only items with free shipping."),
130
+ on_sale: obool("Only items on sale."),
131
+ start: onum("Result offset."),
132
+ gl,
133
+ hl,
134
+ google_domain: googleDomain,
135
+ location,
136
+ device
137
+ }),
138
+ (i) => client.google.shopping(i)
139
+ ),
140
+ tool(
141
+ "GOOGLE_SHOPPING_PRODUCT",
142
+ "Scavio Google Shopping Product",
143
+ "Fetch one Google Shopping product: specs, reviews, and the sellers carrying it. Pass catalog_id together with query for full detail and the seller list. Costs 1 credit.",
144
+ z.object({
145
+ catalog_id: ostr("Durable product catalog id from a shopping search."),
146
+ query: ostr("Product query. Required when catalog_id is set."),
147
+ product_id: ostr("Product id, as an alternative to catalog_id."),
148
+ page_token: ostr("Immersive product page token."),
149
+ sort_by: ostr("Seller sort order: 'base_price', 'total_price', 'promotion', or 'seller_rating'."),
150
+ load_all_stores: obool("Load every available store."),
151
+ more_stores: obool("Fetch additional stores."),
152
+ gl,
153
+ hl,
154
+ google_domain: googleDomain,
155
+ location,
156
+ device: ostr("Device to emulate: 'desktop', 'mobile', or 'tablet'.")
157
+ }),
158
+ (i) => client.google.shoppingProduct(i)
159
+ ),
160
+ tool(
161
+ "GOOGLE_SHOPPING_STORES",
162
+ "Scavio Google Shopping Stores",
163
+ "Fetch the next page of sellers for a Google Shopping product. Continuation of GOOGLE_SHOPPING_PRODUCT: pass the same catalog_id plus that response's next_page_token. Costs 1 credit.",
164
+ z.object({
165
+ catalog_id: str("Durable product catalog id."),
166
+ next_page_token: str("Pagination cursor from GOOGLE_SHOPPING_PRODUCT.")
56
167
  }),
57
- (i) => client.google.search(googleSearchParams(i))
168
+ (i) => client.google.shoppingStores(i)
169
+ ),
170
+ tool(
171
+ "GOOGLE_FLIGHTS",
172
+ "Scavio Google Flights",
173
+ "Search Google Flights. Returns itineraries with prices, airlines, stops, durations and carbon emissions. Airports are IATA codes; dates are YYYY-MM-DD. Costs 1 credit.",
174
+ z.object({
175
+ departure_id: str("Departure IATA code(s), comma-separated allowed, e.g. 'JFK'."),
176
+ arrival_id: str("Arrival IATA code(s), comma-separated allowed, e.g. 'LHR'."),
177
+ outbound_date: str("Outbound date (YYYY-MM-DD)."),
178
+ return_date: ostr("Return date (YYYY-MM-DD). Required when type is 1."),
179
+ type: onum("1 = round trip (default), 2 = one way, 3 = multi-city."),
180
+ adults: onum("Number of adults (1-9)."),
181
+ children: onum("Number of children (0-9)."),
182
+ infants_in_seat: onum("Infants in seat (0-4)."),
183
+ infants_on_lap: onum("Infants on lap (0-4)."),
184
+ travel_class: onum("1 = economy, 2 = premium economy, 3 = business, 4 = first."),
185
+ stops: onum("0 = any, 1 = nonstop, 2 = one stop or fewer, 3 = two or fewer."),
186
+ sort_by: onum("1 = top, 2 = price, 3 = departure, 4 = arrival, 5 = duration, 6 = emissions."),
187
+ include_airlines: ostr("Comma-separated airline or alliance codes to include."),
188
+ exclude_airlines: ostr("Comma-separated airline or alliance codes to exclude."),
189
+ currency,
190
+ gl,
191
+ hl
192
+ }),
193
+ (i) => client.google.flights(i)
194
+ ),
195
+ tool(
196
+ "GOOGLE_HOTELS",
197
+ "Scavio Google Hotels",
198
+ "Search Google Hotels. Returns properties with nightly prices, ratings, amenities and a detail_token for GOOGLE_HOTELS_DETAIL. Phrase the query as '<City> hotels'. Costs 1 credit.",
199
+ z.object({
200
+ query: str("Search query; use a '<City> hotels' form."),
201
+ check_in_date: checkInDate,
202
+ check_out_date: checkOutDate,
203
+ sort_by: onum("3 = lowest price, 8 = highest rating, 13 = most reviewed."),
204
+ min_price: onum("Minimum nightly price."),
205
+ max_price: onum("Maximum nightly price."),
206
+ rating: onum("7 = 3.5+, 8 = 4.0+, 9 = 4.5+."),
207
+ hotel_class: ostr("Comma-separated star ratings (2-5)."),
208
+ amenities: ostr("Comma-separated amenity ids."),
209
+ property_types: ostr("Comma-separated property-type ids, e.g. '12' for vacation rentals."),
210
+ free_cancellation: obool("Only properties with free cancellation."),
211
+ eco_certified: obool("Only eco-certified properties."),
212
+ special_offers: obool("Only properties with special offers."),
213
+ limit: onum("Number of properties to return (1-20)."),
214
+ next_page_token: ostr("Pagination cursor from a prior response."),
215
+ currency,
216
+ gl,
217
+ hl
218
+ }),
219
+ (i) => client.google.hotels(i)
220
+ ),
221
+ tool(
222
+ "GOOGLE_HOTELS_DETAIL",
223
+ "Scavio Google Hotels Detail",
224
+ "Fetch one Google Hotels property in full: room types, per-night prices by provider, amenities, photos and reviews. Needs a detail_token from GOOGLE_HOTELS and the same stay dates. Costs 1 credit.",
225
+ z.object({
226
+ detail_token: str("Property detail token from a hotels listing."),
227
+ check_in_date: checkInDate,
228
+ check_out_date: checkOutDate,
229
+ currency,
230
+ gl,
231
+ hl
232
+ }),
233
+ (i) => client.google.hotelsDetail(i)
234
+ ),
235
+ tool(
236
+ "GOOGLE_NEWS",
237
+ "Scavio Google News",
238
+ "Fetch Google News. Pass query to search, or a topic_token / story_token / publication_token from an earlier response to browse a topic, a full story or one publisher. Costs 1 credit.",
239
+ z.object({
240
+ query: ostr("Keyword search."),
241
+ topic_token: ostr("Browse a news topic."),
242
+ section_token: ostr("Browse a topic section."),
243
+ story_token: ostr("Fetch full coverage of one story."),
244
+ publication_token: ostr("Browse one publication."),
245
+ kgmid: ostr("Knowledge Graph entity id."),
246
+ so: onum("Sort: 0 = relevance, 1 = date. Only applies with query or kgmid."),
247
+ gl,
248
+ hl,
249
+ google_domain: googleDomain
250
+ }),
251
+ (i) => client.google.news(i)
252
+ ),
253
+ tool(
254
+ "GOOGLE_TRENDS",
255
+ "Scavio Google Trends",
256
+ "Fetch Google Trends interest data. Comma-separate the query to compare terms, and pick the dataset with data_type: TIMESERIES for interest over time, GEO_MAP for interest by region, RELATED_QUERIES or RELATED_TOPICS for rising searches. Costs 1 credit.",
257
+ z.object({
258
+ query: str("Search term(s); comma-separated to compare up to five."),
259
+ data_type: ostr("Which trends dataset to return: 'TIMESERIES', 'GEO_MAP', 'GEO_MAP_0', 'RELATED_QUERIES', or 'RELATED_TOPICS'."),
260
+ geo: ostr("Location code, e.g. 'US', 'GB', 'US-CA'. Omit for worldwide."),
261
+ date: ostr("Time range, e.g. 'today 12-m', 'now 7-d'."),
262
+ cat: ostr("Category id."),
263
+ gprop: ostr("Restrict to a Google property: 'images', 'news', 'youtube', or 'froogle'."),
264
+ region: ostr("Resolution for GEO_MAP data: 'COUNTRY', 'REGION', 'DMA', or 'CITY'."),
265
+ tz: ostr("Timezone offset in minutes."),
266
+ hl
267
+ }),
268
+ (i) => client.google.trends(i)
269
+ ),
270
+ tool(
271
+ "GOOGLE_TRENDING",
272
+ "Scavio Google Trending",
273
+ "List what is trending on Google right now for one country. geo is required. Use GOOGLE_TRENDS instead when you already know the term you care about. Costs 1 credit.",
274
+ z.object({
275
+ geo: str("Country code, e.g. 'US'."),
276
+ hours: onum("Trending window in hours: 4, 24, 48 or 168."),
277
+ cat: onum("Category id (0-20)."),
278
+ sort: ostr("Sort order: 'relevance', 'search_volume', 'recency', or 'title'."),
279
+ status: ostr("Filter by trend status: 'all' or 'active'."),
280
+ hl
281
+ }),
282
+ (i) => client.google.trending(i)
58
283
  )
59
284
  );
60
285
  }
@@ -63,39 +288,33 @@ function buildScavioToolkit(options = {}) {
63
288
  tool(
64
289
  "AMAZON_SEARCH",
65
290
  "Scavio Amazon Search",
66
- "Search Amazon for products matching a query.",
291
+ "Search Amazon for products matching a query. Results are unsorted and cannot be filtered by category, merchant or price. Costs 1 credit.",
67
292
  z.object({
68
293
  query: str("The product search query."),
69
- domain: ostr("Amazon domain, e.g. 'amazon.com'."),
70
- country: ostr("Two-letter country code."),
71
- language: ostr("Two-letter language code."),
72
- currency: ostr("Currency code, e.g. 'USD'."),
73
- device: ostr("Device profile: 'desktop' or 'mobile'."),
74
- sort_by: ostr("Sort order for results."),
75
- start_page: onum("First page to return."),
76
- pages: onum("Number of pages to return."),
77
- category_id: ostr("Restrict to an Amazon category id."),
78
- merchant_id: ostr("Restrict to a merchant id."),
79
- zip_code: ostr("Delivery ZIP/postal code."),
80
- autoselect_variant: obool("Auto-select the best product variant when true.")
294
+ country: ostr("Marketplace country code (ISO 3166-1 alpha-2), not a domain: 'us' (default), 'gb' (the UK is gb, not uk), 'ca', 'de', 'fr', 'es', 'it', 'jp', 'in', 'au', 'br', 'mx', 'nl', 'pl', 'se', 'sg', 'ae', 'sa', 'eg', 'cn', 'be', 'tr'. An unknown code falls back to us."),
295
+ page: onum("Result page, 1-based. One page per call, 1 credit each.")
81
296
  }),
82
297
  (i) => client.amazon.search(i)
83
298
  ),
84
299
  tool(
85
300
  "AMAZON_PRODUCT",
86
301
  "Scavio Amazon Product",
87
- "Fetch full Amazon product details by ASIN.",
302
+ "Fetch full Amazon product details by ASIN. price is the buy-box price only. Costs 1 credit.",
88
303
  z.object({
89
304
  asin: str("Amazon Standard Identification Number (ASIN) of the product."),
90
- domain: ostr("Amazon domain, e.g. 'amazon.com'."),
91
- country: ostr("Two-letter country code."),
92
- language: ostr("Two-letter language code."),
93
- currency: ostr("Currency code, e.g. 'USD'."),
94
- device: ostr("Device profile: 'desktop' or 'mobile'."),
95
- zip_code: ostr("Delivery ZIP/postal code."),
96
- autoselect_variant: obool("Auto-select the best product variant when true.")
305
+ country: ostr("Marketplace country code (ISO 3166-1 alpha-2), not a domain: 'us' (default), 'gb' (the UK is gb, not uk), 'ca', 'de', 'fr', 'es', 'it', 'jp', 'in', 'au', 'br', 'mx', 'nl', 'pl', 'se', 'sg', 'ae', 'sa', 'eg', 'cn', 'be', 'tr'. An unknown code falls back to us.")
97
306
  }),
98
307
  (i) => client.amazon.product(i)
308
+ ),
309
+ tool(
310
+ "AMAZON_OFFERS",
311
+ "Scavio Amazon Offers",
312
+ "List every seller offer for one Amazon ASIN: price, seller, condition, shipping, and which offer holds the buy box. Page 1 only. Use this instead of AMAZON_PRODUCT when comparing sellers or checking who owns the buy box. Costs 1 credit.",
313
+ z.object({
314
+ asin: str("Amazon Standard Identification Number (ASIN) of the product."),
315
+ country: ostr("Marketplace country code (ISO 3166-1 alpha-2), not a domain: 'us' (default), 'gb' (the UK is gb, not uk), 'ca', 'de', 'fr', 'es', 'it', 'jp', 'in', 'au', 'br', 'mx', 'nl', 'pl', 'se', 'sg', 'ae', 'sa', 'eg', 'cn', 'be', 'tr'. An unknown code falls back to us.")
316
+ }),
317
+ (i) => client.amazon.offers(i)
99
318
  )
100
319
  );
101
320
  }
@@ -104,7 +323,7 @@ function buildScavioToolkit(options = {}) {
104
323
  tool(
105
324
  "WALMART_SEARCH",
106
325
  "Scavio Walmart Search",
107
- "Search Walmart for products matching a query.",
326
+ "Search Walmart for products matching a query. Costs 1 credit.",
108
327
  z.object({
109
328
  query: str("The product search query."),
110
329
  domain: ostr("Walmart domain."),
@@ -123,7 +342,7 @@ function buildScavioToolkit(options = {}) {
123
342
  tool(
124
343
  "WALMART_PRODUCT",
125
344
  "Scavio Walmart Product",
126
- "Fetch full Walmart product details by product id.",
345
+ "Fetch full Walmart product details by product id. Costs 1 credit.",
127
346
  z.object({
128
347
  product_id: str("Walmart product id."),
129
348
  domain: ostr("Walmart domain."),
@@ -140,13 +359,15 @@ function buildScavioToolkit(options = {}) {
140
359
  tool(
141
360
  "YOUTUBE_SEARCH",
142
361
  "Scavio YouTube Search",
143
- "Search YouTube for videos, channels, or playlists.",
362
+ "Search YouTube for videos, channels, or playlists. Costs 2 credits.",
144
363
  z.object({
145
364
  query: str("The video search query."),
146
- upload_date: ostr("Upload date filter, e.g. 'today', 'week', 'month'."),
147
- type: ostr("Result type, e.g. 'video', 'channel', 'playlist'."),
148
- duration: ostr("Duration filter, e.g. 'short', 'long'."),
149
- sort_by: ostr("Sort order for results."),
365
+ upload_date: ostr("Upload date filter: 'last_hour', 'today', 'this_week', 'this_month', 'this_year'."),
366
+ type: ostr("Result type: 'video', 'channel', 'playlist', or 'movie'."),
367
+ duration: ostr("Duration filter: 'short', 'medium', or 'long'."),
368
+ sort_by: ostr("Sort order: 'relevance', 'date', 'view_count', or 'rating'."),
369
+ features: z.array(z.string()).optional().describe("Feature filters, e.g. ['hd', '4k', 'subtitles', 'creative_commons', 'live', '360', '3d', 'hdr', 'vr180']."),
370
+ cursor: ostr("Pagination cursor from a prior response."),
150
371
  hd: obool("Restrict to HD videos when true."),
151
372
  subtitles: obool("Restrict to videos with subtitles when true."),
152
373
  creative_commons: obool("Restrict to Creative Commons videos when true."),
@@ -155,11 +376,136 @@ function buildScavioToolkit(options = {}) {
155
376
  (i) => client.youtube.search(i)
156
377
  ),
157
378
  tool(
158
- "YOUTUBE_METADATA",
159
- "Scavio YouTube Metadata",
160
- "Fetch metadata for a YouTube video by id.",
161
- z.object({ video_id: str("YouTube video id.") }),
162
- (i) => client.youtube.metadata(i)
379
+ "YOUTUBE_SHORTS",
380
+ "Scavio YouTube Shorts",
381
+ "Search YouTube Shorts. Costs 2 credits.",
382
+ z.object({
383
+ query: str("The Shorts search query."),
384
+ sort_by: ostr("Sort order: 'relevance', 'date', 'view_count', or 'rating'."),
385
+ cursor: ostr("Pagination cursor from a prior response.")
386
+ }),
387
+ (i) => client.youtube.shorts(i)
388
+ ),
389
+ tool(
390
+ "YOUTUBE_SUGGESTIONS",
391
+ "Scavio YouTube Suggestions",
392
+ "Get YouTube search autocomplete suggestions for a partial query. Costs 1 credit.",
393
+ z.object({
394
+ query: str("The partial query to autocomplete."),
395
+ language: ostr("Suggestion language (ISO 639-1, default 'en')."),
396
+ region: ostr("Region code (ISO 3166-1 alpha-2, default 'US').")
397
+ }),
398
+ (i) => client.youtube.suggestions(i)
399
+ ),
400
+ tool(
401
+ "YOUTUBE_VIDEO",
402
+ "Scavio YouTube Video",
403
+ "Fetch full metadata for a YouTube video by id or watch URL. Costs 1 credit.",
404
+ z.object({ video_id: str("YouTube video id or a full watch URL.") }),
405
+ (i) => client.youtube.video(i)
406
+ ),
407
+ tool(
408
+ "YOUTUBE_COMMENTS",
409
+ "Scavio YouTube Comments",
410
+ "List top-level comments on a YouTube video. Costs 1 credit.",
411
+ z.object({
412
+ video_id: str("YouTube video id or a full watch URL."),
413
+ cursor: ostr("Pagination cursor from a prior response.")
414
+ }),
415
+ (i) => client.youtube.comments(i)
416
+ ),
417
+ tool(
418
+ "YOUTUBE_COMMENT_REPLIES",
419
+ "Scavio YouTube Comment Replies",
420
+ "List replies to a YouTube comment using its reply cursor. Costs 1 credit.",
421
+ z.object({
422
+ video_id: str("YouTube video id or a full watch URL."),
423
+ reply_cursor: str("Reply cursor from a parent comment's 'reply_cursor' field."),
424
+ cursor: ostr("Pagination cursor from a prior response.")
425
+ }),
426
+ (i) => client.youtube.commentReplies(i)
427
+ ),
428
+ tool(
429
+ "YOUTUBE_TRANSCRIPT",
430
+ "Scavio YouTube Transcript",
431
+ "Fetch the transcript or timed captions for a YouTube video. Costs 8 credits.",
432
+ z.object({
433
+ video_id: str("YouTube video id or a full watch URL."),
434
+ language: ostr("Caption language code (default 'en')."),
435
+ format: ostr("'text' for a plain transcript or 'srt' for timed subtitles (default 'text').")
436
+ }),
437
+ (i) => client.youtube.transcript(i)
438
+ ),
439
+ tool(
440
+ "YOUTUBE_RELATED",
441
+ "Scavio YouTube Related",
442
+ "List videos related to a YouTube video. Costs 1 credit.",
443
+ z.object({
444
+ video_id: str("YouTube video id or a full watch URL."),
445
+ cursor: ostr("Pagination cursor from a prior response.")
446
+ }),
447
+ (i) => client.youtube.related(i)
448
+ ),
449
+ tool(
450
+ "YOUTUBE_CHANNEL_SEARCH",
451
+ "Scavio YouTube Channel Search",
452
+ "Search YouTube channels by keyword. Costs 1 credit.",
453
+ z.object({
454
+ query: str("The channel search query."),
455
+ cursor: ostr("Pagination cursor from a prior response.")
456
+ }),
457
+ (i) => client.youtube.channelSearch(i)
458
+ ),
459
+ tool(
460
+ "YOUTUBE_CHANNEL",
461
+ "Scavio YouTube Channel",
462
+ "Fetch YouTube channel details by id, @handle, or URL. Costs 1 credit.",
463
+ z.object({ channel_id: str("YouTube channel id, @handle, or channel URL.") }),
464
+ (i) => client.youtube.channel(i)
465
+ ),
466
+ tool(
467
+ "YOUTUBE_CHANNEL_VIDEOS",
468
+ "Scavio YouTube Channel Videos",
469
+ "List videos uploaded by a YouTube channel. Costs 1 credit.",
470
+ z.object({
471
+ channel_id: str("YouTube channel id."),
472
+ cursor: ostr("Pagination cursor from a prior response.")
473
+ }),
474
+ (i) => client.youtube.channelVideos(i)
475
+ ),
476
+ tool(
477
+ "YOUTUBE_CHANNEL_SHORTS",
478
+ "Scavio YouTube Channel Shorts",
479
+ "List Shorts posted by a YouTube channel. Costs 1 credit.",
480
+ z.object({
481
+ channel_id: str("YouTube channel id."),
482
+ cursor: ostr("Pagination cursor from a prior response.")
483
+ }),
484
+ (i) => client.youtube.channelShorts(i)
485
+ ),
486
+ tool(
487
+ "YOUTUBE_CHANNEL_COMMUNITY",
488
+ "Scavio YouTube Channel Community",
489
+ "List community posts from a YouTube channel. Costs 1 credit.",
490
+ z.object({
491
+ channel_id: str("YouTube channel id."),
492
+ cursor: ostr("Pagination cursor from a prior response.")
493
+ }),
494
+ (i) => client.youtube.channelCommunity(i)
495
+ ),
496
+ tool(
497
+ "YOUTUBE_CHANNEL_RESOLVE",
498
+ "Scavio YouTube Channel Resolve",
499
+ "Resolve a YouTube @handle or channel URL to a channel id. Costs 1 credit.",
500
+ z.object({ channel: str("A channel @handle or channel URL to resolve to a channel id.") }),
501
+ (i) => client.youtube.channelResolve(i)
502
+ ),
503
+ tool(
504
+ "YOUTUBE_STREAMS",
505
+ "Scavio YouTube Streams",
506
+ "Fetch playable or downloadable stream formats for a YouTube video. Costs 3 credits.",
507
+ z.object({ video_id: str("YouTube video id or a full watch URL.") }),
508
+ (i) => client.youtube.streams(i)
163
509
  )
164
510
  );
165
511
  }
@@ -168,21 +514,112 @@ function buildScavioToolkit(options = {}) {
168
514
  tool(
169
515
  "REDDIT_SEARCH",
170
516
  "Scavio Reddit Search",
171
- "Search Reddit posts, subreddits, or users.",
517
+ "Search Reddit posts. Returns data.results with next_cursor and has_more; page with cursor. Results cannot be filtered or sorted. Costs 1 credit.",
172
518
  z.object({
173
519
  query: str("The Reddit search query."),
174
- type: ostr("Search type, e.g. 'posts', 'subreddits', 'users'."),
175
- sort: ostr("Sort order, e.g. 'relevance', 'new', 'top'."),
176
- cursor: ostr("Pagination cursor.")
520
+ cursor: ostr("Pagination cursor: pass 'next_cursor' from a prior response.")
177
521
  }),
178
522
  (i) => client.reddit.search(i)
179
523
  ),
524
+ tool(
525
+ "REDDIT_SEARCH_SUGGESTIONS",
526
+ "Scavio Reddit Search Suggestions",
527
+ "Get Reddit search autocomplete suggestions for a query. Returns data.suggestions (plain strings) and total_count. Costs 1 credit.",
528
+ z.object({ query: str("The partial or full query to expand.") }),
529
+ (i) => client.reddit.searchSuggestions(i)
530
+ ),
180
531
  tool(
181
532
  "REDDIT_POST",
182
533
  "Scavio Reddit Post",
183
- "Fetch a Reddit post and its comment thread by URL.",
184
- z.object({ url: str("Full URL of the Reddit post to fetch with its comments.") }),
534
+ "Fetch one Reddit post by URL or post id. Returns a FLAT post object under data (post_id, title, text, url, subreddit, author, score, upvote_ratio, num_comments, created_at, is_nsfw, is_video, thumbnail, media); comments are NOT included - call REDDIT_POST_COMMENTS for those. Costs 1 credit.",
535
+ z.object({
536
+ url: ostr("Full URL of the Reddit post. Provide this or post_id."),
537
+ post_id: ostr("Post fullname (t3_...) or bare id. Provide this or url.")
538
+ }),
185
539
  (i) => client.reddit.post(i)
540
+ ),
541
+ tool(
542
+ "REDDIT_POST_COMMENTS",
543
+ "Scavio Reddit Post Comments",
544
+ "List top-level comments on a Reddit post. Returns data.comments with next_cursor and has_more; each comment carries a reply_cursor for REDDIT_COMMENT_REPLIES. Costs 1 credit.",
545
+ z.object({
546
+ post_id: str("Post fullname (t3_...), bare id, or post URL."),
547
+ sort: ostr("Comment sort order (UPPERCASE): 'HOT', 'NEW', 'TOP', 'BEST', or 'CONTROVERSIAL'. Defaults to TOP."),
548
+ cursor: ostr("Pagination cursor from a prior response.")
549
+ }),
550
+ (i) => client.reddit.postComments(i)
551
+ ),
552
+ tool(
553
+ "REDDIT_COMMENT_REPLIES",
554
+ "Scavio Reddit Comment Replies",
555
+ "List replies to one Reddit comment. cursor is REQUIRED here and must be a comment's 'reply_cursor' from REDDIT_POST_COMMENTS, not a 'next_cursor'. Costs 1 credit.",
556
+ z.object({
557
+ post_id: str("Post fullname (t3_...) the comment belongs to."),
558
+ cursor: str("A comment's 'reply_cursor' from REDDIT_POST_COMMENTS. Required."),
559
+ sort: ostr("Comment sort order (UPPERCASE): 'HOT', 'NEW', 'TOP', 'BEST', or 'CONTROVERSIAL'. Defaults to TOP.")
560
+ }),
561
+ (i) => client.reddit.commentReplies(i)
562
+ ),
563
+ tool(
564
+ "REDDIT_SUBREDDIT",
565
+ "Scavio Reddit Subreddit",
566
+ "Fetch a subreddit's profile: title, description, subscribers, active_count, type, icon, banner and created_at. Costs 1 credit.",
567
+ z.object({ subreddit: str("Subreddit name without the r/ prefix, e.g. 'AskReddit'.") }),
568
+ (i) => client.reddit.subreddit(i)
569
+ ),
570
+ tool(
571
+ "REDDIT_SUBREDDIT_POSTS",
572
+ "Scavio Reddit Subreddit Posts",
573
+ "List posts in a subreddit feed. Returns data.posts with next_cursor and has_more. This feed shape has no text and no thumbnail - use REDDIT_POST for a post's body. Costs 1 credit.",
574
+ z.object({
575
+ subreddit: str("Subreddit name without the r/ prefix."),
576
+ sort: ostr("Feed sort order (UPPERCASE): 'BEST', 'HOT', 'NEW', 'TOP', 'CONTROVERSIAL', or 'RISING'. Defaults to HOT. This is the only endpoint accepting RISING."),
577
+ cursor: ostr("Pagination cursor from a prior response.")
578
+ }),
579
+ (i) => client.reddit.subredditPosts(i)
580
+ ),
581
+ tool(
582
+ "REDDIT_USER",
583
+ "Scavio Reddit User",
584
+ "Fetch a redditor's profile: karma split, account_type, avatar, description, verification flags and created_at. Costs 1 credit.",
585
+ z.object({ username: str("Redditor username without the u/ prefix, e.g. 'spez'.") }),
586
+ (i) => client.reddit.user(i)
587
+ ),
588
+ tool(
589
+ "REDDIT_USER_POSTS",
590
+ "Scavio Reddit User Posts",
591
+ "List posts submitted by a redditor. Returns data.posts with next_cursor and has_more. Costs 1 credit.",
592
+ z.object({
593
+ username: str("Redditor username without the u/ prefix."),
594
+ sort: ostr("Sort order (UPPERCASE): 'HOT', 'NEW', 'TOP', 'BEST', or 'CONTROVERSIAL'. Defaults to NEW."),
595
+ cursor: ostr("Pagination cursor from a prior response.")
596
+ }),
597
+ (i) => client.reddit.userPosts(i)
598
+ ),
599
+ tool(
600
+ "REDDIT_USER_COMMENTS",
601
+ "Scavio Reddit User Comments",
602
+ "List comments written by a redditor. Returns data.comments with next_cursor and has_more; each item nests the post it belongs to. Costs 1 credit.",
603
+ z.object({
604
+ username: str("Redditor username without the u/ prefix."),
605
+ sort: ostr("Sort order (UPPERCASE): 'HOT', 'NEW', 'TOP', 'BEST', or 'CONTROVERSIAL'. Defaults to NEW."),
606
+ cursor: ostr("Pagination cursor from a prior response.")
607
+ }),
608
+ (i) => client.reddit.userComments(i)
609
+ ),
610
+ tool(
611
+ "REDDIT_POPULAR",
612
+ "Scavio Reddit Popular",
613
+ "List the site-wide r/popular feed. Returns data.posts with next_cursor and has_more. Costs 1 credit.",
614
+ z.object({ cursor: ostr("Pagination cursor from a prior response. Omit for page 1.") }),
615
+ (i) => client.reddit.popular(i)
616
+ ),
617
+ tool(
618
+ "REDDIT_TRENDING",
619
+ "Scavio Reddit Trending",
620
+ "List Reddit's currently trending searches. Returns data.trending (query plus raw_query) and total_count. Takes no parameters. Costs 1 credit.",
621
+ z.object({}),
622
+ () => client.reddit.trending()
186
623
  )
187
624
  );
188
625
  }
@@ -191,7 +628,7 @@ function buildScavioToolkit(options = {}) {
191
628
  tool(
192
629
  "TIKTOK_PROFILE",
193
630
  "Scavio TikTok Profile",
194
- "Fetch a TikTok user profile by username or secUid.",
631
+ "Fetch a TikTok user profile by username or secUid. Costs 1 credit.",
195
632
  z.object({
196
633
  username: ostr("TikTok username (without @). Provide this or sec_user_id."),
197
634
  sec_user_id: ostr("TikTok secUid. Provide this or username.")
@@ -201,7 +638,7 @@ function buildScavioToolkit(options = {}) {
201
638
  tool(
202
639
  "TIKTOK_USER_POSTS",
203
640
  "Scavio TikTok User Posts",
204
- "List a TikTok user's posts by secUid.",
641
+ "List a TikTok user's posts by secUid. Costs 1 credit.",
205
642
  z.object({
206
643
  sec_user_id: str("TikTok secUid of the user."),
207
644
  cursor: ostr("Pagination cursor."),
@@ -213,14 +650,14 @@ function buildScavioToolkit(options = {}) {
213
650
  tool(
214
651
  "TIKTOK_VIDEO",
215
652
  "Scavio TikTok Video",
216
- "Fetch a TikTok video by id.",
653
+ "Fetch a TikTok video by id. Costs 1 credit.",
217
654
  z.object({ video_id: str("TikTok video id.") }),
218
655
  (i) => client.tiktok.video(i)
219
656
  ),
220
657
  tool(
221
658
  "TIKTOK_VIDEO_COMMENTS",
222
659
  "Scavio TikTok Video Comments",
223
- "List comments on a TikTok video.",
660
+ "List comments on a TikTok video. Costs 1 credit.",
224
661
  z.object({
225
662
  video_id: str("TikTok video id."),
226
663
  cursor: ostr("Pagination cursor."),
@@ -231,7 +668,7 @@ function buildScavioToolkit(options = {}) {
231
668
  tool(
232
669
  "TIKTOK_COMMENT_REPLIES",
233
670
  "Scavio TikTok Comment Replies",
234
- "List replies to a TikTok video comment.",
671
+ "List replies to a TikTok video comment. Costs 1 credit.",
235
672
  z.object({
236
673
  video_id: str("TikTok video id."),
237
674
  comment_id: str("Parent comment id."),
@@ -243,7 +680,7 @@ function buildScavioToolkit(options = {}) {
243
680
  tool(
244
681
  "TIKTOK_SEARCH_VIDEOS",
245
682
  "Scavio TikTok Search Videos",
246
- "Search TikTok videos by keyword.",
683
+ "Search TikTok videos by keyword. Costs 1 credit.",
247
684
  z.object({
248
685
  keyword: str("Search keyword."),
249
686
  cursor: ostr("Pagination cursor."),
@@ -256,7 +693,7 @@ function buildScavioToolkit(options = {}) {
256
693
  tool(
257
694
  "TIKTOK_SEARCH_USERS",
258
695
  "Scavio TikTok Search Users",
259
- "Search TikTok users by keyword.",
696
+ "Search TikTok users by keyword. Costs 1 credit.",
260
697
  z.object({
261
698
  keyword: str("Search keyword."),
262
699
  cursor: ostr("Pagination cursor."),
@@ -267,7 +704,7 @@ function buildScavioToolkit(options = {}) {
267
704
  tool(
268
705
  "TIKTOK_HASHTAG",
269
706
  "Scavio TikTok Hashtag",
270
- "Fetch a TikTok hashtag by name or id.",
707
+ "Fetch a TikTok hashtag by name or id. Costs 1 credit.",
271
708
  z.object({
272
709
  hashtag_name: ostr("Hashtag name (without #). Provide this or hashtag_id."),
273
710
  hashtag_id: ostr("Hashtag id. Provide this or hashtag_name.")
@@ -277,7 +714,7 @@ function buildScavioToolkit(options = {}) {
277
714
  tool(
278
715
  "TIKTOK_HASHTAG_VIDEOS",
279
716
  "Scavio TikTok Hashtag Videos",
280
- "List videos for a TikTok hashtag by id.",
717
+ "List videos for a TikTok hashtag by id. Costs 1 credit.",
281
718
  z.object({
282
719
  hashtag_id: str("Hashtag id."),
283
720
  cursor: ostr("Pagination cursor."),
@@ -288,7 +725,7 @@ function buildScavioToolkit(options = {}) {
288
725
  tool(
289
726
  "TIKTOK_USER_FOLLOWERS",
290
727
  "Scavio TikTok User Followers",
291
- "List a TikTok user's followers by secUid.",
728
+ "List a TikTok user's followers by secUid. Costs 1 credit.",
292
729
  z.object({
293
730
  sec_user_id: str("TikTok secUid of the user."),
294
731
  count: onum("Number of followers to return."),
@@ -300,7 +737,7 @@ function buildScavioToolkit(options = {}) {
300
737
  tool(
301
738
  "TIKTOK_USER_FOLLOWINGS",
302
739
  "Scavio TikTok User Followings",
303
- "List the accounts a TikTok user follows, by secUid.",
740
+ "List the accounts a TikTok user follows, by secUid. Costs 1 credit.",
304
741
  z.object({
305
742
  sec_user_id: str("TikTok secUid of the user."),
306
743
  count: onum("Number of followings to return."),
@@ -311,12 +748,98 @@ function buildScavioToolkit(options = {}) {
311
748
  )
312
749
  );
313
750
  }
751
+ if (all || enableTiktokShop) {
752
+ tools.push(
753
+ tool(
754
+ "TIKTOK_SHOP_SEARCH",
755
+ "Scavio TikTok Shop Search",
756
+ "Search TikTok Shop products by keyword (US catalog only), up to 30 per page with exact prices, ratings and shop details. One of the three endpoints that return exact prices. Paginate with next_cursor and dedupe by product_id across pages. Costs 1 credit.",
757
+ z.object({
758
+ search: str("Search query (1-200 characters). The keyword field is named 'search', not 'query'."),
759
+ cursor: ostr("Opaque cursor from a prior response's next_cursor.")
760
+ }),
761
+ (i) => client.tiktokShop.search(i)
762
+ ),
763
+ tool(
764
+ "TIKTOK_SHOP_SEARCH_SUGGESTIONS",
765
+ "Scavio TikTok Shop Search Suggestions",
766
+ "Expand a partial TikTok Shop query into keyword suggestions across 8 marketplace regions. Suggestions are not guaranteed prefix matches: a misspelling returns typo corrections, and results can include brand and shop names. Costs 1 credit.",
767
+ z.object({
768
+ search: str("Partial query to expand (1-100 characters). The field is 'search', not 'query'."),
769
+ region: ostr("Marketplace region: 'US' (default), 'GB', 'SG', 'MY', 'PH', 'TH', 'VN', or 'ID'.")
770
+ }),
771
+ (i) => client.tiktokShop.searchSuggestions(i)
772
+ ),
773
+ tool(
774
+ "TIKTOK_SHOP_PRODUCT",
775
+ "Scavio TikTok Shop Product",
776
+ "Full TikTok Shop product detail: description, images, variants with stock, shipping, shop profile, category path and top reviews. Two limits: it does NOT return a price (upstream masks it - get prices from TIKTOK_SHOP_SEARCH, TIKTOK_SHOP_SHOP_PRODUCTS or TIKTOK_SHOP_CATEGORY_PRODUCTS), and it resolves only about 44% of the product ids returned by search, so a 404 is a normal outcome to skip rather than retry. Costs 1 credit.",
777
+ z.object({
778
+ product_id: str("TikTok Shop product id (6-25 digits)."),
779
+ region: ostr("Marketplace region: 'US' (default), 'GB', 'SG', 'MY', 'PH', 'TH', 'VN', or 'ID'.")
780
+ }),
781
+ (i) => client.tiktokShop.product(i)
782
+ ),
783
+ tool(
784
+ "TIKTOK_SHOP_PRODUCT_REVIEWS",
785
+ "Scavio TikTok Shop Product Reviews",
786
+ "Paginated TikTok Shop product reviews with text, images, star histogram and verified-purchase flags, up to 200 per call. total_reviews drifts between calls and must NOT be used to compute a page count - page with has_more instead. Often works for ids TIKTOK_SHOP_PRODUCT cannot resolve. Costs 1 credit.",
787
+ z.object({
788
+ product_id: str("TikTok Shop product id (6-25 digits)."),
789
+ page: onum("1-based page number (1-500, default 1)."),
790
+ page_size: onum("Reviews per page (1-200, default 20)."),
791
+ sort: ostr("'relevant' (default) is text-complete and image-heavy; 'recent' is fresher but far more text-sparse."),
792
+ rating: onum("Only reviews with this star rating (1-5)."),
793
+ has_media: obool("Only reviews with a photo or video. Shares one upstream slot with verified_only, and wins when both are set."),
794
+ verified_only: obool("Only verified purchases. Ignored when has_media is also set."),
795
+ region: ostr("Marketplace region: 'US' (default), 'GB', 'SG', 'MY', 'PH', 'TH', 'VN', or 'ID'.")
796
+ }),
797
+ (i) => client.tiktokShop.productReviews(i)
798
+ ),
799
+ tool(
800
+ "TIKTOK_SHOP_CATEGORIES",
801
+ "Scavio TikTok Shop Categories",
802
+ "The global TikTok Shop category tree: 28 top-level categories, 240 nodes, two levels deep. Category ids are identical in every region and names are always English. Takes no parameters. Costs 1 credit.",
803
+ z.object({}),
804
+ () => client.tiktokShop.categories()
805
+ ),
806
+ tool(
807
+ "TIKTOK_SHOP_CATEGORY_PRODUCTS",
808
+ "Scavio TikTok Shop Category Products",
809
+ "Products listed under a category id from TIKTOK_SHOP_CATEGORIES, with exact prices. Level 1 or 2 ids both work. Page size is inconsistent upstream (15 to 20), so always paginate with next_cursor; when has_more turns false after a few pages that is the end of a shallow listing, not an error. Costs 1 credit.",
810
+ z.object({
811
+ category_id: str("Category id from TIKTOK_SHOP_CATEGORIES; level 1 or 2 both work."),
812
+ cursor: ostr("Opaque cursor from a prior response's next_cursor."),
813
+ region: ostr("Marketplace region, 'US' (default) or 'GB' only - not the 8-region list.")
814
+ }),
815
+ (i) => client.tiktokShop.categoryProducts(i)
816
+ ),
817
+ tool(
818
+ "TIKTOK_SHOP_SHOP_PRODUCTS",
819
+ "Scavio TikTok Shop Shop Products",
820
+ "A shop's TikTok Shop catalog, 30 per page, with exact prices. Shop follower count, location and shop-level rating are not available here - call TIKTOK_SHOP_PRODUCT for the full shop profile. Costs 1 credit.",
821
+ z.object({
822
+ shop_id: str("TikTok Shop seller id (also called seller_id elsewhere on TikTok)."),
823
+ cursor: ostr("Opaque cursor from a prior response's next_cursor."),
824
+ region: ostr("Marketplace region: 'US' (default), 'GB', 'SG', 'MY', 'PH', 'TH', 'VN', or 'ID'.")
825
+ }),
826
+ (i) => client.tiktokShop.shopProducts(i)
827
+ ),
828
+ tool(
829
+ "TIKTOK_SHOP_RESOLVE",
830
+ "Scavio TikTok Shop Resolve",
831
+ "Resolve any TikTok Shop URL or share link to a product_id or shop_id, ready for the other TikTok Shop tools. Accepts shop.tiktok.com product and store pages, tiktok.com/view links, affiliate share links, and vt.tiktok.com short links. Costs 1 credit.",
832
+ z.object({ url: str("A TikTok Shop product or store URL, affiliate share link, or vt.tiktok.com short link.") }),
833
+ (i) => client.tiktokShop.resolve(i)
834
+ )
835
+ );
836
+ }
314
837
  if (all || enableInstagram) {
315
838
  tools.push(
316
839
  tool(
317
840
  "INSTAGRAM_PROFILE",
318
841
  "Scavio Instagram Profile",
319
- "Fetch an Instagram profile by username or user id.",
842
+ "Fetch an Instagram profile by username or user id. Costs 10 credits.",
320
843
  z.object({
321
844
  username: ostr("Instagram username. Provide this or user_id."),
322
845
  user_id: ostr("Instagram user id. Provide this or username.")
@@ -326,7 +849,7 @@ function buildScavioToolkit(options = {}) {
326
849
  tool(
327
850
  "INSTAGRAM_USER_POSTS",
328
851
  "Scavio Instagram User Posts",
329
- "List an Instagram user's posts.",
852
+ "List an Instagram user's posts. Costs 2 credits, the cheapest Instagram endpoint.",
330
853
  z.object({
331
854
  username: ostr("Instagram username. Provide this or user_id."),
332
855
  user_id: ostr("Instagram user id. Provide this or username."),
@@ -338,7 +861,7 @@ function buildScavioToolkit(options = {}) {
338
861
  tool(
339
862
  "INSTAGRAM_USER_REELS",
340
863
  "Scavio Instagram User Reels",
341
- "List an Instagram user's reels.",
864
+ "List an Instagram user's reels. Costs 10 credits.",
342
865
  z.object({
343
866
  username: ostr("Instagram username. Provide this or user_id."),
344
867
  user_id: ostr("Instagram user id. Provide this or username."),
@@ -350,7 +873,7 @@ function buildScavioToolkit(options = {}) {
350
873
  tool(
351
874
  "INSTAGRAM_USER_TAGGED",
352
875
  "Scavio Instagram User Tagged",
353
- "List posts an Instagram user is tagged in.",
876
+ "List posts an Instagram user is tagged in. Costs 10 credits.",
354
877
  z.object({
355
878
  username: ostr("Instagram username. Provide this or user_id."),
356
879
  user_id: ostr("Instagram user id. Provide this or username."),
@@ -362,7 +885,7 @@ function buildScavioToolkit(options = {}) {
362
885
  tool(
363
886
  "INSTAGRAM_USER_STORIES",
364
887
  "Scavio Instagram User Stories",
365
- "Fetch an Instagram user's current stories.",
888
+ "Fetch an Instagram user's current stories. Costs 10 credits.",
366
889
  z.object({
367
890
  username: ostr("Instagram username. Provide this or user_id."),
368
891
  user_id: ostr("Instagram user id. Provide this or username.")
@@ -372,7 +895,7 @@ function buildScavioToolkit(options = {}) {
372
895
  tool(
373
896
  "INSTAGRAM_POST",
374
897
  "Scavio Instagram Post",
375
- "Fetch an Instagram post by URL, media id, or shortcode.",
898
+ "Fetch an Instagram post by URL, media id, or shortcode. Costs 8 credits.",
376
899
  z.object({
377
900
  url: ostr("Post URL. Provide one of url, media_id, or shortcode."),
378
901
  media_id: ostr("Post media id. Provide one of url, media_id, or shortcode."),
@@ -383,7 +906,7 @@ function buildScavioToolkit(options = {}) {
383
906
  tool(
384
907
  "INSTAGRAM_POST_COMMENTS",
385
908
  "Scavio Instagram Post Comments",
386
- "List comments on an Instagram post by shortcode or URL.",
909
+ "List comments on an Instagram post by shortcode or URL. Costs 10 credits.",
387
910
  z.object({
388
911
  shortcode: ostr("Post shortcode. Provide this or url."),
389
912
  url: ostr("Post URL. Provide this or shortcode."),
@@ -395,7 +918,7 @@ function buildScavioToolkit(options = {}) {
395
918
  tool(
396
919
  "INSTAGRAM_COMMENT_REPLIES",
397
920
  "Scavio Instagram Comment Replies",
398
- "List replies to an Instagram post comment.",
921
+ "List replies to an Instagram post comment. Costs 8 credits.",
399
922
  z.object({
400
923
  media_id: str("Post media id."),
401
924
  comment_id: str("Parent comment id."),
@@ -406,7 +929,7 @@ function buildScavioToolkit(options = {}) {
406
929
  tool(
407
930
  "INSTAGRAM_SEARCH_USERS",
408
931
  "Scavio Instagram Search Users",
409
- "Search Instagram users by keyword.",
932
+ "Search Instagram users by keyword. Costs 10 credits.",
410
933
  z.object({
411
934
  keyword: str("Search keyword."),
412
935
  cursor: ostr("Pagination cursor.")
@@ -416,7 +939,7 @@ function buildScavioToolkit(options = {}) {
416
939
  tool(
417
940
  "INSTAGRAM_SEARCH_HASHTAGS",
418
941
  "Scavio Instagram Search Hashtags",
419
- "Search Instagram hashtags by keyword.",
942
+ "Search Instagram hashtags by keyword. Costs 10 credits.",
420
943
  z.object({
421
944
  keyword: str("Search keyword."),
422
945
  cursor: ostr("Pagination cursor.")
@@ -426,7 +949,7 @@ function buildScavioToolkit(options = {}) {
426
949
  tool(
427
950
  "INSTAGRAM_USER_FOLLOWERS",
428
951
  "Scavio Instagram User Followers",
429
- "List an Instagram user's followers.",
952
+ "List an Instagram user's followers. Costs 10 credits.",
430
953
  z.object({
431
954
  username: ostr("Instagram username. Provide this or user_id."),
432
955
  user_id: ostr("Instagram user id. Provide this or username."),
@@ -438,7 +961,7 @@ function buildScavioToolkit(options = {}) {
438
961
  tool(
439
962
  "INSTAGRAM_USER_FOLLOWINGS",
440
963
  "Scavio Instagram User Followings",
441
- "List the accounts an Instagram user follows.",
964
+ "List the accounts an Instagram user follows. Costs 10 credits.",
442
965
  z.object({
443
966
  username: ostr("Instagram username. Provide this or user_id."),
444
967
  user_id: ostr("Instagram user id. Provide this or username."),
@@ -449,9 +972,215 @@ function buildScavioToolkit(options = {}) {
449
972
  )
450
973
  );
451
974
  }
975
+ if (all || enableX) {
976
+ tools.push(
977
+ tool(
978
+ "X_SEARCH",
979
+ "Scavio X Search",
980
+ "Search X (Twitter) posts. Returns data.timeline with next_cursor, prev_cursor and has_more. Costs 1 credit.",
981
+ z.object({
982
+ search: str("Search query (1-500 characters). The field is 'search', not 'query'."),
983
+ search_type: ostr("Result category (Capitalized): 'Top' (default), 'Latest', 'People', 'Photos', or 'Videos'."),
984
+ cursor: ostr("Pagination cursor from a prior response.")
985
+ }),
986
+ (i) => client.x.search(i)
987
+ ),
988
+ tool(
989
+ "X_TWEET",
990
+ "Scavio X Tweet",
991
+ "Fetch one X post by id. Returns the tweet object plus reply_to, in_reply_to_screen_name, in_reply_to_status_id, in_reply_to_user_id and sensitive. Costs 1 credit.",
992
+ z.object({ tweet_id: str("Numeric post id as a string, e.g. '1808168603721650364'.") }),
993
+ (i) => client.x.tweet(i)
994
+ ),
995
+ tool(
996
+ "X_TWEET_COMMENTS",
997
+ "Scavio X Tweet Comments",
998
+ "List replies to an X post. Returns data.timeline with next_cursor, prev_cursor and has_more. rank switches the upstream feed: 'latest' is chronological, 'top' is ranked. Costs 1 credit.",
999
+ z.object({
1000
+ tweet_id: str("Numeric post id as a string."),
1001
+ rank: ostr("Reply ordering (lowercase): 'top' (default, ranked) or 'latest' (chronological)."),
1002
+ cursor: ostr("Pagination cursor from a prior response.")
1003
+ }),
1004
+ (i) => client.x.tweetComments(i)
1005
+ ),
1006
+ tool(
1007
+ "X_TWEET_RETWEETERS",
1008
+ "Scavio X Tweet Retweeters",
1009
+ "List the accounts that reposted an X post. Returns data.retweeters with next_cursor and has_more. Costs 1 credit.",
1010
+ z.object({
1011
+ tweet_id: str("Numeric post id as a string."),
1012
+ cursor: ostr("Pagination cursor from a prior response.")
1013
+ }),
1014
+ (i) => client.x.tweetRetweeters(i)
1015
+ ),
1016
+ tool(
1017
+ "X_USER",
1018
+ "Scavio X User",
1019
+ "Fetch an X profile by handle: followers_count, friends_count, statuses_count, media_count, blue_verified, location, website, avatar, header_image and pinned_tweet_ids. Costs 1 credit.",
1020
+ z.object({ screen_name: str("An X handle WITHOUT the @, e.g. 'elonmusk'.") }),
1021
+ (i) => client.x.user(i)
1022
+ ),
1023
+ tool(
1024
+ "X_USER_TWEETS",
1025
+ "Scavio X User Tweets",
1026
+ "List an X user's posts. Returns data.timeline plus pinned and user; there is no has_more on this endpoint, so page on next_cursor. Costs 1 credit.",
1027
+ z.object({
1028
+ screen_name: str("An X handle WITHOUT the @."),
1029
+ cursor: ostr("Pagination cursor from a prior response.")
1030
+ }),
1031
+ (i) => client.x.userTweets(i)
1032
+ ),
1033
+ tool(
1034
+ "X_USER_REPLIES",
1035
+ "Scavio X User Replies",
1036
+ "List an X user's replies. Returns data.timeline with next_cursor and prev_cursor; no has_more. Costs 1 credit.",
1037
+ z.object({
1038
+ screen_name: str("An X handle WITHOUT the @."),
1039
+ cursor: ostr("Pagination cursor from a prior response.")
1040
+ }),
1041
+ (i) => client.x.userReplies(i)
1042
+ ),
1043
+ tool(
1044
+ "X_USER_MEDIA",
1045
+ "Scavio X User Media",
1046
+ "List an X user's posts that carry photos or videos. Returns data.timeline with next_cursor and prev_cursor; no has_more. Costs 1 credit.",
1047
+ z.object({
1048
+ screen_name: str("An X handle WITHOUT the @."),
1049
+ cursor: ostr("Pagination cursor from a prior response.")
1050
+ }),
1051
+ (i) => client.x.userMedia(i)
1052
+ ),
1053
+ tool(
1054
+ "X_USER_FOLLOWERS",
1055
+ "Scavio X User Followers",
1056
+ "List an X user's followers. Returns data.followers with followers_count, next_cursor and has_more. Costs 1 credit.",
1057
+ z.object({
1058
+ screen_name: str("An X handle WITHOUT the @."),
1059
+ cursor: ostr("Pagination cursor from a prior response.")
1060
+ }),
1061
+ (i) => client.x.userFollowers(i)
1062
+ ),
1063
+ tool(
1064
+ "X_USER_FOLLOWINGS",
1065
+ "Scavio X User Followings",
1066
+ "List the accounts an X user follows. The response array is data.following (singular), not followings, and there is no following_count. Costs 1 credit.",
1067
+ z.object({
1068
+ screen_name: str("An X handle WITHOUT the @."),
1069
+ cursor: ostr("Pagination cursor from a prior response.")
1070
+ }),
1071
+ (i) => client.x.userFollowings(i)
1072
+ ),
1073
+ tool(
1074
+ "X_TRENDING",
1075
+ "Scavio X Trending",
1076
+ "List what is trending on X for one country. Returns data.trends with no cursor and no has_more. Costs 1 credit.",
1077
+ z.object({ country: ostr("Country NAME, not an ISO code, e.g. 'UnitedStates' (the default).") }),
1078
+ (i) => client.x.trending(i)
1079
+ )
1080
+ );
1081
+ }
1082
+ if (all || enableLinkedin) {
1083
+ tools.push(
1084
+ tool(
1085
+ "LINKEDIN_PERSON",
1086
+ "Scavio LinkedIn Person",
1087
+ "Fetch a full LinkedIn profile: headline, about, location, follower and connection counts, current_company, experiences, educations, honors_and_awards, bio_links and similar profiles. Costs 1 credit.",
1088
+ z.object({
1089
+ username: ostr("Public identifier / vanity handle, e.g. 'williamhgates'. Provide this or url."),
1090
+ url: ostr("Full LinkedIn profile URL. Provide this or username.")
1091
+ }),
1092
+ (i) => client.linkedin.person(i)
1093
+ ),
1094
+ tool(
1095
+ "LINKEDIN_PERSON_ABOUT",
1096
+ "Scavio LinkedIn Person About",
1097
+ "The about-only slice of a LinkedIn profile: about, headline, education_summary, experiences, educations, honors_and_awards and bio_links. Same upstream call as LINKEDIN_PERSON, shaped down. Costs 1 credit.",
1098
+ z.object({
1099
+ username: ostr("Public identifier / vanity handle. Provide this or url."),
1100
+ url: ostr("Full LinkedIn profile URL. Provide this or username.")
1101
+ }),
1102
+ (i) => client.linkedin.personAbout(i)
1103
+ ),
1104
+ tool(
1105
+ "LINKEDIN_PERSON_POSTS",
1106
+ "Scavio LinkedIn Person Posts",
1107
+ "A member's LinkedIn feed - their own posts, or posts they commented on or reacted to via type. 50 per page; advance with next_cursor. Costs 10 credits.",
1108
+ z.object({
1109
+ username: ostr("Public identifier / vanity handle. Provide this or url."),
1110
+ url: ostr("Full LinkedIn profile URL. Provide this or username."),
1111
+ type: ostr("Which feed: 'posts' (default), 'comments' (posts they commented on), or 'reactions' (posts they reacted to)."),
1112
+ cursor: ostr("Opaque cursor from a prior response's next_cursor.")
1113
+ }),
1114
+ (i) => client.linkedin.personPosts(i)
1115
+ ),
1116
+ tool(
1117
+ "LINKEDIN_COMPANY",
1118
+ "Scavio LinkedIn Company",
1119
+ "Fetch a LinkedIn company profile: description, website, industries, specialties, employee and follower counts, headquarters, locations, similar and affiliated companies, and featured_employees (a 4-6 person sample, the documented substitute for the retired employee directory). Costs 1 credit.",
1120
+ z.object({
1121
+ company: ostr("Company universal name / slug, e.g. 'microsoft'. Provide this or url."),
1122
+ url: ostr("Full LinkedIn company URL. Provide this or company.")
1123
+ }),
1124
+ (i) => client.linkedin.company(i)
1125
+ ),
1126
+ tool(
1127
+ "LINKEDIN_COMPANY_POSTS",
1128
+ "Scavio LinkedIn Company Posts",
1129
+ "Recent posts from a LinkedIn company page, 50 per page; advance with next_cursor. There is no type selector here - that is person-only. Costs 10 credits.",
1130
+ z.object({
1131
+ company: ostr("Company universal name / slug. Provide this or url."),
1132
+ url: ostr("Full LinkedIn company URL. Provide this or company."),
1133
+ cursor: ostr("Opaque cursor from a prior response's next_cursor.")
1134
+ }),
1135
+ (i) => client.linkedin.companyPosts(i)
1136
+ ),
1137
+ tool(
1138
+ "LINKEDIN_SEARCH_JOBS",
1139
+ "Scavio LinkedIn Search Jobs",
1140
+ "Search LinkedIn job listings, 25 per page; advance with next_cursor. Upstream rotates its result set so pages overlap - dedupe by job id. Costs 10 credits.",
1141
+ z.object({
1142
+ search: str("Search keyword, e.g. 'software engineer'. The field is 'search', not 'query'."),
1143
+ location: ostr("Geographic filter; omit to search everywhere."),
1144
+ cursor: ostr("Opaque cursor from a prior response's next_cursor.")
1145
+ }),
1146
+ (i) => client.linkedin.searchJobs(i)
1147
+ ),
1148
+ tool(
1149
+ "LINKEDIN_JOB",
1150
+ "Scavio LinkedIn Job",
1151
+ "Full detail for one LinkedIn job listing, including the hiring company. The most expensive endpoint in the API - about 1 in 5 ids from LINKEDIN_SEARCH_JOBS has no detail record upstream and answers 404 unbilled, so skip rather than retry. Costs 30 credits.",
1152
+ z.object({
1153
+ job_id: ostr("Job listing id, e.g. '4415427228'. Provide this or url."),
1154
+ url: ostr("Full LinkedIn job URL. Provide this or job_id.")
1155
+ }),
1156
+ (i) => client.linkedin.job(i)
1157
+ ),
1158
+ tool(
1159
+ "LINKEDIN_POST",
1160
+ "Scavio LinkedIn Post",
1161
+ "Full detail for one LinkedIn post: text, media, hashtags, tagged companies and people, reaction and comment counts, author, and its top visible comments. Costs 1 credit.",
1162
+ z.object({
1163
+ post_id: ostr("Post id or activity urn, e.g. '7488618410256523265'. Provide this or url."),
1164
+ url: ostr("Full LinkedIn post URL. Provide this or post_id.")
1165
+ }),
1166
+ (i) => client.linkedin.post(i)
1167
+ ),
1168
+ tool(
1169
+ "LINKEDIN_POST_COMMENTS",
1170
+ "Scavio LinkedIn Post Comments",
1171
+ "Comments on a LinkedIn post with their replies. This is the only LinkedIn endpoint paginated by a 1-based integer page rather than a cursor, and page size varies upstream - keep paging until a page comes back empty. Costs 10 credits.",
1172
+ z.object({
1173
+ post_id: ostr("Post id or activity urn. Provide this or url."),
1174
+ url: ostr("Full LinkedIn post URL. Provide this or post_id."),
1175
+ page: onum("1-based page number (default 1). Page size varies, so page until empty.")
1176
+ }),
1177
+ (i) => client.linkedin.postComments(i)
1178
+ )
1179
+ );
1180
+ }
452
1181
  return experimental_createToolkit("SCAVIO", {
453
1182
  name: "Scavio",
454
- description: "Real-time structured search over Google, YouTube, Amazon, Walmart, Reddit, TikTok, and Instagram.",
1183
+ description: "Real-time structured search over Google (SERP, AI Mode, Maps, Shopping, Flights, Hotels, News, Trends), YouTube, Amazon, Walmart, Reddit, TikTok, TikTok Shop, Instagram, X and LinkedIn.",
455
1184
  tools
456
1185
  });
457
1186
  }