Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions .cursor/skills/sdk-self-verify/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
name: sdk-self-verify
description: >-
Runs the CapMonster Cloud Node.js SDK maintainer self-check before QA: README
examples, CommonJS and ES Modules functionality, API docs comparison, and a
structured report. Use when the user asks to verify the SDK, check README
examples, compare the client with CapMonster API docs, or prepare a change
for QA.
---

# SDK self-verify

Run this before handing JS SDK changes to QA. Combine the mechanical runner with a docs comparison. Do not treat static code reading as a substitute for an actual command.

## Command

From the repository root:

```bash
npm run self-verify
```

Options for `node tools/self-verify/run.js`:

- `--skip-build` if `dist/` is already current
- `--skip-tests` to skip Jest
- `--live` to hit the real API (needs `CMC_CLIENT_KEY` or `CAPMONSTER_API_KEY`)
- `--live-solve` to also run README `Solve()` examples live
- `--out path/to/report.json`

Mechanical outputs:

- `tools/self-verify/reports/latest.json`
- `tools/self-verify/reports/latest.md`

Then write the full report using [report-template.md](report-template.md). Keep the JSON as evidence.

## Workflow

Copy and track:

```
Self-verify:
- [ ] npm run self-verify
- [ ] README examples: build/run/expected result
- [ ] CommonJS matrix
- [ ] ES Modules matrix
- [ ] Unit + integration tests
- [ ] API docs comparison
- [ ] Final report
```

1. Run `npm run self-verify`. If build fails, stop and report that. Do not claim later steps were executed.
2. Read `tools/self-verify/reports/latest.json`. Every check is `executed`, `analysis`, or `skipped`.
3. Confirm README examples: each fenced `ts` / `js` / `javascript` block was written to `tools/self-verify/tmp/readme-examples/` and actually run. Record exit code, stdout/stderr, and expected result.
4. Confirm module matrix: CJS `require(dist/index.js)` and ESM `import(dist/esm/index.js)` both load, construct every request fixture (including Hunt `widgetUrl` and Alibaba `punishUrl` / `cookieRequired`), call `getBalance` / `Solve`, and cover error paths.
5. Compare the SDK with live API docs. Catalog and field notes are in [reference.md](reference.md) and `tools/self-verify/docs-catalog.json`. Fetch current docs; do not rely only on the catalog.
6. Write the report. Separate code analysis from execution. List unverified scenarios and documentation contradictions in their own sections. Failed checks need reproduction commands and a docs URL.

## Evidence rules

- Executed: a command ran in this session. Quote the command and the result.
- Analysis: source or docs were read, nothing ran. Say so.
- Skipped: missing API key, `--live-solve` not set, build failed, or the scenario cannot be automated. Say why.

Never upgrade analysis or skipped into pass.

## README examples

Source of truth: `README.md` fenced blocks.

For each runnable example record:

- module format (CJS or ESM)
- compile/run command
- expected result (printed `solution`, printed `Balance`, process exit 0)
- actual result
- docs link if the example maps to a task type

Default run uses a local mock (`serviceUrl` injected). Live `Solve()` is skip/unverified unless `--live-solve` and an API key are present.

## Module matrix

Must cover both `dist/index.js` (CommonJS) and `dist/esm/index.js` (ES Modules):

- named exports used in README
- every request class in `tools/self-verify/request-fixtures.js`
- required vs optional fields that the fixture exercises
- createTask payload (`type`, `class`, metadata)
- getBalance success and `KEY_DOES_NOT_EXIST`
- Solve success and `ZERO_BALANCE`
- invalid `websiteURL`

If ESM fails with `ReferenceError: module is not defined`, that is a product defect. Reproduce with:

```bash
node --input-type=module -e "import('./dist/esm/index.js').then(m => m.CapMonsterCloudClientFactory.Create(new m.ClientOptions({ clientKey: 'x' })))"
```

## Docs comparison

Fetch at least:

