@outcrawl/sdk 0.1.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 +198 -0
- package/dist/index.js +2493 -0
- package/dist/types/_deps/core/agent-alias.d.ts +219 -0
- package/dist/types/_deps/core/brand.d.ts +62 -0
- package/dist/types/_deps/core/certificate.d.ts +100 -0
- package/dist/types/_deps/core/cron.d.ts +116 -0
- package/dist/types/_deps/core/errors.d.ts +340 -0
- package/dist/types/_deps/core/index.d.ts +17 -0
- package/dist/types/_deps/core/money.d.ts +108 -0
- package/dist/types/_deps/core/registry.d.ts +466 -0
- package/dist/types/_deps/core/rules.d.ts +237 -0
- package/dist/types/_deps/core/secrets.d.ts +278 -0
- package/dist/types/_deps/core/types.d.ts +1677 -0
- package/dist/types/_deps/integrations/connector.d.ts +153 -0
- package/dist/types/_deps/replay/events.d.ts +1004 -0
- package/dist/types/agent.d.ts +313 -0
- package/dist/types/availability.d.ts +86 -0
- package/dist/types/browser.d.ts +107 -0
- package/dist/types/client.d.ts +128 -0
- package/dist/types/hands.d.ts +259 -0
- package/dist/types/index.d.ts +111 -0
- package/dist/types/integrations.d.ts +63 -0
- package/dist/types/monitors.d.ts +24 -0
- package/dist/types/page.d.ts +96 -0
- package/dist/types/profiles.d.ts +26 -0
- package/dist/types/result.d.ts +90 -0
- package/dist/types/rules.d.ts +41 -0
- package/dist/types/scrape.d.ts +68 -0
- package/dist/types/secrets.d.ts +36 -0
- package/dist/types/sessions.d.ts +124 -0
- package/dist/types/transport.d.ts +250 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# @outcrawl/sdk
|
|
2
|
+
|
|
3
|
+
The typed TypeScript client for [Outcrawl](https://outcrawl.ai): scrape, crawl, search, agent runs,
|
|
4
|
+
and real stealth browsers driven over CDP. One credit balance — no separate proxy, browser or
|
|
5
|
+
stealth credits. Zero runtime dependencies; `playwright` and `agenthands` are optional peers,
|
|
6
|
+
reached only through `await import(...)`, so a caller who never opens a browser never installs them.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { Outcrawl } from '@outcrawl/sdk';
|
|
12
|
+
|
|
13
|
+
const oc = new Outcrawl(); // reads OUTCRAWL_API_KEY; pass { apiKey } to override
|
|
14
|
+
|
|
15
|
+
const doc = await oc.scrape('https://example.com', { formats: ['markdown'] });
|
|
16
|
+
console.log(doc.usage); // every call returns what it cost, on the result that caused it
|
|
17
|
+
|
|
18
|
+
const browser = await oc.browser({ profile: 'acme-user-42' });
|
|
19
|
+
const page = await browser.newPage();
|
|
20
|
+
await page.click('#login'); // human input, the default
|
|
21
|
+
await page.raw.click('#login'); // Playwright's own, and recorded on the session
|
|
22
|
+
await browser.close();
|
|
23
|
+
|
|
24
|
+
// No schema: `data` is the agent's own summary of what it found.
|
|
25
|
+
const run = oc.agent({ task: 'Report the title of the top story on Hacker News.', caps: { steps: 20, budget: '2.00', duration: '5m' } });
|
|
26
|
+
const result = await run; // await for the result
|
|
27
|
+
console.log(result.data); // "The top story is …"
|
|
28
|
+
for await (const step of run) {} // or iterate the log — both on one job is fine, it is two
|
|
29
|
+
// requests against one durable row
|
|
30
|
+
|
|
31
|
+
// With a schema, `data` is schema-shaped instead, and satisfying it is the success signal.
|
|
32
|
+
const typed = await oc.agent({
|
|
33
|
+
task: 'Report the title of the top story on Hacker News.',
|
|
34
|
+
caps: { steps: 20, budget: '2.00', duration: '5m' },
|
|
35
|
+
schema: { type: 'object', properties: { title: { type: 'string' } }, required: ['title'] },
|
|
36
|
+
});
|
|
37
|
+
console.log(typed.data); // { title: '…' }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
| Variable | Meaning | Default |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `OUTCRAWL_API_KEY` | your key | required unless you pass `apiKey` or a `transport` |
|
|
45
|
+
| `OUTCRAWL_API_URL` | base url | `https://outcrawl.ai` |
|
|
46
|
+
|
|
47
|
+
The same two names `@outcrawl/cli`, the Outcrawl MCP server and the Python SDK read, exported here
|
|
48
|
+
as `API_KEY_ENV` and `API_URL_ENV` so a caller telling somebody what to set reads the name off the
|
|
49
|
+
client rather than retyping it. The default host is measured, not assumed: on 2026-09-08
|
|
50
|
+
`https://outcrawl.ai/v1/credits` answered `200` and `https://api.outcrawl.ai/v1/credits` did not
|
|
51
|
+
resolve at all.
|
|
52
|
+
|
|
53
|
+
## Surface
|
|
54
|
+
|
|
55
|
+
- `Outcrawl` — one entry point. The wire types (`ScrapeResult`, `AgentResult`, `SessionSummary`,
|
|
56
|
+
`Profile`, `CreditBalance`, `Usage`, …) are re-exported here, because `@outcrawl/core` is not
|
|
57
|
+
published and a type only nameable from there is a type no caller can name.
|
|
58
|
+
- `oc.scrape` / `oc.crawl` / `oc.search` — `crawl` is async-iterable.
|
|
59
|
+
- `oc.agent(...)` — `caps` (`steps`, `budget`, `duration`) is required at the type level and at
|
|
60
|
+
runtime; `steps` and `duration` are hard stops. `result.durationMs` is what agent minutes bill
|
|
61
|
+
from. It returns an `AgentJob` synchronously: `submitted` is the `{ id, status }` round trip,
|
|
62
|
+
`await` polls to settled, `for await` tails the log, and everything an `AgentHandle` does is on
|
|
63
|
+
it too — `status()`, `results()`, `events()`, `cancel()`, and the three human-in-the-loop
|
|
64
|
+
methods: `control('take' | 'release', who?)`, `answer(answer, answeredBy?)` and
|
|
65
|
+
`addFile({ name, bytes, contentType? })`, which takes a `Uint8Array` and base64s it for the
|
|
66
|
+
wire. `settled()` does not treat a PARKED run as settled: a run waiting on a person is about to
|
|
67
|
+
take more steps.
|
|
68
|
+
- `oc.browser(...)` — allocates a session on the fleet and connects over CDP; nothing launches
|
|
69
|
+
locally. `Browser.rawCalls` collects a `RawInputMark` per `page.raw.*` call, posted to the session.
|
|
70
|
+
- `oc.sessions.get` / `list` / `export`; `session.live()` streams replay events and
|
|
71
|
+
`session.takeControl()` hands the wheel to a person.
|
|
72
|
+
- `oc.profiles` and `oc.monitors` — `create` / `list` / `delete` each.
|
|
73
|
+
- `oc.secrets` — `create` / `list` / `delete`, and deliberately **no `get`**: there is no
|
|
74
|
+
`secrets.get` route to widen. `list` answers the handle, the kind and a card's last four
|
|
75
|
+
(`hint`), which is everything a human needs to pick one. A run reaches a value only by naming
|
|
76
|
+
the HANDLE in `AgentRequest.secrets`, and the substitution happens below the model.
|
|
77
|
+
- `oc.rules` — `get` / `set(rules)`. `set` REPLACES the set and answers the classification of
|
|
78
|
+
what it just stored, so a caller learns on that same call which sentences became `enforced`
|
|
79
|
+
gates and which are only `guidance`. Read `class`, and read `unenforceable`: it is present
|
|
80
|
+
exactly when we suspect a customer believes a rule is enforced and it is not.
|
|
81
|
+
- `oc.integrations` — `connect(input)` / `list()` / `delete(id)`, the management half of Outcrawl
|
|
82
|
+
as an MCP **client**. `connect` is not granting: reach is granted per run through
|
|
83
|
+
`AgentRequest.integrations`, and a submit that names nothing reaches nothing however many
|
|
84
|
+
connectors the workspace has. `auth.secret` is a secrets-store handle, never a token. `list`
|
|
85
|
+
takes no query, because this listing is the blast radius.
|
|
86
|
+
- `oc.credits()` — `{ granted, expired, spent, balance, exhausted, malformed }`, every figure a
|
|
87
|
+
decimal **string**; never parse to `Number`.
|
|
88
|
+
- `BrowserOptions` — `profile`, `proxy`, `record`, and nothing else.
|
|
89
|
+
- Every error `errorFromWire` can return is exported, because `instanceof` against a class you
|
|
90
|
+
cannot import is not a contract: `OutcrawlError`, `BadRequestError`, `CapExceededError`,
|
|
91
|
+
`CapabilityUnavailableError`, `ConcurrencyLimitError`, `ExitUnavailableError`, `InternalError`,
|
|
92
|
+
`MethodNotAllowedError`, `NoCapacityError`, `NotFoundError`, `PlanRequiredError`,
|
|
93
|
+
`ProfileInUseError`, `ProfileNotFoundError`, `QuotaExceededError`, `TenantScopeError`,
|
|
94
|
+
`UnauthorizedError`, plus `isOutcrawlError`, `isKnownErrorCode` and `OUTCRAWL_ERROR_CODES`.
|
|
95
|
+
Branch on `error.code`, never on `message`. The Python SDK raises the same classes with the same
|
|
96
|
+
fields and the same messages; `packages/sdk-python/tests/error_parity.json` is the shared
|
|
97
|
+
contract and both suites read it.
|
|
98
|
+
- `CAPABILITY_AVAILABILITY` — `satisfies Record<CapabilityName, Unavailable | null>`, so a new
|
|
99
|
+
registry capability does not compile until somebody states whether anything serves it; a
|
|
100
|
+
capability the table does not declare is refused by name.
|
|
101
|
+
|
|
102
|
+
## Rules
|
|
103
|
+
|
|
104
|
+
- **Identity is minted, not chosen.** `timezoneId`, `timezone`, `locale`, `languages`, `userAgent`,
|
|
105
|
+
`viewport`, `screen`, `deviceScaleFactor`, `geolocation`, `webrtcIp`, `seed`, `identity` and
|
|
106
|
+
`fingerprint` each throw `IdentityOverrideError` naming what pins them — the `PINNED` map is the list.
|
|
107
|
+
- `proxy` takes `outcrawl://<country>[/<region>[/<city>]]` or the equivalent object, never a
|
|
108
|
+
provider URL.
|
|
109
|
+
- **Recording is automatic; declining is explicit.** Only `record: false` declines (on `/connect`,
|
|
110
|
+
only `?record=false` or `?record=0`); a non-boolean `record` on an agent run is a `400`. A declined
|
|
111
|
+
session never has a recorder attached — no replay, no `replay_sessions` row, no blob,
|
|
112
|
+
`sessions.export` answers `404`, no `replayUrl`, and `recordingDeclined` is `true`. Declining
|
|
113
|
+
changes what is kept, never what is charged.
|
|
114
|
+
- `scrape`, `crawl`, `search` and monitor checks record nothing and have no `record` field.
|
|
115
|
+
- A profile is leased while open; a second concurrent open throws `ProfileInUseError`.
|
|
116
|
+
- `oc.sessions.list()` throws `UnsupportedSessionFilterError` for a filter the API does not
|
|
117
|
+
implement, rather than returning an unfiltered page.
|
|
118
|
+
- **Bandwidth is charged where you chose the bytes.** Egress lands on `oc.browser()` sessions and
|
|
119
|
+
agent runs; it is recorded and not charged on `scrape`, `crawl`, `search` and monitor checks,
|
|
120
|
+
where our pipeline decides the byte count. `result.billed.bandwidthBytes` reports it either way.
|
|
121
|
+
- Pricing: [outcrawl.ai/pricing](https://outcrawl.ai/pricing) is authoritative, and
|
|
122
|
+
`OUTCRAWL_RATE_CARD` in `@outcrawl/persistence` is what production computes against — ask it
|
|
123
|
+
rather than trusting a transcribed table.
|
|
124
|
+
- The SDK is optional. An unmodified Playwright script pointed at our CDP endpoint keeps working;
|
|
125
|
+
this package is ergonomics, never the only door.
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
cd packages/sdk
|
|
131
|
+
bun test ./test
|
|
132
|
+
bun run build # the publish path; `prepack` runs the same script
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`bun run build` asserts the bundle evaluates and constructs, and that the shipped declarations
|
|
136
|
+
typecheck as a customer resolves them through the `exports` map, under `moduleResolution: nodenext`
|
|
137
|
+
— twice, because "the way a customer does" is not one configuration: once DOM-flavoured with
|
|
138
|
+
`skipLibCheck: false`, and once as a Node backend with `lib: ["ES2024"]` and `@types/node`. The
|
|
139
|
+
second shape was added after a scratch install found three `TS2749` errors that the first could not
|
|
140
|
+
see; see the `Gaps` section.
|
|
141
|
+
|
|
142
|
+
## Gaps
|
|
143
|
+
|
|
144
|
+
- **Not on npm, and the code is not the blocker.** No `LICENSE` file in this repository and no
|
|
145
|
+
`license` field in this package; the build, the tarball and the version are ready. Proven from a
|
|
146
|
+
scratch install of the tarball outside the repo: scrape, an agent run to `completed` and an
|
|
147
|
+
events-cursor iteration, all three against production.
|
|
148
|
+
- **One customer tsconfig still reports three errors, and they are in a carried declaration.**
|
|
149
|
+
Measured 2026-09-08 from a scratch install, five configurations: the default scaffold, strict with
|
|
150
|
+
`lib: ["ES2024"]` + `@types/node`, strict with DOM and `skipLibCheck: false`, and
|
|
151
|
+
`moduleResolution: bundler` are all clean; strict with `lib: ["ES2024"]` (no DOM) AND
|
|
152
|
+
`skipLibCheck: false` reports three `TS2749` on `CryptoKey` in `_deps/core/secrets.d.ts`. That
|
|
153
|
+
file is carried because `Secret`, `SecretKind` and `SecretCreateRequest` live in it; none of its
|
|
154
|
+
crypto half is on this package's surface. The fix belongs in `@outcrawl/core`, whose
|
|
155
|
+
`importSecretKey` names the ambient DOM `CryptoKey` —
|
|
156
|
+
`Awaited<ReturnType<typeof globalThis.crypto.subtle.importKey>>` resolves under all three lib
|
|
157
|
+
configurations, verified. Not changed here because this is the packaging layer and that is a
|
|
158
|
+
shared contract.
|
|
159
|
+
- **Map is priced and not implemented.** It is on the pricing page with no route, no handler and no
|
|
160
|
+
meter — no method to call and nothing to bill.
|
|
161
|
+
- **Extract has no REST route.** `page.extract()` works only over an open CDP connection, and is
|
|
162
|
+
charged as that session's browser time and bandwidth, never the published per-page rate.
|
|
163
|
+
- **Bring-your-own-model is not available, and `model` is gone as a field.** `AgentRequest` has no
|
|
164
|
+
`model`: a request names a HARNESS — `agent: 'scout-1'` (the default) or `'voyager-1'` — and
|
|
165
|
+
which models a harness runs is ours. A `model` key is refused by the same accept-list check that
|
|
166
|
+
refuses any unknown field, and no route exists for a customer credential to reach a worker.
|
|
167
|
+
- **`caps.budget` binds the INVOICE, and it does not bind model tokens.** It is a ceiling on what
|
|
168
|
+
the run is billed — pages at 1 credit, plus the published +5/minute agent and 1/minute browser
|
|
169
|
+
time — and it is enforced continuously against that figure, so a run that reaches it stops with
|
|
170
|
+
`status: 'capped'` and `stopReason: 'max-budget'`. It may overshoot by at most one step and it
|
|
171
|
+
says so when it does: a run billed over its ceiling never reports `task-complete`. Model tokens
|
|
172
|
+
are still outside it, because the worker deliberately binds no `creditsPerUsd` and sets
|
|
173
|
+
`budgetPolicy: 'warn'` — it does not know the caller's plan tier at inference time — and model
|
|
174
|
+
spend is recorded in USD in the worker's cost ledger. That is not a gap in the ceiling: the rate
|
|
175
|
+
card has no token line at all, and an agent run's model spend is exactly what its billed minutes
|
|
176
|
+
buy. Until 2026-09-08 this entry said `caps.budget` bound nothing but model spend and told you to
|
|
177
|
+
rely on `duration`; that was true, and it meant the ceiling was a no-op on every production run.
|
|
178
|
+
- **`credits().exhausted` is reported, not enforced.** Nothing refuses work when the granted pool
|
|
179
|
+
empties — admission is decided against the plan's monthly allowance. Treat it as a signal to top up.
|
|
180
|
+
- **The availability gate is dormant, and that is a measurement rather than a hope.** Every one of
|
|
181
|
+
the 31 rows in `CAPABILITY_AVAILABILITY` is an explicit `null` as of 2026-09-08 — including the
|
|
182
|
+
eleven that landed that day — so nothing is refused locally; the table is kept for the
|
|
183
|
+
compile-time requirement that a new registry row cannot compile until somebody states whether
|
|
184
|
+
anything serves it. `assertAvailable` still fails closed on a name the table does not declare,
|
|
185
|
+
which is the case `satisfies` cannot cover.
|
|
186
|
+
- **Unrelated server-side refusals share one code.** `capability_unavailable` from the API is not
|
|
187
|
+
this SDK's gate; it means the deployment cannot serve the call. A worker with no model client
|
|
188
|
+
configured answers it for `agent`, one with no exit provisioner answers it for a request naming
|
|
189
|
+
a geography, and a control plane with no `SECRETS_KEY` answers it for `secrets.create` and for
|
|
190
|
+
an agent submit that names `secrets` — the key is optional precisely so a card is never stored
|
|
191
|
+
under a key that dies with the isolate. An agent submit naming `integrations` on a deployment
|
|
192
|
+
with no control plane is refused the same way. Branch on `code`, then read `capability` and
|
|
193
|
+
`missing` — those two fields are what separate these.
|
|
194
|
+
- **`AgentFile` is not nameable from this package.** `AgentHandle.addFile` and `AgentApi.addFile`
|
|
195
|
+
answer `Result<AgentFile>`, the interface is declared and exported in `src/agent.ts`, and
|
|
196
|
+
`src/index.ts` does not re-export it — so a customer resolving through the `exports` map cannot
|
|
197
|
+
name the type their own upload call returns. Same defect the Surface note above describes for
|
|
198
|
+
`@outcrawl/core` types, one hop closer.
|