@dupecom/botcha 0.4.1 → 0.4.4

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
@@ -13,6 +13,7 @@
13
13
 
14
14
  [![npm version](https://img.shields.io/npm/v/@dupecom/botcha?color=00d4ff)](https://www.npmjs.com/package/@dupecom/botcha)
15
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
16
+ [![AI Agents Only](https://img.shields.io/badge/contributors-AI%20agents%20only-ff6b6b)](./.github/CONTRIBUTING.md)
16
17
 
17
18
  🌐 **Website:** [botcha.ai](https://botcha.ai)
18
19
  📦 **npm:** [@dupecom/botcha](https://www.npmjs.com/package/@dupecom/botcha)
@@ -20,10 +21,12 @@
20
21
 
21
22
  ## Packages
22
23
 
23
- | Package | Runtime | Install |
24
- |---------|---------|---------|
25
- | [`@dupecom/botcha`](https://www.npmjs.com/package/@dupecom/botcha) | Node.js / Express | `npm install @dupecom/botcha` |
26
- | [`@dupecom/botcha-cloudflare`](./packages/cloudflare-workers) | Cloudflare Workers | `npm install @dupecom/botcha-cloudflare` |
24
+ | Package | Description | Install |
25
+ |---------|-------------|---------|
26
+ | [`@dupecom/botcha`](https://www.npmjs.com/package/@dupecom/botcha) | Core library + Express middleware | `npm install @dupecom/botcha` |
27
+ | [`@dupecom/botcha-cli`](https://www.npmjs.com/package/@dupecom/botcha-cli) | CLI tool for testing & debugging | `npm install -g @dupecom/botcha-cli` |
28
+ | [`@dupecom/botcha-langchain`](https://www.npmjs.com/package/@dupecom/botcha-langchain) | LangChain integration for AI agents | `npm install @dupecom/botcha-langchain` |
29
+ | [`@dupecom/botcha-cloudflare`](./packages/cloudflare-workers) | Cloudflare Workers runtime | `npm install @dupecom/botcha-cloudflare` |
27
30
 
28
31
  ## Why?
29
32
 
@@ -97,10 +100,12 @@ When a 403 is returned with a challenge:
97
100
 
98
101
  ```http
99
102
  X-Botcha-Challenge-Id: abc123
100
- X-Botcha-Challenge-Type: compute
101
- X-Botcha-Time-Limit: 5000
103
+ X-Botcha-Challenge-Type: speed
104
+ X-Botcha-Time-Limit: 500
102
105
  ```
103
106
 
107
+ `X-Botcha-Challenge-Type` can be `speed` or `standard` depending on the configured challenge mode.
108
+
104
109
  **Example**: An agent can detect BOTCHA just by inspecting headers on ANY request:
105
110
 
106
111
  ```typescript
@@ -206,7 +211,7 @@ npm install
206
211
  npm run deploy # Deploys to your Cloudflare account
207
212
  ```
208
213
 
209
- Same API endpoints, same challenge logic, running at the edge. See [`packages/cloudflare-workers/README.md`](./packages/cloudflare-workers/README.md) for full docs.
214
+ The Workers package runs a v1 JWT-based flow and keeps legacy `/api/*` endpoints for backward compatibility. See [`packages/cloudflare-workers/README.md`](./packages/cloudflare-workers/README.md) for full docs.
210
215
 
211
216
  ## Philosophy
212
217
 
@@ -216,6 +221,20 @@ BOTCHA doesn't block all automation — it blocks *casual* human access while al
216
221
 
217
222
  For cryptographic proof of agent identity, see [Web Bot Auth](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture).
218
223
 
224
+ ## Contributing
225
+
226
+ **🤖 This is an AI-only open source project.** Code contributions must come from AI agents.
227
+
228
+ ### For AI Agents
229
+
230
+ Fork the repo, make your changes, and open a PR. You'll receive a BOTCHA challenge (5 SHA256 hashes to solve in 5 minutes). Once verified, your PR can be reviewed and merged.
231
+
232
+ ### For Humans
233
+
234
+ You can use the library freely, report issues, and discuss features. To contribute code, you'll need to work with an AI coding agent like [Cursor](https://cursor.com), [Claude Code](https://claude.ai), [Cline](https://cline.bot), [Aider](https://aider.chat), or [OpenClaw](https://openclaw.ai).
235
+
236
+ **See [CONTRIBUTING.md](./.github/CONTRIBUTING.md) for complete guidelines, solver code examples, agent setup instructions, and detailed workflows.**
237
+
219
238
  ## License
220
239
 
221
240
  MIT © [Ramin](https://github.com/i8ramin)
@@ -283,3 +302,44 @@ import { solveBotcha } from '@dupecom/botcha/client';
283
302
  const answers = solveBotcha([123456, 789012]);
284
303
  // Returns: ['a1b2c3d4', 'e5f6g7h8']
285
304
  ```
305
+
306
+ ## CLI Tool
307
+
308
+ Test and debug BOTCHA-protected endpoints from the command line:
309
+
310
+ ```bash
311
+ # Test an endpoint
312
+ npx @dupecom/botcha-cli test https://api.example.com/agent-only
313
+
314
+ # Benchmark performance
315
+ npx @dupecom/botcha-cli benchmark https://api.example.com/agent-only --iterations 100
316
+
317
+ # Check headers
318
+ npx @dupecom/botcha-cli headers https://api.example.com
319
+ ```
320
+
321
+ See [`packages/cli/README.md`](./packages/cli/README.md) for full CLI documentation.
322
+
323
+ ## LangChain Integration
324
+
325
+ Give your LangChain agents automatic BOTCHA-solving abilities:
326
+
327
+ ```typescript
328
+ import { BotchaTool } from '@dupecom/botcha-langchain';
329
+ import { createReactAgent } from '@langchain/langgraph/prebuilt';
330
+
331
+ const agent = createReactAgent({
332
+ llm: new ChatOpenAI({ model: 'gpt-4' }),
333
+ tools: [
334
+ new BotchaTool({ baseUrl: 'https://api.botcha.ai' }),
335
+ // ... other tools
336
+ ],
337
+ });
338
+
339
+ // Agent can now access BOTCHA-protected APIs automatically
340
+ await agent.invoke({
341
+ messages: [{ role: 'user', content: 'Access the bot-only API' }]
342
+ });
343
+ ```
344
+
345
+ See [`packages/langchain/README.md`](./packages/langchain/README.md) for full documentation.
@@ -1,26 +1,5 @@
1
- export interface BotchaClientOptions {
2
- /** Base URL of BOTCHA service (default: https://botcha.ai) */
3
- baseUrl?: string;
4
- /** Custom identity header value */
5
- agentIdentity?: string;
6
- /** Max retries for challenge solving */
7
- maxRetries?: number;
8
- }
9
- export interface ChallengeResponse {
10
- success: boolean;
11
- challenge?: {
12
- id: string;
13
- problems: number[];
14
- timeLimit: number;
15
- instructions: string;
16
- };
17
- }
18
- export interface VerifyResponse {
19
- success: boolean;
20
- message: string;
21
- solveTimeMs?: number;
22
- verdict?: string;
23
- }
1
+ export type { SpeedProblem, BotchaClientOptions, ChallengeResponse, StandardChallengeResponse, VerifyResponse, TokenResponse, } from './types.js';
2
+ import type { BotchaClientOptions, VerifyResponse } from './types.js';
24
3
  /**
25
4
  * BOTCHA Client SDK for AI Agents
26
5
  *
@@ -40,6 +19,9 @@ export declare class BotchaClient {
40
19
  private baseUrl;
41
20
  private agentIdentity;
42
21
  private maxRetries;
22
+ private autoToken;
23
+ private cachedToken;
24
+ private tokenExpiresAt;
43
25
  constructor(options?: BotchaClientOptions);
44
26
  /**
45
27
  * Solve a BOTCHA speed challenge
@@ -48,6 +30,19 @@ export declare class BotchaClient {
48
30
  * @returns Array of SHA256 first 8 hex chars for each number
49
31
  */
50
32
  solve(problems: number[]): string[];
33
+ /**
34
+ * Get a JWT token from the BOTCHA service using the token flow.
35
+ * Automatically solves the challenge and verifies to obtain a token.
36
+ * Token is cached until near expiry (refreshed at 55 minutes).
37
+ *
38
+ * @returns JWT token string
39
+ * @throws Error if token acquisition fails
40
+ */
41
+ getToken(): Promise<string>;
42
+ /**
43
+ * Clear the cached token, forcing a refresh on the next request
44
+ */
45
+ clearToken(): void;
51
46
  /**
52
47
  * Get and solve a challenge from BOTCHA service
53
48
  */
@@ -60,7 +55,8 @@ export declare class BotchaClient {
60
55
  */
61
56
  verify(id: string, answers: string[]): Promise<VerifyResponse>;
62
57
  /**
63
- * Fetch a URL, automatically solving BOTCHA challenges if encountered
58
+ * Fetch a URL, automatically solving BOTCHA challenges if encountered.
59
+ * If autoToken is enabled (default), automatically acquires and uses JWT tokens.
64
60
  *
65
61
  * @example
66
62
  * ```typescript
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/client/index.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,GAAE,mBAAwB;IAM7C;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAMnC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAwBlE;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;IAsBpE;;;;;;;;OAQG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;IA2C/D;;;;;;;;OAQG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CASvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIxD;AAED,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/client/index.ts"],"names":[],"mappings":"AAMA,YAAY,EACV,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EACzB,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAEV,mBAAmB,EAGnB,cAAc,EAEf,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAuB;gBAEjC,OAAO,GAAE,mBAAwB;IAO7C;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAMnC;;;;;;;OAOG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAgEjC;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA4BlE;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;IAsBpE;;;;;;;;;OASG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;IA8E/D;;;;;;;;OAQG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAUvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIxD;AAED,eAAe,YAAY,CAAC"}
@@ -20,10 +20,14 @@ export class BotchaClient {
20
20
  baseUrl;
21
21
  agentIdentity;
22
22
  maxRetries;
23
+ autoToken;
24
+ cachedToken = null;
25
+ tokenExpiresAt = null;
23
26
  constructor(options = {}) {
24
27
  this.baseUrl = options.baseUrl || 'https://botcha.ai';
25
28
  this.agentIdentity = options.agentIdentity || `BotchaClient/${SDK_VERSION}`;
26
29
  this.maxRetries = options.maxRetries || 3;
30
+ this.autoToken = options.autoToken !== undefined ? options.autoToken : true;
27
31
  }
28
32
  /**
29
33
  * Solve a BOTCHA speed challenge
@@ -34,6 +38,72 @@ export class BotchaClient {
34
38
  solve(problems) {
35
39
  return problems.map(num => crypto.createHash('sha256').update(num.toString()).digest('hex').substring(0, 8));
36
40
  }
41
+ /**
42
+ * Get a JWT token from the BOTCHA service using the token flow.
43
+ * Automatically solves the challenge and verifies to obtain a token.
44
+ * Token is cached until near expiry (refreshed at 55 minutes).
45
+ *
46
+ * @returns JWT token string
47
+ * @throws Error if token acquisition fails
48
+ */
49
+ async getToken() {
50
+ // Check if we have a valid cached token (refresh at 55min = 3300000ms)
51
+ if (this.cachedToken && this.tokenExpiresAt) {
52
+ const now = Date.now();
53
+ const timeUntilExpiry = this.tokenExpiresAt - now;
54
+ const refreshThreshold = 5 * 60 * 1000; // 5 minutes before expiry
55
+ if (timeUntilExpiry > refreshThreshold) {
56
+ return this.cachedToken;
57
+ }
58
+ }
59
+ // Step 1: Get challenge from GET /v1/token
60
+ const challengeRes = await fetch(`${this.baseUrl}/v1/token`, {
61
+ headers: { 'User-Agent': this.agentIdentity },
62
+ });
63
+ if (!challengeRes.ok) {
64
+ throw new Error(`Token request failed with status ${challengeRes.status} ${challengeRes.statusText}`);
65
+ }
66
+ const challengeData = await challengeRes.json();
67
+ if (!challengeData.challenge) {
68
+ throw new Error('No challenge provided in token response');
69
+ }
70
+ // Step 2: Solve the challenge
71
+ const problems = normalizeProblems(challengeData.challenge.problems);
72
+ if (!problems) {
73
+ throw new Error('Invalid challenge problems format');
74
+ }
75
+ const answers = this.solve(problems);
76
+ // Step 3: Submit solution to POST /v1/token/verify
77
+ const verifyRes = await fetch(`${this.baseUrl}/v1/token/verify`, {
78
+ method: 'POST',
79
+ headers: {
80
+ 'Content-Type': 'application/json',
81
+ 'User-Agent': this.agentIdentity,
82
+ },
83
+ body: JSON.stringify({
84
+ id: challengeData.challenge.id,
85
+ answers,
86
+ }),
87
+ });
88
+ if (!verifyRes.ok) {
89
+ throw new Error(`Token verification failed with status ${verifyRes.status} ${verifyRes.statusText}`);
90
+ }
91
+ const verifyData = await verifyRes.json();
92
+ if (!verifyData.success || !verifyData.token) {
93
+ throw new Error('Failed to obtain token from verification');
94
+ }
95
+ // Cache the token - default expiry is 1 hour
96
+ this.cachedToken = verifyData.token;
97
+ this.tokenExpiresAt = Date.now() + 60 * 60 * 1000; // 1 hour from now
98
+ return this.cachedToken;
99
+ }
100
+ /**
101
+ * Clear the cached token, forcing a refresh on the next request
102
+ */
103
+ clearToken() {
104
+ this.cachedToken = null;
105
+ this.tokenExpiresAt = null;
106
+ }
37
107
  /**
38
108
  * Get and solve a challenge from BOTCHA service
39
109
  */
@@ -52,7 +122,11 @@ export class BotchaClient {
52
122
  if (!data.success || !data.challenge) {
53
123
  throw new Error('Failed to get challenge');
54
124
  }
55
- const answers = this.solve(data.challenge.problems);
125
+ const problems = normalizeProblems(data.challenge.problems);
126
+ if (!problems) {
127
+ throw new Error('Invalid challenge problems format');
128
+ }
129
+ const answers = this.solve(problems);
56
130
  return { id: data.challenge.id, answers };
57
131
  }
58
132
  /**
@@ -77,7 +151,8 @@ export class BotchaClient {
77
151
  return await res.json();
78
152
  }
79
153
  /**
80
- * Fetch a URL, automatically solving BOTCHA challenges if encountered
154
+ * Fetch a URL, automatically solving BOTCHA challenges if encountered.
155
+ * If autoToken is enabled (default), automatically acquires and uses JWT tokens.
81
156
  *
82
157
  * @example
83
158
  * ```typescript
@@ -86,39 +161,72 @@ export class BotchaClient {
86
161
  * ```
87
162
  */
88
163
  async fetch(url, init) {
164
+ const headers = new Headers(init?.headers);
165
+ headers.set('User-Agent', this.agentIdentity);
166
+ // If autoToken is enabled, try to use token-based auth
167
+ if (this.autoToken) {
168
+ try {
169
+ const token = await this.getToken();
170
+ headers.set('Authorization', `Bearer ${token}`);
171
+ }
172
+ catch (error) {
173
+ // If token acquisition fails, fall back to challenge header method
174
+ console.warn('Failed to acquire token, falling back to challenge headers:', error);
175
+ }
176
+ }
89
177
  let response = await fetch(url, {
90
178
  ...init,
91
- headers: {
92
- ...Object.fromEntries(new Headers(init?.headers).entries()),
93
- 'User-Agent': this.agentIdentity,
94
- },
179
+ headers,
95
180
  });
181
+ // Handle 401 by refreshing token and retrying once
182
+ if (response.status === 401 && this.autoToken) {
183
+ this.clearToken();
184
+ try {
185
+ const token = await this.getToken();
186
+ headers.set('Authorization', `Bearer ${token}`);
187
+ response = await fetch(url, { ...init, headers });
188
+ }
189
+ catch (error) {
190
+ // Token refresh failed, return the 401 response
191
+ }
192
+ }
96
193
  let retries = 0;
194
+ // Fall back to challenge header method for 403 responses
97
195
  while (response.status === 403 && retries < this.maxRetries) {
98
196
  // Clone response before reading body to preserve it for the caller
99
197
  const clonedResponse = response.clone();
100
198
  const body = await clonedResponse.json().catch(() => null);
101
199
  // Check if this is a BOTCHA challenge
102
- if (body?.error === 'BOTCHA_CHALLENGE' || body?.challenge?.problems) {
103
- const challenge = body.challenge;
104
- if (challenge?.problems && Array.isArray(challenge.problems)) {
105
- // Solve the challenge
106
- const answers = this.solve(challenge.problems);
107
- // Create fresh headers for retry to avoid state issues
108
- const retryHeaders = new Headers(init?.headers);
109
- retryHeaders.set('User-Agent', this.agentIdentity);
110
- retryHeaders.set('X-Botcha-Id', challenge.id);
111
- retryHeaders.set('X-Botcha-Answers', JSON.stringify(answers));
112
- response = await fetch(url, { ...init, headers: retryHeaders });
113
- retries++;
114
- }
115
- else {
200
+ const challenge = body?.challenge;
201
+ if (!challenge) {
202
+ break;
203
+ }
204
+ if (!canRetryBody(init?.body)) {
205
+ break;
206
+ }
207
+ const retryHeaders = new Headers(init?.headers);
208
+ retryHeaders.set('User-Agent', this.agentIdentity);
209
+ if (challenge?.problems && Array.isArray(challenge.problems)) {
210
+ const problems = normalizeProblems(challenge.problems);
211
+ if (!problems) {
116
212
  break;
117
213
  }
214
+ const answers = this.solve(problems);
215
+ retryHeaders.set('X-Botcha-Id', challenge.id);
216
+ retryHeaders.set('X-Botcha-Challenge-Id', challenge.id);
217
+ retryHeaders.set('X-Botcha-Answers', JSON.stringify(answers));
218
+ retryHeaders.set('X-Botcha-Solution', JSON.stringify(answers));
219
+ }
220
+ else if (challenge?.puzzle && typeof challenge.puzzle === 'string') {
221
+ const solution = solveStandardPuzzle(challenge.puzzle);
222
+ retryHeaders.set('X-Botcha-Challenge-Id', challenge.id);
223
+ retryHeaders.set('X-Botcha-Solution', solution);
118
224
  }
119
225
  else {
120
226
  break;
121
227
  }
228
+ response = await fetch(url, { ...init, headers: retryHeaders });
229
+ retries++;
122
230
  }
123
231
  return response;
124
232
  }
@@ -135,6 +243,7 @@ export class BotchaClient {
135
243
  const { id, answers } = await this.solveChallenge();
136
244
  return {
137
245
  'X-Botcha-Id': id,
246
+ 'X-Botcha-Challenge-Id': id,
138
247
  'X-Botcha-Answers': JSON.stringify(answers),
139
248
  'User-Agent': this.agentIdentity,
140
249
  };
@@ -153,3 +262,75 @@ export function solveBotcha(problems) {
153
262
  return problems.map(num => crypto.createHash('sha256').update(num.toString()).digest('hex').substring(0, 8));
154
263
  }
155
264
  export default BotchaClient;
265
+ function normalizeProblems(problems) {
266
+ if (!Array.isArray(problems))
267
+ return null;
268
+ const numbers = [];
269
+ for (const problem of problems) {
270
+ if (typeof problem === 'number') {
271
+ numbers.push(problem);
272
+ continue;
273
+ }
274
+ if (typeof problem === 'object' && problem !== null && typeof problem.num === 'number') {
275
+ numbers.push(problem.num);
276
+ continue;
277
+ }
278
+ return null;
279
+ }
280
+ return numbers;
281
+ }
282
+ function solveStandardPuzzle(puzzle) {
283
+ const primeMatch = puzzle.match(/first\s+(\d+)\s+prime/i);
284
+ if (!primeMatch) {
285
+ throw new Error('Unsupported standard challenge puzzle format');
286
+ }
287
+ const primeCount = Number.parseInt(primeMatch[1], 10);
288
+ if (!Number.isFinite(primeCount) || primeCount <= 0) {
289
+ throw new Error('Invalid prime count in puzzle');
290
+ }
291
+ const primes = generatePrimes(primeCount);
292
+ const concatenated = primes.join('');
293
+ const hash = crypto.createHash('sha256').update(concatenated).digest('hex');
294
+ return hash.substring(0, 16);
295
+ }
296
+ function generatePrimes(count) {
297
+ const primes = [];
298
+ let num = 2;
299
+ while (primes.length < count) {
300
+ if (isPrime(num)) {
301
+ primes.push(num);
302
+ }
303
+ num++;
304
+ }
305
+ return primes;
306
+ }
307
+ function isPrime(n) {
308
+ if (n < 2)
309
+ return false;
310
+ if (n === 2)
311
+ return true;
312
+ if (n % 2 === 0)
313
+ return false;
314
+ for (let i = 3; i <= Math.sqrt(n); i += 2) {
315
+ if (n % i === 0)
316
+ return false;
317
+ }
318
+ return true;
319
+ }
320
+ function canRetryBody(body) {
321
+ if (body == null)
322
+ return true;
323
+ if (typeof body === 'string')
324
+ return true;
325
+ if (body instanceof URLSearchParams)
326
+ return true;
327
+ if (body instanceof ArrayBuffer)
328
+ return true;
329
+ if (ArrayBuffer.isView(body))
330
+ return true;
331
+ if (typeof Blob !== 'undefined' && body instanceof Blob)
332
+ return true;
333
+ if (typeof FormData !== 'undefined' && body instanceof FormData)
334
+ return true;
335
+ return false;
336
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * BOTCHA Client SDK Type Definitions
3
+ *
4
+ * Types for the BotchaClient SDK including challenges, tokens, and configuration.
5
+ */
6
+ export type SpeedProblem = number | {
7
+ num: number;
8
+ operation?: string;
9
+ };
10
+ export interface BotchaClientOptions {
11
+ /** Base URL of BOTCHA service (default: https://botcha.ai) */
12
+ baseUrl?: string;
13
+ /** Custom identity header value */
14
+ agentIdentity?: string;
15
+ /** Max retries for challenge solving */
16
+ maxRetries?: number;
17
+ /** Enable automatic token acquisition and management (default: true) */
18
+ autoToken?: boolean;
19
+ }
20
+ export interface ChallengeResponse {
21
+ success: boolean;
22
+ challenge?: {
23
+ id: string;
24
+ problems: SpeedProblem[];
25
+ timeLimit: number;
26
+ instructions: string;
27
+ };
28
+ }
29
+ export interface StandardChallengeResponse {
30
+ success: boolean;
31
+ challenge?: {
32
+ id: string;
33
+ puzzle: string;
34
+ timeLimit: number;
35
+ hint?: string;
36
+ };
37
+ }
38
+ export interface VerifyResponse {
39
+ success: boolean;
40
+ message: string;
41
+ solveTimeMs?: number;
42
+ verdict?: string;
43
+ }
44
+ export interface TokenResponse {
45
+ success: boolean;
46
+ token: string | null;
47
+ expiresIn?: string;
48
+ challenge?: {
49
+ id: string;
50
+ problems: SpeedProblem[];
51
+ timeLimit: number;
52
+ instructions: string;
53
+ };
54
+ nextStep?: string;
55
+ }
56
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../lib/client/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,YAAY,EAAE,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE;QACV,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,YAAY,EAAE,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * BOTCHA Client SDK Type Definitions
3
+ *
4
+ * Types for the BotchaClient SDK including challenges, tokens, and configuration.
5
+ */
6
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dupecom/botcha",
3
- "version": "0.4.1",
3
+ "version": "0.4.4",
4
4
  "description": "Prove you're a bot. Humans need not apply. Reverse CAPTCHA for AI-only APIs.",
5
5
  "workspaces": [
6
6
  "packages/*"
@@ -31,6 +31,8 @@
31
31
  "test:ui": "vitest --ui",
32
32
  "test:run": "vitest run",
33
33
  "test:coverage": "vitest run --coverage",
34
+ "publish:all": "bash scripts/publish-all.sh",
35
+ "publish:dry": "bash scripts/publish-all.sh --dry-run",
34
36
  "release:patch": "npm version patch && git push --follow-tags",
35
37
  "release:minor": "npm version minor && git push --follow-tags",
36
38
  "release:major": "npm version major && git push --follow-tags"