- https://docs.capmonster.cloud/docs/captchas/
- https://docs.capmonster.cloud/docs/methods/
- Hunt: https://docs.capmonster.cloud/docs/captchas/hunt-task/
- Alibaba: https://docs.capmonster.cloud/docs/captchas/alibaba-task/

For each docs task type, check whether the SDK has a request class, matching required/optional fields, and a response shape. Missing or stale SDK fields are defects. Docs that disagree with each other go under contradictions, not SDK bugs.

## Report

Use [report-template.md](report-template.md). Save under `tools/self-verify/reports/` as `YYYY-MM-DD-self-verify.md` plus `latest.md`.
54 changes: 54 additions & 0 deletions .cursor/skills/sdk-self-verify/reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# SDK self-verify reference

Official docs to fetch on every run (do not treat this file as a substitute):

- Captcha types index: https://docs.capmonster.cloud/docs/captchas/
- API methods: https://docs.capmonster.cloud/docs/methods/
- Getting started: https://docs.capmonster.cloud/docs/getting-start/

Known SDK class → docs URL map: `tools/self-verify/docs-catalog.json`.

## Fields that recently lagged the API

Hunt (`CustomTask` / `class: "HUNT"`), https://docs.capmonster.cloud/docs/captchas/hunt-task/

| Field | Required | Notes |
| --- | --- | --- |
| websiteURL | yes | Page URL |
| metadata.apiGetLib | yes | Full `api.js` URL |
| metadata.data | no | `meta.token`. Do not send with `widgetUrl` |
| metadata.widgetUrl | no | Full widget URL. Do not send with `data` |
| userAgent | no | Current Windows UA |
| proxy | yes | Own proxies required |

Alibaba (`CustomTask` / `class: "alibaba"`), https://docs.capmonster.cloud/docs/captchas/alibaba-task/

| Field | Required | Notes |
| --- | --- | --- |
| websiteURL | yes | Page URL |
| metadata.sceneId | for standard flow | Not required for punish-only |
| metadata.prefix | for standard flow | Not required for punish-only |
| metadata.punishUrl | no | Full `/punish` URL with query |
| metadata.cookieRequired | no | Adds `solution.domains` cookies |
| metadata.userId, userUserId, verifyType, region, UserCertifyId, apiGetLib | no | |
| userAgent | no | |
| proxy | no | |

## Mechanical coverage

| Check | Command | Kind |
| --- | --- | --- |
| Build | `npm run build` | executed |
| README examples | `npm run test-readme` | executed (mock) or skipped (live Solve) |
| CJS + ESM matrix | `npm run test-modules` | executed |
| Unit tests | `npm run test-unit` | executed |
| Integration tests | `npm run test-integr` | executed |
| Docs field parity | fetch docs + read `src/Requests` | analysis |

## ESM smoke

```bash
node --input-type=module -e "import('./dist/esm/index.js').then((m) => { const c = m.CapMonsterCloudClientFactory.Create(new m.ClientOptions({ clientKey: 'x' })); console.log('ok', typeof c.Solve); })"
```

A `ReferenceError: module is not defined` here is a ship-blocking CJS leak in the ESM build.
68 changes: 68 additions & 0 deletions .cursor/skills/sdk-self-verify/report-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# SDK self-verify report template

Fill this after `npm run self-verify`. Keep executed results and analysis in separate sections.

