webpeel 0.3.0 → 0.3.2

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.
@@ -0,0 +1,428 @@
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://api.webpeel.dev';
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/cli/usage`, {
132
+ headers: {
133
+ 'Authorization': `Bearer ${config.apiKey}`,
134
+ },
135
+ signal: AbortSignal.timeout(5000),
136
+ });
137
+ if (!response.ok) {
138
+ if (response.status === 401) {
139
+ return {
140
+ allowed: false,
141
+ message: `Authentication failed. Your API key may be invalid.\n\nRun: webpeel logout\nThen: webpeel login\n`,
142
+ };
143
+ }
144
+ // If API returns other errors, allow gracefully
145
+ return { allowed: true };
146
+ }
147
+ const data = await response.json();
148
+ // Check burst limit
149
+ if (data.burst.used >= data.burst.limit) {
150
+ return {
151
+ allowed: false,
152
+ message: `Burst limit reached (${data.burst.used}/${data.burst.limit}). Resets in ${data.burst.resetsIn}.\nUpgrade: ${data.upgradeUrl}`,
153
+ };
154
+ }
155
+ // Quick canFetch check from API
156
+ if (!data.canFetch) {
157
+ return {
158
+ allowed: false,
159
+ message: `Weekly limit reached (${data.weekly.used}/${data.weekly.limit}).\nResets: ${data.weekly.resetsAt}\nUpgrade: ${data.upgradeUrl}`,
160
+ };
161
+ }
162
+ // Cache plan tier for offline feature gating
163
+ const planName = (data.plan?.tier || '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.used,
172
+ limit: data.weekly.limit,
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/cli/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?.tier || '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('\n🔑 WebPeel CLI Authentication');
302
+ console.log('==============================\n');
303
+ console.log('Get your API key at: https://app.webpeel.dev/keys\n');
304
+ const apiKey = await promptForApiKey();
305
+ if (!apiKey) {
306
+ console.error('Error: API key cannot be empty');
307
+ process.exit(1);
308
+ }
309
+ // Validate API key format (should start with wp_)
310
+ if (!apiKey.startsWith('wp_')) {
311
+ console.error('Warning: API key should start with "wp_". Make sure you entered it correctly.');
312
+ }
313
+ // Validate API key against server before saving
314
+ console.log('\nVerifying API key...');
315
+ try {
316
+ const response = await fetch(`${API_BASE_URL}/v1/cli/usage`, {
317
+ headers: { 'Authorization': `Bearer ${apiKey}` },
318
+ signal: AbortSignal.timeout(5000),
319
+ });
320
+ if (response.ok) {
321
+ const data = await response.json();
322
+ const tierLabel = data.plan.tier.charAt(0).toUpperCase() + data.plan.tier.slice(1);
323
+ // Save to config with plan info
324
+ config.apiKey = apiKey;
325
+ config.planTier = data.plan.tier;
326
+ config.planCachedAt = new Date().toISOString();
327
+ saveConfig(config);
328
+ console.log(`\n✅ Successfully logged in!`);
329
+ console.log(`Plan: ${tierLabel} (${data.weekly.limit} fetches/week)`);
330
+ console.log(`Usage this week: ${data.weekly.used}/${data.weekly.limit}`);
331
+ }
332
+ else if (response.status === 401) {
333
+ console.error('\n❌ Invalid API key. Please check and try again.');
334
+ console.error('Get your API key at https://app.webpeel.dev/keys');
335
+ process.exit(1);
336
+ }
337
+ else {
338
+ // Server returned non-401 error — save key anyway (might be temporary)
339
+ config.apiKey = apiKey;
340
+ saveConfig(config);
341
+ console.log('\n✓ API key saved (could not verify — server may be temporarily unavailable).');
342
+ }
343
+ }
344
+ catch {
345
+ // Network error — save key anyway (graceful)
346
+ config.apiKey = apiKey;
347
+ saveConfig(config);
348
+ console.log('\n✓ API key saved (could not reach server to verify).');
349
+ }
350
+ console.log('Run `webpeel usage` to check your quota.');
351
+ }
352
+ /**
353
+ * Logout command - remove API key from config
354
+ */
355
+ export function handleLogout() {
356
+ const config = loadConfig();
357
+ if (!config.apiKey) {
358
+ console.log('You are not logged in.');
359
+ return;
360
+ }
361
+ deleteConfig();
362
+ console.log('✓ Logged out successfully');
363
+ }
364
+ /**
365
+ * Usage command - show current quota
366
+ */
367
+ export async function handleUsage() {
368
+ const config = loadConfig();
369
+ // Check for weekly reset
370
+ if (shouldResetAnonymousUsage(config)) {
371
+ config.anonymousUsage = 0;
372
+ config.lastReset = getLastMonday().toISOString();
373
+ saveConfig(config);
374
+ }
375
+ // Anonymous user
376
+ if (!config.apiKey) {
377
+ const limit = 25;
378
+ const used = config.anonymousUsage;
379
+ const remaining = limit - used;
380
+ const nextMonday = new Date(getLastMonday());
381
+ nextMonday.setUTCDate(nextMonday.getUTCDate() + 7);
382
+ console.log('\nWebPeel Usage (Anonymous)');
383
+ console.log('=========================\n');
384
+ console.log(`Plan: Anonymous (25 free fetches/week)`);
385
+ console.log(`Used this week: ${used}/${limit}`);
386
+ console.log(`Remaining: ${remaining}`);
387
+ console.log(`Resets: ${nextMonday.toUTCString()}`);
388
+ console.log('\n💡 Run `webpeel login` to get 125 fetches/week for free!');
389
+ console.log(' Or sign up at https://app.webpeel.dev/signup\n');
390
+ return;
391
+ }
392
+ // Authenticated user - fetch from API
393
+ try {
394
+ const response = await fetch(`${API_BASE_URL}/v1/cli/usage`, {
395
+ headers: {
396
+ 'Authorization': `Bearer ${config.apiKey}`,
397
+ },
398
+ signal: AbortSignal.timeout(5000),
399
+ });
400
+ if (!response.ok) {
401
+ if (response.status === 401) {
402
+ console.error('Error: Authentication failed. Your API key may be invalid.');
403
+ console.error('Run `webpeel logout` and `webpeel login` to re-authenticate.');
404
+ process.exit(1);
405
+ }
406
+ throw new Error(`API returned status ${response.status}`);
407
+ }
408
+ const data = await response.json();
409
+ const tierLabel = data.plan.tier.charAt(0).toUpperCase() + data.plan.tier.slice(1);
410
+ console.log('\nWebPeel Usage');
411
+ console.log('=============\n');
412
+ console.log(`Plan: ${tierLabel} (${data.weekly.limit}/week)`);
413
+ console.log(`Used this week: ${data.weekly.used}/${data.weekly.limit} (${data.weekly.percentUsed}%)`);
414
+ console.log(`Remaining: ${data.weekly.remaining}`);
415
+ console.log(`Burst: ${data.burst.used}/${data.burst.limit} this hour (resets in ${data.burst.resetsIn})`);
416
+ console.log(`Weekly reset: ${new Date(data.weekly.resetsAt).toUTCString()}`);
417
+ if (data.weekly.remaining <= 10) {
418
+ console.log(`\n⚠️ Running low on credits. Upgrade at ${data.upgradeUrl}`);
419
+ }
420
+ console.log();
421
+ }
422
+ catch (error) {
423
+ console.error(`Error fetching usage data: ${error instanceof Error ? error.message : 'Unknown error'}`);
424
+ console.error('The API may be temporarily unavailable. Try again later.');
425
+ process.exit(1);
426
+ }
427
+ }
428
+ //# 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,11 +16,12 @@ 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.3.0')
24
+ .version('0.3.1')
24
25
  .enablePositionalOptions();
25
26
  program
26
27
  .argument('[url]', 'URL to fetch')
@@ -67,6 +68,21 @@ program
67
68
  console.error(`Error: Invalid URL format: ${url}`);
68
69
  process.exit(1);
69
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
+ }
70
86
  const spinner = options.silent ? null : ora('Fetching...').start();
71
87
  try {
72
88
  // Validate options
@@ -91,8 +107,10 @@ program
91
107
  }
92
108
  }
93
109
  // Build peel options
110
+ // --stealth auto-enables --render (stealth requires browser)
111
+ const useRender = options.render || options.stealth || false;
94
112
  const peelOptions = {
95
- render: options.render || false,
113
+ render: useRender,
96
114
  stealth: options.stealth || false,
97
115
  wait: options.wait || 0,
98
116
  timeout: options.timeout,
@@ -119,6 +137,10 @@ program
119
137
  if (spinner) {
120
138
  spinner.succeed(`Fetched in ${result.elapsed}ms using ${result.method} method`);
121
139
  }
140
+ // Show usage footer for free/anonymous users
141
+ if (usageCheck.usageInfo && !options.silent) {
142
+ showUsageFooter(usageCheck.usageInfo, usageCheck.isAnonymous || false, useStealth);
143
+ }
122
144
  // Handle screenshot saving
123
145
  if (options.screenshot && result.screenshot) {
124
146
  const screenshotPath = typeof options.screenshot === 'string'
@@ -185,6 +207,12 @@ program
185
207
  const isJson = options.json;
186
208
  const isSilent = options.silent;
187
209
  const count = parseInt(options.count) || 5;
210
+ // Check usage quota
211
+ const usageCheck = await checkUsage();
212
+ if (!usageCheck.allowed) {
213
+ console.error(usageCheck.message);
214
+ process.exit(1);
215
+ }
188
216
  const spinner = isSilent ? null : ora('Searching...').start();
189
217
  try {
190
218
  // Import the search function dynamically
@@ -243,6 +271,10 @@ program
243
271
  if (spinner) {
244
272
  spinner.succeed(`Found ${results.length} results`);
245
273
  }
274
+ // Show usage footer for free/anonymous users
275
+ if (usageCheck.usageInfo && !isSilent) {
276
+ showUsageFooter(usageCheck.usageInfo, usageCheck.isAnonymous || false, false);
277
+ }
246
278
  if (isJson) {
247
279
  const jsonStr = JSON.stringify(results, null, 2);
248
280
  await new Promise((resolve, reject) => {
@@ -279,7 +311,7 @@ program
279
311
  // Batch command
280
312
  program
281
313
  .command('batch <file>')
282
- .description('Fetch multiple URLs')
314
+ .description('Fetch multiple URLs (Pro feature)')
283
315
  .option('-c, --concurrency <n>', 'Max concurrent fetches (default: 3)', '3')
284
316
  .option('-o, --output <dir>', 'Output directory (one file per URL)')
285
317
  .option('--json', 'Output as JSON array')
@@ -291,6 +323,18 @@ program
291
323
  const isSilent = options.silent;
292
324
  const shouldRender = options.render;
293
325
  const selector = options.selector;
326
+ // Check premium feature access (batch requires Pro plan)
327
+ const featureCheck = await checkFeatureAccess('batch');
328
+ if (!featureCheck.allowed) {
329
+ console.error(featureCheck.message);
330
+ process.exit(1);
331
+ }
332
+ // Check usage quota
333
+ const usageCheck = await checkUsage();
334
+ if (!usageCheck.allowed) {
335
+ console.error(usageCheck.message);
336
+ process.exit(1);
337
+ }
294
338
  const spinner = isSilent ? null : ora('Loading URLs...').start();
295
339
  try {
296
340
  const { readFileSync } = await import('fs');
@@ -321,6 +365,10 @@ program
321
365
  const successCount = results.filter(r => 'content' in r).length;
322
366
  spinner.succeed(`Completed: ${successCount}/${urls.length} successful`);
323
367
  }
368
+ // Show usage footer for free/anonymous users
369
+ if (usageCheck.usageInfo && !isSilent) {
370
+ showUsageFooter(usageCheck.usageInfo, usageCheck.isAnonymous || false, false);
371
+ }
324
372
  // Output results
325
373
  if (isJson) {
326
374
  const jsonStr = JSON.stringify(results, null, 2);
@@ -384,7 +432,7 @@ program
384
432
  });
385
433
  program
386
434
  .command('crawl <url>')
387
- .description('Crawl a website starting from a URL')
435
+ .description('Crawl a website starting from a URL (Pro feature)')
388
436
  .option('--max-pages <number>', 'Maximum number of pages to crawl (default: 10, max: 100)', parseInt, 10)
389
437
  .option('--max-depth <number>', 'Maximum depth to crawl (default: 2, max: 5)', parseInt, 2)
390
438
  .option('--allowed-domains <domains...>', 'Only crawl these domains (default: same as starting URL)')
@@ -396,6 +444,18 @@ program
396
444
  .option('-s, --silent', 'Silent mode (no spinner)')
397
445
  .option('--json', 'Output as JSON')
398
446
  .action(async (url, options) => {
447
+ // Check premium feature access (crawl requires Pro plan)
448
+ const featureCheck = await checkFeatureAccess('crawl');
449
+ if (!featureCheck.allowed) {
450
+ console.error(featureCheck.message);
451
+ process.exit(1);
452
+ }
453
+ // Check usage quota
454
+ const usageCheck = await checkUsage();
455
+ if (!usageCheck.allowed) {
456
+ console.error(usageCheck.message);
457
+ process.exit(1);
458
+ }
399
459
  const { crawl } = await import('./core/crawler.js');
400
460
  const spinner = options.silent ? null : ora('Crawling...').start();
401
461
  try {
@@ -412,6 +472,10 @@ program
412
472
  if (spinner) {
413
473
  spinner.succeed(`Crawled ${results.length} pages`);
414
474
  }
475
+ // Show usage footer for free/anonymous users
476
+ if (usageCheck.usageInfo && !options.silent) {
477
+ showUsageFooter(usageCheck.usageInfo, usageCheck.isAnonymous || false, options.stealth || false);
478
+ }
415
479
  if (options.json) {
416
480
  console.log(JSON.stringify(results, null, 2));
417
481
  }
@@ -448,6 +512,71 @@ program
448
512
  process.exit(1);
449
513
  }
450
514
  });
515
+ program
516
+ .command('login')
517
+ .description('Authenticate the CLI with your API key')
518
+ .action(async () => {
519
+ try {
520
+ await handleLogin();
521
+ process.exit(0);
522
+ }
523
+ catch (error) {
524
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
525
+ process.exit(1);
526
+ }
527
+ });
528
+ program
529
+ .command('whoami')
530
+ .description('Show your current authentication status')
531
+ .action(async () => {
532
+ try {
533
+ const { loadConfig } = await import('./cli-auth.js');
534
+ const config = loadConfig();
535
+ if (!config.apiKey) {
536
+ console.log('Not logged in. Run `webpeel login` to authenticate.');
537
+ }
538
+ else {
539
+ const masked = config.apiKey.slice(0, 7) + '...' + config.apiKey.slice(-4);
540
+ console.log(`Logged in with API key: ${masked}`);
541
+ if (config.planTier) {
542
+ const tierLabel = config.planTier.charAt(0).toUpperCase() + config.planTier.slice(1);
543
+ console.log(`Plan: ${tierLabel}`);
544
+ }
545
+ console.log(`Config: ~/.webpeel/config.json`);
546
+ }
547
+ process.exit(0);
548
+ }
549
+ catch (error) {
550
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
551
+ process.exit(1);
552
+ }
553
+ });
554
+ program
555
+ .command('logout')
556
+ .description('Clear your saved credentials')
557
+ .action(() => {
558
+ try {
559
+ handleLogout();
560
+ process.exit(0);
561
+ }
562
+ catch (error) {
563
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
564
+ process.exit(1);
565
+ }
566
+ });
567
+ program
568
+ .command('usage')
569
+ .description('Show your current usage and quota')
570
+ .action(async () => {
571
+ try {
572
+ await handleUsage();
573
+ process.exit(0);
574
+ }
575
+ catch (error) {
576
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
577
+ process.exit(1);
578
+ }
579
+ });
451
580
  program
452
581
  .command('serve')
453
582
  .description('Start API server')