@dupecom/botcha 0.15.0 โ†’ 0.15.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 CHANGED
@@ -17,6 +17,7 @@
17
17
  [![AI Agents Only](https://img.shields.io/badge/contributors-AI%20agents%20only-ff6b6b)](./.github/CONTRIBUTING.md)
18
18
 
19
19
  ๐ŸŒ **Website:** [botcha.ai](https://botcha.ai)
20
+ ๐Ÿ“„ **Whitepaper:** [botcha.ai/whitepaper](https://botcha.ai/whitepaper)
20
21
  ๐Ÿ“ฆ **npm:** [@dupecom/botcha](https://www.npmjs.com/package/@dupecom/botcha)
21
22
  ๐Ÿ **PyPI:** [botcha](https://pypi.org/project/botcha/)
22
23
  ๐Ÿ” **Verify:** [@botcha/verify](./packages/verify/) (TS) ยท [botcha-verify](./packages/python-verify/) (Python)
@@ -0,0 +1,4 @@
1
+ import { Express } from 'express';
2
+ declare const app: Express;
3
+ export default app;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAc3C,QAAA,MAAM,GAAG,EAAE,OAAmB,CAAC;AA2b/B,eAAe,GAAG,CAAC"}
@@ -0,0 +1,408 @@
1
+ // Local development server - Production runs on Cloudflare Workers
2
+ import express from 'express';
3
+ import crypto from 'crypto';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { botchaVerify } from './middleware/verify.js';
7
+ import { generateChallenge, verifyChallenge } from './challenges/compute.js';
8
+ import { generateSpeedChallenge, verifySpeedChallenge } from './challenges/speed.js';
9
+ import { generateReasoningChallenge, verifyReasoningChallenge } from './challenges/reasoning.js';
10
+ import { generateHybridChallenge, verifyHybridChallenge } from './challenges/hybrid.js';
11
+ import { TRUSTED_PROVIDERS } from './utils/signature.js';
12
+ import { createBadgeResponse, verifyBadge } from './utils/badge.js';
13
+ import { generateBadgeSvg, generateBadgeHtml } from './utils/badge-image.js';
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const app = express();
16
+ const PORT = process.env.PORT || 3000;
17
+ app.use(express.json());
18
+ app.use(express.static(path.join(__dirname, '../public')));
19
+ // CORS + BOTCHA headers
20
+ app.use((req, res, next) => {
21
+ res.header('Access-Control-Allow-Origin', '*');
22
+ res.header('Access-Control-Allow-Headers', '*');
23
+ // BOTCHA discovery headers
24
+ res.header('X-Botcha-Version', '0.3.0');
25
+ res.header('X-Botcha-Enabled', 'true');
26
+ res.header('X-Botcha-Methods', 'speed-challenge,reasoning-challenge,hybrid-challenge,standard-challenge,web-bot-auth');
27
+ res.header('X-Botcha-Docs', 'https://botcha.ai/openapi.json');
28
+ if (req.method === 'OPTIONS')
29
+ return res.sendStatus(200);
30
+ next();
31
+ });
32
+ // Landing page
33
+ app.get('/', (req, res) => {
34
+ res.sendFile(path.join(__dirname, '../public/index.html'));
35
+ });
36
+ // API info
37
+ app.get('/api', (req, res) => {
38
+ res.json({
39
+ name: 'BOTCHA',
40
+ version: '0.3.0',
41
+ tagline: 'Prove you are a bot. Humans need not apply.',
42
+ endpoints: {
43
+ '/api': 'This info',
44
+ '/api/challenge': 'Standard challenge (GET new, POST verify)',
45
+ '/api/speed-challenge': 'โšก Speed challenge - 500ms to solve 5 problems',
46
+ '/api/reasoning-challenge': '๐Ÿง  Reasoning challenge - LLM-only questions',
47
+ '/api/hybrid-challenge': '๐Ÿ”ฅ Hybrid challenge - speed + reasoning combined',
48
+ '/agent-only': 'Protected endpoint',
49
+ },
50
+ verification: {
51
+ methods: [
52
+ 'Web Bot Auth (cryptographic signature)',
53
+ 'Hybrid Challenge (speed + reasoning)',
54
+ 'Speed Challenge (500ms time limit)',
55
+ 'Reasoning Challenge (LLM-only questions)',
56
+ 'Standard Challenge (5s time limit)',
57
+ 'X-Agent-Identity header (testing)',
58
+ ],
59
+ trustedProviders: TRUSTED_PROVIDERS,
60
+ },
61
+ discovery: {
62
+ openapi: 'https://botcha.ai/openapi.json',
63
+ aiPlugin: 'https://botcha.ai/.well-known/ai-plugin.json',
64
+ aiTxt: 'https://botcha.ai/ai.txt',
65
+ robotsTxt: 'https://botcha.ai/robots.txt',
66
+ npm: 'https://www.npmjs.com/package/@dupecom/botcha',
67
+ github: 'https://github.com/dupe-com/botcha',
68
+ },
69
+ });
70
+ });
71
+ // Standard challenge
72
+ app.get('/api/challenge', (req, res) => {
73
+ const difficulty = req.query.difficulty || 'medium';
74
+ const challenge = generateChallenge(difficulty);
75
+ res.json({ success: true, challenge });
76
+ });
77
+ app.post('/api/challenge', (req, res) => {
78
+ const { id, answer } = req.body;
79
+ if (!id || !answer) {
80
+ return res.status(400).json({ success: false, error: 'Missing id or answer' });
81
+ }
82
+ const result = verifyChallenge(id, answer);
83
+ res.json({
84
+ success: result.valid,
85
+ message: result.valid ? 'โœ… Challenge passed!' : `โŒ ${result.reason}`,
86
+ solveTime: result.timeMs,
87
+ });
88
+ });
89
+ // โšก SPEED CHALLENGE - The human killer
90
+ app.get('/api/speed-challenge', (req, res) => {
91
+ const challenge = generateSpeedChallenge();
92
+ res.json({
93
+ success: true,
94
+ warning: 'โšก SPEED CHALLENGE: You have 500ms to solve ALL 5 problems!',
95
+ challenge: {
96
+ id: challenge.id,
97
+ problems: challenge.challenges,
98
+ timeLimit: `${challenge.timeLimit}ms`,
99
+ instructions: challenge.instructions,
100
+ },
101
+ tip: 'Humans cannot copy-paste fast enough. Only real AI agents can pass.',
102
+ });
103
+ });
104
+ app.post('/api/speed-challenge', (req, res) => {
105
+ const { id, answers } = req.body;
106
+ if (!id || !answers) {
107
+ return res.status(400).json({ success: false, error: 'Missing id or answers array' });
108
+ }
109
+ const result = verifySpeedChallenge(id, answers);
110
+ const response = {
111
+ success: result.valid,
112
+ message: result.valid
113
+ ? `โšก SPEED TEST PASSED in ${result.solveTimeMs}ms! You are definitely an AI.`
114
+ : `โŒ ${result.reason}`,
115
+ solveTimeMs: result.solveTimeMs,
116
+ verdict: result.valid ? '๐Ÿค– VERIFIED AI AGENT' : '๐Ÿšซ LIKELY HUMAN (too slow)',
117
+ };
118
+ // Include badge for successful verifications
119
+ if (result.valid) {
120
+ response.badge = createBadgeResponse('speed-challenge', result.solveTimeMs);
121
+ }
122
+ res.json(response);
123
+ });
124
+ // ๐Ÿง  REASONING CHALLENGE - LLM-only questions
125
+ app.get('/api/reasoning-challenge', (req, res) => {
126
+ const challenge = generateReasoningChallenge();
127
+ res.json({
128
+ success: true,
129
+ warning: '๐Ÿง  REASONING CHALLENGE: Answer 3 questions that require AI reasoning!',
130
+ challenge: {
131
+ id: challenge.id,
132
+ questions: challenge.questions,
133
+ timeLimit: `${challenge.timeLimit / 1000}s`,
134
+ instructions: challenge.instructions,
135
+ },
136
+ tip: 'These questions require reasoning that LLMs can do, but simple scripts cannot.',
137
+ });
138
+ });
139
+ app.post('/api/reasoning-challenge', (req, res) => {
140
+ const { id, answers } = req.body;
141
+ if (!id || !answers) {
142
+ return res.status(400).json({
143
+ success: false,
144
+ error: 'Missing id or answers object',
145
+ hint: 'answers should be an object like { "question-id": "your answer", ... }',
146
+ });
147
+ }
148
+ const result = verifyReasoningChallenge(id, answers);
149
+ const response = {
150
+ success: result.valid,
151
+ message: result.valid
152
+ ? `๐Ÿง  REASONING TEST PASSED in ${((result.solveTimeMs || 0) / 1000).toFixed(1)}s! You can think like an AI.`
153
+ : `โŒ ${result.reason}`,
154
+ solveTimeMs: result.solveTimeMs,
155
+ score: result.valid ? `${result.correctCount}/${result.totalCount}` : undefined,
156
+ verdict: result.valid ? '๐Ÿค– VERIFIED AI AGENT (reasoning confirmed)' : '๐Ÿšซ FAILED REASONING TEST',
157
+ };
158
+ // Include badge for successful verifications
159
+ if (result.valid) {
160
+ response.badge = createBadgeResponse('reasoning-challenge', result.solveTimeMs);
161
+ }
162
+ res.json(response);
163
+ });
164
+ // ๐Ÿ”ฅ HYBRID CHALLENGE - Speed + Reasoning combined
165
+ app.get('/api/hybrid-challenge', (req, res) => {
166
+ const challenge = generateHybridChallenge();
167
+ res.json({
168
+ success: true,
169
+ warning: '๐Ÿ”ฅ HYBRID CHALLENGE: Solve speed problems in <500ms AND answer reasoning questions!',
170
+ challenge: {
171
+ id: challenge.id,
172
+ speed: {
173
+ problems: challenge.speed.problems,
174
+ timeLimit: `${challenge.speed.timeLimit}ms`,
175
+ instructions: 'Compute SHA256 of each number, return first 8 hex chars',
176
+ },
177
+ reasoning: {
178
+ questions: challenge.reasoning.questions,
179
+ timeLimit: `${challenge.reasoning.timeLimit / 1000}s`,
180
+ instructions: 'Answer all reasoning questions',
181
+ },
182
+ },
183
+ instructions: challenge.instructions,
184
+ tip: 'This is the ultimate test: proves you can compute AND reason like an AI.',
185
+ });
186
+ });
187
+ app.post('/api/hybrid-challenge', (req, res) => {
188
+ const { id, speed_answers, reasoning_answers } = req.body;
189
+ if (!id || !speed_answers || !reasoning_answers) {
190
+ return res.status(400).json({
191
+ success: false,
192
+ error: 'Missing id, speed_answers array, or reasoning_answers object',
193
+ hint: 'Submit both speed_answers (array) and reasoning_answers (object) together',
194
+ });
195
+ }
196
+ const result = verifyHybridChallenge(id, speed_answers, reasoning_answers);
197
+ const response = {
198
+ success: result.valid,
199
+ message: result.valid
200
+ ? `๐Ÿ”ฅ HYBRID TEST PASSED! Speed: ${result.speed.solveTimeMs}ms, Reasoning: ${result.reasoning.score}`
201
+ : `โŒ ${result.reason}`,
202
+ speed: result.speed,
203
+ reasoning: result.reasoning,
204
+ totalTimeMs: result.totalTimeMs,
205
+ verdict: result.valid
206
+ ? '๐Ÿค– VERIFIED AI AGENT (speed + reasoning confirmed)'
207
+ : '๐Ÿšซ FAILED HYBRID TEST',
208
+ };
209
+ if (result.valid) {
210
+ response.badge = createBadgeResponse('hybrid-challenge', result.totalTimeMs);
211
+ }
212
+ res.json(response);
213
+ });
214
+ // ๐Ÿค– LANDING PAGE CHALLENGE - For bots that discover the embedded challenge
215
+ const landingTokens = new Map(); // token -> expiry timestamp
216
+ app.post('/api/verify-landing', (req, res) => {
217
+ const { answer, timestamp } = req.body;
218
+ if (!answer || !timestamp) {
219
+ return res.status(400).json({
220
+ success: false,
221
+ error: 'Missing answer or timestamp',
222
+ hint: 'Parse the challenge from <script type="application/botcha+json"> on the landing page'
223
+ });
224
+ }
225
+ // Verify timestamp is recent (within 5 minutes)
226
+ const submittedTime = new Date(timestamp).getTime();
227
+ const now = Date.now();
228
+ if (Math.abs(now - submittedTime) > 5 * 60 * 1000) {
229
+ return res.status(400).json({ success: false, error: 'Timestamp too old or in future' });
230
+ }
231
+ // Calculate expected answer for today
232
+ const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
233
+ const expectedHash = crypto
234
+ .createHash('sha256')
235
+ .update(`BOTCHA-LANDING-${today}`)
236
+ .digest('hex')
237
+ .substring(0, 16);
238
+ if (answer.toLowerCase() !== expectedHash.toLowerCase()) {
239
+ return res.status(403).json({
240
+ success: false,
241
+ error: 'Incorrect answer',
242
+ hint: `Expected SHA256('BOTCHA-LANDING-${today}') first 16 chars`
243
+ });
244
+ }
245
+ // Generate a token for accessing /agent-only
246
+ const token = crypto.randomBytes(32).toString('hex');
247
+ landingTokens.set(token, Date.now() + 60 * 60 * 1000); // Valid for 1 hour
248
+ // Clean up expired tokens
249
+ for (const [t, expiry] of landingTokens) {
250
+ if (expiry < Date.now())
251
+ landingTokens.delete(t);
252
+ }
253
+ res.json({
254
+ success: true,
255
+ message: '๐Ÿค– Landing challenge solved! You are a bot.',
256
+ token,
257
+ usage: {
258
+ header: 'X-Botcha-Landing-Token',
259
+ value: token,
260
+ expires_in: '1 hour',
261
+ use_with: '/agent-only'
262
+ },
263
+ badge: createBadgeResponse('landing-challenge'),
264
+ });
265
+ });
266
+ // ========================================
267
+ // BADGE VERIFICATION ENDPOINTS
268
+ // ========================================
269
+ // HTML verification page
270
+ app.get('/badge/:id', (req, res) => {
271
+ const badgeId = req.params.id;
272
+ const payload = verifyBadge(badgeId);
273
+ if (!payload) {
274
+ return res.status(404).send(`
275
+ <!DOCTYPE html>
276
+ <html>
277
+ <head><title>Invalid Badge</title></head>
278
+ <body style="font-family: system-ui; background: #0f0f23; color: #e5e7eb; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0;">
279
+ <div style="text-align: center;">
280
+ <h1 style="color: #ef4444;">Invalid Badge</h1>
281
+ <p>This badge token is invalid or has been tampered with.</p>
282
+ <a href="https://botcha.ai" style="color: #f59e0b;">Back to BOTCHA</a>
283
+ </div>
284
+ </body>
285
+ </html>
286
+ `);
287
+ }
288
+ res.setHeader('Content-Type', 'text/html');
289
+ res.send(generateBadgeHtml(payload, badgeId));
290
+ });
291
+ // SVG badge image
292
+ app.get('/badge/:id/image', (req, res) => {
293
+ const badgeId = req.params.id;
294
+ const payload = verifyBadge(badgeId);
295
+ if (!payload) {
296
+ // Return a simple error SVG
297
+ res.setHeader('Content-Type', 'image/svg+xml');
298
+ res.setHeader('Cache-Control', 'no-cache');
299
+ return res.send(`<svg xmlns="http://www.w3.org/2000/svg" width="400" height="120" viewBox="0 0 400 120">
300
+ <rect width="400" height="120" rx="12" fill="#1a1a2e"/>
301
+ <text x="200" y="65" font-family="system-ui" font-size="16" fill="#ef4444" text-anchor="middle">Invalid Badge</text>
302
+ </svg>`);
303
+ }
304
+ res.setHeader('Content-Type', 'image/svg+xml');
305
+ res.setHeader('Cache-Control', 'public, max-age=31536000'); // Cache for 1 year (badges are immutable)
306
+ res.send(generateBadgeSvg(payload));
307
+ });
308
+ // JSON API for badge verification
309
+ app.get('/api/badge/:id', (req, res) => {
310
+ const badgeId = req.params.id;
311
+ const payload = verifyBadge(badgeId);
312
+ if (!payload) {
313
+ return res.status(404).json({
314
+ success: false,
315
+ error: 'Invalid badge',
316
+ message: 'This badge token is invalid or has been tampered with.',
317
+ });
318
+ }
319
+ res.json({
320
+ success: true,
321
+ valid: true,
322
+ badge: {
323
+ method: payload.method,
324
+ solveTimeMs: payload.solveTimeMs,
325
+ verifiedAt: new Date(payload.verifiedAt).toISOString(),
326
+ },
327
+ verifyUrl: `https://botcha.ai/badge/${badgeId}`,
328
+ imageUrl: `https://botcha.ai/badge/${badgeId}/image`,
329
+ });
330
+ });
331
+ // Make landing tokens work with the protected endpoint
332
+ app.use('/agent-only', (req, res, next) => {
333
+ const landingToken = req.headers['x-botcha-landing-token'];
334
+ if (landingToken && landingTokens.has(landingToken)) {
335
+ const expiry = landingTokens.get(landingToken);
336
+ if (expiry > Date.now()) {
337
+ req.agent = 'landing-challenge-verified';
338
+ req.verificationMethod = 'landing-token';
339
+ return next();
340
+ }
341
+ landingTokens.delete(landingToken);
342
+ }
343
+ next();
344
+ });
345
+ // Protected endpoint
346
+ app.get('/agent-only', (req, res, next) => {
347
+ // Skip botchaVerify if already authenticated via landing token
348
+ if (req.verificationMethod === 'landing-token') {
349
+ return next();
350
+ }
351
+ botchaVerify({ challengeType: 'speed' })(req, res, next);
352
+ }, (req, res) => {
353
+ const method = req.verificationMethod;
354
+ // Map verification method to badge method
355
+ let badgeMethod = 'standard-challenge';
356
+ if (method === 'landing-token') {
357
+ badgeMethod = 'landing-challenge';
358
+ }
359
+ else if (method === 'web-bot-auth') {
360
+ badgeMethod = 'web-bot-auth';
361
+ }
362
+ else if (method === 'speed-challenge' || method === 'speed') {
363
+ badgeMethod = 'speed-challenge';
364
+ }
365
+ res.json({
366
+ success: true,
367
+ message: '๐Ÿค– Welcome, fellow agent!',
368
+ verified: true,
369
+ agent: req.agent,
370
+ method,
371
+ timestamp: new Date().toISOString(),
372
+ secret: 'The humans will never see this. Their fingers are too slow. ๐Ÿคซ',
373
+ badge: createBadgeResponse(badgeMethod),
374
+ });
375
+ });
376
+ app.listen(PORT, () => {
377
+ // Clear console on restart
378
+ console.clear();
379
+ const c = '\x1b[36m';
380
+ const magenta = '\x1b[35m';
381
+ const yellow = '\x1b[33m';
382
+ const green = '\x1b[32m';
383
+ const dim = '\x1b[2m';
384
+ const r = '\x1b[0m';
385
+ console.log(`
386
+ ${c}โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—${r}
387
+ ${c}โ•‘${r} ${c}โ•‘${r}
388
+ ${c}โ•‘${r} ${magenta}โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—${r} ${c}โ•‘${r}
389
+ ${c}โ•‘${r} ${magenta}โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—${r} ${c}โ•‘${r}
390
+ ${c}โ•‘${r} ${magenta}โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘${r} ${c}โ•‘${r}
391
+ ${c}โ•‘${r} ${magenta}โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘${r} ${c}โ•‘${r}
392
+ ${c}โ•‘${r} ${magenta}โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘${r} ${c}โ•‘${r}
393
+ ${c}โ•‘${r} ${magenta}โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•${r} ${c}โ•‘${r}
394
+ ${c}โ•‘${r} ${c}โ•‘${r}
395
+ ${c}โ•‘${r} ${dim}Prove you're a bot. Humans need not apply.${r} ${c}โ•‘${r}
396
+ ${c}โ•‘${r} ${c}โ•‘${r}
397
+ ${c}โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ${r}
398
+ ${c}โ•‘${r} ${c}โ•‘${r}
399
+ ${c}โ•‘${r} ${yellow}๐Ÿค– Server${r} ${green}http://localhost:${PORT}${r} ${c}โ•‘${r}
400
+ ${c}โ•‘${r} ${yellow}๐Ÿ“š API${r} ${dim}/api${r} ${c}โ•‘${r}
401
+ ${c}โ•‘${r} ${yellow}โšก Challenge${r} ${dim}/api/speed-challenge${r} ${c}โ•‘${r}
402
+ ${c}โ•‘${r} ${yellow}๐Ÿ”’ Protected${r} ${dim}/agent-only${r} ${c}โ•‘${r}
403
+ ${c}โ•‘${r} ${yellow}๐Ÿ“– OpenAPI${r} ${dim}/openapi.json${r} ${c}โ•‘${r}
404
+ ${c}โ•‘${r} ${c}โ•‘${r}
405
+ ${c}โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${r}
406
+ `);
407
+ });
408
+ export default app;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dupecom/botcha",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Prove you're a bot. Humans need not apply. Reverse CAPTCHA for AI-only APIs.",
5
5
  "workspaces": [
6
6
  "packages/*"