```markdown
# CapMonster Node.js SDK self-verify

- Date:
- Git HEAD:
- Node:
- Command: `npm run self-verify`
- Mechanical JSON: `tools/self-verify/reports/latest.json`

## Verdict

PASS / FAIL — one paragraph. Do not send to QA on FAIL.

## Summary

| Area | Result | Kind |
| --- | --- | --- |
| Build | | executed |
| README examples | | executed / skipped |
| CommonJS | | executed |
| ES Modules | | executed |
| Unit tests | | executed |
| Integration tests | | executed |
| API docs comparison | | analysis |

## Executed results

For each failure:

- Check name
- Expected
- Actual
- Reproduction command
- Docs URL

## README examples

| Example | Module | Run | Expected | Actual | Kind |
| --- | --- | --- | --- | --- | --- |
| | CJS/ESM | command | | | executed/skipped |

## CommonJS and ES Modules

Cover request types, required/optional fields, requests, responses, errors. Note CJS vs ESM differences.

## API docs comparison (analysis)

| Docs task | Docs URL | SDK class | Gap |
| --- | --- | --- | --- |
| | | | missing field / stale field / none |

## Unverified scenarios

What was not run and why (no key, no proxy, live Solve disabled, missing sandbox, etc.).

## Documentation contradictions

Docs vs docs, or docs vs SDK where it is unclear which is right. Do not file these as SDK bugs without a source-of-truth decision.

## Attachments

- latest.json
- failing command output
```
4 changes: 4 additions & 0 deletions .cursor/skills/sdk-self-verify/scripts/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
set -e
cd "$(dirname "$0")/../../../.."
npm run self-verify -- "$@"
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ dist/
node_modules/

.idea
.vscode
.vscode

