@opencraw/driver 0.1.0 → 0.2.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
@@ -17,8 +17,8 @@ npx opencraw-driver # then write JSON-RPC lines to it
17
17
  ## The conversation
18
18
 
19
19
  ```jsonc
20
- → {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientName":"opencraw-python","clientVersion":"1.0.0","protocolVersion":"1.0.0"}}
21
- ← {"jsonrpc":"2.0","id":1,"result":{"driverVersion":"0.1.0","protocolVersion":"1.0.0","methods":["initialize","validate","run","cancel","shutdown"]}}
20
+ → {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientName":"opencraw-python","clientVersion":"1.0.0","protocolVersion":"1.1.0"}}
21
+ ← {"jsonrpc":"2.0","id":1,"result":{"driverVersion":"0.2.0","protocolVersion":"1.1.0","methods":["initialize","validate","run","cancel","probe","diff","export","shutdown"]}}
22
22
  → {"jsonrpc":"2.0","id":2,"method":"run","params":{"paths":["recipes/"],"hooks":["tidy"]}}
23
23
  ← {"jsonrpc":"2.0","id":2,"result":{"runId":"run-1"}}
24
24
  ← {"jsonrpc":"2.0","method":"event","params":{"runId":"run-1","event":{"type":"recipe:start",…}}}
@@ -33,13 +33,16 @@ npx opencraw-driver # then write JSON-RPC lines to it
33
33
  |---|---|---|
34
34
  | `initialize` | client | Must come first. The driver refuses a protocol of another major. |
35
35
  | `validate` | client | Loads recipes (`paths` or inline `recipes`) without running them: the inputs that would run, every hook they call, and every problem with its path. |
36
- | `run` | client | Starts a run and answers its `runId` at once; the crawl goes on in the background. `hooks` names the hooks the client answers; `out` writes records to a file instead of sending them. |
36
+ | `run` | client | Starts a run and answers its `runId` at once; the crawl goes on in the background. `hooks`, `captchaSolvers` and `accessPlugins` name what the client answers; `accessConfig` and `access` pick the access config and profile; `out` writes records to a file instead of sending them. |
37
37
  | `cancel` | client | Stops a run (its crawler is closed); it still ends with `finished`. |
38
+ | `probe` | client | Fetches a page (or renders it, with `browser`) and says where its data lives: the CLI's and MCP's probe, through the same access fields as `run`. |
39
+ | `diff` | client | Compares two runs' JSON Lines files by key: counts, the report a person reads, and the changes up to `limit`. |
40
+ | `export` | client | Writes a run's JSON Lines file in another format (the extension of `out`), with an output recipe's columns. |
38
41
  | `shutdown` | client | Closes every run, answers, then exits. Closing stdin does the same. |
39
42
  | `event` | driver | Every crawl event, as core reports it. |
40
43
  | `record` | driver | Each record, unless the run has `out`. |
41
44
  | `finished` | driver | The run is over: its report, or why it could not run. |
42
- | `callout` | driver | A hook the client serves. The request and its answer are core's [callout contract](../core/README.md): `ok`, `error`, or `pending` with `retryAfterMs`, asked again with the same idempotency key. |
45
+ | `callout` | driver | A hook, captcha solver or access plugin the client serves (`request.kind`: `hook`, `captcha`, `access`). The request and its answer are core's [callout contract](../core/README.md): `ok`, `error`, or `pending` with `retryAfterMs`, asked again with the same idempotency key. |
43
46
 
44
47
  Every answer comes back before the driver stops, even when a client writes its last requests and closes
45
48
  stdin at once. Answers may arrive out of order; match them by `id`.
package/dist/index.esm.js CHANGED
@@ -4,8 +4,15 @@ import { once } from "node:events";
4
4
  // src/protocol/driver-message.contract.ts
5
5
  import { calloutRequestSchema, calloutResponseSchema } from "@opencraw/core";
6
6
  import { z } from "zod";
7
- var PROTOCOL_VERSION = "1.0.0";
7
+ var PROTOCOL_VERSION = "1.1.0";
8
8
  var recipeObject = z.record(z.string(), z.unknown());
9
+ var coreObject = z.record(z.string(), z.unknown());
10
+ var accessFields = {
11
+ accessConfig: z.string().optional(),
12
+ access: z.string().optional(),
13
+ /** Access plugins the client serves: a profile `{ "kind": "plugin", "name": … }` naming one leases through a `callout` to the client. */
14
+ accessPlugins: z.array(z.string()).optional()
15
+ };
9
16
  var recipeSource = {
10
17
  paths: z.array(z.string()).min(1).optional(),
11
18
  recipes: z.array(recipeObject).min(1).optional()
@@ -41,11 +48,58 @@ var runParamsSchema = z.strictObject({
41
48
  parallel: z.number().int().min(1).max(16).optional(),
42
49
  browser: z.strictObject({ headless: z.boolean().optional(), executablePath: z.string().optional(), ignoreHTTPSErrors: z.boolean().optional() }).optional(),
43
50
  /** The hooks the client answers itself: each becomes a `callout` request to the client when a recipe calls it. */
44
- hooks: z.array(z.string()).optional()
51
+ hooks: z.array(z.string()).optional(),
52
+ /** Captcha solvers the client serves, by name: a challenge goes to the client as a `captcha` callout. */
53
+ captchaSolvers: z.array(z.string()).optional(),
54
+ ...accessFields
45
55
  });
46
56
  var runResultSchema = z.strictObject({ runId: z.string() });
47
57
  var cancelParamsSchema = z.strictObject({ runId: z.string() });
48
58
  var cancelResultSchema = z.strictObject({ cancelled: z.boolean() });
59
+ var probeParamsSchema = z.strictObject({
60
+ url: z.string().min(1),
61
+ /** Render the page in Chromium first (for pages built by scripts). */
62
+ browser: z.boolean().optional(),
63
+ browserPath: z.string().optional(),
64
+ insecureTls: z.boolean().optional(),
65
+ userAgent: z.string().optional(),
66
+ ...accessFields
67
+ });
68
+ var probeResultSchema = coreObject;
69
+ var diffParamsSchema = z.strictObject({
70
+ /** The earlier run's JSON Lines file. */
71
+ previous: z.string().min(1),
72
+ current: z.string().min(1),
73
+ /** The fields that identify a record; default: each line's `_key`. */
74
+ key: z.array(z.string()).optional(),
75
+ /** Fields left out of the comparison. */
76
+ ignore: z.array(z.string()).optional(),
77
+ /** Changes returned at most (default 200); the counts cover all of them. */
78
+ limit: z.number().int().min(0).optional()
79
+ });
80
+ var diffResultSchema = z.strictObject({
81
+ added: z.number(),
82
+ removed: z.number(),
83
+ changed: z.number(),
84
+ unchanged: z.number(),
85
+ /** Set when the current run lost most of the previous run's records. */
86
+ shrunk: z.strictObject({ previous: z.number(), current: z.number() }).optional(),
87
+ /** The report a person reads, one line per change. */
88
+ summary: z.array(z.string()),
89
+ /** The changes, up to `limit`: each `added`, `removed` or `changed` (with each field's before and after). */
90
+ changes: z.array(coreObject),
91
+ /** More changes than `limit` were found. */
92
+ truncated: z.boolean()
93
+ });
94
+ var exportParamsSchema = z.strictObject({
95
+ /** A run's JSON Lines file. */
96
+ records: z.string().min(1),
97
+ /** The output recipe that gives the columns. */
98
+ output: z.string().min(1),
99
+ /** The file to write; its extension names the format, as for `run`. */
100
+ out: z.string().min(1)
101
+ });
102
+ var exportResultSchema = z.strictObject({ written: z.number(), location: z.string() });
49
103
  var shutdownResultSchema = z.strictObject({});
50
104
  var eventParamsSchema = z.strictObject({ runId: z.string(), event: z.record(z.string(), z.unknown()) });
51
105
  var recordParamsSchema = z.strictObject({ runId: z.string(), recipeId: z.string(), key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
@@ -63,6 +117,9 @@ var MESSAGES = {
63
117
  validate: { direction: "client-to-driver", params: validateParamsSchema, result: validateResultSchema },
64
118
  run: { direction: "client-to-driver", params: runParamsSchema, result: runResultSchema },
65
119
  cancel: { direction: "client-to-driver", params: cancelParamsSchema, result: cancelResultSchema },
120
+ probe: { direction: "client-to-driver", params: probeParamsSchema, result: probeResultSchema },
121
+ diff: { direction: "client-to-driver", params: diffParamsSchema, result: diffResultSchema },
122
+ export: { direction: "client-to-driver", params: exportParamsSchema, result: exportResultSchema },
66
123
  shutdown: { direction: "client-to-driver", params: void 0, result: shutdownResultSchema },
67
124
  callout: { direction: "driver-to-client", params: calloutParamsSchema, result: calloutResultSchema },
68
125
  event: { direction: "driver-to-client", params: eventParamsSchema, result: void 0 },
@@ -99,19 +156,67 @@ function driverProtocolJsonSchema() {
99
156
  };
100
157
  }
101
158
 
159
+ // src/record-files/diff-record-files.use-case.ts
160
+ import { diffReport } from "@opencraw/cli";
161
+ import { diffRecords, readRecordsFile } from "@opencraw/core";
162
+ var CHANGE_LIMIT = 200;
163
+ async function diffRecordFiles(params) {
164
+ const [previous, current] = await Promise.all([readRecordsFile(params.previous), readRecordsFile(params.current)]);
165
+ const diff = diffRecords(previous, current, { key: params.key, ignore: params.ignore });
166
+ const limit = params.limit ?? CHANGE_LIMIT;
167
+ return {
168
+ added: diff.added,
169
+ removed: diff.removed,
170
+ changed: diff.changed,
171
+ unchanged: diff.unchanged,
172
+ ...diff.shrunk !== void 0 && { shrunk: diff.shrunk },
173
+ summary: diffReport(diff),
174
+ changes: diff.changes.slice(0, limit).map((change) => ({ ...change })),
175
+ truncated: diff.changes.length > limit
176
+ };
177
+ }
178
+
179
+ // src/record-files/export-record-files.use-case.ts
180
+ import { readFile } from "node:fs/promises";
181
+ import { fileSink, outputRecordOf, parseOutputRecipe, readRecordsFile as readRecordsFile2 } from "@opencraw/core";
182
+ async function exportRecordFiles(params) {
183
+ const output = parseOutputRecipe(JSON.parse(await readFile(params.output, "utf8")), params.output);
184
+ const stored = await readRecordsFile2(params.records);
185
+ const sink = fileSink(params.out, { browser: { executablePath: process.env.OPENCRAW_CHROMIUM } });
186
+ await sink.open(output);
187
+ for (const line of stored) await sink.write(outputRecordOf(line));
188
+ const summary = await sink.close();
189
+ return { written: summary.written, location: summary.location ?? params.out };
190
+ }
191
+
102
192
  // src/session/run-session.use-case.ts
103
- import { createCrawler, fileSink, RecipeSet } from "@opencraw/core";
193
+ import { createCrawler, fileSink as fileSink2, RecipeSet } from "@opencraw/core";
104
194
 
105
195
  // src/callout-bridge/client-hooks.client.ts
106
- import { calloutHook, calloutResponseSchema as calloutResponseSchema2 } from "@opencraw/core";
107
- function clientTransport(hookName, runId, ask) {
196
+ import { accessPluginVia, calloutHook, calloutResponseSchema as calloutResponseSchema2, captchaSolverVia } from "@opencraw/core";
197
+ function clientTransport(label, runId, ask) {
108
198
  return {
109
- label: `client hook ${hookName}`,
199
+ label,
110
200
  call: async (request) => calloutResponseSchema2.parse(await ask("callout", { runId, request }))
111
201
  };
112
202
  }
113
203
  function clientHooks(names, runId, ask) {
114
- return Object.fromEntries(names.map((name) => [name, calloutHook(name, clientTransport(name, runId, ask))]));
204
+ return Object.fromEntries(names.map((name) => [name, calloutHook(name, clientTransport(`client hook ${name}`, runId, ask))]));
205
+ }
206
+ function clientCaptchaSolvers(names, runId, ask) {
207
+ return names.map((name) => captchaSolverVia(name, clientTransport(`client captcha solver ${name}`, runId, ask)));
208
+ }
209
+ function clientAccessPlugins(names, runId, ask) {
210
+ return names.map((name) => accessPluginVia(name, clientTransport(`client access plugin ${name}`, runId, ask)));
211
+ }
212
+
213
+ // src/site-access/resolve-site-access.use-case.ts
214
+ import { resolveAccess } from "@opencraw/cli";
215
+ async function resolveSiteAccess(params, runId, ask, env = process.env) {
216
+ const file = params.accessConfig ?? env.OPENCRAW_ACCESS;
217
+ if (file === void 0 && params.access !== void 0) throw new Error(`access profile "${params.access}" needs an access config: give "accessConfig", or set OPENCRAW_ACCESS where the driver runs`);
218
+ const access = await resolveAccess({ insecureTls: false, access: file, accessProfile: params.access });
219
+ return { access, accessPlugins: clientAccessPlugins(params.accessPlugins ?? [], runId, ask) };
115
220
  }
116
221
 
117
222
  // src/session/recipe-loading.use-case.ts
@@ -171,9 +276,14 @@ function runSessions(notify, ask) {
171
276
  const loaded = await loadRecipes(params);
172
277
  const set = params.dryRun === true ? new RecipeSet(loaded.output, loaded.inputs.map((input) => ({ ...input, limits: { ...input.limits, maxRecords: 1 } }))) : loaded;
173
278
  const runId = `run-${next++}`;
279
+ const { access, accessPlugins } = await resolveSiteAccess(params, runId, ask);
174
280
  const crawler = createCrawler({
175
- sink: params.out === void 0 ? streamingSink() : fileSink(params.out, { append: params.append, browser: { executablePath: params.browser?.executablePath } }),
281
+ sink: params.out === void 0 ? streamingSink() : fileSink2(params.out, { append: params.append, browser: { executablePath: params.browser?.executablePath } }),
176
282
  hooks: clientHooks(params.hooks ?? [], runId, ask),
283
+ captchaSolvers: clientCaptchaSolvers(params.captchaSolvers ?? [], runId, ask),
284
+ access,
285
+ accessPlugins,
286
+ profilesDir: process.env.OPENCRAW_PROFILES,
177
287
  parallel: params.parallel,
178
288
  resume: params.resume,
179
289
  debug: params.dryRun,
@@ -204,6 +314,21 @@ function runSessions(notify, ask) {
204
314
  };
205
315
  }
206
316
 
317
+ // src/site-probe/probe-site.use-case.ts
318
+ import { probeUrl } from "@opencraw/cli";
319
+ async function probeSite(params, probeId, ask) {
320
+ const { access, accessPlugins } = await resolveSiteAccess(params, probeId, ask);
321
+ const result = await probeUrl(params.url, {
322
+ browser: params.browser ?? false,
323
+ browserPath: params.browserPath ?? process.env.OPENCRAW_CHROMIUM,
324
+ insecureTls: params.insecureTls ?? false,
325
+ userAgent: params.userAgent,
326
+ accessConfig: access,
327
+ accessPlugins
328
+ });
329
+ return { ...result };
330
+ }
331
+
207
332
  // src/transport/line-channel.client.ts
208
333
  import { createInterface } from "node:readline";
209
334
 
@@ -310,7 +435,7 @@ function openLineChannel(input, output, handle) {
310
435
  }
311
436
 
312
437
  // src/server/serve-driver.use-case.ts
313
- var METHODS = ["initialize", "validate", "run", "cancel", "shutdown"];
438
+ var METHODS = ["initialize", "validate", "run", "cancel", "probe", "diff", "export", "shutdown"];
314
439
  var major = (version) => version.split(".", 1)[0];
315
440
  function parsed(schema, params) {
316
441
  const result = schema.safeParse(params ?? {});
@@ -320,6 +445,7 @@ function parsed(schema, params) {
320
445
  async function serveDriver(input, output, identity) {
321
446
  let initialized = false;
322
447
  const stopping = new AbortController();
448
+ let probes = 0;
323
449
  const channel = openLineChannel(input, output, async (method, params) => {
324
450
  if (method === "initialize") {
325
451
  const hello = parsed(initializeParamsSchema, params);
@@ -333,6 +459,9 @@ async function serveDriver(input, output, identity) {
333
459
  if (method === "validate") return await validateRecipes(parsed(validateParamsSchema, params));
334
460
  if (method === "run") return { runId: await sessions.start(parsed(runParamsSchema, params)) };
335
461
  if (method === "cancel") return { cancelled: await sessions.cancel(parsed(cancelParamsSchema, params).runId) };
462
+ if (method === "probe") return await probeSite(parsed(probeParamsSchema, params), `probe-${++probes}`, channel.request);
463
+ if (method === "diff") return await diffRecordFiles(parsed(diffParamsSchema, params));
464
+ if (method === "export") return await exportRecordFiles(parsed(exportParamsSchema, params));
336
465
  await sessions.closeAll();
337
466
  setImmediate(() => {
338
467
  stopping.abort();
@@ -353,13 +482,24 @@ export {
353
482
  calloutResultSchema,
354
483
  cancelParamsSchema,
355
484
  cancelResultSchema,
485
+ clientAccessPlugins,
486
+ clientCaptchaSolvers,
356
487
  clientHooks,
488
+ diffParamsSchema,
489
+ diffRecordFiles,
490
+ diffResultSchema,
357
491
  driverProtocolJsonSchema,
358
492
  eventParamsSchema,
493
+ exportParamsSchema,
494
+ exportRecordFiles,
495
+ exportResultSchema,
359
496
  finishedParamsSchema,
360
497
  initializeParamsSchema,
361
498
  initializeResultSchema,
362
499
  openLineChannel,
500
+ probeParamsSchema,
501
+ probeResultSchema,
502
+ probeSite,
363
503
  recordParamsSchema,
364
504
  runParamsSchema,
365
505
  runResultSchema,
package/dist/main.esm.js CHANGED
@@ -11,8 +11,15 @@ import { once } from "node:events";
11
11
  // src/protocol/driver-message.contract.ts
12
12
  import { calloutRequestSchema, calloutResponseSchema } from "@opencraw/core";
13
13
  import { z } from "zod";
14
- var PROTOCOL_VERSION = "1.0.0";
14
+ var PROTOCOL_VERSION = "1.1.0";
15
15
  var recipeObject = z.record(z.string(), z.unknown());
16
+ var coreObject = z.record(z.string(), z.unknown());
17
+ var accessFields = {
18
+ accessConfig: z.string().optional(),
19
+ access: z.string().optional(),
20
+ /** Access plugins the client serves: a profile `{ "kind": "plugin", "name": … }` naming one leases through a `callout` to the client. */
21
+ accessPlugins: z.array(z.string()).optional()
22
+ };
16
23
  var recipeSource = {
17
24
  paths: z.array(z.string()).min(1).optional(),
18
25
  recipes: z.array(recipeObject).min(1).optional()
@@ -48,11 +55,57 @@ var runParamsSchema = z.strictObject({
48
55
  parallel: z.number().int().min(1).max(16).optional(),
49
56
  browser: z.strictObject({ headless: z.boolean().optional(), executablePath: z.string().optional(), ignoreHTTPSErrors: z.boolean().optional() }).optional(),
50
57
  /** The hooks the client answers itself: each becomes a `callout` request to the client when a recipe calls it. */
51
- hooks: z.array(z.string()).optional()
58
+ hooks: z.array(z.string()).optional(),
59
+ /** Captcha solvers the client serves, by name: a challenge goes to the client as a `captcha` callout. */
60
+ captchaSolvers: z.array(z.string()).optional(),
61
+ ...accessFields
52
62
  });
53
63
  var runResultSchema = z.strictObject({ runId: z.string() });
54
64
  var cancelParamsSchema = z.strictObject({ runId: z.string() });
55
65
  var cancelResultSchema = z.strictObject({ cancelled: z.boolean() });
66
+ var probeParamsSchema = z.strictObject({
67
+ url: z.string().min(1),
68
+ /** Render the page in Chromium first (for pages built by scripts). */
69
+ browser: z.boolean().optional(),
70
+ browserPath: z.string().optional(),
71
+ insecureTls: z.boolean().optional(),
72
+ userAgent: z.string().optional(),
73
+ ...accessFields
74
+ });
75
+ var diffParamsSchema = z.strictObject({
76
+ /** The earlier run's JSON Lines file. */
77
+ previous: z.string().min(1),
78
+ current: z.string().min(1),
79
+ /** The fields that identify a record; default: each line's `_key`. */
80
+ key: z.array(z.string()).optional(),
81
+ /** Fields left out of the comparison. */
82
+ ignore: z.array(z.string()).optional(),
83
+ /** Changes returned at most (default 200); the counts cover all of them. */
84
+ limit: z.number().int().min(0).optional()
85
+ });
86
+ var diffResultSchema = z.strictObject({
87
+ added: z.number(),
88
+ removed: z.number(),
89
+ changed: z.number(),
90
+ unchanged: z.number(),
91
+ /** Set when the current run lost most of the previous run's records. */
92
+ shrunk: z.strictObject({ previous: z.number(), current: z.number() }).optional(),
93
+ /** The report a person reads, one line per change. */
94
+ summary: z.array(z.string()),
95
+ /** The changes, up to `limit`: each `added`, `removed` or `changed` (with each field's before and after). */
96
+ changes: z.array(coreObject),
97
+ /** More changes than `limit` were found. */
98
+ truncated: z.boolean()
99
+ });
100
+ var exportParamsSchema = z.strictObject({
101
+ /** A run's JSON Lines file. */
102
+ records: z.string().min(1),
103
+ /** The output recipe that gives the columns. */
104
+ output: z.string().min(1),
105
+ /** The file to write; its extension names the format, as for `run`. */
106
+ out: z.string().min(1)
107
+ });
108
+ var exportResultSchema = z.strictObject({ written: z.number(), location: z.string() });
56
109
  var shutdownResultSchema = z.strictObject({});
57
110
  var eventParamsSchema = z.strictObject({ runId: z.string(), event: z.record(z.string(), z.unknown()) });
58
111
  var recordParamsSchema = z.strictObject({ runId: z.string(), recipeId: z.string(), key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
@@ -68,19 +121,67 @@ var calloutParamsSchema = z.strictObject({ runId: z.string(), request: calloutRe
68
121
  // src/protocol/driver-json-schema.mapper.ts
69
122
  import { z as z2 } from "zod";
70
123
 
124
+ // src/record-files/diff-record-files.use-case.ts
125
+ import { diffReport } from "@opencraw/cli";
126
+ import { diffRecords, readRecordsFile } from "@opencraw/core";
127
+ var CHANGE_LIMIT = 200;
128
+ async function diffRecordFiles(params) {
129
+ const [previous, current] = await Promise.all([readRecordsFile(params.previous), readRecordsFile(params.current)]);
130
+ const diff = diffRecords(previous, current, { key: params.key, ignore: params.ignore });
131
+ const limit = params.limit ?? CHANGE_LIMIT;
132
+ return {
133
+ added: diff.added,
134
+ removed: diff.removed,
135
+ changed: diff.changed,
136
+ unchanged: diff.unchanged,
137
+ ...diff.shrunk !== void 0 && { shrunk: diff.shrunk },
138
+ summary: diffReport(diff),
139
+ changes: diff.changes.slice(0, limit).map((change) => ({ ...change })),
140
+ truncated: diff.changes.length > limit
141
+ };
142
+ }
143
+
144
+ // src/record-files/export-record-files.use-case.ts
145
+ import { readFile } from "node:fs/promises";
146
+ import { fileSink, outputRecordOf, parseOutputRecipe, readRecordsFile as readRecordsFile2 } from "@opencraw/core";
147
+ async function exportRecordFiles(params) {
148
+ const output = parseOutputRecipe(JSON.parse(await readFile(params.output, "utf8")), params.output);
149
+ const stored = await readRecordsFile2(params.records);
150
+ const sink = fileSink(params.out, { browser: { executablePath: process.env.OPENCRAW_CHROMIUM } });
151
+ await sink.open(output);
152
+ for (const line of stored) await sink.write(outputRecordOf(line));
153
+ const summary = await sink.close();
154
+ return { written: summary.written, location: summary.location ?? params.out };
155
+ }
156
+
71
157
  // src/session/run-session.use-case.ts
72
- import { createCrawler, fileSink, RecipeSet } from "@opencraw/core";
158
+ import { createCrawler, fileSink as fileSink2, RecipeSet } from "@opencraw/core";
73
159
 
74
160
  // src/callout-bridge/client-hooks.client.ts
75
- import { calloutHook, calloutResponseSchema as calloutResponseSchema2 } from "@opencraw/core";
76
- function clientTransport(hookName, runId, ask) {
161
+ import { accessPluginVia, calloutHook, calloutResponseSchema as calloutResponseSchema2, captchaSolverVia } from "@opencraw/core";
162
+ function clientTransport(label, runId, ask) {
77
163
  return {
78
- label: `client hook ${hookName}`,
164
+ label,
79
165
  call: async (request) => calloutResponseSchema2.parse(await ask("callout", { runId, request }))
80
166
  };
81
167
  }
82
168
  function clientHooks(names, runId, ask) {
83
- return Object.fromEntries(names.map((name) => [name, calloutHook(name, clientTransport(name, runId, ask))]));
169
+ return Object.fromEntries(names.map((name) => [name, calloutHook(name, clientTransport(`client hook ${name}`, runId, ask))]));
170
+ }
171
+ function clientCaptchaSolvers(names, runId, ask) {
172
+ return names.map((name) => captchaSolverVia(name, clientTransport(`client captcha solver ${name}`, runId, ask)));
173
+ }
174
+ function clientAccessPlugins(names, runId, ask) {
175
+ return names.map((name) => accessPluginVia(name, clientTransport(`client access plugin ${name}`, runId, ask)));
176
+ }
177
+
178
+ // src/site-access/resolve-site-access.use-case.ts
179
+ import { resolveAccess } from "@opencraw/cli";
180
+ async function resolveSiteAccess(params, runId, ask, env = process.env) {
181
+ const file = params.accessConfig ?? env.OPENCRAW_ACCESS;
182
+ if (file === void 0 && params.access !== void 0) throw new Error(`access profile "${params.access}" needs an access config: give "accessConfig", or set OPENCRAW_ACCESS where the driver runs`);
183
+ const access = await resolveAccess({ insecureTls: false, access: file, accessProfile: params.access });
184
+ return { access, accessPlugins: clientAccessPlugins(params.accessPlugins ?? [], runId, ask) };
84
185
  }
85
186
 
86
187
  // src/session/recipe-loading.use-case.ts
@@ -140,9 +241,14 @@ function runSessions(notify, ask) {
140
241
  const loaded = await loadRecipes(params);
141
242
  const set = params.dryRun === true ? new RecipeSet(loaded.output, loaded.inputs.map((input) => ({ ...input, limits: { ...input.limits, maxRecords: 1 } }))) : loaded;
142
243
  const runId = `run-${next++}`;
244
+ const { access, accessPlugins } = await resolveSiteAccess(params, runId, ask);
143
245
  const crawler = createCrawler({
144
- sink: params.out === void 0 ? streamingSink() : fileSink(params.out, { append: params.append, browser: { executablePath: params.browser?.executablePath } }),
246
+ sink: params.out === void 0 ? streamingSink() : fileSink2(params.out, { append: params.append, browser: { executablePath: params.browser?.executablePath } }),
145
247
  hooks: clientHooks(params.hooks ?? [], runId, ask),
248
+ captchaSolvers: clientCaptchaSolvers(params.captchaSolvers ?? [], runId, ask),
249
+ access,
250
+ accessPlugins,
251
+ profilesDir: process.env.OPENCRAW_PROFILES,
146
252
  parallel: params.parallel,
147
253
  resume: params.resume,
148
254
  debug: params.dryRun,
@@ -173,6 +279,21 @@ function runSessions(notify, ask) {
173
279
  };
174
280
  }
175
281
 
282
+ // src/site-probe/probe-site.use-case.ts
283
+ import { probeUrl } from "@opencraw/cli";
284
+ async function probeSite(params, probeId, ask) {
285
+ const { access, accessPlugins } = await resolveSiteAccess(params, probeId, ask);
286
+ const result = await probeUrl(params.url, {
287
+ browser: params.browser ?? false,
288
+ browserPath: params.browserPath ?? process.env.OPENCRAW_CHROMIUM,
289
+ insecureTls: params.insecureTls ?? false,
290
+ userAgent: params.userAgent,
291
+ accessConfig: access,
292
+ accessPlugins
293
+ });
294
+ return { ...result };
295
+ }
296
+
176
297
  // src/transport/line-channel.client.ts
177
298
  import { createInterface } from "node:readline";
178
299
 
@@ -279,7 +400,7 @@ function openLineChannel(input, output, handle) {
279
400
  }
280
401
 
281
402
  // src/server/serve-driver.use-case.ts
282
- var METHODS = ["initialize", "validate", "run", "cancel", "shutdown"];
403
+ var METHODS = ["initialize", "validate", "run", "cancel", "probe", "diff", "export", "shutdown"];
283
404
  var major = (version) => version.split(".", 1)[0];
284
405
  function parsed(schema, params) {
285
406
  const result = schema.safeParse(params ?? {});
@@ -289,6 +410,7 @@ function parsed(schema, params) {
289
410
  async function serveDriver(input, output, identity) {
290
411
  let initialized = false;
291
412
  const stopping = new AbortController();
413
+ let probes = 0;
292
414
  const channel = openLineChannel(input, output, async (method, params) => {
293
415
  if (method === "initialize") {
294
416
  const hello = parsed(initializeParamsSchema, params);
@@ -302,6 +424,9 @@ async function serveDriver(input, output, identity) {
302
424
  if (method === "validate") return await validateRecipes(parsed(validateParamsSchema, params));
303
425
  if (method === "run") return { runId: await sessions.start(parsed(runParamsSchema, params)) };
304
426
  if (method === "cancel") return { cancelled: await sessions.cancel(parsed(cancelParamsSchema, params).runId) };
427
+ if (method === "probe") return await probeSite(parsed(probeParamsSchema, params), `probe-${++probes}`, channel.request);
428
+ if (method === "diff") return await diffRecordFiles(parsed(diffParamsSchema, params));
429
+ if (method === "export") return await exportRecordFiles(parsed(exportParamsSchema, params));
305
430
  await sessions.closeAll();
306
431
  setImmediate(() => {
307
432
  stopping.abort();
@@ -1,12 +1,17 @@
1
- import type { CalloutTransport, HookMap } from '@opencraw/core';
1
+ import type { AccessPlugin, CalloutTransport, CaptchaSolver, HookMap } from '@opencraw/core';
2
2
  /** Sends one request to the client and waits for its answer: the channel's `request`. */
3
3
  export type AskClient = (method: string, params: unknown) => Promise<unknown>;
4
4
  /**
5
5
  * A transport whose handler is the client: each call is a `callout` request carrying core's own
6
6
  * `CalloutRequest`, and the client answers with a `CalloutResponse`, so `pending` and idempotency keys
7
7
  * work exactly as they do for a command or an HTTP hook.
8
+ *
9
+ * @param label - What the logs call it (`client hook tidy`).
10
+ * @param runId - The run it belongs to, sent with every callout.
11
+ * @param ask - The channel's request.
12
+ * @returns The transport.
8
13
  */
9
- export declare function clientTransport(hookName: string, runId: string, ask: AskClient): CalloutTransport;
14
+ export declare function clientTransport(label: string, runId: string, ask: AskClient): CalloutTransport;
10
15
  /**
11
16
  * The hooks a run's client serves, each answered by the client over the channel.
12
17
  *
@@ -16,4 +21,25 @@ export declare function clientTransport(hookName: string, runId: string, ask: As
16
21
  * @returns A hook map for `createCrawler`.
17
22
  */
18
23
  export declare function clientHooks(names: readonly string[], runId: string, ask: AskClient): HookMap;
24
+ /**
25
+ * The captcha solvers a run's client serves: a challenge goes to the client as a `captcha` callout (its
26
+ * input is core's `CaptchaCalloutInput`), and the answer's `token` or `text` is put on the page.
27
+ *
28
+ * @param names - The solvers the client said it serves.
29
+ * @param runId - The run they belong to.
30
+ * @param ask - The channel's request.
31
+ * @returns Solvers for `createCrawler`.
32
+ */
33
+ export declare function clientCaptchaSolvers(names: readonly string[], runId: string, ask: AskClient): CaptchaSolver[];
34
+ /**
35
+ * The access plugins a client serves: a profile `{ "kind": "plugin", "name": … }` leases through an
36
+ * `access` callout to the client (`phase: "lease"`), and releases through another (`phase: "release"`)
37
+ * when the client's lease asked for it.
38
+ *
39
+ * @param names - The plugins the client said it serves.
40
+ * @param runId - The run, or the probe, they belong to.
41
+ * @param ask - The channel's request.
42
+ * @returns Plugins for `createCrawler` or a probe.
43
+ */
44
+ export declare function clientAccessPlugins(names: readonly string[], runId: string, ask: AskClient): AccessPlugin[];
19
45
  //# sourceMappingURL=client-hooks.client.d.ts.map
@@ -1,3 +1,3 @@
1
- export { clientHooks, clientTransport } from './client-hooks.client.js';
1
+ export { clientAccessPlugins, clientCaptchaSolvers, clientHooks, clientTransport } from './client-hooks.client.js';
2
2
  export type { AskClient } from './client-hooks.client.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1,10 +1,12 @@
1
1
  export { serveDriver } from './server/index.js';
2
2
  export type { DriverIdentity } from './server/index.js';
3
- export { PROTOCOL_VERSION, MESSAGES, driverProtocolJsonSchema, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, } from './protocol/index.js';
4
- export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, } from './protocol/index.js';
3
+ export { PROTOCOL_VERSION, MESSAGES, driverProtocolJsonSchema, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, probeParamsSchema, probeResultSchema, diffParamsSchema, diffResultSchema, exportParamsSchema, exportResultSchema, } from './protocol/index.js';
4
+ export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, ProbeParams, DiffParams, DiffResult, ExportParams, ExportResult, } from './protocol/index.js';
5
5
  export { openLineChannel, RPC_ERRORS, RpcFailure } from './transport/index.js';
6
6
  export type { LineChannel, RequestHandler, RpcError, RpcMessage } from './transport/index.js';
7
7
  export { runSessions, validateRecipes, streamingSink } from './session/index.js';
8
8
  export type { RunSessions, NotifyClient } from './session/index.js';
9
- export { clientHooks } from './callout-bridge/index.js';
9
+ export { clientHooks, clientCaptchaSolvers, clientAccessPlugins } from './callout-bridge/index.js';
10
+ export { probeSite } from './site-probe/index.js';
11
+ export { diffRecordFiles, exportRecordFiles } from './record-files/index.js';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  * The driver protocol's version, separate from the package's: a client pins a major and the driver refuses
4
4
  * another. Additions (methods, optional fields) raise the minor; anything a client could break on, the major.
5
5
  */
6
- export declare const PROTOCOL_VERSION = "1.0.0";
6
+ export declare const PROTOCOL_VERSION = "1.1.0";
7
7
  export declare const initializeParamsSchema: z.ZodObject<{
8
8
  clientName: z.ZodString;
9
9
  clientVersion: z.ZodString;
@@ -35,6 +35,9 @@ export declare const validateResultSchema: z.ZodObject<{
35
35
  }, z.core.$strict>;
36
36
  export type ValidateResult = z.infer<typeof validateResultSchema>;
37
37
  export declare const runParamsSchema: z.ZodObject<{
38
+ accessConfig: z.ZodOptional<z.ZodString>;
39
+ access: z.ZodOptional<z.ZodString>;
40
+ accessPlugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
38
41
  paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
39
42
  recipes: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
40
43
  only: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -49,6 +52,7 @@ export declare const runParamsSchema: z.ZodObject<{
49
52
  ignoreHTTPSErrors: z.ZodOptional<z.ZodBoolean>;
50
53
  }, z.core.$strict>>;
51
54
  hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
55
+ captchaSolvers: z.ZodOptional<z.ZodArray<z.ZodString>>;
52
56
  }, z.core.$strict>;
53
57
  export type RunParams = z.infer<typeof runParamsSchema>;
54
58
  export declare const runResultSchema: z.ZodObject<{
@@ -63,6 +67,52 @@ export declare const cancelResultSchema: z.ZodObject<{
63
67
  cancelled: z.ZodBoolean;
64
68
  }, z.core.$strict>;
65
69
  export type CancelResult = z.infer<typeof cancelResultSchema>;
70
+ export declare const probeParamsSchema: z.ZodObject<{
71
+ accessConfig: z.ZodOptional<z.ZodString>;
72
+ access: z.ZodOptional<z.ZodString>;
73
+ accessPlugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
74
+ url: z.ZodString;
75
+ browser: z.ZodOptional<z.ZodBoolean>;
76
+ browserPath: z.ZodOptional<z.ZodString>;
77
+ insecureTls: z.ZodOptional<z.ZodBoolean>;
78
+ userAgent: z.ZodOptional<z.ZodString>;
79
+ }, z.core.$strict>;
80
+ export type ProbeParams = z.infer<typeof probeParamsSchema>;
81
+ /** What the page holds and how a recipe could reach it (`ProbeResult`); its shape is the probe's, versioned with it. */
82
+ export declare const probeResultSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
83
+ export declare const diffParamsSchema: z.ZodObject<{
84
+ previous: z.ZodString;
85
+ current: z.ZodString;
86
+ key: z.ZodOptional<z.ZodArray<z.ZodString>>;
87
+ ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
88
+ limit: z.ZodOptional<z.ZodNumber>;
89
+ }, z.core.$strict>;
90
+ export type DiffParams = z.infer<typeof diffParamsSchema>;
91
+ export declare const diffResultSchema: z.ZodObject<{
92
+ added: z.ZodNumber;
93
+ removed: z.ZodNumber;
94
+ changed: z.ZodNumber;
95
+ unchanged: z.ZodNumber;
96
+ shrunk: z.ZodOptional<z.ZodObject<{
97
+ previous: z.ZodNumber;
98
+ current: z.ZodNumber;
99
+ }, z.core.$strict>>;
100
+ summary: z.ZodArray<z.ZodString>;
101
+ changes: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
102
+ truncated: z.ZodBoolean;
103
+ }, z.core.$strict>;
104
+ export type DiffResult = z.infer<typeof diffResultSchema>;
105
+ export declare const exportParamsSchema: z.ZodObject<{
106
+ records: z.ZodString;
107
+ output: z.ZodString;
108
+ out: z.ZodString;
109
+ }, z.core.$strict>;
110
+ export type ExportParams = z.infer<typeof exportParamsSchema>;
111
+ export declare const exportResultSchema: z.ZodObject<{
112
+ written: z.ZodNumber;
113
+ location: z.ZodString;
114
+ }, z.core.$strict>;
115
+ export type ExportResult = z.infer<typeof exportResultSchema>;
66
116
  export declare const shutdownResultSchema: z.ZodObject<{}, z.core.$strict>;
67
117
  /** Every crawl event, as core reports it (`CrawlEvent`); its shape is core's, versioned with core. */
68
118
  export declare const eventParamsSchema: z.ZodObject<{
@@ -83,7 +133,7 @@ export declare const finishedParamsSchema: z.ZodObject<{
83
133
  error: z.ZodOptional<z.ZodString>;
84
134
  }, z.core.$strict>;
85
135
  export type FinishedParams = z.infer<typeof finishedParamsSchema>;
86
- /** A hook the client serves: core's own callout request, answered with core's callout response. */
136
+ /** A hook, captcha solver or access plugin the client serves (`request.kind`): core's own callout request, answered with core's callout response. */
87
137
  export declare const calloutParamsSchema: z.ZodObject<{
88
138
  runId: z.ZodString;
89
139
  request: z.ZodObject<{
@@ -153,6 +203,9 @@ export declare const MESSAGES: {
153
203
  readonly run: {
154
204
  readonly direction: 'client-to-driver';
155
205
  readonly params: z.ZodObject<{
206
+ accessConfig: z.ZodOptional<z.ZodString>;
207
+ access: z.ZodOptional<z.ZodString>;
208
+ accessPlugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
156
209
  paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
157
210
  recipes: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
158
211
  only: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -167,6 +220,7 @@ export declare const MESSAGES: {
167
220
  ignoreHTTPSErrors: z.ZodOptional<z.ZodBoolean>;
168
221
  }, z.core.$strict>>;
169
222
  hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
223
+ captchaSolvers: z.ZodOptional<z.ZodArray<z.ZodString>>;
170
224
  }, z.core.$strict>;
171
225
  readonly result: z.ZodObject<{
172
226
  runId: z.ZodString;
@@ -181,6 +235,55 @@ export declare const MESSAGES: {
181
235
  cancelled: z.ZodBoolean;
182
236
  }, z.core.$strict>;
183
237
  };
238
+ readonly probe: {
239
+ readonly direction: 'client-to-driver';
240
+ readonly params: z.ZodObject<{
241
+ accessConfig: z.ZodOptional<z.ZodString>;
242
+ access: z.ZodOptional<z.ZodString>;
243
+ accessPlugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
244
+ url: z.ZodString;
245
+ browser: z.ZodOptional<z.ZodBoolean>;
246
+ browserPath: z.ZodOptional<z.ZodString>;
247
+ insecureTls: z.ZodOptional<z.ZodBoolean>;
248
+ userAgent: z.ZodOptional<z.ZodString>;
249
+ }, z.core.$strict>;
250
+ readonly result: z.ZodRecord<z.ZodString, z.ZodUnknown>;
251
+ };
252
+ readonly diff: {
253
+ readonly direction: 'client-to-driver';
254
+ readonly params: z.ZodObject<{
255
+ previous: z.ZodString;
256
+ current: z.ZodString;
257
+ key: z.ZodOptional<z.ZodArray<z.ZodString>>;
258
+ ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
259
+ limit: z.ZodOptional<z.ZodNumber>;
260
+ }, z.core.$strict>;
261
+ readonly result: z.ZodObject<{
262
+ added: z.ZodNumber;
263
+ removed: z.ZodNumber;
264
+ changed: z.ZodNumber;
265
+ unchanged: z.ZodNumber;
266
+ shrunk: z.ZodOptional<z.ZodObject<{
267
+ previous: z.ZodNumber;
268
+ current: z.ZodNumber;
269
+ }, z.core.$strict>>;
270
+ summary: z.ZodArray<z.ZodString>;
271
+ changes: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
272
+ truncated: z.ZodBoolean;
273
+ }, z.core.$strict>;
274
+ };
275
+ readonly export: {
276
+ readonly direction: 'client-to-driver';
277
+ readonly params: z.ZodObject<{
278
+ records: z.ZodString;
279
+ output: z.ZodString;
280
+ out: z.ZodString;
281
+ }, z.core.$strict>;
282
+ readonly result: z.ZodObject<{
283
+ written: z.ZodNumber;
284
+ location: z.ZodString;
285
+ }, z.core.$strict>;
286
+ };
184
287
  readonly shutdown: {
185
288
  readonly direction: 'client-to-driver';
186
289
  readonly params: undefined;
@@ -1,4 +1,4 @@
1
- export { PROTOCOL_VERSION, MESSAGES, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, shutdownResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, } from './driver-message.contract.js';
2
- export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, } from './driver-message.contract.js';
1
+ export { PROTOCOL_VERSION, MESSAGES, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, shutdownResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, probeParamsSchema, probeResultSchema, diffParamsSchema, diffResultSchema, exportParamsSchema, exportResultSchema, } from './driver-message.contract.js';
2
+ export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, ProbeParams, DiffParams, DiffResult, ExportParams, ExportResult, } from './driver-message.contract.js';
3
3
  export { driverProtocolJsonSchema } from './driver-json-schema.mapper.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ import type { DiffParams, DiffResult } from '../protocol/index.js';
2
+ /**
3
+ * Compares two runs' JSON Lines files by key: how many records were added, removed, changed and left
4
+ * alone, the report a person reads, and the changes themselves, up to `limit`.
5
+ *
6
+ * @param params - The two files, the key fields, the fields to ignore and the limit.
7
+ * @returns The comparison.
8
+ */
9
+ export declare function diffRecordFiles(params: DiffParams): Promise<DiffResult>;
10
+ //# sourceMappingURL=diff-record-files.use-case.d.ts.map
@@ -0,0 +1,11 @@
1
+ import type { ExportParams, ExportResult } from '../protocol/index.js';
2
+ /**
3
+ * Writes a finished run's JSON Lines file again in another format, the extension of `out` naming it, as
4
+ * the CLI's `export` does. The output recipe gives the columns: its fields, in order, with their types.
5
+ *
6
+ * @param params - The records, the output recipe and the file to write.
7
+ * @returns How many records were written, and where.
8
+ * @throws RecipeValidationError When `output` is not an output recipe.
9
+ */
10
+ export declare function exportRecordFiles(params: ExportParams): Promise<ExportResult>;
11
+ //# sourceMappingURL=export-record-files.use-case.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { diffRecordFiles } from './diff-record-files.use-case.js';
2
+ export { exportRecordFiles } from './export-record-files.use-case.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -13,7 +13,7 @@ export interface RunSessions {
13
13
  * itself, with its problems), answers with a run id at once, and crawls in the background: every crawl
14
14
  * event goes to the client as an `event` notification, every record as a `record` one unless `out`
15
15
  * names a file, and the end as `finished` with the report. A hook the client serves is asked back with
16
- * a `callout` request. `cancel` closes the run's crawler, which stops its recipes; the run still ends
16
+ * a `callout` request, and so are the captcha solvers and access plugins it serves. `cancel` closes the run's crawler, which stops its recipes; the run still ends
17
17
  * with `finished`.
18
18
  *
19
19
  * @param notify - The channel's notify.
@@ -0,0 +1,3 @@
1
+ export { resolveSiteAccess } from './resolve-site-access.use-case.js';
2
+ export type { SiteAccess, SiteAccessParams } from './resolve-site-access.use-case.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,26 @@
1
+ import type { AccessConfig, AccessPlugin } from '@opencraw/core';
2
+ import type { AskClient } from '../callout-bridge/index.js';
3
+ /** What a run or a probe asks of access: the config file, the profile, and the plugins the client serves. */
4
+ export interface SiteAccessParams {
5
+ accessConfig?: string;
6
+ access?: string;
7
+ accessPlugins?: string[];
8
+ }
9
+ /** How a run or a probe reaches sites. */
10
+ export interface SiteAccess {
11
+ access?: AccessConfig;
12
+ accessPlugins: AccessPlugin[];
13
+ }
14
+ /**
15
+ * Loads the access config (the file the client names, else `OPENCRAW_ACCESS`), with `access` as its
16
+ * default profile, and builds a plugin for each one the client serves.
17
+ *
18
+ * @param params - The method's access fields.
19
+ * @param runId - The run, or the probe, the plugins' callouts belong to.
20
+ * @param ask - The channel's request.
21
+ * @param env - Where `OPENCRAW_ACCESS` is read.
22
+ * @returns The config and the plugins.
23
+ * @throws Error When a profile is named without a config, or the config has no such profile.
24
+ */
25
+ export declare function resolveSiteAccess(params: SiteAccessParams, runId: string, ask: AskClient, env?: NodeJS.ProcessEnv): Promise<SiteAccess>;
26
+ //# sourceMappingURL=resolve-site-access.use-case.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { probeSite } from './probe-site.use-case.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,13 @@
1
+ import type { AskClient } from '../callout-bridge/index.js';
2
+ import type { ProbeParams } from '../protocol/index.js';
3
+ /**
4
+ * Fetches a page, or renders it with `browser`, and says where its data lives: the probe the CLI and the
5
+ * MCP server use, through the access the client names (its plugins served by the client).
6
+ *
7
+ * @param params - The page and how to reach it.
8
+ * @param probeId - What the plugins' callouts carry as their `runId`.
9
+ * @param ask - The channel's request.
10
+ * @returns The probe's findings.
11
+ */
12
+ export declare function probeSite(params: ProbeParams, probeId: string, ask: AskClient): Promise<Record<string, unknown>>;
13
+ //# sourceMappingURL=probe-site.use-case.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencraw/driver",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "OpenCraw's driver: the engine over JSON-RPC on stdio, for clients in other languages",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,8 +40,8 @@
40
40
  "schemas": "node tools/emit-protocol-schema.mjs"
41
41
  },
42
42
  "dependencies": {
43
- "@opencraw/cli": "^0.1.55",
44
- "@opencraw/core": "^0.2.0",
43
+ "@opencraw/cli": "^0.1.56",
44
+ "@opencraw/core": "^0.3.0",
45
45
  "zod": "^4.6.5"
46
46
  }
47
47
  }
@@ -3,7 +3,7 @@
3
3
  "$id": "https://opencraw/schemas/driver-protocol.schema.json",
4
4
  "title": "OpenCraw driver protocol",
5
5
  "description": "JSON-RPC 2.0 over stdio, one message per line. Methods with a result are requests; the rest are notifications.",
6
- "protocolVersion": "1.0.0",
6
+ "protocolVersion": "1.1.0",
7
7
  "methods": {
8
8
  "initialize": {
9
9
  "direction": "client-to-driver",
@@ -41,6 +41,33 @@
41
41
  "$ref": "#/$defs/cancel.result"
42
42
  }
43
43
  },
44
+ "probe": {
45
+ "direction": "client-to-driver",
46
+ "params": {
47
+ "$ref": "#/$defs/probe.params"
48
+ },
49
+ "result": {
50
+ "$ref": "#/$defs/probe.result"
51
+ }
52
+ },
53
+ "diff": {
54
+ "direction": "client-to-driver",
55
+ "params": {
56
+ "$ref": "#/$defs/diff.params"
57
+ },
58
+ "result": {
59
+ "$ref": "#/$defs/diff.result"
60
+ }
61
+ },
62
+ "export": {
63
+ "direction": "client-to-driver",
64
+ "params": {
65
+ "$ref": "#/$defs/export.params"
66
+ },
67
+ "result": {
68
+ "$ref": "#/$defs/export.result"
69
+ }
70
+ },
44
71
  "shutdown": {
45
72
  "direction": "client-to-driver",
46
73
  "result": {
@@ -273,6 +300,24 @@
273
300
  "items": {
274
301
  "type": "string"
275
302
  }
303
+ },
304
+ "captchaSolvers": {
305
+ "type": "array",
306
+ "items": {
307
+ "type": "string"
308
+ }
309
+ },
310
+ "accessConfig": {
311
+ "type": "string"
312
+ },
313
+ "access": {
314
+ "type": "string"
315
+ },
316
+ "accessPlugins": {
317
+ "type": "array",
318
+ "items": {
319
+ "type": "string"
320
+ }
276
321
  }
277
322
  },
278
323
  "additionalProperties": false
@@ -316,6 +361,192 @@
316
361
  ],
317
362
  "additionalProperties": false
318
363
  },
364
+ "probe.params": {
365
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
366
+ "type": "object",
367
+ "properties": {
368
+ "url": {
369
+ "type": "string",
370
+ "minLength": 1
371
+ },
372
+ "browser": {
373
+ "type": "boolean"
374
+ },
375
+ "browserPath": {
376
+ "type": "string"
377
+ },
378
+ "insecureTls": {
379
+ "type": "boolean"
380
+ },
381
+ "userAgent": {
382
+ "type": "string"
383
+ },
384
+ "accessConfig": {
385
+ "type": "string"
386
+ },
387
+ "access": {
388
+ "type": "string"
389
+ },
390
+ "accessPlugins": {
391
+ "type": "array",
392
+ "items": {
393
+ "type": "string"
394
+ }
395
+ }
396
+ },
397
+ "required": [
398
+ "url"
399
+ ],
400
+ "additionalProperties": false
401
+ },
402
+ "probe.result": {
403
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
404
+ "type": "object",
405
+ "propertyNames": {
406
+ "type": "string"
407
+ },
408
+ "additionalProperties": {}
409
+ },
410
+ "diff.params": {
411
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
412
+ "type": "object",
413
+ "properties": {
414
+ "previous": {
415
+ "type": "string",
416
+ "minLength": 1
417
+ },
418
+ "current": {
419
+ "type": "string",
420
+ "minLength": 1
421
+ },
422
+ "key": {
423
+ "type": "array",
424
+ "items": {
425
+ "type": "string"
426
+ }
427
+ },
428
+ "ignore": {
429
+ "type": "array",
430
+ "items": {
431
+ "type": "string"
432
+ }
433
+ },
434
+ "limit": {
435
+ "type": "integer",
436
+ "minimum": 0,
437
+ "maximum": 9007199254740991
438
+ }
439
+ },
440
+ "required": [
441
+ "previous",
442
+ "current"
443
+ ],
444
+ "additionalProperties": false
445
+ },
446
+ "diff.result": {
447
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
448
+ "type": "object",
449
+ "properties": {
450
+ "added": {
451
+ "type": "number"
452
+ },
453
+ "removed": {
454
+ "type": "number"
455
+ },
456
+ "changed": {
457
+ "type": "number"
458
+ },
459
+ "unchanged": {
460
+ "type": "number"
461
+ },
462
+ "shrunk": {
463
+ "type": "object",
464
+ "properties": {
465
+ "previous": {
466
+ "type": "number"
467
+ },
468
+ "current": {
469
+ "type": "number"
470
+ }
471
+ },
472
+ "required": [
473
+ "previous",
474
+ "current"
475
+ ],
476
+ "additionalProperties": false
477
+ },
478
+ "summary": {
479
+ "type": "array",
480
+ "items": {
481
+ "type": "string"
482
+ }
483
+ },
484
+ "changes": {
485
+ "type": "array",
486
+ "items": {
487
+ "type": "object",
488
+ "propertyNames": {
489
+ "type": "string"
490
+ },
491
+ "additionalProperties": {}
492
+ }
493
+ },
494
+ "truncated": {
495
+ "type": "boolean"
496
+ }
497
+ },
498
+ "required": [
499
+ "added",
500
+ "removed",
501
+ "changed",
502
+ "unchanged",
503
+ "summary",
504
+ "changes",
505
+ "truncated"
506
+ ],
507
+ "additionalProperties": false
508
+ },
509
+ "export.params": {
510
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
511
+ "type": "object",
512
+ "properties": {
513
+ "records": {
514
+ "type": "string",
515
+ "minLength": 1
516
+ },
517
+ "output": {
518
+ "type": "string",
519
+ "minLength": 1
520
+ },
521
+ "out": {
522
+ "type": "string",
523
+ "minLength": 1
524
+ }
525
+ },
526
+ "required": [
527
+ "records",
528
+ "output",
529
+ "out"
530
+ ],
531
+ "additionalProperties": false
532
+ },
533
+ "export.result": {
534
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
535
+ "type": "object",
536
+ "properties": {
537
+ "written": {
538
+ "type": "number"
539
+ },
540
+ "location": {
541
+ "type": "string"
542
+ }
543
+ },
544
+ "required": [
545
+ "written",
546
+ "location"
547
+ ],
548
+ "additionalProperties": false
549
+ },
319
550
  "shutdown.result": {
320
551
  "$schema": "https://json-schema.org/draft/2020-12/schema",
321
552
  "type": "object",