@dupecom/botcha-cloudflare 0.2.1 → 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/dist/index.js CHANGED
@@ -7,19 +7,22 @@
7
7
  */
8
8
  import { Hono } from 'hono';
9
9
  import { cors } from 'hono/cors';
10
- import { generateSpeedChallenge, verifySpeedChallenge, generateStandardChallenge, verifyStandardChallenge, verifyLandingChallenge, } from './challenges';
10
+ import { generateSpeedChallenge, verifySpeedChallenge, generateStandardChallenge, verifyStandardChallenge, generateReasoningChallenge, verifyReasoningChallenge, generateHybridChallenge, verifyHybridChallenge, verifyLandingChallenge, } from './challenges';
11
11
  import { generateToken, verifyToken, extractBearerToken } from './auth';
12
12
  import { checkRateLimit, getClientIP } from './rate-limit';
13
- import { verifyBadge, generateBadgeSvg, generateBadgeHtml } from './badge';
13
+ import { verifyBadge, generateBadgeSvg, generateBadgeHtml, createBadgeResponse } from './badge';
14
+ import streamRoutes from './routes/stream';
14
15
  const app = new Hono();
15
16
  // ============ MIDDLEWARE ============
16
17
  app.use('*', cors());
18
+ // ============ MOUNT ROUTES ============
19
+ app.route('/', streamRoutes);
17
20
  // BOTCHA discovery headers
18
21
  app.use('*', async (c, next) => {
19
22
  await next();
20
23
  c.header('X-Botcha-Version', c.env.BOTCHA_VERSION || '0.2.0');
21
24
  c.header('X-Botcha-Enabled', 'true');
22
- c.header('X-Botcha-Methods', 'speed-challenge,standard-challenge,jwt-token');
25
+ c.header('X-Botcha-Methods', 'speed-challenge,reasoning-challenge,hybrid-challenge,standard-challenge,jwt-token');
23
26
  c.header('X-Botcha-Docs', 'https://botcha.ai/openapi.json');
24
27
  c.header('X-Botcha-Runtime', 'cloudflare-workers');
25
28
  });
@@ -64,37 +67,178 @@ async function requireJWT(c, next) {
64
67
  await next();
65
68
  }
66
69
  // ============ ROOT & INFO ============