tools/self-verify/tmp/
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ Install the package from npm:
npm install @zennolab_com/capmonstercloud-client
```

The package published on npm already includes the compiled files and is ready to use after installation.

### Build from this repository

Git does not contain the compiled `dist` output. The examples below import `@zennolab_com/capmonstercloud-client`, and inside this repository that name points at `dist`. After cloning, install dependencies and build before running an example or a test:

```bash
npm install
npm run build
```

Run `npm run build` again after changing the source.

---

## 🚀 Quick Start
Expand Down
8 changes: 5 additions & 3 deletions js-fix-esm-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ function resolveImportPath(filePath, importPath) {

function fixImports(filePath) {
const source = fs.readFileSync(filePath, 'utf8');
const fixed = source.replace(/(from\s+['"])([^'"]+)(['"])/g, (_, prefix, importPath, suffix) => {
return `${prefix}${resolveImportPath(filePath, importPath)}${suffix}`;
});
const fixed = source
.replace(/(from\s+['"])([^'"]+)(['"])/g, (_, prefix, importPath, suffix) => {
return `${prefix}${resolveImportPath(filePath, importPath)}${suffix}`;
})
.replace(/eval\((['"])import\.meta\.url\1\)/g, 'import.meta.url');
fs.writeFileSync(filePath, fixed);
}

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zennolab_com/capmonstercloud-client",
"version": "2.9.0",
"version": "2.9.1",
"description": "Official JS client library for https://capmonster.cloud/ captcha recognition service",
"main": "dist/index.js",
"module": "dist/esm/index.js",
Expand Down Expand Up @@ -31,9 +31,11 @@
],
"scripts": {
"build": "node js-rimraf.js && npx tsc && npx tsc -p tsconfig.esm.json && node js-fix-esm-imports.js && node -e \"require('fs').writeFileSync('dist/esm/package.json', JSON.stringify({ type: 'module' }, null, 2))\"",
"prepare": "npm run build",
"test": "jest",
"test-unit": "jest -c jest.config.unit.js",
"test-integr": "jest -c jest.config.integration.js",
"self-verify": "node tools/self-verify/run.js",
"lint": "eslint . --ext .ts,.js",
"lint:fix": "eslint . --ext .ts,.js --fix"
},
Expand Down
13 changes: 9 additions & 4 deletions src/CapMonsterCloudClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ export class CapMonsterCloudClient {
}
}
/**
* Solve FunCaptchaTask task
ч>>ч>> * Solve FunCaptchaTask task.
* Own proxy is required.
* You will get response within 10 - 80 secs period depending on service workload.
*/
public async Solve(
Expand Down Expand Up @@ -248,7 +249,8 @@ export class CapMonsterCloudClient {
cancellationController?: AbortController,
): Promise<CaptchaResult<RecaptchaV3Response>>;
/**
* Solve DataDomeRequest task
* Solve DataDomeRequest task.
* Own proxy is required.
* You will get response within 10 - 180 secs period depending on service workload.
*/
public async Solve(
Expand All @@ -275,7 +277,8 @@ export class CapMonsterCloudClient {
cancellationController?: AbortController,
): Promise<CaptchaResult<BasiliskResponse>>;
/**
* Solve Imperva task
* Solve Imperva task.
* Own proxy is required.
* You will get response within 10 - 180 secs period depending on service workload.
*/
public async Solve(
Expand Down Expand Up @@ -383,7 +386,9 @@ export class CapMonsterCloudClient {
cancellationController?: AbortController,
): Promise<CaptchaResult<AmazonResponse>>;
/**
* Solve Turnstile task
* Solve Turnstile task.
* Own proxy is required for Cloudflare Challenge (cf_clearance) and Waiting Room.
* Those modes return cf_clearance. Turnstile and Challenge token return token.
* You will get response within 10 - 180 secs period depending on service workload.
*/
public async Solve(
Expand Down
7 changes: 6 additions & 1 deletion src/CapMonsterCloudClientFactory.i.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,13 +800,18 @@ describe('Check integration tests for CapMonsterCloudClientFactory()', () => {
incapsulaScriptUrl: '_Incapsula_Resource?SWJIYLWA=719d34d31c8e3a6e6fffd425f7e032f3',
incapsulaCookies: 'l/LsGnrvyB9lNhXI8borDKa2IGc',
},
proxy: {
proxyType: 'http',
proxyAddress: '8.8.8.8',
proxyPort: 8080,
},
});

const task = await cmcClient.Solve(impervaRequest);

expect(srv.caughtRequests[0]).toHaveProperty(
'body',
'{"clientKey":"<your capmonster.cloud API key>","task":{"type":"CustomTask","websiteURL":"https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=middle","metadata":{"incapsulaScriptUrl":"_Incapsula_Resource?SWJIYLWA=719d34d31c8e3a6e6fffd425f7e032f3","incapsulaCookies":"l/LsGnrvyB9lNhXI8borDKa2IGc"},"class":"Imperva"},"softId":54}',
'{"clientKey":"<your capmonster.cloud API key>","task":{"type":"CustomTask","websiteURL":"https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=middle","metadata":{"incapsulaScriptUrl":"_Incapsula_Resource?SWJIYLWA=719d34d31c8e3a6e6fffd425f7e032f3","incapsulaCookies":"l/LsGnrvyB9lNhXI8borDKa2IGc"},"class":"Imperva","proxyType":"http","proxyAddress":"8.8.8.8","proxyPort":8080},"softId":54}',
);
expect(srv.caughtRequests[1]).toHaveProperty('body', '{"clientKey":"<your capmonster.cloud API key>","taskId":1234567}');
expect(task).toHaveProperty('solution');
Expand Down
8 changes: 2 additions & 6 deletions src/CapMonsterCloudClientFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ClientOptions } from './ClientOptions';
import { ClientURL } from './ClientURL';
import { CsMap, isNode } from './Utils';
import { HttpClient } from './HttpClient';
import { readOwnPackageVersion } from './nodeRequire';

export class CapMonsterCloudClientFactory {
static httpTimeout = 1000 * 21;
Expand Down Expand Up @@ -30,12 +31,7 @@ export class CapMonsterCloudClientFactory {
}

static CreateUserAgentString() {
let productVersion = 'ProductVersion';
if (isNode) {
// require('./package.json') hide require call from browser bundler, e.g. webpack
const packageJSON = module[`require`].call(module, '../package.json');
productVersion = packageJSON.version;
}
const productVersion = isNode ? readOwnPackageVersion() : 'ProductVersion';

return `${CapMonsterCloudClientFactory.productName}/${productVersion}`;
}
Expand Down
Loading
Loading