firecrawl-cli 1.16.2 → 1.17.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🔥 Firecrawl CLI
2
2
 
3
- Command-line interface for Firecrawl. Search, scrape, and interact with the web directly from your terminal.
3
+ Command-line interface for Firecrawl. Search, scrape, interact, crawl, map, and run agent jobs directly from your terminal.
4
4
 
5
5
  ## Installation
6
6
 
@@ -20,7 +20,7 @@ npx -y firecrawl-cli@1.16.2 init -y --browser
20
20
 
21
21
  ### Setup Skills and MCP
22
22
 
23
- If you are using an AI coding agent like Claude Code, you can also install the skill individually with:
23
+ If you are using an AI coding agent like Claude Code, you can also install the skills manually with:
24
24
 
25
25
  ```bash
26
26
  firecrawl setup skills
@@ -32,7 +32,7 @@ This installs skills globally across all detected coding editors by default. Use
32
32
 
33
33
  The init command installs both sets of Firecrawl agent skills into AI coding agents (Cursor, Claude Code, Windsurf, etc.):
34
34
 
35
- - **CLI skills** — teach agents how to use the Firecrawl CLI for live web work (search, scrape, interact, map, crawl)
35
+ - **CLI skills** — teach agents how to use the Firecrawl CLI for live web work (search, scrape, interact, map, crawl, agent)
36
36
  - **Build skills** — teach agents how to integrate Firecrawl into application code (choose endpoints, wire SDKs, set up API keys)
37
37
 
38
38
  To reinstall skills manually:
@@ -18,18 +18,30 @@ vitest_1.vi.mock('../../utils/client', async () => {
18
18
  });
19
19
  (0, vitest_1.describe)('executeSearch', () => {
20
20
  let mockClient;
21
+ let mockHttpPost;
22
+ // Wrap a payload in the axios envelope returned by `client.http.post`.
23
+ // Mirrors the `/v2/search` response shape:
24
+ // { success, data: { web?, news?, images? }, id?, creditsUsed?, warning? }
25
+ const mockSearchResponse = (payload, extras = {}) => {
26
+ const inner = {
27
+ success: true,
28
+ data: Array.isArray(payload) ? { web: payload } : payload,
29
+ ...extras,
30
+ };
31
+ return { data: inner };
32
+ };
21
33
  (0, vitest_1.beforeEach)(() => {
22
34
  (0, mock_client_1.setupTest)();
23
- // Initialize config with test API key
24
35
  (0, config_1.initializeConfig)({
25
36
  apiKey: 'test-api-key',
26
37
  apiUrl: 'https://api.firecrawl.dev',
27
38
  });
28
- // Create mock client
39
+ mockHttpPost = vitest_1.vi.fn();
29
40
  mockClient = {
30
- search: vitest_1.vi.fn(),
41
+ http: {
42
+ post: mockHttpPost,
43
+ },
31
44
  };
32
- // Mock getClient to return our mock
33
45
  vitest_1.vi.mocked(client_1.getClient).mockReturnValue(mockClient);
34
46
  });
35
47
  (0, vitest_1.afterEach)(() => {
@@ -37,25 +49,28 @@ vitest_1.vi.mock('../../utils/client', async () => {
37
49
  vitest_1.vi.clearAllMocks();
38
50
  });
39
51
  (0, vitest_1.describe)('API call generation', () => {
40
- (0, vitest_1.it)('should call search with correct query and default options', async () => {
41
- const mockResponse = {
52
+ (0, vitest_1.it)('should call /v2/search with correct query and default options', async () => {
53
+ mockHttpPost.mockResolvedValue(mockSearchResponse({
42
54
  web: [
43
- { url: 'https://example.com', title: 'Example', description: 'Test' },
55
+ {
56
+ url: 'https://example.com',
57
+ title: 'Example',
58
+ description: 'Test',
59
+ },
44
60
  ],
45
- };
46
- mockClient.search.mockResolvedValue(mockResponse);
61
+ }));
47
62
  await (0, search_1.executeSearch)({
48
63
  query: 'test query',
49
64
  });
50
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledTimes(1);
51
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('test query', {
65
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledTimes(1);
66
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', {
67
+ query: 'test query',
52
68
  limit: undefined,
53
69
  integration: 'cli',
54
70
  });
55
71
  });
56
72
  (0, vitest_1.it)('should pass apiUrl to getClient when provided', async () => {
57
- const mockResponse = { web: [] };
58
- mockClient.search.mockResolvedValue(mockResponse);
73
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
59
74
  await (0, search_1.executeSearch)({
60
75
  query: 'test query',
61
76
  apiUrl: 'http://localhost:3002',
@@ -66,8 +81,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
66
81
  });
67
82
  });
68
83
  (0, vitest_1.it)('should pass both apiKey and apiUrl to getClient when provided', async () => {
69
- const mockResponse = { web: [] };
70
- mockClient.search.mockResolvedValue(mockResponse);
84
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
71
85
  await (0, search_1.executeSearch)({
72
86
  query: 'test query',
73
87
  apiKey: 'fc-custom-key',
@@ -79,157 +93,157 @@ vitest_1.vi.mock('../../utils/client', async () => {
79
93
  });
80
94
  });
81
95
  (0, vitest_1.it)('should include limit option when provided', async () => {
82
- const mockResponse = { web: [] };
83
- mockClient.search.mockResolvedValue(mockResponse);
96
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
84
97
  await (0, search_1.executeSearch)({
85
98
  query: 'AI news',
86
99
  limit: 10,
87
100
  });
88
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('AI news', vitest_1.expect.objectContaining({
101
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
102
+ query: 'AI news',
89
103
  limit: 10,
90
104
  }));
91
105
  });
92
106
  (0, vitest_1.it)('should include sources option when provided', async () => {
93
- const mockResponse = { web: [], images: [], news: [] };
94
- mockClient.search.mockResolvedValue(mockResponse);
107
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [], images: [], news: [] }));
95
108
  await (0, search_1.executeSearch)({
96
109
  query: 'test query',
97
110
  sources: ['web', 'images', 'news'],
98
111
  });
99
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('test query', vitest_1.expect.objectContaining({
112
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
113
+ query: 'test query',
100
114
  sources: [{ type: 'web' }, { type: 'images' }, { type: 'news' }],
101
115
  }));
102
116
  });
103
117
  (0, vitest_1.it)('should include single source correctly', async () => {
104
- const mockResponse = { news: [] };
105
- mockClient.search.mockResolvedValue(mockResponse);
118
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ news: [] }));
106
119
  await (0, search_1.executeSearch)({
107
120
  query: 'tech news',
108
121
  sources: ['news'],
109
122
  });
110
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('tech news', vitest_1.expect.objectContaining({
123
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
124
+ query: 'tech news',
111
125
  sources: [{ type: 'news' }],
112
126
  }));
113
127
  });
114
128
  (0, vitest_1.it)('should include categories option when provided', async () => {
115
- const mockResponse = { web: [] };
116
- mockClient.search.mockResolvedValue(mockResponse);
129
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
117
130
  await (0, search_1.executeSearch)({
118
131
  query: 'web scraping python',
119
132
  categories: ['github'],
120
133
  });
121
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('web scraping python', vitest_1.expect.objectContaining({
134
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
135
+ query: 'web scraping python',
122
136
  categories: [{ type: 'github' }],
123
137
  }));
124
138
  });
125
139
  (0, vitest_1.it)('should include multiple categories correctly', async () => {
126
- const mockResponse = { web: [] };
127
- mockClient.search.mockResolvedValue(mockResponse);
140
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
128
141
  await (0, search_1.executeSearch)({
129
142
  query: 'transformer architecture',
130
143
  categories: ['research', 'pdf'],
131
144
  });
132
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('transformer architecture', vitest_1.expect.objectContaining({
145
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
146
+ query: 'transformer architecture',
133
147
  categories: [{ type: 'research' }, { type: 'pdf' }],
134
148
  }));
135
149
  });
136
150
  (0, vitest_1.it)('should include tbs (time-based search) option when provided', async () => {
137
- const mockResponse = { web: [] };
138
- mockClient.search.mockResolvedValue(mockResponse);
151
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
139
152
  await (0, search_1.executeSearch)({
140
153
  query: 'AI announcements',
141
154
  tbs: 'qdr:d', // Past day
142
155
  });
143
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('AI announcements', vitest_1.expect.objectContaining({
156
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
157
+ query: 'AI announcements',
144
158
  tbs: 'qdr:d',
145
159
  }));
146
160
  });
147
161
  (0, vitest_1.it)('should include location option when provided', async () => {
148
- const mockResponse = { web: [] };
149
- mockClient.search.mockResolvedValue(mockResponse);
162
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
150
163
  await (0, search_1.executeSearch)({
151
164
  query: 'restaurants',
152
165
  location: 'San Francisco,California,United States',
153
166
  });
154
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('restaurants', vitest_1.expect.objectContaining({
167
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
168
+ query: 'restaurants',
155
169
  location: 'San Francisco,California,United States',
156
170
  }));
157
171
  });
158
172
  (0, vitest_1.it)('should include country option when provided', async () => {
159
- const mockResponse = { web: [] };
160
- mockClient.search.mockResolvedValue(mockResponse);
173
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
161
174
  await (0, search_1.executeSearch)({
162
175
  query: 'local news',
163
176
  country: 'DE',
164
177
  });
165
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('local news', vitest_1.expect.objectContaining({
178
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
179
+ query: 'local news',
166
180
  country: 'DE',
167
181
  }));
168
182
  });
169
183
  (0, vitest_1.it)('should include timeout option when provided', async () => {
170
- const mockResponse = { web: [] };
171
- mockClient.search.mockResolvedValue(mockResponse);
184
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
172
185
  await (0, search_1.executeSearch)({
173
186
  query: 'test query',
174
187
  timeout: 30000,
175
188
  });
176
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('test query', vitest_1.expect.objectContaining({
189
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
190
+ query: 'test query',
177
191
  timeout: 30000,
178
192
  }));
179
193
  });
180
194
  (0, vitest_1.it)('should include ignoreInvalidUrls option when provided', async () => {
181
- const mockResponse = { web: [] };
182
- mockClient.search.mockResolvedValue(mockResponse);
195
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
183
196
  await (0, search_1.executeSearch)({
184
197
  query: 'test query',
185
198
  ignoreInvalidUrls: true,
186
199
  });
187
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('test query', vitest_1.expect.objectContaining({
200
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
201
+ query: 'test query',
188
202
  ignoreInvalidURLs: true,
189
203
  }));
190
204
  });
191
205
  (0, vitest_1.it)('should include scrape options when scrape is enabled', async () => {
192
- const mockResponse = {
206
+ mockHttpPost.mockResolvedValue(mockSearchResponse({
193
207
  web: [{ url: 'https://example.com', markdown: '# Test' }],
194
- };
195
- mockClient.search.mockResolvedValue(mockResponse);
208
+ }));
196
209
  await (0, search_1.executeSearch)({
197
210
  query: 'firecrawl tutorials',
198
211
  scrape: true,
199
212
  });
200
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('firecrawl tutorials', vitest_1.expect.objectContaining({
213
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
214
+ query: 'firecrawl tutorials',
201
215
  scrapeOptions: {
202
216
  formats: [{ type: 'markdown' }],
203
217
  },
204
218
  }));
205
219
  });
206
220
  (0, vitest_1.it)('should include custom scrape formats when provided', async () => {
207
- const mockResponse = {
221
+ mockHttpPost.mockResolvedValue(mockSearchResponse({
208
222
  web: [{ url: 'https://example.com', markdown: '# Test', links: [] }],
209
- };
210
- mockClient.search.mockResolvedValue(mockResponse);
223
+ }));
211
224
  await (0, search_1.executeSearch)({
212
225
  query: 'API docs',
213
226
  scrape: true,
214
227
  scrapeFormats: ['markdown', 'links'],
215
228
  });
216
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('API docs', vitest_1.expect.objectContaining({
229
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
230
+ query: 'API docs',
217
231
  scrapeOptions: {
218
232
  formats: [{ type: 'markdown' }, { type: 'links' }],
219
233
  },
220
234
  }));
221
235
  });
222
236
  (0, vitest_1.it)('should include onlyMainContent in scrape options when provided', async () => {
223
- const mockResponse = {
237
+ mockHttpPost.mockResolvedValue(mockSearchResponse({
224
238
  web: [{ url: 'https://example.com', markdown: '# Test' }],
225
- };
226
- mockClient.search.mockResolvedValue(mockResponse);
239
+ }));
227
240
  await (0, search_1.executeSearch)({
228
241
  query: 'test query',
229
242
  scrape: true,
230
243
  onlyMainContent: true,
231
244
  });
232
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('test query', vitest_1.expect.objectContaining({
245
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({
246
+ query: 'test query',
233
247
  scrapeOptions: {
234
248
  formats: [{ type: 'markdown' }],
235
249
  onlyMainContent: true,
@@ -237,11 +251,10 @@ vitest_1.vi.mock('../../utils/client', async () => {
237
251
  }));
238
252
  });
239
253
  (0, vitest_1.it)('should combine all options correctly', async () => {
240
- const mockResponse = {
254
+ mockHttpPost.mockResolvedValue(mockSearchResponse({
241
255
  web: [{ url: 'https://example.com', markdown: '# Test' }],
242
256
  news: [{ url: 'https://news.example.com', title: 'News' }],
243
- };
244
- mockClient.search.mockResolvedValue(mockResponse);
257
+ }));
245
258
  await (0, search_1.executeSearch)({
246
259
  query: 'comprehensive test',
247
260
  limit: 20,
@@ -255,7 +268,8 @@ vitest_1.vi.mock('../../utils/client', async () => {
255
268
  scrapeFormats: ['markdown', 'links'],
256
269
  onlyMainContent: true,
257
270
  });
258
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('comprehensive test', {
271
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', {
272
+ query: 'comprehensive test',
259
273
  limit: 20,
260
274
  integration: 'cli',
261
275
  sources: [{ type: 'web' }, { type: 'news' }],
@@ -273,74 +287,62 @@ vitest_1.vi.mock('../../utils/client', async () => {
273
287
  });
274
288
  (0, vitest_1.describe)('Response handling', () => {
275
289
  (0, vitest_1.it)('should return success result with web results', async () => {
276
- const mockResponse = {
277
- web: [
278
- {
279
- url: 'https://example.com',
280
- title: 'Example',
281
- description: 'Test description',
282
- },
283
- {
284
- url: 'https://example2.com',
285
- title: 'Example 2',
286
- description: 'Another test',
287
- },
288
- ],
289
- };
290
- mockClient.search.mockResolvedValue(mockResponse);
290
+ const web = [
291
+ {
292
+ url: 'https://example.com',
293
+ title: 'Example',
294
+ description: 'Test description',
295
+ },
296
+ {
297
+ url: 'https://example2.com',
298
+ title: 'Example 2',
299
+ description: 'Another test',
300
+ },
301
+ ];
302
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web }));
291
303
  const result = await (0, search_1.executeSearch)({
292
304
  query: 'test query',
293
305
  });
294
306
  (0, vitest_1.expect)(result.success).toBe(true);
295
- (0, vitest_1.expect)(result.data).toEqual({
296
- web: mockResponse.web,
297
- });
307
+ (0, vitest_1.expect)(result.data).toEqual({ web });
298
308
  });
299
309
  (0, vitest_1.it)('should return success result with image results', async () => {
300
- const mockResponse = {
301
- images: [
302
- {
303
- imageUrl: 'https://example.com/image.jpg',
304
- url: 'https://example.com',
305
- title: 'Image 1',
306
- imageWidth: 800,
307
- imageHeight: 600,
308
- },
309
- ],
310
- };
311
- mockClient.search.mockResolvedValue(mockResponse);
310
+ const images = [
311
+ {
312
+ imageUrl: 'https://example.com/image.jpg',
313
+ url: 'https://example.com',
314
+ title: 'Image 1',
315
+ imageWidth: 800,
316
+ imageHeight: 600,
317
+ },
318
+ ];
319
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ images }));
312
320
  const result = await (0, search_1.executeSearch)({
313
321
  query: 'landscapes',
314
322
  sources: ['images'],
315
323
  });
316
324
  (0, vitest_1.expect)(result.success).toBe(true);
317
- (0, vitest_1.expect)(result.data).toEqual({
318
- images: mockResponse.images,
319
- });
325
+ (0, vitest_1.expect)(result.data).toEqual({ images });
320
326
  });
321
327
  (0, vitest_1.it)('should return success result with news results', async () => {
322
- const mockResponse = {
323
- news: [
324
- {
325
- url: 'https://news.example.com',
326
- title: 'Breaking News',
327
- snippet: 'Something happened',
328
- date: '2024-01-15',
329
- },
330
- ],
331
- };
332
- mockClient.search.mockResolvedValue(mockResponse);
328
+ const news = [
329
+ {
330
+ url: 'https://news.example.com',
331
+ title: 'Breaking News',
332
+ snippet: 'Something happened',
333
+ date: '2024-01-15',
334
+ },
335
+ ];
336
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ news }));
333
337
  const result = await (0, search_1.executeSearch)({
334
338
  query: 'tech news',
335
339
  sources: ['news'],
336
340
  });
337
341
  (0, vitest_1.expect)(result.success).toBe(true);
338
- (0, vitest_1.expect)(result.data).toEqual({
339
- news: mockResponse.news,
340
- });
342
+ (0, vitest_1.expect)(result.data).toEqual({ news });
341
343
  });
342
344
  (0, vitest_1.it)('should handle combined results from multiple sources', async () => {
343
- const mockResponse = {
345
+ const payload = {
344
346
  web: [{ url: 'https://example.com', title: 'Web Result' }],
345
347
  images: [
346
348
  {
@@ -350,20 +352,16 @@ vitest_1.vi.mock('../../utils/client', async () => {
350
352
  ],
351
353
  news: [{ url: 'https://news.example.com', title: 'News' }],
352
354
  };
353
- mockClient.search.mockResolvedValue(mockResponse);
355
+ mockHttpPost.mockResolvedValue(mockSearchResponse(payload));
354
356
  const result = await (0, search_1.executeSearch)({
355
357
  query: 'machine learning',
356
358
  sources: ['web', 'images', 'news'],
357
359
  });
358
360
  (0, vitest_1.expect)(result.success).toBe(true);
359
- (0, vitest_1.expect)(result.data).toEqual({
360
- web: mockResponse.web,
361
- images: mockResponse.images,
362
- news: mockResponse.news,
363
- });
361
+ (0, vitest_1.expect)(result.data).toEqual(payload);
364
362
  });
365
363
  (0, vitest_1.it)('should handle response with scraped content', async () => {
366
- const mockResponse = {
364
+ mockHttpPost.mockResolvedValue(mockSearchResponse({
367
365
  web: [
368
366
  {
369
367
  url: 'https://example.com',
@@ -371,8 +369,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
371
369
  markdown: '# Page Content\n\nThis is the content.',
372
370
  },
373
371
  ],
374
- };
375
- mockClient.search.mockResolvedValue(mockResponse);
372
+ }));
376
373
  const result = await (0, search_1.executeSearch)({
377
374
  query: 'test',
378
375
  scrape: true,
@@ -380,39 +377,8 @@ vitest_1.vi.mock('../../utils/client', async () => {
380
377
  (0, vitest_1.expect)(result.success).toBe(true);
381
378
  (0, vitest_1.expect)(result.data?.web?.[0].markdown).toBe('# Page Content\n\nThis is the content.');
382
379
  });
383
- (0, vitest_1.it)('should handle nested data response format', async () => {
384
- const mockResponse = {
385
- data: {
386
- web: [{ url: 'https://example.com', title: 'Test' }],
387
- },
388
- };
389
- mockClient.search.mockResolvedValue(mockResponse);
390
- const result = await (0, search_1.executeSearch)({
391
- query: 'test',
392
- });
393
- (0, vitest_1.expect)(result.success).toBe(true);
394
- (0, vitest_1.expect)(result.data?.web).toEqual([
395
- { url: 'https://example.com', title: 'Test' },
396
- ]);
397
- });
398
- (0, vitest_1.it)('should handle array response format (legacy)', async () => {
399
- const mockResponse = [
400
- { url: 'https://example.com', title: 'Test 1' },
401
- { url: 'https://example2.com', title: 'Test 2' },
402
- ];
403
- mockClient.search.mockResolvedValue(mockResponse);
404
- const result = await (0, search_1.executeSearch)({
405
- query: 'test',
406
- });
407
- (0, vitest_1.expect)(result.success).toBe(true);
408
- (0, vitest_1.expect)(result.data?.web).toEqual(mockResponse);
409
- });
410
380
  (0, vitest_1.it)('should include warning in result when present', async () => {
411
- const mockResponse = {
412
- web: [{ url: 'https://example.com', title: 'Test' }],
413
- warning: 'Some warning message',
414
- };
415
- mockClient.search.mockResolvedValue(mockResponse);
381
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [{ url: 'https://example.com', title: 'Test' }] }, { warning: 'Some warning message' }));
416
382
  const result = await (0, search_1.executeSearch)({
417
383
  query: 'test',
418
384
  });
@@ -420,11 +386,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
420
386
  (0, vitest_1.expect)(result.warning).toBe('Some warning message');
421
387
  });
422
388
  (0, vitest_1.it)('should include id in result when present', async () => {
423
- const mockResponse = {
424
- web: [{ url: 'https://example.com', title: 'Test' }],
425
- id: 'search-123',
426
- };
427
- mockClient.search.mockResolvedValue(mockResponse);
389
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [{ url: 'https://example.com', title: 'Test' }] }, { id: 'search-123' }));
428
390
  const result = await (0, search_1.executeSearch)({
429
391
  query: 'test',
430
392
  });
@@ -432,11 +394,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
432
394
  (0, vitest_1.expect)(result.id).toBe('search-123');
433
395
  });
434
396
  (0, vitest_1.it)('should include creditsUsed in result when present', async () => {
435
- const mockResponse = {
436
- web: [{ url: 'https://example.com', title: 'Test' }],
437
- creditsUsed: 5,
438
- };
439
- mockClient.search.mockResolvedValue(mockResponse);
397
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [{ url: 'https://example.com', title: 'Test' }] }, { creditsUsed: 5 }));
440
398
  const result = await (0, search_1.executeSearch)({
441
399
  query: 'test',
442
400
  });
@@ -444,8 +402,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
444
402
  (0, vitest_1.expect)(result.creditsUsed).toBe(5);
445
403
  });
446
404
  (0, vitest_1.it)('should handle empty results', async () => {
447
- const mockResponse = {};
448
- mockClient.search.mockResolvedValue(mockResponse);
405
+ mockHttpPost.mockResolvedValue(mockSearchResponse({}));
449
406
  const result = await (0, search_1.executeSearch)({
450
407
  query: 'nonexistent content xyz123',
451
408
  });
@@ -454,7 +411,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
454
411
  });
455
412
  (0, vitest_1.it)('should return error result when search fails', async () => {
456
413
  const errorMessage = 'API Error: Rate limit exceeded';
457
- mockClient.search.mockRejectedValue(new Error(errorMessage));
414
+ mockHttpPost.mockRejectedValue(new Error(errorMessage));
458
415
  const result = await (0, search_1.executeSearch)({
459
416
  query: 'test query',
460
417
  });
@@ -464,7 +421,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
464
421
  });
465
422
  });
466
423
  (0, vitest_1.it)('should handle non-Error exceptions', async () => {
467
- mockClient.search.mockRejectedValue('String error');
424
+ mockHttpPost.mockRejectedValue('String error');
468
425
  const result = await (0, search_1.executeSearch)({
469
426
  query: 'test query',
470
427
  });
@@ -474,44 +431,44 @@ vitest_1.vi.mock('../../utils/client', async () => {
474
431
  });
475
432
  (0, vitest_1.describe)('Time-based search parameters', () => {
476
433
  (0, vitest_1.it)('should support qdr:h for past hour', async () => {
477
- mockClient.search.mockResolvedValue({ web: [] });
434
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
478
435
  await (0, search_1.executeSearch)({
479
436
  query: 'breaking news',
480
437
  tbs: 'qdr:h',
481
438
  });
482
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('breaking news', vitest_1.expect.objectContaining({ tbs: 'qdr:h' }));
439
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({ tbs: 'qdr:h' }));
483
440
  });
484
441
  (0, vitest_1.it)('should support qdr:d for past day', async () => {
485
- mockClient.search.mockResolvedValue({ web: [] });
442
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
486
443
  await (0, search_1.executeSearch)({
487
444
  query: 'AI announcements',
488
445
  tbs: 'qdr:d',
489
446
  });
490
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('AI announcements', vitest_1.expect.objectContaining({ tbs: 'qdr:d' }));
447
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({ tbs: 'qdr:d' }));
491
448
  });
492
449
  (0, vitest_1.it)('should support qdr:w for past week', async () => {
493
- mockClient.search.mockResolvedValue({ web: [] });
450
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
494
451
  await (0, search_1.executeSearch)({
495
452
  query: 'tech news',
496
453
  tbs: 'qdr:w',
497
454
  });
498
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('tech news', vitest_1.expect.objectContaining({ tbs: 'qdr:w' }));
455
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({ tbs: 'qdr:w' }));
499
456
  });
500
457
  (0, vitest_1.it)('should support qdr:m for past month', async () => {
501
- mockClient.search.mockResolvedValue({ web: [] });
458
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
502
459
  await (0, search_1.executeSearch)({
503
460
  query: 'startup funding',
504
461
  tbs: 'qdr:m',
505
462
  });
506
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('startup funding', vitest_1.expect.objectContaining({ tbs: 'qdr:m' }));
463
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({ tbs: 'qdr:m' }));
507
464
  });
508
465
  (0, vitest_1.it)('should support qdr:y for past year', async () => {
509
- mockClient.search.mockResolvedValue({ web: [] });
466
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
510
467
  await (0, search_1.executeSearch)({
511
468
  query: 'yearly review',
512
469
  tbs: 'qdr:y',
513
470
  });
514
- (0, vitest_1.expect)(mockClient.search).toHaveBeenCalledWith('yearly review', vitest_1.expect.objectContaining({ tbs: 'qdr:y' }));
471
+ (0, vitest_1.expect)(mockHttpPost).toHaveBeenCalledWith('/v2/search', vitest_1.expect.objectContaining({ tbs: 'qdr:y' }));
515
472
  });
516
473
  });
517
474
  (0, vitest_1.describe)('Type safety', () => {
@@ -521,7 +478,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
521
478
  'images',
522
479
  'news',
523
480
  ];
524
- mockClient.search.mockResolvedValue({ web: [], images: [], news: [] });
481
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [], images: [], news: [] }));
525
482
  for (const source of sourceList) {
526
483
  const result = await (0, search_1.executeSearch)({
527
484
  query: 'test',
@@ -536,7 +493,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
536
493
  'research',
537
494
  'pdf',
538
495
  ];
539
- mockClient.search.mockResolvedValue({ web: [] });
496
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [] }));
540
497
  for (const category of categoryList) {
541
498
  const result = await (0, search_1.executeSearch)({
542
499
  query: 'test',
@@ -553,9 +510,7 @@ vitest_1.vi.mock('../../utils/client', async () => {
553
510
  'links',
554
511
  ];
555
512
  for (const format of formatList) {
556
- mockClient.search.mockResolvedValue({
557
- web: [{ url: 'https://example.com' }],
558
- });
513
+ mockHttpPost.mockResolvedValue(mockSearchResponse({ web: [{ url: 'https://example.com' }] }));
559
514
  const result = await (0, search_1.executeSearch)({
560
515
  query: 'test',
561
516
  scrape: true,