@vinitngr/serper-v 1.0.1 → 1.0.5
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/SKILL.md +16 -12
- package/dist/index.js +115 -28
- package/package.json +3 -2
package/SKILL.md
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: serper
|
|
3
|
-
description:
|
|
3
|
+
description: Professional search (news, places, maps, reviews, scholar, patents) and bulk scraping via Serper API.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Serper Search
|
|
7
7
|
|
|
8
8
|
## Usage
|
|
9
9
|
```bash
|
|
10
|
-
serperV search
|
|
10
|
+
serperV search -q "Apple Inc" -t search --tbs qdr:h --page 3
|
|
11
|
+
serperV scrape -u "https://site1.com, https://site2.com"
|
|
11
12
|
```
|
|
12
|
-
|
|
13
|
-
- **Types**: `search
|
|
13
|
+
|
|
14
|
+
- **All Types**: `search`, `places`, `maps`, `news`, `shopping`, `scholar`, `patents`.
|
|
15
|
+
- **Date Range (`--tbs`)**: `qdr:h` (hour), `qdr:d` (day), `qdr:w` (week), `qdr:m` (month), `qdr:y` (year).
|
|
14
16
|
- **Flags**:
|
|
15
|
-
- `-q` / `--query`: Search
|
|
16
|
-
- `-
|
|
17
|
-
- `-
|
|
18
|
-
- `-
|
|
19
|
-
- `-
|
|
20
|
-
- `-
|
|
17
|
+
- `-q` / `--query`: Search query (autocorrect enabled by default).
|
|
18
|
+
- `-u` / `--url`: One or more URLs (comma-separated).
|
|
19
|
+
- `-t` / `--type`: Endpoint (Default: `search`).
|
|
20
|
+
- `-l` / `--limit`: Number of results.
|
|
21
|
+
- `-g` / `--gl`: Country code.
|
|
22
|
+
- `-h` / `--hl`: Language code.
|
|
23
|
+
- `-p` / `--page`: Specific result page.
|
|
21
24
|
|
|
22
|
-
##
|
|
23
|
-
`npm install -g @vinitngr/serper-v --force`
|
|
25
|
+
## Setup
|
|
26
|
+
1. `npm install -g @vinitngr/serper-v --force`
|
|
27
|
+
2. `serperV auth <api_key>`
|
package/dist/index.js
CHANGED
|
@@ -18,12 +18,14 @@ function getStoredApiKey() {
|
|
|
18
18
|
function parseArgs(args) {
|
|
19
19
|
const options = {
|
|
20
20
|
query: '',
|
|
21
|
+
url: '',
|
|
21
22
|
limit: 10,
|
|
22
23
|
type: 'search',
|
|
23
24
|
gl: 'us',
|
|
24
25
|
hl: 'en',
|
|
25
26
|
page: 1,
|
|
26
|
-
autocorrect: true
|
|
27
|
+
autocorrect: true,
|
|
28
|
+
tbs: ''
|
|
27
29
|
};
|
|
28
30
|
for (let i = 0; i < args.length; i++) {
|
|
29
31
|
const arg = args[i];
|
|
@@ -32,6 +34,10 @@ function parseArgs(args) {
|
|
|
32
34
|
options.query = nextArg;
|
|
33
35
|
i++;
|
|
34
36
|
}
|
|
37
|
+
else if (arg === '-u' || arg === '--url') {
|
|
38
|
+
options.url = nextArg;
|
|
39
|
+
i++;
|
|
40
|
+
}
|
|
35
41
|
else if (arg === '-l' || arg === '--limit') {
|
|
36
42
|
options.limit = parseInt(nextArg);
|
|
37
43
|
i++;
|
|
@@ -56,11 +62,16 @@ function parseArgs(args) {
|
|
|
56
62
|
options.autocorrect = nextArg === 'true';
|
|
57
63
|
i++;
|
|
58
64
|
}
|
|
65
|
+
else if (arg === '--tbs') {
|
|
66
|
+
options.tbs = nextArg;
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
59
69
|
}
|
|
60
70
|
return options;
|
|
61
71
|
}
|
|
62
72
|
const args = process.argv.slice(2);
|
|
63
73
|
const command = args[0];
|
|
74
|
+
const apiKey = process.env.SERPER_API_KEY || getStoredApiKey();
|
|
64
75
|
if (command === 'auth') {
|
|
65
76
|
const key = args[1];
|
|
66
77
|
if (!key) {
|
|
@@ -86,35 +97,128 @@ else if (command === 'search') {
|
|
|
86
97
|
console.error('\n❌ Error: Query required. Use -q or --query\n');
|
|
87
98
|
process.exit(1);
|
|
88
99
|
}
|
|
89
|
-
const apiKey = process.env.SERPER_API_KEY || getStoredApiKey();
|
|
90
100
|
if (!apiKey) {
|
|
91
101
|
console.error('\n❌ Error: Serper API key not found. Run: serperV auth <key>\n');
|
|
92
102
|
process.exit(1);
|
|
93
103
|
}
|
|
94
|
-
const
|
|
104
|
+
const payload = {
|
|
95
105
|
q: options.query,
|
|
96
106
|
num: options.limit,
|
|
97
107
|
gl: options.gl,
|
|
98
108
|
hl: options.hl,
|
|
99
109
|
page: options.page,
|
|
100
110
|
autocorrect: options.autocorrect
|
|
101
|
-
}
|
|
111
|
+
};
|
|
112
|
+
if (options.tbs)
|
|
113
|
+
payload.tbs = options.tbs;
|
|
114
|
+
makeRequest(`/${options.type}`, JSON.stringify(payload));
|
|
115
|
+
}
|
|
116
|
+
else if (command === 'scrape') {
|
|
117
|
+
const options = parseArgs(args.slice(1));
|
|
118
|
+
if (!options.url) {
|
|
119
|
+
console.error('\n❌ Error: URL required. Use -u or --url\n');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
if (!apiKey) {
|
|
123
|
+
console.error('\n❌ Error: Serper API key not found. Run: serperV auth <key>\n');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const urls = options.url.split(',').map((u) => u.trim());
|
|
127
|
+
if (urls.length === 1) {
|
|
128
|
+
makeRequest('/scrape', JSON.stringify({ url: urls[0] }));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Bulk scraping
|
|
132
|
+
process.stdout.write('[\n');
|
|
133
|
+
const processNext = (index) => {
|
|
134
|
+
if (index >= urls.length) {
|
|
135
|
+
process.stdout.write('\n]\n');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const data = JSON.stringify({ url: urls[index] });
|
|
139
|
+
const req = https.request({
|
|
140
|
+
hostname: 'google.serper.dev',
|
|
141
|
+
path: '/scrape',
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: {
|
|
144
|
+
'X-API-KEY': apiKey || '',
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
'Content-Length': data.length
|
|
147
|
+
}
|
|
148
|
+
}, (res) => {
|
|
149
|
+
let body = '';
|
|
150
|
+
res.on('data', (chunk) => body += chunk);
|
|
151
|
+
res.on('end', () => {
|
|
152
|
+
try {
|
|
153
|
+
process.stdout.write(JSON.stringify(JSON.parse(body), null, 2));
|
|
154
|
+
if (index < urls.length - 1)
|
|
155
|
+
process.stdout.write(',\n');
|
|
156
|
+
processNext(index + 1);
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
console.error(`\n❌ Error parsing response for ${urls[index]}`);
|
|
160
|
+
processNext(index + 1);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
req.on('error', (err) => {
|
|
165
|
+
console.error(`\n❌ Request failed for ${urls[index]}:`, err.message);
|
|
166
|
+
processNext(index + 1);
|
|
167
|
+
});
|
|
168
|
+
req.write(data);
|
|
169
|
+
req.end();
|
|
170
|
+
};
|
|
171
|
+
processNext(0);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
console.log(`
|
|
176
|
+
Optimal Serper CLI (Zero Dependencies) - v2.5.0
|
|
177
|
+
|
|
178
|
+
Usage:
|
|
179
|
+
serperV auth <key> Store API key
|
|
180
|
+
serperV search [options] Search (places, news, scholar, etc.)
|
|
181
|
+
serperV scrape [options] Scrape website content
|
|
182
|
+
|
|
183
|
+
Options:
|
|
184
|
+
-q, --query <string> Search query
|
|
185
|
+
-u, --url <string> URL(s) to scrape (comma-separated for bulk)
|
|
186
|
+
-t, --type <string> Endpoint (search, places, maps, news, shopping, scholar, patents)
|
|
187
|
+
-l, --limit <number> Result limit (Default 10)
|
|
188
|
+
-g, --gl <string> Country code (Default 'us')
|
|
189
|
+
-h, --hl <string> Language code (Default 'en')
|
|
190
|
+
-p, --page <number> Page number
|
|
191
|
+
-a, --autocorrect <bool>
|
|
192
|
+
--tbs <string> Date range (e.g. qdr:h, qdr:d, qdr:w, qdr:m, qdr:y)
|
|
193
|
+
|
|
194
|
+
Examples:
|
|
195
|
+
serperV search -q "Apple" --tbs qdr:h
|
|
196
|
+
serperV scrape -u "https://google.com, https://apple.com"
|
|
197
|
+
`);
|
|
198
|
+
}
|
|
199
|
+
function makeRequest(path, data) {
|
|
102
200
|
const reqOptions = {
|
|
103
201
|
hostname: 'google.serper.dev',
|
|
104
|
-
path:
|
|
202
|
+
path: path,
|
|
105
203
|
method: 'POST',
|
|
106
204
|
headers: {
|
|
107
|
-
'X-API-KEY': apiKey,
|
|
205
|
+
'X-API-KEY': apiKey || '',
|
|
108
206
|
'Content-Type': 'application/json',
|
|
109
207
|
'Content-Length': data.length
|
|
110
|
-
}
|
|
208
|
+
},
|
|
209
|
+
timeout: 30000
|
|
111
210
|
};
|
|
112
211
|
const req = https.request(reqOptions, (res) => {
|
|
113
212
|
let body = '';
|
|
114
213
|
res.on('data', (chunk) => body += chunk);
|
|
115
214
|
res.on('end', () => {
|
|
116
215
|
if (res.statusCode === 200) {
|
|
117
|
-
|
|
216
|
+
try {
|
|
217
|
+
process.stdout.write(JSON.stringify(JSON.parse(body), null, 2) + '\n');
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
process.stdout.write(body + '\n');
|
|
221
|
+
}
|
|
118
222
|
}
|
|
119
223
|
else {
|
|
120
224
|
const errorMsg = res.statusCode === 401 || res.statusCode === 403
|
|
@@ -127,28 +231,11 @@ else if (command === 'search') {
|
|
|
127
231
|
}
|
|
128
232
|
});
|
|
129
233
|
});
|
|
130
|
-
req.on('
|
|
131
|
-
|
|
234
|
+
req.on('timeout', () => {
|
|
235
|
+
req.destroy();
|
|
236
|
+
console.error('\n❌ Request timed out after 30 seconds');
|
|
132
237
|
process.exit(1);
|
|
133
238
|
});
|
|
134
239
|
req.write(data);
|
|
135
240
|
req.end();
|
|
136
241
|
}
|
|
137
|
-
else {
|
|
138
|
-
console.log(`
|
|
139
|
-
Optimal Serper CLI (Zero Dependencies) - v2.2.0
|
|
140
|
-
|
|
141
|
-
Usage:
|
|
142
|
-
serperV auth <key> Store API key
|
|
143
|
-
serperV search [options] Search the web
|
|
144
|
-
|
|
145
|
-
Options:
|
|
146
|
-
-q, --query <string> Search query (Required)
|
|
147
|
-
-l, --limit <number> Result limit (Default 10)
|
|
148
|
-
-t, --type <string> Type (search, news, places, etc.)
|
|
149
|
-
-g, --gl <string> Country code (Default 'us')
|
|
150
|
-
-h, --hl <string> Language code (Default 'en')
|
|
151
|
-
-p, --page <number> Page number
|
|
152
|
-
-a, --autocorrect <bool>
|
|
153
|
-
`);
|
|
154
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vinitngr/serper-v",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Optimal Serper Search CLI for AI Agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc && chmod +x dist/index.js",
|
|
16
16
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
17
|
-
"test:search": "node test-serper.js"
|
|
17
|
+
"test:search": "node test-serper.js",
|
|
18
|
+
"test:all": "node test-all.js"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|
|
20
21
|
"serper",
|