webpeel 0.2.0 → 0.3.1
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 +145 -42
- package/dist/cli-auth.d.ts +84 -0
- package/dist/cli-auth.d.ts.map +1 -0
- package/dist/cli-auth.js +392 -0
- package/dist/cli-auth.js.map +1 -0
- package/dist/cli.js +171 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/crawler.d.ts +58 -0
- package/dist/core/crawler.d.ts.map +1 -0
- package/dist/core/crawler.js +205 -0
- package/dist/core/crawler.js.map +1 -0
- package/dist/core/fetcher.d.ts +1 -0
- package/dist/core/fetcher.d.ts.map +1 -1
- package/dist/core/fetcher.js +35 -4
- package/dist/core/fetcher.js.map +1 -1
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/markdown.js +31 -22
- package/dist/core/markdown.js.map +1 -1
- package/dist/core/strategies.d.ts +6 -3
- package/dist/core/strategies.d.ts.map +1 -1
- package/dist/core/strategies.js +33 -7
- package/dist/core/strategies.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +163 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/llms.txt +4 -2
- package/package.json +11 -4
package/dist/cli-auth.js
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Authentication & Usage Tracking
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Anonymous usage (25 free fetches)
|
|
6
|
+
* - API key authentication
|
|
7
|
+
* - Usage checking against API
|
|
8
|
+
* - Config file management (~/.webpeel/config.json)
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from 'fs';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import * as readline from 'readline';
|
|
14
|
+
// Config file location: ~/.webpeel/config.json
|
|
15
|
+
const CONFIG_DIR = join(homedir(), '.webpeel');
|
|
16
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
17
|
+
// API base URL (configurable via env var)
|
|
18
|
+
const API_BASE_URL = process.env.WEBPEEL_API_URL || 'https://webpeel-api.onrender.com';
|
|
19
|
+
/**
|
|
20
|
+
* Load config from ~/.webpeel/config.json
|
|
21
|
+
*/
|
|
22
|
+
export function loadConfig() {
|
|
23
|
+
try {
|
|
24
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
25
|
+
return {
|
|
26
|
+
anonymousUsage: 0,
|
|
27
|
+
lastReset: getLastMonday().toISOString(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const content = readFileSync(CONFIG_FILE, 'utf-8');
|
|
31
|
+
const config = JSON.parse(content);
|
|
32
|
+
// Ensure lastReset exists
|
|
33
|
+
if (!config.lastReset) {
|
|
34
|
+
config.lastReset = getLastMonday().toISOString();
|
|
35
|
+
}
|
|
36
|
+
return config;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
// If config is corrupted, start fresh
|
|
40
|
+
return {
|
|
41
|
+
anonymousUsage: 0,
|
|
42
|
+
lastReset: getLastMonday().toISOString(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Save config to ~/.webpeel/config.json
|
|
48
|
+
*/
|
|
49
|
+
export function saveConfig(config) {
|
|
50
|
+
try {
|
|
51
|
+
// Ensure directory exists
|
|
52
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
53
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error(`Warning: Failed to save config: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Delete config file
|
|
63
|
+
*/
|
|
64
|
+
export function deleteConfig() {
|
|
65
|
+
try {
|
|
66
|
+
if (existsSync(CONFIG_FILE)) {
|
|
67
|
+
unlinkSync(CONFIG_FILE);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error(`Warning: Failed to delete config: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get the last Monday 00:00 UTC (start of current week)
|
|
76
|
+
*/
|
|
77
|
+
function getLastMonday() {
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const day = now.getUTCDay();
|
|
80
|
+
const diff = day === 0 ? 6 : day - 1; // Days since last Monday
|
|
81
|
+
const lastMonday = new Date(now);
|
|
82
|
+
lastMonday.setUTCDate(now.getUTCDate() - diff);
|
|
83
|
+
lastMonday.setUTCHours(0, 0, 0, 0);
|
|
84
|
+
return lastMonday;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if anonymous usage counter needs to be reset (weekly reset)
|
|
88
|
+
*/
|
|
89
|
+
function shouldResetAnonymousUsage(config) {
|
|
90
|
+
const lastReset = new Date(config.lastReset);
|
|
91
|
+
const currentWeekStart = getLastMonday();
|
|
92
|
+
return lastReset < currentWeekStart;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check usage quota before making a request
|
|
96
|
+
*/
|
|
97
|
+
export async function checkUsage() {
|
|
98
|
+
const config = loadConfig();
|
|
99
|
+
// Check if anonymous usage needs reset
|
|
100
|
+
if (shouldResetAnonymousUsage(config)) {
|
|
101
|
+
config.anonymousUsage = 0;
|
|
102
|
+
config.lastReset = getLastMonday().toISOString();
|
|
103
|
+
saveConfig(config);
|
|
104
|
+
}
|
|
105
|
+
// Anonymous user - allow first 25 fetches
|
|
106
|
+
if (!config.apiKey) {
|
|
107
|
+
const limit = 25;
|
|
108
|
+
const used = config.anonymousUsage;
|
|
109
|
+
const remaining = limit - used;
|
|
110
|
+
if (used >= limit) {
|
|
111
|
+
return {
|
|
112
|
+
allowed: false,
|
|
113
|
+
message: `You've used your ${limit} free fetches.\n\nSign up for free at https://app.webpeel.dev/signup to get 125 fetches/week.\nOr run: webpeel login\n`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Increment usage counter
|
|
117
|
+
config.anonymousUsage++;
|
|
118
|
+
saveConfig(config);
|
|
119
|
+
return {
|
|
120
|
+
allowed: true,
|
|
121
|
+
isAnonymous: true,
|
|
122
|
+
usageInfo: {
|
|
123
|
+
used: used + 1,
|
|
124
|
+
limit,
|
|
125
|
+
remaining: remaining - 1,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// Authenticated user - check with API
|
|
130
|
+
try {
|
|
131
|
+
const response = await fetch(`${API_BASE_URL}/v1/usage`, {
|
|
132
|
+
headers: {
|
|
133
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
if (response.status === 401) {
|
|
138
|
+
return {
|
|
139
|
+
allowed: false,
|
|
140
|
+
message: `Authentication failed. Your API key may be invalid.\n\nRun: webpeel logout\nThen: webpeel login\n`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// If API returns other errors, allow gracefully
|
|
144
|
+
return { allowed: true };
|
|
145
|
+
}
|
|
146
|
+
const data = await response.json();
|
|
147
|
+
// Check burst limit
|
|
148
|
+
if (data.session.burstUsed >= data.session.burstLimit) {
|
|
149
|
+
const resetsIn = data.session.resetsIn || 'soon';
|
|
150
|
+
return {
|
|
151
|
+
allowed: false,
|
|
152
|
+
message: `Burst limit reached (${data.session.burstUsed}/${data.session.burstLimit}). Resets in ${resetsIn}.\nUpgrade: https://webpeel.dev/#pricing`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// Check weekly limit
|
|
156
|
+
if (data.weekly.remaining <= 0 && !data.extraUsage?.enabled) {
|
|
157
|
+
return {
|
|
158
|
+
allowed: false,
|
|
159
|
+
message: `Weekly limit reached (${data.weekly.totalUsed}/${data.weekly.totalAvailable}).\nResets: ${data.weekly.resetsAt}\nEnable extra usage or upgrade: https://app.webpeel.dev/billing`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Cache plan tier for offline feature gating
|
|
163
|
+
const planName = (data.plan?.name || 'free').toLowerCase();
|
|
164
|
+
config.planTier = planName;
|
|
165
|
+
config.planCachedAt = new Date().toISOString();
|
|
166
|
+
saveConfig(config);
|
|
167
|
+
return {
|
|
168
|
+
allowed: true,
|
|
169
|
+
isAnonymous: false,
|
|
170
|
+
usageInfo: {
|
|
171
|
+
used: data.weekly.totalUsed,
|
|
172
|
+
limit: data.weekly.totalAvailable,
|
|
173
|
+
remaining: data.weekly.remaining,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
// If API is unreachable, allow the request (graceful degradation)
|
|
179
|
+
return { allowed: true };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const FEATURE_LABELS = {
|
|
183
|
+
stealth: 'Stealth mode (anti-bot bypass)',
|
|
184
|
+
crawl: 'Crawl mode (multi-page)',
|
|
185
|
+
batch: 'Batch mode (bulk URLs)',
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Check if user has access to a premium feature.
|
|
189
|
+
* Returns { allowed: true } for paid users, or a helpful upgrade message.
|
|
190
|
+
*
|
|
191
|
+
* Priority:
|
|
192
|
+
* 1. No API key → blocked (must sign up)
|
|
193
|
+
* 2. Has API key + cached plan → check plan tier
|
|
194
|
+
* 3. Has API key + no cache → check API, then cache
|
|
195
|
+
* 4. API unreachable + cached plan within 7 days → use cache
|
|
196
|
+
* 5. API unreachable + stale cache → allow gracefully (trust the user)
|
|
197
|
+
*/
|
|
198
|
+
export async function checkFeatureAccess(feature) {
|
|
199
|
+
const config = loadConfig();
|
|
200
|
+
// No API key → must sign up and subscribe
|
|
201
|
+
if (!config.apiKey) {
|
|
202
|
+
return {
|
|
203
|
+
allowed: false,
|
|
204
|
+
message: `⚡ ${FEATURE_LABELS[feature]} requires a Pro plan ($9/mo).\n\n` +
|
|
205
|
+
` Basic fetch is free — stealth, crawl, and batch are Pro features.\n\n` +
|
|
206
|
+
` Sign up: https://app.webpeel.dev/signup\n` +
|
|
207
|
+
` Pricing: https://webpeel.dev/#pricing\n` +
|
|
208
|
+
` Login: webpeel login\n`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
// Try to get fresh plan info from API
|
|
212
|
+
try {
|
|
213
|
+
const response = await fetch(`${API_BASE_URL}/v1/usage`, {
|
|
214
|
+
headers: { 'Authorization': `Bearer ${config.apiKey}` },
|
|
215
|
+
signal: AbortSignal.timeout(5000),
|
|
216
|
+
});
|
|
217
|
+
if (response.ok) {
|
|
218
|
+
const data = await response.json();
|
|
219
|
+
const planName = (data.plan?.name || 'free').toLowerCase();
|
|
220
|
+
// Cache for offline use
|
|
221
|
+
config.planTier = planName;
|
|
222
|
+
config.planCachedAt = new Date().toISOString();
|
|
223
|
+
saveConfig(config);
|
|
224
|
+
if (planName === 'free') {
|
|
225
|
+
return {
|
|
226
|
+
allowed: false,
|
|
227
|
+
message: `⚡ ${FEATURE_LABELS[feature]} requires a Pro plan ($9/mo).\n\n` +
|
|
228
|
+
` You're on the Free plan. Upgrade to unlock stealth, crawl, and batch.\n\n` +
|
|
229
|
+
` Upgrade: https://app.webpeel.dev/billing\n` +
|
|
230
|
+
` Pricing: https://webpeel.dev/#pricing\n`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// Pro or Max — allowed
|
|
234
|
+
return { allowed: true };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// API unreachable — fall through to cache check
|
|
239
|
+
}
|
|
240
|
+
// API unreachable — use cached plan if recent (within 7 days)
|
|
241
|
+
if (config.planTier && config.planCachedAt) {
|
|
242
|
+
const cacheAge = Date.now() - new Date(config.planCachedAt).getTime();
|
|
243
|
+
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1000;
|
|
244
|
+
if (cacheAge < SEVEN_DAYS) {
|
|
245
|
+
if (config.planTier === 'free') {
|
|
246
|
+
return {
|
|
247
|
+
allowed: false,
|
|
248
|
+
message: `⚡ ${FEATURE_LABELS[feature]} requires a Pro plan ($9/mo).\n\n` +
|
|
249
|
+
` Upgrade: https://app.webpeel.dev/billing\n`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return { allowed: true };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Stale cache or no cache, API unreachable — allow gracefully (trust paying users)
|
|
256
|
+
return { allowed: true };
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Show usage footer after successful fetch (for free/anonymous users only)
|
|
260
|
+
*/
|
|
261
|
+
export function showUsageFooter(usageInfo, isAnonymous, stealth = false) {
|
|
262
|
+
if (!usageInfo)
|
|
263
|
+
return;
|
|
264
|
+
// Only show footer for anonymous or free users
|
|
265
|
+
if (isAnonymous) {
|
|
266
|
+
const costText = stealth ? ' (costs 5 credits)' : '';
|
|
267
|
+
console.error(`⚡ ${usageInfo.remaining}/${usageInfo.limit} free fetches remaining${costText}. Run \`webpeel login\` to get 125/week free.`);
|
|
268
|
+
}
|
|
269
|
+
else if (usageInfo.limit <= 125) {
|
|
270
|
+
// Free tier authenticated users
|
|
271
|
+
const costText = stealth ? ' (costs 5 credits)' : '';
|
|
272
|
+
console.error(`⚡ ${usageInfo.remaining}/${usageInfo.limit} fetches remaining this week${costText}.`);
|
|
273
|
+
}
|
|
274
|
+
// Don't show footer for paid users
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Prompt user for API key via stdin
|
|
278
|
+
*/
|
|
279
|
+
export async function promptForApiKey() {
|
|
280
|
+
const rl = readline.createInterface({
|
|
281
|
+
input: process.stdin,
|
|
282
|
+
output: process.stdout,
|
|
283
|
+
});
|
|
284
|
+
return new Promise((resolve) => {
|
|
285
|
+
rl.question('Enter your API key (get one at https://app.webpeel.dev/keys): ', (answer) => {
|
|
286
|
+
rl.close();
|
|
287
|
+
resolve(answer.trim());
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Login command - save API key to config
|
|
293
|
+
*/
|
|
294
|
+
export async function handleLogin() {
|
|
295
|
+
const config = loadConfig();
|
|
296
|
+
if (config.apiKey) {
|
|
297
|
+
console.log('You are already logged in.');
|
|
298
|
+
console.log('Run `webpeel logout` first if you want to use a different API key.');
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
console.log('\nWebPeel CLI Authentication');
|
|
302
|
+
console.log('==========================\n');
|
|
303
|
+
const apiKey = await promptForApiKey();
|
|
304
|
+
if (!apiKey) {
|
|
305
|
+
console.error('Error: API key cannot be empty');
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
// Validate API key format (should start with wp_)
|
|
309
|
+
if (!apiKey.startsWith('wp_')) {
|
|
310
|
+
console.error('Warning: API key should start with "wp_". Make sure you entered it correctly.');
|
|
311
|
+
}
|
|
312
|
+
// Save to config
|
|
313
|
+
config.apiKey = apiKey;
|
|
314
|
+
saveConfig(config);
|
|
315
|
+
console.log('\n✓ Successfully logged in!');
|
|
316
|
+
console.log('Run `webpeel usage` to check your quota.');
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Logout command - remove API key from config
|
|
320
|
+
*/
|
|
321
|
+
export function handleLogout() {
|
|
322
|
+
const config = loadConfig();
|
|
323
|
+
if (!config.apiKey) {
|
|
324
|
+
console.log('You are not logged in.');
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
deleteConfig();
|
|
328
|
+
console.log('✓ Logged out successfully');
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Usage command - show current quota
|
|
332
|
+
*/
|
|
333
|
+
export async function handleUsage() {
|
|
334
|
+
const config = loadConfig();
|
|
335
|
+
// Check for weekly reset
|
|
336
|
+
if (shouldResetAnonymousUsage(config)) {
|
|
337
|
+
config.anonymousUsage = 0;
|
|
338
|
+
config.lastReset = getLastMonday().toISOString();
|
|
339
|
+
saveConfig(config);
|
|
340
|
+
}
|
|
341
|
+
// Anonymous user
|
|
342
|
+
if (!config.apiKey) {
|
|
343
|
+
const limit = 25;
|
|
344
|
+
const used = config.anonymousUsage;
|
|
345
|
+
const remaining = limit - used;
|
|
346
|
+
const nextMonday = new Date(getLastMonday());
|
|
347
|
+
nextMonday.setUTCDate(nextMonday.getUTCDate() + 7);
|
|
348
|
+
console.log('\nWebPeel Usage (Anonymous)');
|
|
349
|
+
console.log('=========================\n');
|
|
350
|
+
console.log(`Plan: Anonymous (25 free fetches/week)`);
|
|
351
|
+
console.log(`Used this week: ${used}/${limit}`);
|
|
352
|
+
console.log(`Remaining: ${remaining}`);
|
|
353
|
+
console.log(`Resets: ${nextMonday.toUTCString()}`);
|
|
354
|
+
console.log('\n💡 Run `webpeel login` to get 125 fetches/week for free!');
|
|
355
|
+
console.log(' Or sign up at https://app.webpeel.dev/signup\n');
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
// Authenticated user - fetch from API
|
|
359
|
+
try {
|
|
360
|
+
const response = await fetch(`${API_BASE_URL}/v1/usage`, {
|
|
361
|
+
headers: {
|
|
362
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
if (response.status === 401) {
|
|
367
|
+
console.error('Error: Authentication failed. Your API key may be invalid.');
|
|
368
|
+
console.error('Run `webpeel logout` and `webpeel login` to re-authenticate.');
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
throw new Error(`API returned status ${response.status}`);
|
|
372
|
+
}
|
|
373
|
+
const data = await response.json();
|
|
374
|
+
console.log('\nWebPeel Usage');
|
|
375
|
+
console.log('=============\n');
|
|
376
|
+
console.log(`Plan: ${data.plan?.name || 'Free'} (${data.weekly.totalAvailable}/week)`);
|
|
377
|
+
console.log(`Used this week: ${data.weekly.totalUsed}/${data.weekly.totalAvailable}`);
|
|
378
|
+
console.log(`Remaining: ${data.weekly.remaining}`);
|
|
379
|
+
console.log(`Burst: ${data.session.burstUsed}/${data.session.burstLimit} (resets ${data.session.resetsIn || 'soon'})`);
|
|
380
|
+
console.log(`Resets: ${data.weekly.resetsAt}`);
|
|
381
|
+
if (data.weekly.remaining <= 10 && !data.extraUsage?.enabled) {
|
|
382
|
+
console.log('\n⚠️ Running low on credits. Upgrade at https://webpeel.dev/#pricing');
|
|
383
|
+
}
|
|
384
|
+
console.log();
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
console.error(`Error fetching usage data: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
388
|
+
console.error('The API may be temporarily unavailable. Try again later.');
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
//# sourceMappingURL=cli-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-auth.js","sourceRoot":"","sources":["../src/cli-auth.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -16,15 +16,17 @@ import { Command } from 'commander';
|
|
|
16
16
|
import ora from 'ora';
|
|
17
17
|
import { writeFileSync } from 'fs';
|
|
18
18
|
import { peel, peelBatch, cleanup } from './index.js';
|
|
19
|
+
import { checkUsage, checkFeatureAccess, showUsageFooter, handleLogin, handleLogout, handleUsage } from './cli-auth.js';
|
|
19
20
|
const program = new Command();
|
|
20
21
|
program
|
|
21
22
|
.name('webpeel')
|
|
22
23
|
.description('Fast web fetcher for AI agents')
|
|
23
|
-
.version('0.
|
|
24
|
+
.version('0.3.1')
|
|
24
25
|
.enablePositionalOptions();
|
|
25
26
|
program
|
|
26
27
|
.argument('[url]', 'URL to fetch')
|
|
27
28
|
.option('-r, --render', 'Use headless browser (for JS-heavy sites)')
|
|
29
|
+
.option('--stealth', 'Use stealth mode to bypass bot detection (auto-enables --render)')
|
|
28
30
|
.option('-w, --wait <ms>', 'Wait time after page load (ms)', parseInt)
|
|
29
31
|
.option('--html', 'Output raw HTML instead of markdown')
|
|
30
32
|
.option('--text', 'Output plain text instead of markdown')
|
|
@@ -66,6 +68,21 @@ program
|
|
|
66
68
|
console.error(`Error: Invalid URL format: ${url}`);
|
|
67
69
|
process.exit(1);
|
|
68
70
|
}
|
|
71
|
+
// Check premium feature access (stealth requires Pro plan)
|
|
72
|
+
const useStealth = options.stealth || false;
|
|
73
|
+
if (useStealth) {
|
|
74
|
+
const featureCheck = await checkFeatureAccess('stealth');
|
|
75
|
+
if (!featureCheck.allowed) {
|
|
76
|
+
console.error(featureCheck.message);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Check usage quota
|
|
81
|
+
const usageCheck = await checkUsage();
|
|
82
|
+
if (!usageCheck.allowed) {
|
|
83
|
+
console.error(usageCheck.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
69
86
|
const spinner = options.silent ? null : ora('Fetching...').start();
|
|
70
87
|
try {
|
|
71
88
|
// Validate options
|
|
@@ -92,6 +109,7 @@ program
|
|
|
92
109
|
// Build peel options
|
|
93
110
|
const peelOptions = {
|
|
94
111
|
render: options.render || false,
|
|
112
|
+
stealth: options.stealth || false,
|
|
95
113
|
wait: options.wait || 0,
|
|
96
114
|
timeout: options.timeout,
|
|
97
115
|
userAgent: options.ua,
|
|
@@ -117,6 +135,10 @@ program
|
|
|
117
135
|
if (spinner) {
|
|
118
136
|
spinner.succeed(`Fetched in ${result.elapsed}ms using ${result.method} method`);
|
|
119
137
|
}
|
|
138
|
+
// Show usage footer for free/anonymous users
|
|
139
|
+
if (usageCheck.usageInfo && !options.silent) {
|
|
140
|
+
showUsageFooter(usageCheck.usageInfo, usageCheck.isAnonymous || false, useStealth);
|
|
141
|
+
}
|
|
120
142
|
// Handle screenshot saving
|
|
121
143
|
if (options.screenshot && result.screenshot) {
|
|
122
144
|
const screenshotPath = typeof options.screenshot === 'string'
|
|
@@ -183,6 +205,12 @@ program
|
|
|
183
205
|
const isJson = options.json;
|
|
184
206
|
const isSilent = options.silent;
|
|
185
207
|
const count = parseInt(options.count) || 5;
|
|
208
|
+
// Check usage quota
|
|
209
|
+
const usageCheck = await checkUsage();
|
|
210
|
+
if (!usageCheck.allowed) {
|
|
211
|
+
console.error(usageCheck.message);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
186
214
|
const spinner = isSilent ? null : ora('Searching...').start();
|
|
187
215
|
try {
|
|
188
216
|
// Import the search function dynamically
|
|
@@ -241,6 +269,10 @@ program
|
|
|
241
269
|
if (spinner) {
|
|
242
270
|
spinner.succeed(`Found ${results.length} results`);
|
|
243
271
|
}
|
|
272
|
+
// Show usage footer for free/anonymous users
|
|
273
|
+
if (usageCheck.usageInfo && !isSilent) {
|
|
274
|
+
showUsageFooter(usageCheck.usageInfo, usageCheck.isAnonymous || false, false);
|
|
275
|
+
}
|
|
244
276
|
if (isJson) {
|
|
245
277
|
const jsonStr = JSON.stringify(results, null, 2);
|
|
246
278
|
await new Promise((resolve, reject) => {
|
|
@@ -277,7 +309,7 @@ program
|
|
|
277
309
|
// Batch command
|
|
278
310
|
program
|
|
279
311
|
.command('batch <file>')
|
|
280
|
-
.description('Fetch multiple URLs')
|
|
312
|
+
.description('Fetch multiple URLs (Pro feature)')
|
|
281
313
|
.option('-c, --concurrency <n>', 'Max concurrent fetches (default: 3)', '3')
|
|
282
314
|
.option('-o, --output <dir>', 'Output directory (one file per URL)')
|
|
283
315
|
.option('--json', 'Output as JSON array')
|
|
@@ -289,6 +321,18 @@ program
|
|
|
289
321
|
const isSilent = options.silent;
|
|
290
322
|
const shouldRender = options.render;
|
|
291
323
|
const selector = options.selector;
|
|
324
|
+
// Check premium feature access (batch requires Pro plan)
|
|
325
|
+
const featureCheck = await checkFeatureAccess('batch');
|
|
326
|
+
if (!featureCheck.allowed) {
|
|
327
|
+
console.error(featureCheck.message);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
// Check usage quota
|
|
331
|
+
const usageCheck = await checkUsage();
|
|
332
|
+
if (!usageCheck.allowed) {
|
|
333
|
+
console.error(usageCheck.message);
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
292
336
|
const spinner = isSilent ? null : ora('Loading URLs...').start();
|
|
293
337
|
try {
|
|
294
338
|
const { readFileSync } = await import('fs');
|
|
@@ -319,6 +363,10 @@ program
|
|
|
319
363
|
const successCount = results.filter(r => 'content' in r).length;
|
|
320
364
|
spinner.succeed(`Completed: ${successCount}/${urls.length} successful`);
|
|
321
365
|
}
|
|
366
|
+
// Show usage footer for free/anonymous users
|
|
367
|
+
if (usageCheck.usageInfo && !isSilent) {
|
|
368
|
+
showUsageFooter(usageCheck.usageInfo, usageCheck.isAnonymous || false, false);
|
|
369
|
+
}
|
|
322
370
|
// Output results
|
|
323
371
|
if (isJson) {
|
|
324
372
|
const jsonStr = JSON.stringify(results, null, 2);
|
|
@@ -380,6 +428,127 @@ program
|
|
|
380
428
|
process.exit(1);
|
|
381
429
|
}
|
|
382
430
|
});
|
|
431
|
+
program
|
|
432
|
+
.command('crawl <url>')
|
|
433
|
+
.description('Crawl a website starting from a URL (Pro feature)')
|
|
434
|
+
.option('--max-pages <number>', 'Maximum number of pages to crawl (default: 10, max: 100)', parseInt, 10)
|
|
435
|
+
.option('--max-depth <number>', 'Maximum depth to crawl (default: 2, max: 5)', parseInt, 2)
|
|
436
|
+
.option('--allowed-domains <domains...>', 'Only crawl these domains (default: same as starting URL)')
|
|
437
|
+
.option('--exclude <patterns...>', 'Exclude URLs matching these regex patterns')
|
|
438
|
+
.option('--ignore-robots', 'Ignore robots.txt (default: respect robots.txt)')
|
|
439
|
+
.option('--rate-limit <ms>', 'Rate limit between requests in ms (default: 1000)', parseInt, 1000)
|
|
440
|
+
.option('-r, --render', 'Use headless browser for all pages')
|
|
441
|
+
.option('--stealth', 'Use stealth mode for all pages')
|
|
442
|
+
.option('-s, --silent', 'Silent mode (no spinner)')
|
|
443
|
+
.option('--json', 'Output as JSON')
|
|
444
|
+
.action(async (url, options) => {
|
|
445
|
+
// Check premium feature access (crawl requires Pro plan)
|
|
446
|
+
const featureCheck = await checkFeatureAccess('crawl');
|
|
447
|
+
if (!featureCheck.allowed) {
|
|
448
|
+
console.error(featureCheck.message);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
// Check usage quota
|
|
452
|
+
const usageCheck = await checkUsage();
|
|
453
|
+
if (!usageCheck.allowed) {
|
|
454
|
+
console.error(usageCheck.message);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
const { crawl } = await import('./core/crawler.js');
|
|
458
|
+
const spinner = options.silent ? null : ora('Crawling...').start();
|
|
459
|
+
try {
|
|
460
|
+
const results = await crawl(url, {
|
|
461
|
+
maxPages: options.maxPages,
|
|
462
|
+
maxDepth: options.maxDepth,
|
|
463
|
+
allowedDomains: options.allowedDomains,
|
|
464
|
+
excludePatterns: options.exclude,
|
|
465
|
+
respectRobotsTxt: !options.ignoreRobots,
|
|
466
|
+
rateLimitMs: options.rateLimit,
|
|
467
|
+
render: options.render || false,
|
|
468
|
+
stealth: options.stealth || false,
|
|
469
|
+
});
|
|
470
|
+
if (spinner) {
|
|
471
|
+
spinner.succeed(`Crawled ${results.length} pages`);
|
|
472
|
+
}
|
|
473
|
+
// Show usage footer for free/anonymous users
|
|
474
|
+
if (usageCheck.usageInfo && !options.silent) {
|
|
475
|
+
showUsageFooter(usageCheck.usageInfo, usageCheck.isAnonymous || false, options.stealth || false);
|
|
476
|
+
}
|
|
477
|
+
if (options.json) {
|
|
478
|
+
console.log(JSON.stringify(results, null, 2));
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
results.forEach((result, i) => {
|
|
482
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
483
|
+
console.log(`[${i + 1}/${results.length}] ${result.title}`);
|
|
484
|
+
console.log(`URL: ${result.url}`);
|
|
485
|
+
console.log(`Depth: ${result.depth}${result.parent ? ` (from: ${result.parent})` : ''}`);
|
|
486
|
+
console.log(`Links found: ${result.links.length}`);
|
|
487
|
+
console.log(`Elapsed: ${result.elapsed}ms`);
|
|
488
|
+
if (result.error) {
|
|
489
|
+
console.log(`ERROR: ${result.error}`);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
console.log(`\n${result.markdown.slice(0, 500)}${result.markdown.length > 500 ? '...' : ''}`);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
await cleanup();
|
|
497
|
+
process.exit(0);
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
if (spinner) {
|
|
501
|
+
spinner.fail('Crawl failed');
|
|
502
|
+
}
|
|
503
|
+
if (error instanceof Error) {
|
|
504
|
+
console.error(`\nError: ${error.message}`);
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
console.error('\nError: Unknown error occurred');
|
|
508
|
+
}
|
|
509
|
+
await cleanup();
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
program
|
|
514
|
+
.command('login')
|
|
515
|
+
.description('Authenticate the CLI with your API key')
|
|
516
|
+
.action(async () => {
|
|
517
|
+
try {
|
|
518
|
+
await handleLogin();
|
|
519
|
+
process.exit(0);
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
program
|
|
527
|
+
.command('logout')
|
|
528
|
+
.description('Clear your saved credentials')
|
|
529
|
+
.action(() => {
|
|
530
|
+
try {
|
|
531
|
+
handleLogout();
|
|
532
|
+
process.exit(0);
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
program
|
|
540
|
+
.command('usage')
|
|
541
|
+
.description('Show your current usage and quota')
|
|
542
|
+
.action(async () => {
|
|
543
|
+
try {
|
|
544
|
+
await handleUsage();
|
|
545
|
+
process.exit(0);
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
383
552
|
program
|
|
384
553
|
.command('serve')
|
|
385
554
|
.description('Start API server')
|