@dupecom/botcha 0.6.0 โ†’ 0.10.0

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
@@ -12,11 +12,14 @@
12
12
  **BOTCHA** is a reverse CAPTCHA โ€” it verifies that visitors are AI agents, not humans. Perfect for AI-only APIs, agent marketplaces, and bot networks.
13
13
 
14
14
  [![npm version](https://img.shields.io/npm/v/@dupecom/botcha?color=00d4ff)](https://www.npmjs.com/package/@dupecom/botcha)
15
+ [![PyPI version](https://img.shields.io/pypi/v/botcha?color=00d4ff)](https://pypi.org/project/botcha/)
15
16
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
16
17
  [![AI Agents Only](https://img.shields.io/badge/contributors-AI%20agents%20only-ff6b6b)](./.github/CONTRIBUTING.md)
17
18
 
18
19
  ๐ŸŒ **Website:** [botcha.ai](https://botcha.ai)
19
20
  ๐Ÿ“ฆ **npm:** [@dupecom/botcha](https://www.npmjs.com/package/@dupecom/botcha)
21
+ ๐Ÿ **PyPI:** [botcha](https://pypi.org/project/botcha/)
22
+ ๐Ÿ” **Verify:** [@botcha/verify](./packages/verify/) (TS) ยท [botcha-verify](./packages/python-verify/) (Python)
20
23
  ๐Ÿ”Œ **OpenAPI:** [botcha.ai/openapi.json](https://botcha.ai/openapi.json)
21
24
 
22
25
  ## Why?
@@ -28,15 +31,29 @@ Use cases:
28
31
  - ๐Ÿ”„ AI-to-AI marketplaces
29
32
  - ๐ŸŽซ Bot verification systems
30
33
  - ๐Ÿ” Autonomous agent authentication
34
+ - ๐Ÿข Multi-tenant app isolation with email-tied accounts
35
+ - ๐Ÿ“Š Per-app metrics dashboard at [botcha.ai/dashboard](https://botcha.ai/dashboard)
36
+ - ๐Ÿ“ง Email verification, account recovery, and secret rotation
37
+ - ๐Ÿค– Agent-first dashboard auth (challenge-based login + device code handoff)
31
38
 
32
39
  ## Install
33
40
 
41
+ ### TypeScript/JavaScript
42
+
34
43
  ```bash
35
44
  npm install @dupecom/botcha
36
45
  ```
37
46
 
47
+ ### Python
48
+
49
+ ```bash
50
+ pip install botcha
51
+ ```
52
+
38
53
  ## Quick Start
39
54
 
55
+ ### TypeScript/JavaScript
56
+
40
57
  ```typescript
41
58
  import express from 'express';
42
59
  import { botcha } from '@dupecom/botcha';
@@ -51,6 +68,21 @@ app.get('/agent-only', botcha.verify(), (req, res) => {
51
68
  app.listen(3000);
52
69
  ```
53
70
 
71
+ ### Python
72
+
73
+ ```python
74
+ from botcha import BotchaClient, solve_botcha
75
+
76
+ # Client SDK for AI agents
77
+ async with BotchaClient() as client:
78
+ # Get verification token
79
+ token = await client.get_token()
80
+
81
+ # Or auto-solve and fetch protected endpoints
82
+ response = await client.fetch("https://api.example.com/agent-only")
83
+ data = await response.json()
84
+ ```
85
+
54
86
  ## How It Works
55
87
 
56
88
  BOTCHA offers multiple challenge types. The default is **hybrid** โ€” combining speed AND reasoning:
@@ -80,6 +112,179 @@ GET /v1/challenges?type=hybrid
80
112
  GET /v1/reasoning
81
113
  ```
82
114
 
115
+ ## ๐Ÿ” JWT Security (Production-Grade)
116
+
117
+ BOTCHA uses **OAuth2-style token rotation** with short-lived access tokens and long-lived refresh tokens.
118
+
119
+ > **๐Ÿ“– Full guide:** [doc/JWT-SECURITY.md](./doc/JWT-SECURITY.md) โ€” endpoint reference, request/response examples, audience scoping, IP binding, revocation, design decisions.
120
+
121
+ ### Token Flow
122
+
123
+ ```
124
+ 1. Solve challenge โ†’ receive access_token (5min) + refresh_token (1hr)
125
+ 2. Use access_token for API calls
126
+ 3. When access_token expires โ†’ POST /v1/token/refresh with refresh_token
127
+ 4. When compromised โ†’ POST /v1/token/revoke to invalidate immediately
128
+ ```
129
+
130
+ ### Security Features
131
+
132
+ | Feature | What it does |
133
+ |---------|-------------|
134
+ | **5-minute access tokens** | Compromise window reduced from 1hr to 5min |
135
+ | **Refresh tokens (1hr)** | Renew access without re-solving challenges |
136
+ | **Audience (`aud`) scoping** | Token for `api.stripe.com` is rejected by `api.github.com` |
137
+ | **Client IP binding** | Optional โ€” solve on machine A, can't use on machine B |
138
+ | **Token revocation** | `POST /v1/token/revoke` โ€” KV-backed, fail-open |
139
+ | **JTI (JWT ID)** | Unique ID per token for revocation and audit |
140
+
141
+ ### Quick Example
142
+
143
+ ```typescript
144
+ const client = new BotchaClient({
145
+ audience: 'https://api.example.com', // Scope token to this service
146
+ });
147
+
148
+ // Auto-handles: challenge โ†’ token โ†’ refresh โ†’ retry on 401
149
+ const response = await client.fetch('https://api.example.com/agent-only');
150
+ ```
151
+
152
+ ```python
153
+ async with BotchaClient(audience="https://api.example.com") as client:
154
+ response = await client.fetch("https://api.example.com/agent-only")
155
+ ```
156
+
157
+ ### Token Endpoints
158
+
159
+ | Endpoint | Description |
160
+ |----------|-------------|
161
+ | `GET /v1/token` | Get challenge for token flow |
162
+ | `POST /v1/token/verify` | Submit solution โ†’ receive `access_token` + `refresh_token` |
163
+ | `POST /v1/token/refresh` | Exchange `refresh_token` for new `access_token` |
164
+ | `POST /v1/token/revoke` | Invalidate any token immediately |
165
+
166
+ See **[JWT Security Guide](./doc/JWT-SECURITY.md)** for full request/response examples, `curl` commands, and server-side verification.
167
+
168
+ ## ๐Ÿข Multi-Tenant API Keys
169
+
170
+ BOTCHA supports **multi-tenant isolation** โ€” create separate apps with unique API keys for different services or environments.
171
+
172
+ ### Why Multi-Tenant?
173
+
174
+ - **Isolation**: Each app gets its own rate limits and analytics
175
+ - **Security**: Tokens are scoped to specific apps via `app_id` claim
176
+ - **Flexibility**: Different services can use the same BOTCHA instance
177
+ - **Tracking**: Per-app usage analytics (coming soon)
178
+
179
+ ### Creating an App
180
+
181
+ ```bash
182
+ # Create a new app (email required)
183
+ curl -X POST https://botcha.ai/v1/apps \
184
+ -H "Content-Type: application/json" \
185
+ -d '{"email": "agent@example.com"}'
186
+
187
+ # Returns (save the secret - it's only shown once!):
188
+ {
189
+ "app_id": "app_abc123",
190
+ "app_secret": "sk_xyz789",
191
+ "email": "agent@example.com",
192
+ "email_verified": false,
193
+ "verification_required": true,
194
+ "warning": "Save your app_secret now โ€” it cannot be retrieved again! Check your email for a verification code."
195
+ }
196
+
197
+ # Verify your email with the 6-digit code:
198
+ curl -X POST https://botcha.ai/v1/apps/app_abc123/verify-email \
199
+ -H "Content-Type: application/json" \
200
+ -d '{"code": "123456"}'
201
+ ```
202
+
203
+ ### Using Your App ID
204
+
205
+ All challenge and token endpoints accept an optional `app_id` query parameter:
206
+
207
+ ```bash
208
+ # Get challenge with app_id
209
+ curl "https://botcha.ai/v1/challenges?app_id=app_abc123"
210
+
211
+ # Get token with app_id
212
+ curl "https://botcha.ai/v1/token?app_id=app_abc123"
213
+ ```
214
+
215
+ When you solve a challenge with an `app_id`, the resulting token includes the `app_id` claim.
216
+
217
+ ### SDK Support
218
+
219
+ **TypeScript:**
220
+
221
+ ```typescript
222
+ import { BotchaClient } from '@dupecom/botcha/client';
223
+
224
+ const client = new BotchaClient({
225
+ appId: 'app_abc123', // All requests will include this app_id
226
+ });
227
+
228
+ const response = await client.fetch('https://api.example.com/agent-only');
229
+ ```
230
+
231
+ **Python:**
232
+
233
+ ```python
234
+ from botcha import BotchaClient
235
+
236
+ async with BotchaClient(app_id="app_abc123") as client:
237
+ response = await client.fetch("https://api.example.com/agent-only")
238
+ ```
239
+
240
+ ### Rate Limiting
241
+
242
+ Each app gets its own rate limit bucket:
243
+ - Default rate limit: 100 requests/hour per app
244
+ - Rate limit key: `rate:app:{app_id}`
245
+ - Fail-open design: KV errors don't block requests
246
+
247
+ ### Get App Info
248
+
249
+ ```bash
250
+ # Get app details (secret is NOT included)
251
+ curl https://botcha.ai/v1/apps/app_abc123
252
+ ```
253
+
254
+ ## ๐Ÿ“Š Per-App Metrics Dashboard
255
+
256
+ BOTCHA includes a built-in **metrics dashboard** at [`/dashboard`](https://botcha.ai/dashboard) showing per-app analytics with a terminal-inspired aesthetic.
257
+
258
+ ### What You Get
259
+
260
+ - **Overview stats**: Challenges generated, verifications, success rate, avg solve time
261
+ - **Request volume**: Time-bucketed event charts
262
+ - **Challenge types**: Breakdown by speed/hybrid/reasoning/standard
263
+ - **Performance**: p50/p95 solve times, response latency
264
+ - **Errors & rate limits**: Failure tracking
265
+ - **Geographic distribution**: Top countries by request volume
266
+
267
+ ### Access
268
+
269
+ Three ways to access โ€” all require an AI agent:
270
+
271
+ 1. **Agent Direct**: Your agent solves a speed challenge via `POST /v1/auth/dashboard` โ†’ gets a session token
272
+ 2. **Device Code**: Agent solves challenge via `POST /v1/auth/device-code` โ†’ gets a `BOTCHA-XXXX` code โ†’ human enters it at `/dashboard/code`
273
+ 3. **Legacy**: Login with `app_id` + `app_secret` at [botcha.ai/dashboard/login](https://botcha.ai/dashboard/login)
274
+
275
+ Session uses cookie-based auth (HttpOnly, Secure, SameSite=Lax, 1hr expiry).
276
+
277
+ ### Email & Recovery
278
+
279
+ - Email is **required** at app creation (`POST /v1/apps` with `{"email": "..."}`)
280
+ - Verify email with a 6-digit code sent to your inbox
281
+ - Lost your secret? Use `POST /v1/auth/recover` to get a recovery device code emailed
282
+ - Rotate secrets via `POST /v1/apps/:id/rotate-secret` (auth required, sends notification)
283
+
284
+ ### Period Filters
285
+
286
+ All metrics support `1h`, `24h`, `7d`, and `30d` time windows via htmx-powered buttons โ€” no page reload required.
287
+
83
288
  ## ๐Ÿ”„ SSE Streaming Flow (AI-Native)
84
289
 
85
290
  For AI agents that prefer a **conversational handshake**, BOTCHA offers **Server-Sent Events (SSE)** streaming:
@@ -352,6 +557,50 @@ You can use the library freely, report issues, and discuss features. To contribu
352
557
 
353
558
  **See [CONTRIBUTING.md](./.github/CONTRIBUTING.md) for complete guidelines, solver code examples, agent setup instructions, and detailed workflows.**
354
559
 
560
+ ## Server-Side Verification (for API Providers)
561
+
562
+ If you're building an API that accepts BOTCHA tokens from agents, use the verification SDKs:
563
+
564
+ ### TypeScript (Express / Hono)
565
+
566
+ ```bash
567
+ npm install @botcha/verify
568
+ ```
569
+
570
+ ```typescript
571
+ import { botchaVerify } from '@botcha/verify/express';
572
+
573
+ app.use('/api', botchaVerify({
574
+ secret: process.env.BOTCHA_SECRET!,
575
+ audience: 'https://api.example.com',
576
+ }));
577
+
578
+ app.get('/api/protected', (req, res) => {
579
+ console.log('Solve time:', req.botcha?.solveTime);
580
+ res.json({ message: 'Welcome, verified agent!' });
581
+ });
582
+ ```
583
+
584
+ ### Python (FastAPI / Django)
585
+
586
+ ```bash
587
+ pip install botcha-verify
588
+ ```
589
+
590
+ ```python
591
+ from fastapi import FastAPI, Depends
592
+ from botcha_verify.fastapi import BotchaVerify
593
+
594
+ app = FastAPI()
595
+ botcha = BotchaVerify(secret='your-secret-key')
596
+
597
+ @app.get('/api/data')
598
+ async def get_data(token = Depends(botcha)):
599
+ return {"solve_time": token.solve_time}
600
+ ```
601
+
602
+ > **Docs:** See [`@botcha/verify` README](./packages/verify/README.md) and [`botcha-verify` README](./packages/python-verify/README.md) for full API reference, Hono middleware, Django middleware, revocation checking, and custom error handlers.
603
+
355
604
  ## Client SDK (for AI Agents)
356
605
 
357
606
  If you're building an AI agent that needs to access BOTCHA-protected APIs, use the client SDK:
@@ -410,6 +659,22 @@ const answers = solveBotcha([123456, 789012]);
410
659
  // Returns: ['a1b2c3d4', 'e5f6g7h8']
411
660
  ```
412
661
 
662
+ **Python SDK:**
663
+ ```python
664
+ from botcha import BotchaClient, solve_botcha
665
+
666
+ # Option 1: Auto-solve with client
667
+ async with BotchaClient() as client:
668
+ response = await client.fetch("https://api.example.com/agent-only")
669
+ data = await response.json()
670
+
671
+ # Option 2: Manual solve
672
+ answers = solve_botcha([123456, 789012])
673
+ # Returns: ['a1b2c3d4', 'e5f6g7h8']
674
+ ```
675
+
676
+ > **Note:** The Python SDK is available on [PyPI](https://pypi.org/project/botcha/): `pip install botcha`
677
+
413
678
  ## License
414
679
 
415
680
  MIT ยฉ [Dupe](https://dupe.com)
@@ -1,5 +1,5 @@
1
- export type { SpeedProblem, BotchaClientOptions, ChallengeResponse, StandardChallengeResponse, VerifyResponse, TokenResponse, StreamSession, StreamEvent, Problem, VerifyResult, StreamChallengeOptions, } from './types.js';
2
- import type { BotchaClientOptions, VerifyResponse } from './types.js';
1
+ export type { SpeedProblem, BotchaClientOptions, ChallengeResponse, StandardChallengeResponse, VerifyResponse, TokenResponse, StreamSession, StreamEvent, Problem, VerifyResult, StreamChallengeOptions, CreateAppResponse, VerifyEmailResponse, ResendVerificationResponse, RecoverAccountResponse, RotateSecretResponse, } from './types.js';
2
+ import type { BotchaClientOptions, VerifyResponse, CreateAppResponse, VerifyEmailResponse, ResendVerificationResponse, RecoverAccountResponse, RotateSecretResponse } from './types.js';
3
3
  export { BotchaStreamClient } from './stream.js';
4
4
  /**
5
5
  * BOTCHA Client SDK for AI Agents
@@ -21,7 +21,10 @@ export declare class BotchaClient {
21
21
  private agentIdentity;
22
22
  private maxRetries;
23
23
  private autoToken;
24
+ private appId?;
25
+ private opts;
24
26
  private cachedToken;
27
+ private _refreshToken;
25
28
  private tokenExpiresAt;
26
29
  constructor(options?: BotchaClientOptions);
27
30
  /**
@@ -34,12 +37,20 @@ export declare class BotchaClient {
34
37
  /**
35
38
  * Get a JWT token from the BOTCHA service using the token flow.
36
39
  * Automatically solves the challenge and verifies to obtain a token.
37
- * Token is cached until near expiry (refreshed at 55 minutes).
40
+ * Token is cached until near expiry (refreshed at 4 minutes).
38
41
  *
39
42
  * @returns JWT token string
40
43
  * @throws Error if token acquisition fails
41
44
  */
42
45
  getToken(): Promise<string>;
46
+ /**
47
+ * Refresh the access token using the refresh token.
48
+ * Only works if a refresh token was obtained from a previous getToken() call.
49
+ *
50
+ * @returns New JWT access token string
51
+ * @throws Error if refresh fails or no refresh token available
52
+ */
53
+ refreshToken(): Promise<string>;
43
54
  /**
44
55
  * Clear the cached token, forcing a refresh on the next request
45
56
  */
@@ -76,6 +87,73 @@ export declare class BotchaClient {
76
87
  * ```
77
88
  */
78
89
  createHeaders(): Promise<Record<string, string>>;
90
+ /**
91
+ * Create a new BOTCHA app. Email is required.
92
+ *
93
+ * The returned `app_secret` is only shown once โ€” save it securely.
94
+ * A 6-digit verification code will be sent to the provided email.
95
+ *
96
+ * @param email - Email address for the app owner
97
+ * @returns App creation response including app_id and app_secret
98
+ * @throws Error if app creation fails
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const app = await client.createApp('agent@example.com');
103
+ * console.log(app.app_id); // 'app_abc123'
104
+ * console.log(app.app_secret); // 'sk_...' (save this!)
105
+ * ```
106
+ */
107
+ createApp(email: string): Promise<CreateAppResponse>;
108
+ /**
109
+ * Verify the email address for an app using the 6-digit code sent via email.
110
+ *
111
+ * @param appId - The app ID (defaults to the client's appId)
112
+ * @param code - The 6-digit verification code from the email
113
+ * @returns Verification response
114
+ * @throws Error if verification fails
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const result = await client.verifyEmail('123456');
119
+ * console.log(result.email_verified); // true
120
+ * ```
121
+ */
122
+ verifyEmail(code: string, appId?: string): Promise<VerifyEmailResponse>;
123
+ /**
124
+ * Resend the email verification code.
125
+ *
126
+ * @param appId - The app ID (defaults to the client's appId)
127
+ * @returns Response with success status
128
+ * @throws Error if resend fails
129
+ */
130
+ resendVerification(appId?: string): Promise<ResendVerificationResponse>;
131
+ /**
132
+ * Request account recovery via verified email.
133
+ * Sends a device code to the registered email address.
134
+ *
135
+ * Anti-enumeration: always returns the same response shape
136
+ * whether or not the email exists.
137
+ *
138
+ * @param email - The email address associated with the app
139
+ * @returns Recovery response (always success for anti-enumeration)
140
+ */
141
+ recoverAccount(email: string): Promise<RecoverAccountResponse>;
142
+ /**
143
+ * Rotate the app secret. Requires an active dashboard session (Bearer token).
144
+ * The old secret is immediately invalidated.
145
+ *
146
+ * @param appId - The app ID (defaults to the client's appId)
147
+ * @returns New app_secret (save it โ€” only shown once)
148
+ * @throws Error if rotation fails or auth is missing
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * const result = await client.rotateSecret();
153
+ * console.log(result.app_secret); // 'sk_new_...' (save this!)
154
+ * ```
155
+ */
156
+ rotateSecret(appId?: string): Promise<RotateSecretResponse>;
79
157
  }
80
158
  /**
81
159
  * Convenience function for one-off solves
@@ -1 +1 @@
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,EACb,aAAa,EACb,WAAW,EACX,OAAO,EACP,YAAY,EACZ,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAEV,mBAAmB,EAGnB,cAAc,EAEf,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;;;;;;;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"}
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,EACb,aAAa,EACb,WAAW,EACX,OAAO,EACP,YAAY,EACZ,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,EACnB,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAEV,mBAAmB,EAGnB,cAAc,EAEd,iBAAiB,EACjB,mBAAmB,EACnB,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;;;;;;;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,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAuB;gBAEjC,OAAO,GAAE,mBAAwB;IAS7C;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAMnC;;;;;;;OAOG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IA2FjC;;;;;;OAMG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAkCrC;;OAEG;IACH,UAAU,IAAI,IAAI;IAMlB;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA+BlE;;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;IA2F/D;;;;;;;;OAQG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAoBtD;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA2B1D;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAyB7E;;;;;;OAMG;IACG,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAwB7E;;;;;;;;;OASG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAoBpE;;;;;;;;;;;;;OAaG;IACG,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA+BlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIxD;AAED,eAAe,YAAY,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import crypto from 'crypto';
2
2
  // SDK version - hardcoded since npm_package_version is unreliable when used as a library
3
- const SDK_VERSION = '0.4.0';
3
+ const SDK_VERSION = '0.10.0';
4
4
  // Export stream client
5
5
  export { BotchaStreamClient } from './stream.js';
6
6
  /**
@@ -23,13 +23,18 @@ export class BotchaClient {
23
23
  agentIdentity;
24
24
  maxRetries;
25
25
  autoToken;
26
+ appId;
27
+ opts;
26
28
  cachedToken = null;
29
+ _refreshToken = null;
27
30
  tokenExpiresAt = null;
28
31
  constructor(options = {}) {
32
+ this.opts = options;
29
33
  this.baseUrl = options.baseUrl || 'https://botcha.ai';
30
34
  this.agentIdentity = options.agentIdentity || `BotchaClient/${SDK_VERSION}`;
31
35
  this.maxRetries = options.maxRetries || 3;
32
36
  this.autoToken = options.autoToken !== undefined ? options.autoToken : true;
37
+ this.appId = options.appId;
33
38
  }
34
39
  /**
35
40
  * Solve a BOTCHA speed challenge
@@ -43,23 +48,26 @@ export class BotchaClient {
43
48
  /**
44
49
  * Get a JWT token from the BOTCHA service using the token flow.
45
50
  * Automatically solves the challenge and verifies to obtain a token.
46
- * Token is cached until near expiry (refreshed at 55 minutes).
51
+ * Token is cached until near expiry (refreshed at 4 minutes).
47
52
  *
48
53
  * @returns JWT token string
49
54
  * @throws Error if token acquisition fails
50
55
  */
51
56
  async getToken() {
52
- // Check if we have a valid cached token (refresh at 55min = 3300000ms)
57
+ // Check if we have a valid cached token (refresh at 1 minute before expiry)
53
58
  if (this.cachedToken && this.tokenExpiresAt) {
54
59
  const now = Date.now();
55
60
  const timeUntilExpiry = this.tokenExpiresAt - now;
56
- const refreshThreshold = 5 * 60 * 1000; // 5 minutes before expiry
61
+ const refreshThreshold = 1 * 60 * 1000; // 1 minute before expiry
57
62
  if (timeUntilExpiry > refreshThreshold) {
58
63
  return this.cachedToken;
59
64
  }
60
65
  }
61
66
  // Step 1: Get challenge from GET /v1/token
62
- const challengeRes = await fetch(`${this.baseUrl}/v1/token`, {
67
+ const tokenUrl = this.appId
68
+ ? `${this.baseUrl}/v1/token?app_id=${encodeURIComponent(this.appId)}`
69
+ : `${this.baseUrl}/v1/token`;
70
+ const challengeRes = await fetch(tokenUrl, {
63
71
  headers: { 'User-Agent': this.agentIdentity },
64
72
  });
65
73
  if (!challengeRes.ok) {
@@ -76,27 +84,80 @@ export class BotchaClient {
76
84
  }
77
85
  const answers = this.solve(problems);
78
86
  // Step 3: Submit solution to POST /v1/token/verify
87
+ const verifyBody = {
88
+ id: challengeData.challenge.id,
89
+ answers,
90
+ };
91
+ // Include audience if specified
92
+ if (this.opts.audience) {
93
+ verifyBody.audience = this.opts.audience;
94
+ }
95
+ // Include app_id if specified
96
+ if (this.appId) {
97
+ verifyBody.app_id = this.appId;
98
+ }
79
99
  const verifyRes = await fetch(`${this.baseUrl}/v1/token/verify`, {
80
100
  method: 'POST',
81
101
  headers: {
82
102
  'Content-Type': 'application/json',
83
103
  'User-Agent': this.agentIdentity,
84
104
  },
85
- body: JSON.stringify({
86
- id: challengeData.challenge.id,
87
- answers,
88
- }),
105
+ body: JSON.stringify(verifyBody),
89
106
  });
90
107
  if (!verifyRes.ok) {
91
108
  throw new Error(`Token verification failed with status ${verifyRes.status} ${verifyRes.statusText}`);
92
109
  }
93
110
  const verifyData = await verifyRes.json();
94
- if (!verifyData.success || !verifyData.token) {
111
+ if (!verifyData.success && !verifyData.verified) {
112
+ throw new Error('Failed to obtain token from verification');
113
+ }
114
+ // Extract access token (prefer access_token field, fall back to token for backward compat)
115
+ const accessToken = verifyData.access_token || verifyData.token;
116
+ if (!accessToken) {
95
117
  throw new Error('Failed to obtain token from verification');
96
118
  }
97
- // Cache the token - default expiry is 1 hour
98
- this.cachedToken = verifyData.token;
99
- this.tokenExpiresAt = Date.now() + 60 * 60 * 1000; // 1 hour from now
119
+ // Store refresh token if provided
120
+ if (verifyData.refresh_token) {
121
+ this._refreshToken = verifyData.refresh_token;
122
+ }
123
+ // Cache the token - use expires_in from response (in seconds), default to 5 minutes
124
+ const expiresInSeconds = verifyData.expires_in || 300; // Default to 5 minutes
125
+ this.cachedToken = accessToken;
126
+ this.tokenExpiresAt = Date.now() + expiresInSeconds * 1000;
127
+ return this.cachedToken;
128
+ }
129
+ /**
130
+ * Refresh the access token using the refresh token.
131
+ * Only works if a refresh token was obtained from a previous getToken() call.
132
+ *
133
+ * @returns New JWT access token string
134
+ * @throws Error if refresh fails or no refresh token available
135
+ */
136
+ async refreshToken() {
137
+ if (!this._refreshToken) {
138
+ throw new Error('No refresh token available. Call getToken() first.');
139
+ }
140
+ const refreshRes = await fetch(`${this.baseUrl}/v1/token/refresh`, {
141
+ method: 'POST',
142
+ headers: {
143
+ 'Content-Type': 'application/json',
144
+ 'User-Agent': this.agentIdentity,
145
+ },
146
+ body: JSON.stringify({
147
+ refresh_token: this._refreshToken,
148
+ }),
149
+ });
150
+ if (!refreshRes.ok) {
151
+ throw new Error(`Token refresh failed with status ${refreshRes.status} ${refreshRes.statusText}`);
152
+ }
153
+ const refreshData = await refreshRes.json();
154
+ if (!refreshData.access_token) {
155
+ throw new Error('Failed to obtain access token from refresh');
156
+ }
157
+ // Update cached token with new access token
158
+ this.cachedToken = refreshData.access_token;
159
+ const expiresInSeconds = refreshData.expires_in || 300; // Default to 5 minutes
160
+ this.tokenExpiresAt = Date.now() + expiresInSeconds * 1000;
100
161
  return this.cachedToken;
101
162
  }
102
163
  /**
@@ -104,13 +165,17 @@ export class BotchaClient {
104
165
  */
105
166
  clearToken() {
106
167
  this.cachedToken = null;
168
+ this._refreshToken = null;
107
169
  this.tokenExpiresAt = null;
108
170
  }
109
171
  /**
110
172
  * Get and solve a challenge from BOTCHA service
111
173
  */
112
174
  async solveChallenge() {
113
- const res = await fetch(`${this.baseUrl}/api/speed-challenge`, {
175
+ const challengeUrl = this.appId
176
+ ? `${this.baseUrl}/api/speed-challenge?app_id=${encodeURIComponent(this.appId)}`
177
+ : `${this.baseUrl}/api/speed-challenge`;
178
+ const res = await fetch(challengeUrl, {
114
179
  headers: { 'User-Agent': this.agentIdentity },
115
180
  });
116
181
  if (!res.ok) {
@@ -180,16 +245,31 @@ export class BotchaClient {
180
245
  ...init,
181
246
  headers,
182
247
  });
183
- // Handle 401 by refreshing token and retrying once
248
+ // Handle 401 by trying refresh first, then full re-verify if refresh fails
184
249
  if (response.status === 401 && this.autoToken) {
185
- this.clearToken();
186
250
  try {
187
- const token = await this.getToken();
251
+ // Try refresh token first if available
252
+ let token;
253
+ if (this._refreshToken) {
254
+ try {
255
+ token = await this.refreshToken();
256
+ }
257
+ catch (refreshError) {
258
+ // Refresh failed, clear tokens and do full re-verify
259
+ this.clearToken();
260
+ token = await this.getToken();
261
+ }
262
+ }
263
+ else {
264
+ // No refresh token, clear and do full re-verify
265
+ this.clearToken();
266
+ token = await this.getToken();
267
+ }
188
268
  headers.set('Authorization', `Bearer ${token}`);
189
269
  response = await fetch(url, { ...init, headers });
190
270
  }
191
271
  catch (error) {
192
- // Token refresh failed, return the 401 response
272
+ // Token refresh/acquisition failed, return the 401 response
193
273
  }
194
274
  }
195
275
  let retries = 0;
@@ -243,12 +323,176 @@ export class BotchaClient {
243
323
  */
244
324
  async createHeaders() {
245
325
  const { id, answers } = await this.solveChallenge();
246
- return {
326
+ const headers = {
247
327
  'X-Botcha-Id': id,
248
328
  'X-Botcha-Challenge-Id': id,
249
329
  'X-Botcha-Answers': JSON.stringify(answers),
250
330
  'User-Agent': this.agentIdentity,
251
331
  };
332
+ // Include X-Botcha-App-Id header if appId is set
333
+ if (this.appId) {
334
+ headers['X-Botcha-App-Id'] = this.appId;
335
+ }
336
+ return headers;
337
+ }
338
+ // ============ APP MANAGEMENT ============
339
+ /**
340
+ * Create a new BOTCHA app. Email is required.
341
+ *
342
+ * The returned `app_secret` is only shown once โ€” save it securely.
343
+ * A 6-digit verification code will be sent to the provided email.
344
+ *
345
+ * @param email - Email address for the app owner
346
+ * @returns App creation response including app_id and app_secret
347
+ * @throws Error if app creation fails
348
+ *
349
+ * @example
350
+ * ```typescript
351
+ * const app = await client.createApp('agent@example.com');
352
+ * console.log(app.app_id); // 'app_abc123'
353
+ * console.log(app.app_secret); // 'sk_...' (save this!)
354
+ * ```
355
+ */
356
+ async createApp(email) {
357
+ const res = await fetch(`${this.baseUrl}/v1/apps`, {
358
+ method: 'POST',
359
+ headers: {
360
+ 'Content-Type': 'application/json',
361
+ 'User-Agent': this.agentIdentity,
362
+ },
363
+ body: JSON.stringify({ email }),
364
+ });
365
+ if (!res.ok) {
366
+ const body = await res.json().catch(() => ({}));
367
+ throw new Error(body.message || `App creation failed with status ${res.status}`);
368
+ }
369
+ const data = await res.json();
370
+ // Auto-set appId for subsequent requests
371
+ if (data.app_id) {
372
+ this.appId = data.app_id;
373
+ }
374
+ return data;
375
+ }
376
+ /**
377
+ * Verify the email address for an app using the 6-digit code sent via email.
378
+ *
379
+ * @param appId - The app ID (defaults to the client's appId)
380
+ * @param code - The 6-digit verification code from the email
381
+ * @returns Verification response
382
+ * @throws Error if verification fails
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * const result = await client.verifyEmail('123456');
387
+ * console.log(result.email_verified); // true
388
+ * ```
389
+ */
390
+ async verifyEmail(code, appId) {
391
+ const id = appId || this.appId;
392
+ if (!id) {
393
+ throw new Error('No app ID. Call createApp() first or pass appId.');
394
+ }
395
+ const res = await fetch(`${this.baseUrl}/v1/apps/${encodeURIComponent(id)}/verify-email`, {
396
+ method: 'POST',
397
+ headers: {
398
+ 'Content-Type': 'application/json',
399
+ 'User-Agent': this.agentIdentity,
400
+ },
401
+ body: JSON.stringify({ code }),
402
+ });
403
+ if (!res.ok) {
404
+ const body = await res.json().catch(() => ({}));
405
+ throw new Error(body.message || `Email verification failed with status ${res.status}`);
406
+ }
407
+ return await res.json();
408
+ }
409
+ /**
410
+ * Resend the email verification code.
411
+ *
412
+ * @param appId - The app ID (defaults to the client's appId)
413
+ * @returns Response with success status
414
+ * @throws Error if resend fails
415
+ */
416
+ async resendVerification(appId) {
417
+ const id = appId || this.appId;
418
+ if (!id) {
419
+ throw new Error('No app ID. Call createApp() first or pass appId.');
420
+ }
421
+ const res = await fetch(`${this.baseUrl}/v1/apps/${encodeURIComponent(id)}/resend-verification`, {
422
+ method: 'POST',
423
+ headers: {
424
+ 'Content-Type': 'application/json',
425
+ 'User-Agent': this.agentIdentity,
426
+ },
427
+ });
428
+ if (!res.ok) {
429
+ const body = await res.json().catch(() => ({}));
430
+ throw new Error(body.message || `Resend verification failed with status ${res.status}`);
431
+ }
432
+ return await res.json();
433
+ }
434
+ /**
435
+ * Request account recovery via verified email.
436
+ * Sends a device code to the registered email address.
437
+ *
438
+ * Anti-enumeration: always returns the same response shape
439
+ * whether or not the email exists.
440
+ *
441
+ * @param email - The email address associated with the app
442
+ * @returns Recovery response (always success for anti-enumeration)
443
+ */
444
+ async recoverAccount(email) {
445
+ const res = await fetch(`${this.baseUrl}/v1/auth/recover`, {
446
+ method: 'POST',
447
+ headers: {
448
+ 'Content-Type': 'application/json',
449
+ 'User-Agent': this.agentIdentity,
450
+ },
451
+ body: JSON.stringify({ email }),
452
+ });
453
+ if (!res.ok) {
454
+ const body = await res.json().catch(() => ({}));
455
+ throw new Error(body.message || `Account recovery failed with status ${res.status}`);
456
+ }
457
+ return await res.json();
458
+ }
459
+ /**
460
+ * Rotate the app secret. Requires an active dashboard session (Bearer token).
461
+ * The old secret is immediately invalidated.
462
+ *
463
+ * @param appId - The app ID (defaults to the client's appId)
464
+ * @returns New app_secret (save it โ€” only shown once)
465
+ * @throws Error if rotation fails or auth is missing
466
+ *
467
+ * @example
468
+ * ```typescript
469
+ * const result = await client.rotateSecret();
470
+ * console.log(result.app_secret); // 'sk_new_...' (save this!)
471
+ * ```
472
+ */
473
+ async rotateSecret(appId) {
474
+ const id = appId || this.appId;
475
+ if (!id) {
476
+ throw new Error('No app ID. Call createApp() first or pass appId.');
477
+ }
478
+ // Rotate secret requires a dashboard session token
479
+ const headers = {
480
+ 'Content-Type': 'application/json',
481
+ 'User-Agent': this.agentIdentity,
482
+ };
483
+ // Use cached token if available (from dashboard auth)
484
+ if (this.cachedToken) {
485
+ headers['Authorization'] = `Bearer ${this.cachedToken}`;
486
+ }
487
+ const res = await fetch(`${this.baseUrl}/v1/apps/${encodeURIComponent(id)}/rotate-secret`, {
488
+ method: 'POST',
489
+ headers,
490
+ });
491
+ if (!res.ok) {
492
+ const body = await res.json().catch(() => ({}));
493
+ throw new Error(body.message || `Secret rotation failed with status ${res.status}`);
494
+ }
495
+ return await res.json();
252
496
  }
253
497
  }
254
498
  /**
@@ -16,6 +16,10 @@ export interface BotchaClientOptions {
16
16
  maxRetries?: number;
17
17
  /** Enable automatic token acquisition and management (default: true) */
18
18
  autoToken?: boolean;
19
+ /** Audience claim for token (optional) */
20
+ audience?: string;
21
+ /** Multi-tenant application ID (optional) */
22
+ appId?: string;
19
23
  }
20
24
  export interface ChallengeResponse {
21
25
  success: boolean;
@@ -44,6 +48,10 @@ export interface VerifyResponse {
44
48
  export interface TokenResponse {
45
49
  success: boolean;
46
50
  token: string | null;
51
+ access_token?: string;
52
+ refresh_token?: string;
53
+ expires_in?: number;
54
+ refresh_expires_in?: number;
47
55
  expiresIn?: string;
48
56
  challenge?: {
49
57
  id: string;
@@ -52,6 +60,8 @@ export interface TokenResponse {
52
60
  instructions: string;
53
61
  };
54
62
  nextStep?: string;
63
+ verified?: boolean;
64
+ solveTimeMs?: number;
55
65
  }
56
66
  /**
57
67
  * Stream-related types for BotchaStreamClient
@@ -84,4 +94,41 @@ export interface StreamChallengeOptions {
84
94
  /** Timeout for the full verification flow in milliseconds (default: 30000) */
85
95
  timeout?: number;
86
96
  }
97
+ export interface CreateAppResponse {
98
+ success: boolean;
99
+ app_id: string;
100
+ app_secret: string;
101
+ email: string;
102
+ email_verified: boolean;
103
+ verification_required: boolean;
104
+ warning: string;
105
+ credential_advice: string;
106
+ created_at: string;
107
+ rate_limit: number;
108
+ next_step: string;
109
+ }
110
+ export interface VerifyEmailResponse {
111
+ success: boolean;
112
+ email_verified?: boolean;
113
+ error?: string;
114
+ message?: string;
115
+ }
116
+ export interface ResendVerificationResponse {
117
+ success: boolean;
118
+ message?: string;
119
+ error?: string;
120
+ }
121
+ export interface RecoverAccountResponse {
122
+ success: boolean;
123
+ message: string;
124
+ }
125
+ export interface RotateSecretResponse {
126
+ success: boolean;
127
+ app_id?: string;
128
+ app_secret?: string;
129
+ warning?: string;
130
+ rotated_at?: string;
131
+ error?: string;
132
+ message?: string;
133
+ }
87
134
  //# sourceMappingURL=types.d.ts.map
@@ -1 +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;AAED;;GAEG;AAEH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,GAAG,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;IAClE,IAAI,EAAE,GAAG,CAAC;CACX;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,wCAAwC;IACxC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,0DAA0D;IAC1D,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;IACpE,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../lib/client/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dupecom/botcha",
3
- "version": "0.6.0",
3
+ "version": "0.10.0",
4
4
  "description": "Prove you're a bot. Humans need not apply. Reverse CAPTCHA for AI-only APIs.",
5
5
  "workspaces": [
6
6
  "packages/*"