polite-crawler 0.1.3 → 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 +50 -121
- package/build/{browser-CKRNbIDT.mjs → browser-56dBBLec.mjs} +7 -3
- package/build/browser-56dBBLec.mjs.map +1 -0
- package/build/classify.d.mts +50 -2
- package/build/{cli-daemon--cTmq0Z5.mjs → cli-daemon-qS4PH2bM.mjs} +3 -3
- package/build/{cli-daemon--cTmq0Z5.mjs.map → cli-daemon-qS4PH2bM.mjs.map} +1 -1
- package/build/cli-recipes-qbLRVThF.mjs +59 -0
- package/build/cli-recipes-qbLRVThF.mjs.map +1 -0
- package/build/cli.mjs +431 -27
- package/build/cli.mjs.map +1 -1
- package/build/{daemon-fZTE24P5.mjs → daemon-BXBbUrni.mjs} +11 -7
- package/build/daemon-BXBbUrni.mjs.map +1 -0
- package/build/{diff-NQcQqKe9.mjs → diff-CpKN0Jdc.mjs} +7 -3
- package/build/{diff-NQcQqKe9.mjs.map → diff-CpKN0Jdc.mjs.map} +1 -1
- package/build/{extract-ByFfc8Zk.mjs → extract-DqeqiNHb.mjs} +12 -4
- package/build/extract-DqeqiNHb.mjs.map +1 -0
- package/build/{html-BACljlkf.d.mts → html-DMgIeQfJ.d.mts} +16 -2
- package/build/html.d.mts +1 -2
- package/build/index.d.mts +4227 -40
- package/build/index.mjs +17 -901
- package/build/{log-Dx-wXjJl.mjs → log-CmBWcyQm.mjs} +5 -1
- package/build/log-CmBWcyQm.mjs.map +1 -0
- package/build/mcp-package-Y0bREnOS.mjs +40 -0
- package/build/mcp-package-Y0bREnOS.mjs.map +1 -0
- package/build/{ollama-BJxkjny4.mjs → ollama-D1xRh3hY.mjs} +7 -3
- package/build/ollama-D1xRh3hY.mjs.map +1 -0
- package/build/{open-C4fhXJgD.mjs → open-CEo22Hpu.mjs} +84 -296
- package/build/open-CEo22Hpu.mjs.map +1 -0
- package/build/politeness.d.mts +366 -2
- package/build/recipe-B2SkepDZ.mjs +285 -0
- package/build/recipe-B2SkepDZ.mjs.map +1 -0
- package/build/recipe-product-CyZE3Ukd.mjs +914 -0
- package/build/recipe-product-CyZE3Ukd.mjs.map +1 -0
- package/build/{render-C1CUraWF.mjs → render-CKE2LILH.mjs} +10 -3
- package/build/render-CKE2LILH.mjs.map +1 -0
- package/build/robots.d.mts +143 -2
- package/build/robots.mjs +1 -1
- package/build/robots.mjs.map +1 -1
- package/build/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-8H4AJuhK.mjs} +1 -0
- package/build/{runtime-JToRO63o.mjs → runtime-C_2BQibH.mjs} +84 -18
- package/build/runtime-C_2BQibH.mjs.map +1 -0
- package/build/signals.d.mts +45 -2
- package/build/{transformers-BQMlaGj4.mjs → transformers-BNiuiLxT.mjs} +3 -3
- package/build/transformers-BNiuiLxT.mjs.map +1 -0
- package/build/{version-ntbY-6wh.mjs → version-Djva7JkN.mjs} +7 -2
- package/build/version-Djva7JkN.mjs.map +1 -0
- package/build/{wacz-BFNG42Kt.mjs → wacz-DKk2HBmx.mjs} +4 -4
- package/build/{wacz-BFNG42Kt.mjs.map → wacz-DKk2HBmx.mjs.map} +1 -1
- package/build/{warc-CVS0t1fW.mjs → warc-C_ew76bV.mjs} +4 -4
- package/build/{warc-CVS0t1fW.mjs.map → warc-C_ew76bV.mjs.map} +1 -1
- package/build/{webbotauth-CjJdTAfE.mjs → webbotauth-Dq_dA_u3.mjs} +2 -2
- package/build/webbotauth-Dq_dA_u3.mjs.map +1 -0
- package/package.json +5 -11
- package/build/browser-CKRNbIDT.mjs.map +0 -1
- package/build/classify-CrfyXcwo.d.mts +0 -51
- package/build/daemon-DP4EqjFL.d.mts +0 -3772
- package/build/daemon-fZTE24P5.mjs.map +0 -1
- package/build/extract-ByFfc8Zk.mjs.map +0 -1
- package/build/index.mjs.map +0 -1
- package/build/log-Dx-wXjJl.mjs.map +0 -1
- package/build/loopback-C_QYowSf.mjs +0 -22
- package/build/loopback-C_QYowSf.mjs.map +0 -1
- package/build/mcp.d.mts +0 -199
- package/build/mcp.mjs +0 -1555
- package/build/mcp.mjs.map +0 -1
- package/build/ollama-BJxkjny4.mjs.map +0 -1
- package/build/open-C4fhXJgD.mjs.map +0 -1
- package/build/politeness-yI3xBEx9.d.mts +0 -367
- package/build/render-C1CUraWF.mjs.map +0 -1
- package/build/robots-R-qaEBOb.d.mts +0 -144
- package/build/runtime-JToRO63o.mjs.map +0 -1
- package/build/signals-BHpy2xb2.d.mts +0 -46
- package/build/transformers-BQMlaGj4.mjs.map +0 -1
- package/build/urls-BTDSgDLC.d.mts +0 -16
- package/build/version-ntbY-6wh.mjs.map +0 -1
- package/build/webbotauth-CjJdTAfE.mjs.map +0 -1
package/build/cli.mjs
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as durationMs, r as VERSION } from "./version-
|
|
3
|
-
import { t as isLoopbackHost } from "./loopback-C_QYowSf.mjs";
|
|
2
|
+
import { i as durationMs, r as VERSION } from "./version-Djva7JkN.mjs";
|
|
4
3
|
import { join, resolve } from "node:path";
|
|
5
4
|
import { readFileSync } from "node:fs";
|
|
5
|
+
import { isIP } from "node:net";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
|
+
//#region src/loopback.ts
|
|
9
|
+
/**
|
|
10
|
+
* This function tells if a host name or an address is a loopback host: `localhost`, an address in
|
|
11
|
+
* `127.0.0.0/8`, or `::1`. The MCP server over HTTP binds to a loopback host unless the operator
|
|
12
|
+
* permits a different host. The function imports only Node.js, so the command line uses it before
|
|
13
|
+
* the store opens.
|
|
14
|
+
*/
|
|
15
|
+
const isLoopbackHost = (host) => {
|
|
16
|
+
const name = host.trim().toLowerCase().replace(/^\[(.*)\]$/, "$1");
|
|
17
|
+
if (name === "localhost" || name.endsWith(".localhost")) return true;
|
|
18
|
+
const mapped = name.startsWith("::ffff:") ? name.slice(7) : name;
|
|
19
|
+
if (isIP(mapped) === 4) return mapped.startsWith("127.");
|
|
20
|
+
if (isIP(name) === 6) return /^(?:0{0,4}:){7}0{0,3}1$|^::1$/.test(name);
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
8
24
|
//#region src/cli-line.ts
|
|
9
25
|
/** A usage error is a wrong command line. It exits with code 2. */
|
|
10
26
|
var UsageError = class extends Error {};
|
|
@@ -103,6 +119,18 @@ const OPTIONS = {
|
|
|
103
119
|
"ollama-url": { type: "string" },
|
|
104
120
|
"allow-remote-ollama": { type: "boolean" },
|
|
105
121
|
"max-docs": { type: "string" },
|
|
122
|
+
name: { type: "string" },
|
|
123
|
+
"require-name": { type: "boolean" },
|
|
124
|
+
address: { type: "string" },
|
|
125
|
+
lat: { type: "string" },
|
|
126
|
+
lon: { type: "string" },
|
|
127
|
+
osm: { type: "string" },
|
|
128
|
+
pages: { type: "string" },
|
|
129
|
+
compare: { type: "string" },
|
|
130
|
+
"on-behalf-of": {
|
|
131
|
+
type: "string",
|
|
132
|
+
multiple: true
|
|
133
|
+
},
|
|
106
134
|
...EXPORT_OPTIONS
|
|
107
135
|
};
|
|
108
136
|
/** These options belong to `daemon`, and not to `daemon status`. */
|
|
@@ -120,23 +148,34 @@ const DAEMON_RUN_OPTIONS = [
|
|
|
120
148
|
"mcp-token-file",
|
|
121
149
|
"allow-remote"
|
|
122
150
|
];
|
|
151
|
+
/** These options belong to `venue` only. */
|
|
152
|
+
const VENUE_OPTIONS = [
|
|
153
|
+
"name",
|
|
154
|
+
"require-name",
|
|
155
|
+
"address",
|
|
156
|
+
"lat",
|
|
157
|
+
"lon",
|
|
158
|
+
"osm",
|
|
159
|
+
"pages",
|
|
160
|
+
"compare"
|
|
161
|
+
];
|
|
123
162
|
/** These options belong to one command, or to the commands that the text names. */
|
|
124
163
|
const OWNER = {
|
|
125
|
-
render: "fetch crawl watch",
|
|
126
|
-
"max-age": "fetch crawl discover",
|
|
164
|
+
render: "fetch crawl watch venue product",
|
|
165
|
+
"max-age": "fetch crawl discover venue product",
|
|
127
166
|
limit: "find",
|
|
128
167
|
kind: "find embed",
|
|
129
168
|
rank: "find",
|
|
130
169
|
from: "read diff",
|
|
131
170
|
length: "read",
|
|
132
|
-
"user-provided": "read extract",
|
|
171
|
+
"user-provided": "read extract venue product",
|
|
133
172
|
"allow-user-provided": "mcp daemon",
|
|
134
173
|
...Object.fromEntries(Object.keys(EXPORT_OPTIONS).map((option) => [option, "export"])),
|
|
135
174
|
"max-urls": "discover",
|
|
136
175
|
feed: "discover",
|
|
137
176
|
"max-pages": "crawl watch",
|
|
138
177
|
"max-depth": "crawl",
|
|
139
|
-
budget: "crawl watch embed",
|
|
178
|
+
budget: "crawl watch embed venue product",
|
|
140
179
|
include: "crawl",
|
|
141
180
|
exclude: "crawl",
|
|
142
181
|
sitemaps: "crawl",
|
|
@@ -147,10 +186,10 @@ const OWNER = {
|
|
|
147
186
|
confirm: "watch",
|
|
148
187
|
scope: "watch",
|
|
149
188
|
label: "watch",
|
|
150
|
-
"archive-fallback": "fetch",
|
|
151
|
-
"archive-despite-robots": "fetch",
|
|
152
|
-
"sign-key": "fetch discover crawl watch mcp key-directory daemon",
|
|
153
|
-
"signature-agent": "fetch discover crawl watch mcp key-directory daemon",
|
|
189
|
+
"archive-fallback": "fetch venue product",
|
|
190
|
+
"archive-despite-robots": "fetch venue product",
|
|
191
|
+
"sign-key": "fetch discover crawl watch mcp key-directory daemon venue product",
|
|
192
|
+
"signature-agent": "fetch discover crawl watch mcp key-directory daemon venue product",
|
|
154
193
|
...Object.fromEntries(DAEMON_RUN_OPTIONS.map((option) => [option, "daemon"])),
|
|
155
194
|
field: "extract",
|
|
156
195
|
capture: "extract",
|
|
@@ -158,7 +197,9 @@ const OWNER = {
|
|
|
158
197
|
"embed-model": "find embed mcp",
|
|
159
198
|
"ollama-url": "find embed mcp",
|
|
160
199
|
"allow-remote-ollama": "find embed mcp",
|
|
161
|
-
"max-docs": "embed"
|
|
200
|
+
"max-docs": "embed",
|
|
201
|
+
...Object.fromEntries(VENUE_OPTIONS.map((option) => [option, "venue"])),
|
|
202
|
+
"on-behalf-of": "venue product"
|
|
162
203
|
};
|
|
163
204
|
/** These are the commands, with the smallest and the largest count of arguments. */
|
|
164
205
|
const COMMANDS = {
|
|
@@ -236,6 +277,16 @@ const COMMANDS = {
|
|
|
236
277
|
min: 0,
|
|
237
278
|
max: 0,
|
|
238
279
|
wants: "embed takes no argument: give the model with --embedder"
|
|
280
|
+
},
|
|
281
|
+
venue: {
|
|
282
|
+
min: 1,
|
|
283
|
+
max: 1,
|
|
284
|
+
wants: "venue wants the URL of the site of one venue"
|
|
285
|
+
},
|
|
286
|
+
product: {
|
|
287
|
+
min: 1,
|
|
288
|
+
max: 1,
|
|
289
|
+
wants: "product wants the URL of one product page"
|
|
239
290
|
}
|
|
240
291
|
};
|
|
241
292
|
const parse = (args) => {
|
|
@@ -692,6 +743,311 @@ const extractCode = (result) => {
|
|
|
692
743
|
const done = result.summary.verified + result.summary.normalized;
|
|
693
744
|
return done === result.summary.fields && done > 0 ? 0 : 1;
|
|
694
745
|
};
|
|
746
|
+
/** This function reads a latitude or a longitude of the command line. */
|
|
747
|
+
const coordinateOf = (name, text, limit) => {
|
|
748
|
+
const value = /^\s*-?\d+(?:\.\d+)?\s*$/.test(text) ? Number(text) : NaN;
|
|
749
|
+
if (!Number.isFinite(value) || Math.abs(value) > limit) throw new UsageError(`--${name} wants a number from -${String(limit)} to ${String(limit)}, not "${text}"`);
|
|
750
|
+
return value;
|
|
751
|
+
};
|
|
752
|
+
/** This function checks the options of `venue` and `product`, and gives the options of the recipe. */
|
|
753
|
+
const recipeLineOf = (recipe, values, rest, page) => {
|
|
754
|
+
const url = rest[0] ?? "";
|
|
755
|
+
if (!/^https?:\/\//i.test(url) || !URL.canParse(url)) throw new UsageError(`${recipe} wants an http or https URL, not "${url}"`);
|
|
756
|
+
for (const agent of values["on-behalf-of"] ?? []) if (!/^[A-Za-z0-9_-]+$/.test(agent)) throw new UsageError(`--on-behalf-of wants the robots.txt token of an agent, such as Claude-User, not "${agent}"`);
|
|
757
|
+
const name = values.name;
|
|
758
|
+
if (name !== void 0 && name.trim() === "") throw new UsageError("--name wants the name of the venue");
|
|
759
|
+
if (values["require-name"] === true && name === void 0) throw new UsageError("--require-name goes with --name");
|
|
760
|
+
const address = values.address;
|
|
761
|
+
if (address !== void 0 && address.trim() === "") throw new UsageError("--address wants a street address");
|
|
762
|
+
if (values.lat === void 0 !== (values.lon === void 0)) throw new UsageError("--lat and --lon go together");
|
|
763
|
+
const lat = values.lat === void 0 ? void 0 : coordinateOf("lat", values.lat, 90);
|
|
764
|
+
const lon = values.lon === void 0 ? void 0 : coordinateOf("lon", values.lon, 180);
|
|
765
|
+
const osm = values.osm;
|
|
766
|
+
if (osm !== void 0 && osm.trim() === "") throw new UsageError("--osm wants an opening_hours value, such as \"Mo-Fr 08:00-18:00\"");
|
|
767
|
+
const pages = values.pages === void 0 ? void 0 : whole("pages", values.pages, 0, 0);
|
|
768
|
+
if (pages !== void 0 && pages > 8) throw new UsageError(`--pages wants a whole number from 0 to ${String(8)}`);
|
|
769
|
+
const compare = values.compare;
|
|
770
|
+
if (compare !== void 0 && compare.trim() === "") throw new UsageError("--compare wants a file");
|
|
771
|
+
return {
|
|
772
|
+
recipe,
|
|
773
|
+
url,
|
|
774
|
+
render: page.render,
|
|
775
|
+
maxAge: page.maxAge,
|
|
776
|
+
budget: page.budget,
|
|
777
|
+
onBehalfOf: values["on-behalf-of"],
|
|
778
|
+
userProvided: values["user-provided"] === true,
|
|
779
|
+
fallback: values["archive-fallback"] === true ? "archive" : void 0,
|
|
780
|
+
archiveDespiteRobots: values["archive-despite-robots"] === true,
|
|
781
|
+
name,
|
|
782
|
+
requireName: values["require-name"] === true,
|
|
783
|
+
location: address === void 0 && lat === void 0 ? void 0 : {
|
|
784
|
+
...address === void 0 ? {} : { address },
|
|
785
|
+
...lat === void 0 ? {} : {
|
|
786
|
+
lat,
|
|
787
|
+
lon
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
osm,
|
|
791
|
+
pages,
|
|
792
|
+
compare
|
|
793
|
+
};
|
|
794
|
+
};
|
|
795
|
+
/** The days of a week in their order, as `DAYS` of `hours.ts`. */
|
|
796
|
+
const WEEK_DAYS = [
|
|
797
|
+
"mon",
|
|
798
|
+
"tue",
|
|
799
|
+
"wed",
|
|
800
|
+
"thu",
|
|
801
|
+
"fri",
|
|
802
|
+
"sat",
|
|
803
|
+
"sun"
|
|
804
|
+
];
|
|
805
|
+
const DAY_LABELS = {
|
|
806
|
+
mon: "Mon",
|
|
807
|
+
tue: "Tue",
|
|
808
|
+
wed: "Wed",
|
|
809
|
+
thu: "Thu",
|
|
810
|
+
fri: "Fri",
|
|
811
|
+
sat: "Sat",
|
|
812
|
+
sun: "Sun"
|
|
813
|
+
};
|
|
814
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
815
|
+
/** This function tells if a value is a time of a kept week: `HH:MM` or `H:MM`, to `24:00`. */
|
|
816
|
+
const isClock = (value) => {
|
|
817
|
+
if (typeof value !== "string") return false;
|
|
818
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(value);
|
|
819
|
+
return match !== null && Number(match[1]) <= 24 && Number(match[2]) < 60;
|
|
820
|
+
};
|
|
821
|
+
const intervalOf = (value) => Array.isArray(value) && value.length === 2 && isClock(value[0]) && isClock(value[1]) ? [value[0], value[1]] : void 0;
|
|
822
|
+
/**
|
|
823
|
+
* This function reads the kept week of `--compare`, a JSON file. The file is a week, or an object
|
|
824
|
+
* with the field `week` (a reading of `venue --json`) or `hours_week`. Each day gives a list of
|
|
825
|
+
* intervals (`[]` for closed), one interval, or null for closed. An error names the file.
|
|
826
|
+
*/
|
|
827
|
+
const keptWeekOf = (path) => {
|
|
828
|
+
const file = fileOf(path);
|
|
829
|
+
let text;
|
|
830
|
+
try {
|
|
831
|
+
text = readFileSync(file, "utf8");
|
|
832
|
+
} catch (error) {
|
|
833
|
+
const code = error.code;
|
|
834
|
+
throw new UsageError(`--compare: the file ${file} does not open (${typeof code === "string" ? code : "error"})`, { cause: error });
|
|
835
|
+
}
|
|
836
|
+
let data;
|
|
837
|
+
try {
|
|
838
|
+
data = JSON.parse(text.charCodeAt(0) === 65279 ? text.slice(1) : text);
|
|
839
|
+
} catch (error) {
|
|
840
|
+
throw new UsageError(`--compare: the file ${file} is not JSON`, { cause: error });
|
|
841
|
+
}
|
|
842
|
+
if (isRecord(data) && (isRecord(data["week"]) || isRecord(data["hours_week"]))) data = isRecord(data["week"]) ? data["week"] : data["hours_week"];
|
|
843
|
+
const wants = `--compare: the file ${file} is not a week. Write a list of intervals, one interval or null for each day, such as {"mon": [["09:00", "17:00"]], "sun": []}`;
|
|
844
|
+
if (!isRecord(data)) throw new UsageError(wants);
|
|
845
|
+
const week = {};
|
|
846
|
+
for (const [key, value] of Object.entries(data)) {
|
|
847
|
+
const day = WEEK_DAYS.find((one) => one === key);
|
|
848
|
+
if (day === void 0) throw new UsageError(`--compare: the file ${file} names "${key}", not a day: use mon, tue, wed, thu, fri, sat or sun`);
|
|
849
|
+
const one = intervalOf(value);
|
|
850
|
+
const intervals = value === null ? [] : one !== void 0 ? [one] : Array.isArray(value) ? value.map(intervalOf) : [void 0];
|
|
851
|
+
if (intervals.some((interval) => interval === void 0)) throw new UsageError(`${wants} (the value of ${key} is not)`);
|
|
852
|
+
week[day] = intervals;
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
file,
|
|
856
|
+
week
|
|
857
|
+
};
|
|
858
|
+
};
|
|
859
|
+
/** This function writes the intervals of one day, as `describeWeek` of `hours.ts` does. */
|
|
860
|
+
const dayText = (intervals) => intervals === void 0 ? "not stated" : intervals.length === 0 ? "closed" : intervals.map(([open, close]) => open === "00:00" && close === "00:00" ? "open 24 hours" : `${open}–${close}`).join(", ");
|
|
861
|
+
/** This function gives the days of a comparison in their order. A day that no week states is not there. */
|
|
862
|
+
const compareDays = (kept, read, comparison) => {
|
|
863
|
+
const days = [];
|
|
864
|
+
for (const day of WEEK_DAYS) {
|
|
865
|
+
const one = {
|
|
866
|
+
day,
|
|
867
|
+
kept: kept[day],
|
|
868
|
+
read: read[day]
|
|
869
|
+
};
|
|
870
|
+
if (one.kept === void 0 && one.read === void 0) continue;
|
|
871
|
+
const status = comparison.status === "new" || comparison.added.includes(day) ? "new" : comparison.days.includes(day) ? "changed" : comparison.skipped.includes(day) ? "skipped" : one.read === void 0 ? "not read" : "same";
|
|
872
|
+
if (comparison.status === "new" && one.read === void 0) continue;
|
|
873
|
+
days.push({
|
|
874
|
+
...one,
|
|
875
|
+
status
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
return days;
|
|
879
|
+
};
|
|
880
|
+
const QUOTE_CHARS = 200;
|
|
881
|
+
const shortText = (text) => text.length > QUOTE_CHARS ? `${text.slice(0, QUOTE_CHARS)}…` : text;
|
|
882
|
+
/** This function gives the lines of one reading: its rank, its source, its week and its quote. */
|
|
883
|
+
const readingLines = (one) => {
|
|
884
|
+
const marks = [
|
|
885
|
+
one.schedule === "other" ? "not the opening hours" : null,
|
|
886
|
+
one.inferred ? "am or pm guessed" : null,
|
|
887
|
+
one.partial ? "a rule left out" : null,
|
|
888
|
+
one.season === null ? null : `season ${one.season}`
|
|
889
|
+
].filter((mark) => mark !== null);
|
|
890
|
+
const where = [
|
|
891
|
+
one.url,
|
|
892
|
+
one.path,
|
|
893
|
+
one.capture === null ? null : `capture ${one.capture}`,
|
|
894
|
+
one.archived === void 0 ? null : `archived copy (${one.archived.source}, captured ${one.archived.timestamp})`
|
|
895
|
+
].filter((part) => part !== null);
|
|
896
|
+
return [
|
|
897
|
+
` rank ${String(one.rank)} ${one.from}${one.label === null ? "" : ` "${one.label}"`} ${one.hours === "" ? "no day" : one.hours}${marks.length > 0 ? ` (${marks.join(", ")})` : ""}`,
|
|
898
|
+
...where.length > 0 ? [` ${where.join(" · ")}`] : [],
|
|
899
|
+
one.from === "text" ? ` quote: ${JSON.stringify(shortText(one.raw))}` : ` value: ${shortText(one.raw.replace(/\s+/g, " "))}`
|
|
900
|
+
];
|
|
901
|
+
};
|
|
902
|
+
const pageLine = (page) => {
|
|
903
|
+
const facts = [
|
|
904
|
+
`${page.outcome} ${String(page.status)}`,
|
|
905
|
+
page.from_cache ? "from the store" : null,
|
|
906
|
+
page.rendered ? "rendered" : null,
|
|
907
|
+
page.held_back ? "held back" : null,
|
|
908
|
+
page.archived === void 0 ? null : `archived copy (${page.archived.source})`
|
|
909
|
+
].filter((fact) => fact !== null);
|
|
910
|
+
return ` ${page.outcome === "ok" || page.outcome === "not-modified" || page.archived !== void 0 ? "✓" : "✗"} ${page.url} ${facts.join(" · ")}`;
|
|
911
|
+
};
|
|
912
|
+
const WALL_OUTCOMES = /* @__PURE__ */ new Set([
|
|
913
|
+
"blocked",
|
|
914
|
+
"challenge",
|
|
915
|
+
"payment-required"
|
|
916
|
+
]);
|
|
917
|
+
/**
|
|
918
|
+
* This function tells if a site refused the crawler, with the rule of `wall` of `readProduct`. The
|
|
919
|
+
* causes are a 401, a 402 or a 403, a challenge, a bot check, robots.txt or a stop of the host.
|
|
920
|
+
*/
|
|
921
|
+
const refusedPage = (page) => WALL_OUTCOMES.has(page.outcome) || page.outcome === "skipped" && /^(?:robots\.txt disallows|host stopped)/.test(page.note ?? "") || page.doc === null && /\bbot check\b/.test(page.note ?? "");
|
|
922
|
+
/**
|
|
923
|
+
* This function gives the exit code of `venue`. It is 0 when the site gave a reading of the opening
|
|
924
|
+
* hours. It is `WALL` when the home page refused the crawler, and `NOTHING_READ` for a different
|
|
925
|
+
* result. An OpenStreetMap value alone does not count.
|
|
926
|
+
*/
|
|
927
|
+
const venueCode = (result) => {
|
|
928
|
+
if (result.readings.some((one) => one.from !== "osm" && one.schedule === "opening")) return 0;
|
|
929
|
+
const home = result.pages[0];
|
|
930
|
+
return home !== void 0 && refusedPage(home) ? 4 : 3;
|
|
931
|
+
};
|
|
932
|
+
/** This function gives the exit code of `product`: 0 for Product data, `WALL` for a refusal, else `NOTHING_READ`. */
|
|
933
|
+
const productCode = (result) => result.found ? 0 : result.wall ? 4 : 3;
|
|
934
|
+
/** This line tells how to read a held-back page. */
|
|
935
|
+
const HELD_BACK_LINE = "Add --user-provided if you gave this URL yourself. The crawler logs that read.";
|
|
936
|
+
/** This function gives the line of a page that needs a browser, when rendering is off. */
|
|
937
|
+
const renderLine = (notes, renderOff) => renderOff !== void 0 && notes.some((note) => note !== null && /no browser/i.test(note)) ? [`rendering is off: ${renderOff}`] : [];
|
|
938
|
+
/**
|
|
939
|
+
* This function gives the lines of `venue`: the week of the site, and each reading with its source.
|
|
940
|
+
* Then come the days where the readings disagree, the menus, the phone, the pages and the notes.
|
|
941
|
+
* With `--compare`, the lines give each day of the comparison.
|
|
942
|
+
*/
|
|
943
|
+
const describeVenue = (result, compare, renderOff) => {
|
|
944
|
+
const code = venueCode(result);
|
|
945
|
+
const lines = [`${code === 0 ? "✓" : "✗"} ${result.home ?? result.url}`];
|
|
946
|
+
const site = result.from_site;
|
|
947
|
+
const home = result.pages[0];
|
|
948
|
+
if (site !== null) {
|
|
949
|
+
const source = [
|
|
950
|
+
`${site.from}, rank ${String(site.rank)}`,
|
|
951
|
+
site.url,
|
|
952
|
+
site.capture === null ? null : `capture ${site.capture}`
|
|
953
|
+
].filter((part) => part !== null);
|
|
954
|
+
lines.push(` the site: ${site.hours}`, ` from ${source.join(", ")}`);
|
|
955
|
+
} else if (result.best !== null) lines.push(` no reading of the site with no guess. The best reading (${result.best.from}, rank ${String(result.best.rank)}): ${result.best.hours}`);
|
|
956
|
+
else if (code === 4 && home !== void 0) lines.push(` wall: the site refused the crawler (${home.outcome} ${String(home.status)}). The crawler does not try a different identity.`);
|
|
957
|
+
else lines.push(" no opening hours found");
|
|
958
|
+
if (result.readings.length > 0) {
|
|
959
|
+
lines.push("readings, the best first:");
|
|
960
|
+
for (const one of result.readings) lines.push(...readingLines(one));
|
|
961
|
+
}
|
|
962
|
+
for (const day of result.disagree) {
|
|
963
|
+
const said = result.readings.filter((one) => one.rank <= 2 && one.week[day] !== void 0).map((one) => `${dayText(one.week[day])} (rank ${String(one.rank)} ${one.from})`);
|
|
964
|
+
lines.push(`the readings disagree on ${DAY_LABELS[day]}: ${said.join(" · ")}`);
|
|
965
|
+
}
|
|
966
|
+
if (result.menus.length > 0) lines.push("menus:", ...result.menus.map((menu) => ` ${menu.url}${menu.text === "" ? "" : ` ${menu.text}`}`));
|
|
967
|
+
if (result.phone !== null) lines.push(`phone: ${result.phone}`);
|
|
968
|
+
if (result.moved !== null) lines.push(`moved: the site now goes to ${result.moved}`);
|
|
969
|
+
if (result.names_venue !== null) lines.push(result.names_venue ? "names the venue: yes" : "names the venue: no. The domain can belong to a different business now.");
|
|
970
|
+
if (result.held_back) lines.push(`held back: a page asked not to be AI input, so the recipe read nothing from it. ${HELD_BACK_LINE}`);
|
|
971
|
+
if (result.pages.length > 0) lines.push(`pages (${String(result.pages.length)}):`, ...result.pages.map(pageLine));
|
|
972
|
+
if (compare !== void 0) {
|
|
973
|
+
const comparison = compare.comparison;
|
|
974
|
+
if (comparison === null || site === null) lines.push(`compare with ${compare.file}: no reading of the site with no guess, so nothing to compare`);
|
|
975
|
+
else {
|
|
976
|
+
const changed = comparison.days.length > 0 ? ` (${comparison.days.map((day) => DAY_LABELS[day]).join(", ")})` : "";
|
|
977
|
+
lines.push(`compare with ${compare.file}: ${comparison.status}${changed}`);
|
|
978
|
+
for (const one of compareDays(compare.kept, site.week, comparison)) {
|
|
979
|
+
const text = one.status === "changed" || one.status === "skipped" ? `${dayText(one.kept)} → ${dayText(one.read)}` : one.status === "not read" ? `kept ${dayText(one.kept)}` : dayText(one.read);
|
|
980
|
+
lines.push(` ${DAY_LABELS[one.day]} ${one.status.padEnd(8)} ${text}`);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (result.note !== null) lines.push(`note: ${result.note}`);
|
|
985
|
+
lines.push(...renderLine([result.note], renderOff));
|
|
986
|
+
return lines;
|
|
987
|
+
};
|
|
988
|
+
/** This function gives the number of digits after the decimal point of a currency: 2 for EUR, 0 for JPY. */
|
|
989
|
+
const fractionOf = (currency) => {
|
|
990
|
+
try {
|
|
991
|
+
return new Intl.NumberFormat("en", {
|
|
992
|
+
style: "currency",
|
|
993
|
+
currency
|
|
994
|
+
}).resolvedOptions().maximumFractionDigits ?? 2;
|
|
995
|
+
} catch {
|
|
996
|
+
return 2;
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
/** This function writes an amount of money with its ISO 4217 code: `1,299.00 EUR`, `980 JPY`. */
|
|
1000
|
+
const moneyText = (money) => {
|
|
1001
|
+
const digits = fractionOf(money.currency);
|
|
1002
|
+
return `${new Intl.NumberFormat("en", {
|
|
1003
|
+
minimumFractionDigits: digits,
|
|
1004
|
+
maximumFractionDigits: Math.max(digits, 4)
|
|
1005
|
+
}).format(money.amount)} ${money.currency}`;
|
|
1006
|
+
};
|
|
1007
|
+
/**
|
|
1008
|
+
* This function gives the lines of `product`. They are the name, the brand and the codes, the price
|
|
1009
|
+
* as money, the availability, the variant, the seller, the page and the notes. A wall and a
|
|
1010
|
+
* held-back page say so.
|
|
1011
|
+
*/
|
|
1012
|
+
const describeProduct = (result, renderOff) => {
|
|
1013
|
+
const code = productCode(result);
|
|
1014
|
+
const lines = [`${code === 0 ? "✓" : "✗"} ${result.name ?? result.url}`];
|
|
1015
|
+
if (result.name !== null) lines.push(` ${result.url}`);
|
|
1016
|
+
if (code === 4) lines.push(` wall: the shop refused the crawler (${result.outcome} ${String(result.status)}). The crawler does not try a different identity.`);
|
|
1017
|
+
if (result.held_back) lines.push(` held back: the page asked not to be AI input. ${HELD_BACK_LINE}`);
|
|
1018
|
+
const codes = [
|
|
1019
|
+
result.brand === null ? null : `brand ${result.brand}`,
|
|
1020
|
+
result.sku === null ? null : `sku ${result.sku}`,
|
|
1021
|
+
result.gtin === null ? null : `gtin ${result.gtin}`
|
|
1022
|
+
].filter((part) => part !== null);
|
|
1023
|
+
if (codes.length > 0) lines.push(` ${codes.join(" · ")}`);
|
|
1024
|
+
if (result.price !== null) lines.push(result.price_to === null ? ` price ${moneyText(result.price)}` : ` price ${moneyText(result.price)} to ${moneyText(result.price_to)}, the range of the offers`);
|
|
1025
|
+
if (result.availability !== null) lines.push(` availability ${result.availability}${result.availabilities.length > 1 ? ` (the offers: ${result.availabilities.join(", ")})` : ""}`);
|
|
1026
|
+
const variant = result.variant;
|
|
1027
|
+
if (variant !== null) {
|
|
1028
|
+
const parts = [
|
|
1029
|
+
variant.name,
|
|
1030
|
+
variant.sku === null ? null : `sku ${variant.sku}`,
|
|
1031
|
+
variant.price === null ? null : moneyText(variant.price),
|
|
1032
|
+
variant.availability
|
|
1033
|
+
].filter((part) => part !== null);
|
|
1034
|
+
lines.push(` variant ${parts.length > 0 ? parts.join(" · ") : "with no data"}`);
|
|
1035
|
+
}
|
|
1036
|
+
if (result.seller !== null) lines.push(` seller ${result.seller}`);
|
|
1037
|
+
if (result.image !== null) lines.push(` image ${result.image}`);
|
|
1038
|
+
if (result.archived !== void 0) lines.push(` archived copy (${result.archived.source}, captured ${result.archived.timestamp}): ${result.archived.url}`);
|
|
1039
|
+
const facts = [
|
|
1040
|
+
`${result.outcome} ${String(result.status)}`,
|
|
1041
|
+
result.doc === null ? null : `doc ${result.doc}`,
|
|
1042
|
+
result.capture === null ? null : `capture ${result.capture}`,
|
|
1043
|
+
result.rendered ? "rendered" : null,
|
|
1044
|
+
result.from_cache ? "from the store" : null
|
|
1045
|
+
].filter((fact) => fact !== null);
|
|
1046
|
+
lines.push(` ${facts.join(" · ")}`);
|
|
1047
|
+
if (result.note !== null) lines.push(` note: ${result.note}`);
|
|
1048
|
+
lines.push(...renderLine([result.note], renderOff).map((line) => ` ${line}`));
|
|
1049
|
+
return lines;
|
|
1050
|
+
};
|
|
695
1051
|
/** This function checks the options of `export`, and gives its format and its options. */
|
|
696
1052
|
const exportPlan = (values) => {
|
|
697
1053
|
const out = values.out;
|
|
@@ -780,6 +1136,11 @@ const readLine = (args, env) => {
|
|
|
780
1136
|
} catch (error) {
|
|
781
1137
|
throw new UsageError(`--max-age: ${error.message}`, { cause: error });
|
|
782
1138
|
}
|
|
1139
|
+
const recipeLine = command === "venue" || command === "product" ? recipeLineOf(command, values, rest, {
|
|
1140
|
+
render,
|
|
1141
|
+
maxAge,
|
|
1142
|
+
budget: values.budget === void 0 ? void 0 : crawlLimits.budgetMs
|
|
1143
|
+
}) : void 0;
|
|
783
1144
|
return {
|
|
784
1145
|
kind: "command",
|
|
785
1146
|
command,
|
|
@@ -806,7 +1167,8 @@ const readLine = (args, env) => {
|
|
|
806
1167
|
signKey,
|
|
807
1168
|
signatureAgent,
|
|
808
1169
|
embedder,
|
|
809
|
-
maxDocs
|
|
1170
|
+
maxDocs,
|
|
1171
|
+
recipeLine
|
|
810
1172
|
};
|
|
811
1173
|
};
|
|
812
1174
|
//#endregion
|
|
@@ -827,7 +1189,7 @@ Usage:
|
|
|
827
1189
|
polite-crawler read <doc> print a range of a stored document
|
|
828
1190
|
polite-crawler verify <doc> <quote> check that a quote is in a stored document
|
|
829
1191
|
polite-crawler reindex rebuild the search index from the capture log
|
|
830
|
-
polite-crawler mcp
|
|
1192
|
+
polite-crawler mcp start the MCP server of polite-crawler-mcp (deprecated)
|
|
831
1193
|
polite-crawler export --out <file> write the stored captures as a WARC or WACZ archive
|
|
832
1194
|
polite-crawler discover <site> list the pages of a site from its sitemaps and feeds
|
|
833
1195
|
polite-crawler crawl <url...> fetch a site and follow its links, within a budget
|
|
@@ -843,6 +1205,8 @@ Usage:
|
|
|
843
1205
|
polite-crawler extract <doc> --field name=value[:kind]...
|
|
844
1206
|
check proposed field values against a stored page
|
|
845
1207
|
polite-crawler embed --embedder <name> give vectors to the stored pages, for find --rank hybrid
|
|
1208
|
+
polite-crawler venue <url> read the opening hours of a venue from its own site
|
|
1209
|
+
polite-crawler product <url> read the schema.org Product data of a product page
|
|
846
1210
|
|
|
847
1211
|
Options:
|
|
848
1212
|
--dir <path> the store (default $POLITE_CRAWLER_DIR, or ~/.polite-crawler)
|
|
@@ -937,6 +1301,20 @@ Options:
|
|
|
937
1301
|
embed --budget <time> the longest time (default 10m); no embedding starts after it
|
|
938
1302
|
--max-docs <n> how many texts get vectors at most
|
|
939
1303
|
--kind <kind> only this kind of document
|
|
1304
|
+
venue --name <text> the name of the venue: the result tells if the home page names it
|
|
1305
|
+
--require-name with --name: read no hours from a site that does not name the venue
|
|
1306
|
+
--address <text> the street address, for a page that lists more than one location
|
|
1307
|
+
--lat <n> --lon <n> the position of the venue, for the same use (within 150 m);
|
|
1308
|
+
write a value below 0 after =, such as --lon=-20.5
|
|
1309
|
+
--osm <value> an OpenStreetMap opening_hours value, read as one more reading;
|
|
1310
|
+
no request goes to OpenStreetMap
|
|
1311
|
+
--pages <n> the pages after the home page: 0 to 8 (default 3)
|
|
1312
|
+
--compare <file> compare the week of the site with a kept week (JSON), day by day
|
|
1313
|
+
venue, product --render, --max-age, --archive-fallback, --archive-despite-robots as for fetch
|
|
1314
|
+
--budget <time> no request starts after this time, such as 2m (default: no limit)
|
|
1315
|
+
--on-behalf-of <agent> the robots.txt rules of this agent apply too, such as
|
|
1316
|
+
Claude-User (repeat it)
|
|
1317
|
+
--user-provided as for read
|
|
940
1318
|
|
|
941
1319
|
Exit codes:
|
|
942
1320
|
0 done
|
|
@@ -946,19 +1324,24 @@ Exit codes:
|
|
|
946
1324
|
or an error
|
|
947
1325
|
(fetch: a page with an archived copy counts as done)
|
|
948
1326
|
2 the command line is wrong
|
|
1327
|
+
3 venue, product: nothing read: no opening hours of the site or no Product data, a page
|
|
1328
|
+
held back, or a page that did not come
|
|
1329
|
+
4 venue, product: the site refused the crawler (a wall): a 401, a 402 or a 403, a
|
|
1330
|
+
challenge, a bot check, robots.txt or a stopped host. The crawler does not try a
|
|
1331
|
+
different identity
|
|
949
1332
|
`;
|
|
950
1333
|
/**
|
|
951
1334
|
* This function does the command `export`, and gives the exit code. `readLine` checked the whole
|
|
952
1335
|
* command line first. The export only reads the store: it makes no store, and it sends no request.
|
|
953
1336
|
*/
|
|
954
1337
|
const exportCommand = async (plan, dir, json) => {
|
|
955
|
-
const result = plan.format === "wacz" ? await (await import("./wacz-
|
|
1338
|
+
const result = plan.format === "wacz" ? await (await import("./wacz-DKk2HBmx.mjs").then((n) => n.n)).exportWacz(dir, plan.options) : await (await import("./warc-C_ew76bV.mjs").then((n) => n.a)).exportWarc(dir, plan.options);
|
|
956
1339
|
process.stdout.write(`${json ? JSON.stringify(result, null, 2) : describeExport(result, plan.options.includeRefusals)}\n`);
|
|
957
1340
|
return 0;
|
|
958
1341
|
};
|
|
959
1342
|
/** This function prints the key directory of a key file, and tells where to host it. */
|
|
960
1343
|
const keyDirectoryCommand = async (key, agent) => {
|
|
961
|
-
const { KEY_DIRECTORY_PATH, KEY_DIRECTORY_TYPE, keyDirectory } = await import("./webbotauth-
|
|
1344
|
+
const { KEY_DIRECTORY_PATH, KEY_DIRECTORY_TYPE, keyDirectory } = await import("./webbotauth-Dq_dA_u3.mjs").then((n) => n.s);
|
|
962
1345
|
let directory;
|
|
963
1346
|
try {
|
|
964
1347
|
directory = keyDirectory([key]);
|
|
@@ -1074,7 +1457,7 @@ const runWatchCommand = async (crawler, line, values, limits, json, out) => {
|
|
|
1074
1457
|
};
|
|
1075
1458
|
/** This function gives the text of a diff for a person: the two captures, then the text of `formatDiff`. */
|
|
1076
1459
|
const describeDiff = async (diff) => {
|
|
1077
|
-
const { formatDiff } = await import("./diff-
|
|
1460
|
+
const { formatDiff } = await import("./diff-CpKN0Jdc.mjs").then((n) => n.i);
|
|
1078
1461
|
return [
|
|
1079
1462
|
`${diff.doc} ${diff.from.capture} (${diff.from.fetched_at}) -> ${diff.to.capture} (${diff.to.fetched_at})`,
|
|
1080
1463
|
formatDiff(diff),
|
|
@@ -1084,7 +1467,7 @@ const describeDiff = async (diff) => {
|
|
|
1084
1467
|
};
|
|
1085
1468
|
/** This function checks the fields against a stored document, and prints a line for each field. The exit code is 0 when each field is verified or normalized. */
|
|
1086
1469
|
const runExtract = async (crawler, doc, fields, options, json, out) => {
|
|
1087
|
-
const { extractDocument } = await import("./extract-
|
|
1470
|
+
const { extractDocument } = await import("./extract-DqeqiNHb.mjs").then((n) => n.i);
|
|
1088
1471
|
const claims = fields;
|
|
1089
1472
|
const result = crawler.extract === void 0 ? await extractDocument(crawler.runtime, doc, claims, options) : await crawler.extract(doc, claims, options);
|
|
1090
1473
|
if (json) out(JSON.stringify(result, null, 2));
|
|
@@ -1098,14 +1481,14 @@ const runExtract = async (crawler, doc, fields, options, json, out) => {
|
|
|
1098
1481
|
const embedderFor = async (choice, dir) => {
|
|
1099
1482
|
try {
|
|
1100
1483
|
if (choice.kind === "ollama") {
|
|
1101
|
-
const { ollamaEmbedder } = await import("./ollama-
|
|
1484
|
+
const { ollamaEmbedder } = await import("./ollama-D1xRh3hY.mjs").then((n) => n.a);
|
|
1102
1485
|
return ollamaEmbedder({
|
|
1103
1486
|
...choice.model === void 0 ? {} : { model: choice.model },
|
|
1104
1487
|
...choice.url === void 0 ? {} : { baseUrl: choice.url },
|
|
1105
1488
|
allowRemote: choice.allowRemote
|
|
1106
1489
|
});
|
|
1107
1490
|
}
|
|
1108
|
-
const { transformersEmbedder } = await import("./transformers-
|
|
1491
|
+
const { transformersEmbedder } = await import("./transformers-BNiuiLxT.mjs").then((n) => n.i);
|
|
1109
1492
|
return transformersEmbedder({
|
|
1110
1493
|
...choice.model === void 0 ? {} : { model: choice.model },
|
|
1111
1494
|
cacheDir: join(dir, "models")
|
|
@@ -1149,7 +1532,7 @@ const main = async (args) => {
|
|
|
1149
1532
|
if (command === "key-directory") return keyDirectoryCommand(keyFile(signKey), signatureAgent);
|
|
1150
1533
|
let signer;
|
|
1151
1534
|
if (signKey !== void 0 && signatureAgent !== void 0) {
|
|
1152
|
-
const { createWebBotAuthSigner } = await import("./webbotauth-
|
|
1535
|
+
const { createWebBotAuthSigner } = await import("./webbotauth-Dq_dA_u3.mjs").then((n) => n.s);
|
|
1153
1536
|
const privateKey = keyFile(signKey);
|
|
1154
1537
|
try {
|
|
1155
1538
|
signer = createWebBotAuthSigner({
|
|
@@ -1170,15 +1553,24 @@ const main = async (args) => {
|
|
|
1170
1553
|
...embedder === void 0 ? {} : { embedder }
|
|
1171
1554
|
};
|
|
1172
1555
|
if (command === "mcp") {
|
|
1173
|
-
const {
|
|
1174
|
-
|
|
1556
|
+
const { McpPackageMissing, loadMcpPackage } = await import("./mcp-package-Y0bREnOS.mjs");
|
|
1557
|
+
let mcp;
|
|
1558
|
+
try {
|
|
1559
|
+
mcp = await loadMcpPackage();
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
if (!(error instanceof McpPackageMissing)) throw error;
|
|
1562
|
+
process.stderr.write(`polite-crawler: ${error.message}\n`);
|
|
1563
|
+
return 2;
|
|
1564
|
+
}
|
|
1565
|
+
process.stderr.write("polite-crawler: the command mcp is deprecated, and 0.3.0 removes it. Start npx polite-crawler-mcp.\n");
|
|
1566
|
+
await mcp.startMcpServer({
|
|
1175
1567
|
...options,
|
|
1176
1568
|
allowUserProvided: line.allowUserProvided
|
|
1177
1569
|
});
|
|
1178
1570
|
return 0;
|
|
1179
1571
|
}
|
|
1180
1572
|
if (command === "daemon") {
|
|
1181
|
-
const { daemonCommand } = await import("./cli-daemon
|
|
1573
|
+
const { daemonCommand } = await import("./cli-daemon-qS4PH2bM.mjs");
|
|
1182
1574
|
return daemonCommand(line.daemonLine, options, {
|
|
1183
1575
|
allowUserProvided: line.allowUserProvided,
|
|
1184
1576
|
json: values.json === true,
|
|
@@ -1186,12 +1578,15 @@ const main = async (args) => {
|
|
|
1186
1578
|
});
|
|
1187
1579
|
}
|
|
1188
1580
|
if (line.exportPlan !== void 0) return exportCommand(line.exportPlan, dir, values.json === true);
|
|
1189
|
-
const
|
|
1581
|
+
const recipeLine = line.recipeLine;
|
|
1582
|
+
const recipes = recipeLine === void 0 ? void 0 : await import("./cli-recipes-qbLRVThF.mjs");
|
|
1583
|
+
const recipeInput = recipes === void 0 || recipeLine === void 0 ? void 0 : recipes.prepareRecipe(recipeLine);
|
|
1584
|
+
const { openCrawler } = await import("./open-CEo22Hpu.mjs").then((n) => n.a);
|
|
1190
1585
|
let renderOff;
|
|
1191
1586
|
let renderer = options.renderer;
|
|
1192
|
-
if (!(command === "fetch" || command === "crawl" || watchLine?.action === "run") || render === "never") renderer = false;
|
|
1587
|
+
if (!(command === "fetch" || command === "crawl" || recipeLine !== void 0 || watchLine?.action === "run") || render === "never") renderer = false;
|
|
1193
1588
|
else if (renderer === void 0) {
|
|
1194
|
-
const { browserMissing } = await import("./browser-
|
|
1589
|
+
const { browserMissing } = await import("./browser-56dBBLec.mjs").then((n) => n.n);
|
|
1195
1590
|
renderOff = await browserMissing();
|
|
1196
1591
|
if (renderOff !== void 0) renderer = false;
|
|
1197
1592
|
}
|
|
@@ -1273,6 +1668,15 @@ const main = async (args) => {
|
|
|
1273
1668
|
maxDocs: line.maxDocs,
|
|
1274
1669
|
kind: values.kind
|
|
1275
1670
|
}, values.json === true, out);
|
|
1671
|
+
case "venue":
|
|
1672
|
+
case "product": {
|
|
1673
|
+
const io = {
|
|
1674
|
+
json: values.json === true,
|
|
1675
|
+
out,
|
|
1676
|
+
renderOff
|
|
1677
|
+
};
|
|
1678
|
+
return command === "venue" ? await recipes.venueCommand(crawler, recipeLine, recipeInput, io) : await recipes.productCommand(crawler, recipeLine, io);
|
|
1679
|
+
}
|
|
1276
1680
|
default: {
|
|
1277
1681
|
const counts = await crawler.reindex();
|
|
1278
1682
|
print(counts, () => describeReindex(counts));
|
|
@@ -1292,6 +1696,6 @@ main(process.argv.slice(2)).then((code) => {
|
|
|
1292
1696
|
process.exitCode = usage ? 2 : 1;
|
|
1293
1697
|
});
|
|
1294
1698
|
//#endregion
|
|
1295
|
-
export { describeDaemonStatus as t };
|
|
1699
|
+
export { keptWeekOf as a, describeVenue as i, describeDaemonStatus as n, productCode as o, describeProduct as r, venueCode as s, UsageError as t };
|
|
1296
1700
|
|
|
1297
1701
|
//# sourceMappingURL=cli.mjs.map
|