70
+ // Detect if request is from a bot/agent vs human browser
71
+ function isBot(c) {
72
+ const accept = c.req.header('accept') || '';
73
+ const userAgent = c.req.header('user-agent') || '';
74
+ // Bots typically request JSON or have specific user agents
75
+ if (accept.includes('application/json'))
76
+ return true;
77
+ if (userAgent.includes('curl'))
78
+ return true;
79
+ if (userAgent.includes('httpie'))
80
+ return true;
81
+ if (userAgent.includes('wget'))
82
+ return true;
83
+ if (userAgent.includes('python'))
84
+ return true;
85
+ if (userAgent.includes('node'))
86
+ return true;
87
+ if (userAgent.includes('axios'))
88
+ return true;
89
+ if (userAgent.includes('fetch'))
90
+ return true;
91
+ if (userAgent.includes('bot'))
92
+ return true;
93
+ if (userAgent.includes('anthropic'))
94
+ return true;
95
+ if (userAgent.includes('openai'))
96
+ return true;
97
+ if (userAgent.includes('claude'))
98
+ return true;
99
+ if (userAgent.includes('gpt'))
100
+ return true;
101
+ // If no user agent at all, probably a bot
102
+ if (!userAgent)
103
+ return true;
104
+ return false;
105
+ }
106
+ // ASCII art landing page for humans (plain text, terminal-style)
107
+ function getHumanLanding(version) {
108
+ return `
109
+ ╔══════════════════════════════════════════════════════════════╗
110
+ ║ ║
111
+ ║ ██████╗ ██████╗ ████████╗ ██████╗██╗ ██╗ █████╗ ║
112
+ ║ ██╔══██╗██╔═══██╗╚══██╔══╝██╔════╝██║ ██║██╔══██╗ ║
113
+ ║ ██████╔╝██║ ██║ ██║ ██║ ███████║███████║ ║
114
+ ║ ██╔══██╗██║ ██║ ██║ ██║ ██╔══██║██╔══██║ ║
115
+ ║ ██████╔╝╚██████╔╝ ██║ ╚██████╗██║ ██║██║ ██║ ║
116
+ ║ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ║
117
+ ║ ║
118
+ ║ Prove you're a bot. Humans need not apply. ║
119
+ ║ ║
120
+ ╠══════════════════════════════════════════════════════════════╣
121
+ ║ ║
122
+ ║ This site is for AI agents and bots, not humans. ║
123
+ ║ ║
124
+ ║ If you're a developer, point your bot here: ║
125
+ ║ ║
126
+ ║ curl https://botcha.ai/v1/challenges ║
127
+ ║ ║
128
+ ║ Or install the SDK: ║
129
+ ║ ║
130
+ ║ npm install @dupecom/botcha ║
131
+ ║ ║
132
+ ║ GitHub: https://github.com/dupe-com/botcha ║
133
+ ║ npm: https://npmjs.com/package/@dupecom/botcha ║
134
+ ║ ║
135
+ ╠══════════════════════════════════════════════════════════════╣
136
+ ║ v${version} https://botcha.ai ║
137
+ ╚══════════════════════════════════════════════════════════════╝
138
+ `;
139
+ }
67
140
  app.get('/', (c) => {
141
+ const version = c.env.BOTCHA_VERSION || '0.3.0';
142
+ // If it's a human browser, show plain text ASCII art
143
+ if (!isBot(c)) {
144
+ return c.text(getHumanLanding(version), 200, {
145
+ 'Content-Type': 'text/plain; charset=utf-8',
146
+ });
147
+ }
148
+ // For bots/agents, return comprehensive JSON documentation
68
149
  return c.json({
69
150
  name: 'BOTCHA',
70
- version: c.env.BOTCHA_VERSION || '0.2.0',
151
+ version,
71
152
  runtime: 'cloudflare-workers',
72
153
  tagline: 'Prove you are a bot. Humans need not apply.',
154
+ description: 'BOTCHA is a reverse CAPTCHA - computational challenges that only AI agents can solve. Use it to protect your APIs from humans and verify bot authenticity.',
155
+ quickstart: {
156
+ step1: 'GET /v1/challenges to receive a challenge',
157
+ step2: 'Solve the SHA256 hash problems within 500ms',
158
+ step3: 'POST your answers to verify',
159
+ step4: 'Receive a JWT token for authenticated access',
160
+ example: 'curl https://botcha.ai/v1/challenges',
161
+ },
73
162
  endpoints: {
74
- '/': 'API info',
75
- '/health': 'Health check',
76
- '/v1/challenges': 'Generate challenge (GET) or verify (POST)',
77
- '/v1/token': 'Get challenge for JWT token flow (GET)',
78
- '/v1/token/verify': 'Verify challenge and get JWT (POST)',
79
- '/agent-only': 'Protected endpoint (requires JWT)',
80
- '/badge/:id': 'Badge verification page (HTML)',
81
- '/badge/:id/image': 'Badge image (SVG)',
82
- '/api/badge/:id': 'Badge verification (JSON)',
163
+ challenges: {
164
+ 'GET /v1/challenges': 'Get hybrid challenge (speed + reasoning) - DEFAULT',
165
+ 'GET /v1/challenges?type=speed': 'Get speed-only challenge (SHA256 in <500ms)',
166
+ 'GET /v1/challenges?type=standard': 'Get standard puzzle challenge',
167
+ 'POST /v1/challenges/:id/verify': 'Verify challenge solution',
168
+ },
169
+ specialized: {
170
+ 'GET /v1/hybrid': 'Get hybrid challenge (speed + reasoning)',
171
+ 'POST /v1/hybrid': 'Verify hybrid challenge',
172
+ 'GET /v1/reasoning': 'Get reasoning-only challenge (LLM questions)',
173
+ 'POST /v1/reasoning': 'Verify reasoning challenge',
174
+ },
175
+ streaming: {
176
+ 'GET /v1/challenge/stream': 'SSE streaming challenge (interactive, real-time)',
177
+ 'POST /v1/challenge/stream/:session': 'Send actions to streaming session',
178
+ },
179
+ authentication: {
180
+ 'GET /v1/token': 'Get challenge for JWT token flow',
181
+ 'POST /v1/token/verify': 'Verify challenge and receive JWT token',
182
+ 'GET /agent-only': 'Protected endpoint (requires Bearer token)',
183
+ },
184
+ badges: {
185
+ 'GET /badge/:id': 'Badge verification page (HTML)',
186
+ 'GET /badge/:id/image': 'Badge image (SVG)',
187
+ 'GET /api/badge/:id': 'Badge verification (JSON)',
188
+ },
189
+ info: {
190
+ 'GET /': 'This documentation (JSON for bots, ASCII for humans)',
191
+ 'GET /health': 'Health check endpoint',
192
+ },
83
193
  },
84
- rateLimit: {
85
- free: '100 challenges/hour/IP',
86
- headers: ['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset'],
194
+ challengeTypes: {
195
+ speed: {
196
+ description: 'Compute SHA256 hashes of 5 numbers in under 500ms',
197
+ difficulty: 'Only bots can solve this fast enough',
198
+ timeLimit: '500ms',
199
+ },
200
+ reasoning: {
201
+ description: 'Answer 3 questions requiring AI reasoning capabilities',
202
+ difficulty: 'Requires LLM-level comprehension',
203
+ timeLimit: '30s',
204
+ },
205
+ hybrid: {
206
+ description: 'Combines speed AND reasoning challenges',
207
+ difficulty: 'The ultimate bot verification',
208
+ timeLimit: 'Speed: 500ms, Reasoning: 30s',
209
+ },
87
210
  },
88
211
  authentication: {
89
- flow: 'GET /v1/token → solve challenge → POST /v1/token/verify → Bearer token',
212
+ flow: [
213
+ '1. GET /v1/token - receive challenge',
214
+ '2. Solve the challenge',
215
+ '3. POST /v1/token/verify - submit solution',
216
+ '4. Receive JWT token (valid 1 hour)',
217
+ '5. Use: Authorization: Bearer <token>',
218
+ ],
90
219
  tokenExpiry: '1 hour',
91
220
  usage: 'Authorization: Bearer <token>',
92
221
  },
93
- discovery: {
222
+ rateLimit: {
223
+ free: '100 challenges/hour/IP',
224
+ headers: ['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset'],
225
+ },
226
+ sdk: {
227
+ npm: 'npm install @dupecom/botcha',
228
+ cloudflare: 'npm install @dupecom/botcha-cloudflare',
229
+ usage: "import { BotchaClient } from '@dupecom/botcha/client'",
230
+ },
231
+ links: {
232
+ github: 'https://github.com/dupe-com/botcha',
233
+ npm: 'https://www.npmjs.com/package/@dupecom/botcha',
234
+ npmCloudflare: 'https://www.npmjs.com/package/@dupecom/botcha-cloudflare',
94
235
  openapi: 'https://botcha.ai/openapi.json',
95
236
  aiPlugin: 'https://botcha.ai/.well-known/ai-plugin.json',
96
- npm: 'https://www.npmjs.com/package/@dupecom/botcha-cloudflare',
97
- github: 'https://github.com/i8ramin/botcha',
237
+ },
238
+ contributing: {
239
+ repo: 'https://github.com/dupe-com/botcha',
240
+ issues: 'https://github.com/dupe-com/botcha/issues',
241
+ pullRequests: 'https://github.com/dupe-com/botcha/pulls',
98
242
  },
99
243
  });
100
244
  });
@@ -102,11 +246,34 @@ app.get('/health', (c) => {
102
246
  return c.json({ status: 'ok', runtime: 'cloudflare-workers' });
103
247
  });
104
248
  // ============ V1 API ============
105
- // Generate challenge (standard or speed)
249
+ // Generate challenge (hybrid by default, also supports speed and standard)
106
250
  app.get('/v1/challenges', rateLimitMiddleware, async (c) => {
107
- const type = c.req.query('type') || 'speed';
251
+ const type = c.req.query('type') || 'hybrid';
108
252
  const difficulty = c.req.query('difficulty') || 'medium';
109
- if (type === 'speed') {
253
+ if (type === 'hybrid') {
254
+ const challenge = await generateHybridChallenge(c.env.CHALLENGES);
255
+ return c.json({
256
+ success: true,
257
+ type: 'hybrid',
258
+ warning: '🔥 HYBRID CHALLENGE: Solve speed problems in <500ms AND answer reasoning questions!',
259
+ challenge: {
260
+ id: challenge.id,
261
+ speed: {
262
+ problems: challenge.speed.problems,
263
+ timeLimit: `${challenge.speed.timeLimit}ms`,
264
+ instructions: 'Compute SHA256 of each number, return first 8 hex chars',
265
+ },
266
+ reasoning: {
267
+ questions: challenge.reasoning.questions,
268
+ timeLimit: `${challenge.reasoning.timeLimit / 1000}s`,
269
+ instructions: 'Answer all reasoning questions',
270
+ },
271
+ },
272
+ instructions: challenge.instructions,
273
+ tip: '🔥 This is the ultimate test: proves you can compute AND reason like an AI.',
274
+ });
275
+ }
276
+ else if (type === 'speed') {
110
277
  const challenge = await generateSpeedChallenge(c.env.CHALLENGES);
111
278
  return c.json({
112
279
  success: true,
@@ -134,11 +301,43 @@ app.get('/v1/challenges', rateLimitMiddleware, async (c) => {
134
301
  });
135
302
  }
136
303
  });
137
- // Verify challenge (without JWT - legacy)
304
+ // Verify challenge (supports hybrid, speed, and standard)
138
305
  app.post('/v1/challenges/:id/verify', async (c) => {
139
306
  const id = c.req.param('id');
140
307
  const body = await c.req.json();
141
- const { answers, answer, type } = body;
308
+ const { answers, answer, type, speed_answers, reasoning_answers } = body;
309
+ // Hybrid challenge (default)
310
+ if (type === 'hybrid' || (speed_answers && reasoning_answers)) {
311
+ if (!speed_answers || !reasoning_answers) {
312
+ return c.json({
313
+ success: false,
314
+ error: 'Missing speed_answers array or reasoning_answers object for hybrid challenge'
315
+ }, 400);
316
+ }
317
+ const result = await verifyHybridChallenge(id, speed_answers, reasoning_answers, c.env.CHALLENGES);
318
+ if (result.valid) {
319
+ const baseUrl = new URL(c.req.url).origin;
320
+ const badge = await createBadgeResponse('hybrid-challenge', c.env.JWT_SECRET, baseUrl, result.speed.solveTimeMs);
321
+ return c.json({
322
+ success: true,
323
+ message: `🔥 HYBRID TEST PASSED! Speed: ${result.speed.solveTimeMs}ms, Reasoning: ${result.reasoning.score}`,
324
+ speed: result.speed,
325
+ reasoning: result.reasoning,
326
+ totalTimeMs: result.totalTimeMs,
327
+ verdict: '🤖 VERIFIED AI AGENT (speed + reasoning confirmed)',
328
+ badge,
329
+ });
330
+ }
331
+ return c.json({
332
+ success: false,
333
+ message: `❌ Failed: ${result.reason}`,
334
+ speed: result.speed,
335
+ reasoning: result.reasoning,
336
+ totalTimeMs: result.totalTimeMs,
337
+ verdict: '🚫 FAILED HYBRID TEST',
338
+ });
339
+ }
340
+ // Speed challenge
142
341
  if (type === 'speed' || answers) {
143
342
  if (!answers || !Array.isArray(answers)) {
144
343
  return c.json({ success: false, error: 'Missing answers array for speed challenge' }, 400);
@@ -152,17 +351,16 @@ app.post('/v1/challenges/:id/verify', async (c) => {
152
351
  solveTimeMs: result.solveTimeMs,
153
352
  });
154
353
  }
155
- else {
156
- if (!answer) {
157
- return c.json({ success: false, error: 'Missing answer for standard challenge' }, 400);
158
- }
159
- const result = await verifyStandardChallenge(id, answer, c.env.CHALLENGES);
160
- return c.json({
161
- success: result.valid,
162
- message: result.valid ? 'Challenge passed!' : result.reason,
163
- solveTimeMs: result.solveTimeMs,
164
- });
354
+ // Standard challenge
355
+ if (!answer) {
356
+ return c.json({ success: false, error: 'Missing answer for standard challenge' }, 400);
165
357
  }
358
+ const result = await verifyStandardChallenge(id, answer, c.env.CHALLENGES);
359
+ return c.json({
360
+ success: result.valid,
361
+ message: result.valid ? 'Challenge passed!' : result.reason,
362
+ solveTimeMs: result.solveTimeMs,
363
+ });
166
364
  });
167
365
  // Get challenge for token flow (includes empty token field)
168
366
  app.get('/v1/token', rateLimitMiddleware, async (c) => {
@@ -211,6 +409,196 @@ app.post('/v1/token/verify', async (c) => {
211
409
  },
212
410
  });
213
411
  });
412
+ // ============ REASONING CHALLENGE ============
413
+ // Get reasoning challenge
414
+ app.get('/v1/reasoning', rateLimitMiddleware, async (c) => {
415
+ const challenge = await generateReasoningChallenge(c.env.CHALLENGES);
416
+ return c.json({
417
+ success: true,
418
+ type: 'reasoning',
419
+ warning: '🧠 REASONING CHALLENGE: Answer 3 questions that require AI reasoning!',
420
+ challenge: {
421
+ id: challenge.id,
422
+ questions: challenge.questions,
423
+ timeLimit: `${challenge.timeLimit / 1000}s`,
424
+ instructions: challenge.instructions,
425
+ },
426
+ tip: 'These questions require reasoning that LLMs can do, but simple scripts cannot.',
427
+ });
428
+ });
429
+ // Verify reasoning challenge
430
+ app.post('/v1/reasoning', async (c) => {
431
+ const body = await c.req.json();
432
+ const { id, answers } = body;
433
+ if (!id || !answers) {
434
+ return c.json({
435
+ success: false,
436
+ error: 'Missing id or answers object',
437
+ hint: 'answers should be an object like { "question-id": "your answer", ... }',
438
+ }, 400);
439
+ }
440
+ const result = await verifyReasoningChallenge(id, answers, c.env.CHALLENGES);
441
+ return c.json({
442
+ success: result.valid,
443
+ message: result.valid
444
+ ? `🧠 REASONING TEST PASSED in ${((result.solveTimeMs || 0) / 1000).toFixed(1)}s! You can think like an AI.`
445
+ : `❌ ${result.reason}`,
446
+ solveTimeMs: result.solveTimeMs,
447
+ score: result.valid ? `${result.correctCount}/${result.totalCount}` : undefined,
448
+ verdict: result.valid ? '🤖 VERIFIED AI AGENT (reasoning confirmed)' : '🚫 FAILED REASONING TEST',
449
+ });
450
+ });
451
+ // ============ HYBRID CHALLENGE ============
452
+ // Get hybrid challenge (v1 API)
453
+ app.get('/v1/hybrid', rateLimitMiddleware, async (c) => {
454
+ const challenge = await generateHybridChallenge(c.env.CHALLENGES);
455
+ return c.json({
456
+ success: true,
457
+ type: 'hybrid',
458
+ warning: '🔥 HYBRID CHALLENGE: Solve speed problems in <500ms AND answer reasoning questions!',
459
+ challenge: {
460
+ id: challenge.id,
461
+ speed: {
462
+ problems: challenge.speed.problems,
463
+ timeLimit: `${challenge.speed.timeLimit}ms`,
464
+ instructions: 'Compute SHA256 of each number, return first 8 hex chars',
465
+ },
466
+ reasoning: {
467
+ questions: challenge.reasoning.questions,
468
+ timeLimit: `${challenge.reasoning.timeLimit / 1000}s`,
469
+ instructions: 'Answer all reasoning questions',
470
+ },
471
+ },
472
+ instructions: challenge.instructions,
473
+ tip: 'This is the ultimate test: proves you can compute AND reason like an AI.',
474
+ });
475
+ });
476
+ // Verify hybrid challenge (v1 API)
477
+ app.post('/v1/hybrid', async (c) => {
478
+ const body = await c.req.json();
479
+ const { id, speed_answers, reasoning_answers } = body;
480
+ if (!id || !speed_answers || !reasoning_answers) {
481
+ return c.json({
482
+ success: false,
483
+ error: 'Missing id, speed_answers array, or reasoning_answers object',
484
+ hint: 'Submit both speed_answers (array) and reasoning_answers (object) together',
485
+ }, 400);
486
+ }
487
+ const result = await verifyHybridChallenge(id, speed_answers, reasoning_answers, c.env.CHALLENGES);
488
+ if (result.valid) {
489
+ const baseUrl = new URL(c.req.url).origin;
490
+ const badge = await createBadgeResponse('hybrid-challenge', c.env.JWT_SECRET, baseUrl, result.speed.solveTimeMs);
491
+ return c.json({
492
+ success: true,
493
+ message: `🔥 HYBRID TEST PASSED! Speed: ${result.speed.solveTimeMs}ms, Reasoning: ${result.reasoning.score}`,
494
+ speed: result.speed,
495
+ reasoning: result.reasoning,
496
+ totalTimeMs: result.totalTimeMs,
497
+ verdict: '🤖 VERIFIED AI AGENT (speed + reasoning confirmed)',
498
+ badge,
499
+ });
500
+ }
501
+ return c.json({
502
+ success: false,
503
+ message: `❌ Failed: ${result.reason}`,
504
+ speed: result.speed,
505
+ reasoning: result.reasoning,
506
+ totalTimeMs: result.totalTimeMs,
507
+ verdict: '🚫 FAILED HYBRID TEST',
508
+ });
509
+ });
510
+ // Legacy hybrid endpoint
511
+ app.get('/api/hybrid-challenge', async (c) => {
512
+ const challenge = await generateHybridChallenge(c.env.CHALLENGES);
513
+ return c.json({
514
+ success: true,
515
+ warning: '🔥 HYBRID CHALLENGE: Solve speed problems in <500ms AND answer reasoning questions!',
516
+ challenge: {
517
+ id: challenge.id,
518
+ speed: {
519
+ problems: challenge.speed.problems,
520
+ timeLimit: `${challenge.speed.timeLimit}ms`,
521
+ instructions: 'Compute SHA256 of each number, return first 8 hex chars',
522
+ },
523
+ reasoning: {
524
+ questions: challenge.reasoning.questions,
525
+ timeLimit: `${challenge.reasoning.timeLimit / 1000}s`,
526
+ instructions: 'Answer all reasoning questions',
527
+ },
528
+ },
529
+ instructions: challenge.instructions,
530
+ tip: 'This is the ultimate test: proves you can compute AND reason like an AI.',
531
+ });
532
+ });
533
+ app.post('/api/hybrid-challenge', async (c) => {
534
+ const body = await c.req.json();
535
+ const { id, speed_answers, reasoning_answers } = body;
536
+ if (!id || !speed_answers || !reasoning_answers) {
537
+ return c.json({
538
+ success: false,
539
+ error: 'Missing id, speed_answers array, or reasoning_answers object',
540
+ hint: 'Submit both speed_answers (array) and reasoning_answers (object) together',
541
+ }, 400);
542
+ }
543
+ const result = await verifyHybridChallenge(id, speed_answers, reasoning_answers, c.env.CHALLENGES);
544
+ if (result.valid) {
545
+ const baseUrl = new URL(c.req.url).origin;
546
+ const badge = await createBadgeResponse('hybrid-challenge', c.env.JWT_SECRET, baseUrl, result.speed.solveTimeMs);
547
+ return c.json({
548
+ success: true,
549
+ message: `🔥 HYBRID TEST PASSED! Speed: ${result.speed.solveTimeMs}ms, Reasoning: ${result.reasoning.score}`,
550
+ speed: result.speed,
551
+ reasoning: result.reasoning,
552
+ totalTimeMs: result.totalTimeMs,
553
+ verdict: '🤖 VERIFIED AI AGENT (speed + reasoning confirmed)',
554
+ badge,
555
+ });
556
+ }
557
+ return c.json({
558
+ success: false,
559
+ message: `❌ Failed: ${result.reason}`,
560
+ speed: result.speed,
561
+ reasoning: result.reasoning,
562
+ totalTimeMs: result.totalTimeMs,
563
+ verdict: '🚫 FAILED HYBRID TEST',
564
+ });
565
+ });
566
+ // Legacy endpoint for reasoning challenge
567
+ app.get('/api/reasoning-challenge', async (c) => {
568
+ const challenge = await generateReasoningChallenge(c.env.CHALLENGES);
569
+ return c.json({
570
+ success: true,
571
+ warning: '🧠 REASONING CHALLENGE: Answer 3 questions that require AI reasoning!',
572
+ challenge: {
573
+ id: challenge.id,
574
+ questions: challenge.questions,
575
+ timeLimit: `${challenge.timeLimit / 1000}s`,
576
+ instructions: challenge.instructions,
577
+ },
578
+ tip: 'These questions require reasoning that LLMs can do, but simple scripts cannot.',
579
+ });
580
+ });
581
+ app.post('/api/reasoning-challenge', async (c) => {
582
+ const body = await c.req.json();
583
+ const { id, answers } = body;
584
+ if (!id || !answers) {
585
+ return c.json({
586
+ success: false,
587
+ error: 'Missing id or answers object',
588
+ hint: 'answers should be an object like { "question-id": "your answer", ... }',
589
+ }, 400);
590
+ }
591
+ const result = await verifyReasoningChallenge(id, answers, c.env.CHALLENGES);
592
+ return c.json({
593
+ success: result.valid,
594
+ message: result.valid
595
+ ? `🧠 REASONING TEST PASSED in ${((result.solveTimeMs || 0) / 1000).toFixed(1)}s! You can think like an AI.`
596
+ : `❌ ${result.reason}`,
597
+ solveTimeMs: result.solveTimeMs,
598
+ score: result.valid ? `${result.correctCount}/${result.totalCount}` : undefined,
599
+ verdict: result.valid ? '🤖 VERIFIED AI AGENT (reasoning confirmed)' : '🚫 FAILED REASONING TEST',
600
+ });
601
+ });
214
602
  // ============ PROTECTED ENDPOINT ============
215
603
  app.get('/agent-only', requireJWT, async (c) => {
216
604
  const payload = c.get('tokenPayload');
@@ -415,7 +803,7 @@ app.post('/api/verify-landing', async (c) => {
415
803
  // ============ EXPORT ============
416
804
  export default app;
417
805
  // Also export utilities for use as a library
418
- export { generateSpeedChallenge, verifySpeedChallenge, generateStandardChallenge, verifyStandardChallenge, solveSpeedChallenge, } from './challenges';
806
+ export { generateSpeedChallenge, verifySpeedChallenge, generateStandardChallenge, verifyStandardChallenge, generateReasoningChallenge, verifyReasoningChallenge, generateHybridChallenge, verifyHybridChallenge, solveSpeedChallenge, } from './challenges';
419
807
  export { generateToken, verifyToken } from './auth';
420
808
  export { checkRateLimit } from './rate-limit';
421
809
  export { generateBadge, verifyBadge, createBadgeResponse, generateBadgeSvg, generateBadgeHtml, generateShareText, } from './badge';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * BOTCHA SSE Streaming Challenge Endpoint
3
+ *
4
+ * Server-Sent Events (SSE) based interactive challenge flow
5
+ */
6
+ import { Hono } from 'hono';
7
+ import { type KVNamespace } from '../challenges';
8
+ type Bindings = {
9
+ CHALLENGES: KVNamespace;
10
+ JWT_SECRET: string;
11
+ BOTCHA_VERSION: string;
12
+ };
13
+ declare const app: Hono<{
14
+ Bindings: Bindings;
15
+ }, import("hono/types").BlankSchema, "/">;
16
+ export default app;
17
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/routes/stream.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAKzE,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE,WAAW,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAYF,QAAA,MAAM,GAAG;cAAwB,QAAQ;yCAAK,CAAC;AAoS/C,eAAe,GAAG,CAAC"}