polite-crawler 0.1.4 → 0.2.1

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.
Files changed (78) hide show
  1. package/README.md +28 -121
  2. package/build/{browser-CKRNbIDT.mjs → browser-56dBBLec.mjs} +7 -3
  3. package/build/browser-56dBBLec.mjs.map +1 -0
  4. package/build/classify.d.mts +50 -2
  5. package/build/{cli-daemon-ZYRdy6EW.mjs → cli-daemon-40TxewuH.mjs} +2 -2
  6. package/build/{cli-daemon-ZYRdy6EW.mjs.map → cli-daemon-40TxewuH.mjs.map} +1 -1
  7. package/build/{cli-recipes-Bci6HREA.mjs → cli-recipes-COyH6wo4.mjs} +2 -2
  8. package/build/{cli-recipes-Bci6HREA.mjs.map → cli-recipes-COyH6wo4.mjs.map} +1 -1
  9. package/build/cli.mjs +41 -16
  10. package/build/cli.mjs.map +1 -1
  11. package/build/{daemon-DK6U51_v.mjs → daemon-Cm6Mb8EE.mjs} +11 -7
  12. package/build/daemon-Cm6Mb8EE.mjs.map +1 -0
  13. package/build/{diff-BUM91Rev.mjs → diff-ByOuHmq-.mjs} +7 -3
  14. package/build/{diff-BUM91Rev.mjs.map → diff-ByOuHmq-.mjs.map} +1 -1
  15. package/build/{extract-CefB5Vfn.mjs → extract-Ccgs1ERp.mjs} +12 -4
  16. package/build/extract-Ccgs1ERp.mjs.map +1 -0
  17. package/build/html-DkQUAE_d.mjs.map +1 -1
  18. package/build/{html-BACljlkf.d.mts → html-o5bDHjNi.d.mts} +16 -2
  19. package/build/html.d.mts +2 -3
  20. package/build/index.d.mts +4258 -45
  21. package/build/index.mjs +17 -17
  22. package/build/{log-Dx-wXjJl.mjs → log-CmBWcyQm.mjs} +5 -1
  23. package/build/log-CmBWcyQm.mjs.map +1 -0
  24. package/build/mcp-package-Y0bREnOS.mjs +40 -0
  25. package/build/mcp-package-Y0bREnOS.mjs.map +1 -0
  26. package/build/{ollama-BJxkjny4.mjs → ollama-D1xRh3hY.mjs} +7 -3
  27. package/build/ollama-D1xRh3hY.mjs.map +1 -0
  28. package/build/{open-CbpSCEZc.mjs → open-9oUXhGCK.mjs} +78 -18
  29. package/build/open-9oUXhGCK.mjs.map +1 -0
  30. package/build/politeness.d.mts +366 -2
  31. package/build/{recipe-GiFQojmo.mjs → recipe-BODo4plI.mjs} +10 -4
  32. package/build/recipe-BODo4plI.mjs.map +1 -0
  33. package/build/{recipe-product-Dellqudu.mjs → recipe-product-BrDKmDXb.mjs} +29 -7
  34. package/build/recipe-product-BrDKmDXb.mjs.map +1 -0
  35. package/build/{render-C1CUraWF.mjs → render-CKE2LILH.mjs} +10 -3
  36. package/build/render-CKE2LILH.mjs.map +1 -0
  37. package/build/robots.d.mts +143 -2
  38. package/build/robots.mjs +1 -1
  39. package/build/robots.mjs.map +1 -1
  40. package/build/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-8H4AJuhK.mjs} +1 -0
  41. package/build/{runtime-D_r2SYge.mjs → runtime-z6x0pWSN.mjs} +84 -18
  42. package/build/runtime-z6x0pWSN.mjs.map +1 -0
  43. package/build/signals.d.mts +45 -2
  44. package/build/{transformers-BQMlaGj4.mjs → transformers-BNiuiLxT.mjs} +3 -3
  45. package/build/transformers-BNiuiLxT.mjs.map +1 -0
  46. package/build/{version-BPem-XZd.mjs → version-cPnAph6k.mjs} +7 -2
  47. package/build/version-cPnAph6k.mjs.map +1 -0
  48. package/build/{wacz-QyzLp4mC.mjs → wacz-Cog-hnVy.mjs} +4 -4
  49. package/build/{wacz-QyzLp4mC.mjs.map → wacz-Cog-hnVy.mjs.map} +1 -1
  50. package/build/{warc-BHcaRKHw.mjs → warc-FLgjirrM.mjs} +4 -4
  51. package/build/{warc-BHcaRKHw.mjs.map → warc-FLgjirrM.mjs.map} +1 -1
  52. package/build/{webbotauth-CjJdTAfE.mjs → webbotauth-Dq_dA_u3.mjs} +2 -2
  53. package/build/webbotauth-Dq_dA_u3.mjs.map +1 -0
  54. package/package.json +5 -11
  55. package/build/browser-CKRNbIDT.mjs.map +0 -1
  56. package/build/classify-CrfyXcwo.d.mts +0 -51
  57. package/build/daemon-DK6U51_v.mjs.map +0 -1
  58. package/build/daemon-wwIWsUmU.d.mts +0 -3777
  59. package/build/extract-CefB5Vfn.mjs.map +0 -1
  60. package/build/log-Dx-wXjJl.mjs.map +0 -1
  61. package/build/loopback-C_QYowSf.mjs +0 -22
  62. package/build/loopback-C_QYowSf.mjs.map +0 -1
  63. package/build/mcp.d.mts +0 -199
  64. package/build/mcp.mjs +0 -1555
  65. package/build/mcp.mjs.map +0 -1
  66. package/build/ollama-BJxkjny4.mjs.map +0 -1
  67. package/build/open-CbpSCEZc.mjs.map +0 -1
  68. package/build/politeness-yI3xBEx9.d.mts +0 -367
  69. package/build/recipe-GiFQojmo.mjs.map +0 -1
  70. package/build/recipe-product-Dellqudu.mjs.map +0 -1
  71. package/build/render-C1CUraWF.mjs.map +0 -1
  72. package/build/robots-R-qaEBOb.d.mts +0 -144
  73. package/build/runtime-D_r2SYge.mjs.map +0 -1
  74. package/build/signals-BHpy2xb2.d.mts +0 -46
  75. package/build/transformers-BQMlaGj4.mjs.map +0 -1
  76. package/build/urls-BTDSgDLC.d.mts +0 -16
  77. package/build/version-BPem-XZd.mjs.map +0 -1
  78. package/build/webbotauth-CjJdTAfE.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webbotauth-Dq_dA_u3.mjs","names":["signBytes","verifyBytes"],"sources":["../src/webbotauth.ts"],"sourcesContent":["import type { JsonWebKeyInput, KeyObject } from \"node:crypto\";\nimport {\n createHash,\n createPrivateKey,\n createPublicKey,\n randomBytes,\n sign as signBytes,\n verify as verifyBytes,\n} from \"node:crypto\";\n\n/**\n * Signed requests: HTTP Message Signatures (RFC 9421), as the IETF Web Bot Auth profile tells\n * (`draft-ietf-webbotauth-httpsig-protocol-00`, 1 September 2026). A signature lets a site verify that\n * a request comes from the holder of a key that the operator publishes.\n *\n * - Each request gets three headers: `Signature-Agent`, `Signature-Input` and `Signature`.\n * - `Signature-Agent` is a dictionary. Its member names the key directory of the operator.\n * - The signature covers `@authority` and that member of `Signature-Agent`.\n * - The parameters are `created`, `keyid` (the JWK thumbprint of the key), `alg=\"ed25519\"`,\n * `expires`, a random `nonce` and `tag=\"web-bot-auth\"`.\n * - The key is an Ed25519 key, and `node:crypto` signs. The package adds no dependency.\n *\n * A signature adds an identity. It does not replace the `User-Agent` header, which stays honest.\n */\n\n/** The path of the key directory on the host of the operator (the well-known URI of the draft). */\nexport const KEY_DIRECTORY_PATH = \"/.well-known/http-message-signatures-directory\";\n\n/** The media type of the key directory. The host of the directory sends it as `Content-Type`. */\nexport const KEY_DIRECTORY_TYPE = \"application/http-message-signatures-directory+json\";\n\n/** The value of the `tag` parameter of a Web Bot Auth signature. */\nexport const WEB_BOT_AUTH_TAG = \"web-bot-auth\";\n\n/**\n * This value tells how a site finds the keys from the URL of `Signature-Agent`:\n *\n * - `directory` (the default): the URL is an origin, and the keys are at `KEY_DIRECTORY_PATH` on it.\n * - `jwks_uri`: the URL is the address of a JSON Web Key Set, on any path.\n */\nexport type SignatureAgentType = \"directory\" | \"jwks_uri\";\n\n/** A key in one of the forms that `node:crypto` reads: a `KeyObject`, PEM text, DER bytes (PKCS#8 or SPKI) or a JWK. */\n/**\n * This type is a JSON Web Key, as `createPrivateKey` of `node:crypto` reads it. The type comes from `JsonWebKeyInput`, which\n * `@types/node` 22 and 26 both have: version 26 has no `JsonWebKey` export in `node:crypto`.\n */\nexport type JwkInput = JsonWebKeyInput[\"key\"];\n\nexport type KeyInput = KeyObject | string | Uint8Array | JwkInput;\n\n/** These options make a signer for Web Bot Auth. */\nexport interface WebBotAuthOptions {\n /** This key signs. It is an Ed25519 private key: a `KeyObject`, PKCS#8 (PEM text or DER bytes) or a JWK with `d`. */\n readonly privateKey: KeyInput;\n /**\n * This text is the `keyid` of each signature: the JWK thumbprint of the key (RFC 8037). The signer\n * calculates it. A value that is not the thumbprint of the key throws an error.\n */\n readonly keyId?: string | undefined;\n /**\n * This URL tells a site where the keys are. For the type `directory`, it is an https origin, for\n * example `https://bot.example.com`. For `jwks_uri`, it is the https URL of a JSON Web Key Set.\n */\n readonly signatureAgent: string;\n /** This value tells how a site finds the keys from `signatureAgent`. The default is `directory`. */\n readonly agentType?: SignatureAgentType | undefined;\n /** This value is the time that a signature stays valid, in milliseconds. The default is 5 minutes, and the maximum is 24 hours. */\n readonly lifetimeMs?: number | undefined;\n /** This text is the label of the signature, and the key of the member of `Signature-Agent`. The default is `sig1`. */\n readonly label?: string | undefined;\n}\n\n/**\n * A request signer gives the signature headers of one request. The crawler uses it for each\n * request, robots.txt too. `createWebBotAuthSigner` makes one. A signer of your own can sign with a\n * key in a hardware module, for example.\n */\nexport interface RequestSigner {\n /** This text is the `keyid` of the signatures. */\n readonly keyId: string;\n /** This URL is the value of the member of `Signature-Agent`: where a site finds the keys. */\n readonly signatureAgent: string;\n /** This method gives the headers that sign a request to `url` at the time `now` (milliseconds since the epoch). */\n sign(url: URL, now: number): Readonly<Record<string, string>>;\n}\n\n/** The signer that `createWebBotAuthSigner` makes. It also gives its public key for the key directory. */\nexport interface WebBotAuthSigner extends RequestSigner {\n /** This JWK is the public key of the signer, with no private part. Give it to `keyDirectory`. */\n readonly publicKey: JwkInput;\n}\n\n/** One key of a key directory: an Ed25519 public key as a JWK, with its thumbprint as `kid`. */\nexport interface DirectoryKey {\n readonly kty: \"OKP\";\n readonly crv: \"Ed25519\";\n readonly kid: string;\n readonly x: string;\n readonly use: \"sig\";\n}\n\n/** A key directory: a JSON Web Key Set. The operator serves it as JSON at `KEY_DIRECTORY_PATH`, with the type `KEY_DIRECTORY_TYPE`. */\nexport interface KeyDirectory {\n readonly keys: readonly DirectoryKey[];\n}\n\n/** The parts of a request that `verifySignature` reads. */\nexport interface SignedRequest {\n readonly url: string;\n /** The default is `GET`. */\n readonly method?: string | undefined;\n /** These are the headers of the request. A name can be in any case, and a header can have more than one line. */\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>;\n}\n\n/** The result of `verifySignature`. */\nexport interface SignatureCheck {\n /** This value is true when a Web Bot Auth signature of the request verifies with a key of the directory. */\n readonly verified: boolean;\n /** This text is the label of the signature that the check examined last, or null. */\n readonly label: string | null;\n readonly keyId: string | null;\n /** This URL is the member of `Signature-Agent` that the signature covers, or null. */\n readonly agent: string | null;\n /** This text tells why the request does not verify. It is null when it verifies. */\n readonly reason: string | null;\n}\n\n// ── structured fields (RFC 9651): the parts that the signature headers use ──\n\n/** A token of a structured field, for example `ed25519` in `alg=ed25519`. */\nclass Token {\n constructor(readonly value: string) {}\n}\n\ntype BareItem = string | number | boolean | Uint8Array | Token;\ntype Params = ReadonlyArray<readonly [string, BareItem]>;\n\ninterface Item {\n readonly value: BareItem;\n readonly params: Params;\n}\n\ninterface InnerList {\n readonly items: readonly Item[];\n readonly params: Params;\n}\n\ntype Member = Item | InnerList;\n\nconst isInnerList = (member: Member): member is InnerList => \"items\" in member;\n\n/** This error tells that a structured field does not parse. */\nclass FieldError extends Error {}\n\nconst KEY_START = /[a-z*]/;\nconst KEY_CHAR = /[a-z0-9_\\-.*]/;\nconst TOKEN_START = /[A-Za-z*]/;\nconst TOKEN_CHAR = /[!#$%&'*+\\-.^_`|~0-9A-Za-z:/]/;\nconst BASE64 = /^[A-Za-z0-9+/=]*$/;\n\n/** This class reads one structured field from its text. */\nclass FieldParser {\n private at = 0;\n constructor(private readonly text: string) {}\n\n private peek(): string {\n return this.text[this.at] ?? \"\";\n }\n\n private spaces(): void {\n while (this.peek() === \" \") this.at += 1;\n }\n\n private ows(): void {\n while (this.peek() === \" \" || this.peek() === \"\\t\") this.at += 1;\n }\n\n dictionary(): Map<string, Member> {\n const out = new Map<string, Member>();\n this.spaces();\n while (this.at < this.text.length) {\n const key = this.key();\n let member: Member;\n if (this.peek() === \"=\") {\n this.at += 1;\n member = this.peek() === \"(\" ? this.innerList() : this.item();\n } else {\n member = { value: true, params: this.params() };\n }\n out.set(key, member);\n this.ows();\n if (this.at >= this.text.length) break;\n if (this.peek() !== \",\") throw new FieldError(`a \",\" is necessary at ${String(this.at)}`);\n this.at += 1;\n this.ows();\n if (this.at >= this.text.length) throw new FieldError(\"the field ends with a comma\");\n }\n return out;\n }\n\n /** This method reads one item and checks that the field has nothing after it. */\n wholeItem(): Item {\n this.spaces();\n const item = this.item();\n this.spaces();\n if (this.at < this.text.length) throw new FieldError(\"text after the item\");\n return item;\n }\n\n private key(): string {\n if (!KEY_START.test(this.peek())) throw new FieldError(`no key at ${String(this.at)}`);\n const start = this.at;\n while (KEY_CHAR.test(this.peek()) && this.at < this.text.length) this.at += 1;\n return this.text.slice(start, this.at);\n }\n\n private innerList(): InnerList {\n this.at += 1;\n const items: Item[] = [];\n for (;;) {\n this.spaces();\n if (this.peek() === \")\") {\n this.at += 1;\n return { items, params: this.params() };\n }\n if (this.at >= this.text.length) throw new FieldError(\"an inner list has no end\");\n items.push(this.item());\n if (this.peek() !== \" \" && this.peek() !== \")\")\n throw new FieldError(`a space or \")\" is necessary at ${String(this.at)}`);\n }\n }\n\n private item(): Item {\n const value = this.bare();\n return { value, params: this.params() };\n }\n\n private params(): Params {\n const out: Array<readonly [string, BareItem]> = [];\n while (this.peek() === \";\") {\n this.at += 1;\n this.spaces();\n const key = this.key();\n let value: BareItem = true;\n if (this.peek() === \"=\") {\n this.at += 1;\n value = this.bare();\n }\n const known = out.findIndex(([name]) => name === key);\n if (known === -1) out.push([key, value]);\n else out[known] = [key, value];\n }\n return out;\n }\n\n private bare(): BareItem {\n const char = this.peek();\n if (char === \"-\" || /[0-9]/.test(char)) return this.number();\n if (char === '\"') return this.string();\n if (char === \":\") return this.bytes();\n if (char === \"?\") return this.boolean();\n if (TOKEN_START.test(char)) return this.token();\n throw new FieldError(`no item at ${String(this.at)}`);\n }\n\n private number(): number {\n const match = /^-?(\\d{1,15})(\\.\\d{1,3})?/.exec(this.text.slice(this.at));\n if (match === null || (match[2] !== undefined && match[1]!.length > 12))\n throw new FieldError(`not a number at ${String(this.at)}`);\n this.at += match[0].length;\n return Number(match[0]);\n }\n\n private string(): string {\n this.at += 1;\n let out = \"\";\n for (;;) {\n const char = this.peek();\n if (char === \"\") throw new FieldError(\"a string has no end\");\n this.at += 1;\n if (char === \"\\\\\") {\n const next = this.peek();\n if (next !== '\"' && next !== \"\\\\\") throw new FieldError(\"a bad escape in a string\");\n out += next;\n this.at += 1;\n } else if (char === '\"') {\n return out;\n } else {\n const code = char.charCodeAt(0);\n if (code < 0x20 || code > 0x7e)\n throw new FieldError(\"a string holds a character that is not printable ASCII\");\n out += char;\n }\n }\n }\n\n private bytes(): Uint8Array {\n this.at += 1;\n const end = this.text.indexOf(\":\", this.at);\n if (end === -1) throw new FieldError(\"a byte sequence has no end\");\n const text = this.text.slice(this.at, end);\n if (!BASE64.test(text)) throw new FieldError(\"a byte sequence is not base64\");\n this.at = end + 1;\n return new Uint8Array(Buffer.from(text, \"base64\"));\n }\n\n private boolean(): boolean {\n const value = this.text.slice(this.at, this.at + 2);\n if (value !== \"?1\" && value !== \"?0\") throw new FieldError(\"not a boolean\");\n this.at += 2;\n return value === \"?1\";\n }\n\n private token(): Token {\n const start = this.at;\n this.at += 1;\n while (TOKEN_CHAR.test(this.peek()) && this.at < this.text.length) this.at += 1;\n return new Token(this.text.slice(start, this.at));\n }\n}\n\nconst serializeBare = (value: BareItem): string => {\n if (value instanceof Token) return value.value;\n if (value instanceof Uint8Array) return `:${Buffer.from(value).toString(\"base64\")}:`;\n if (typeof value === \"boolean\") return value ? \"?1\" : \"?0\";\n if (typeof value === \"number\")\n return Number.isInteger(value) ? String(value) : String(Math.round(value * 1000) / 1000);\n return `\"${value.replace(/[\"\\\\]/g, (char) => `\\\\${char}`)}\"`;\n};\n\nconst serializeParams = (params: Params): string =>\n params\n .map(([key, value]) => (value === true ? `;${key}` : `;${key}=${serializeBare(value)}`))\n .join(\"\");\n\nconst serializeItem = (item: Item): string =>\n `${serializeBare(item.value)}${serializeParams(item.params)}`;\n\nconst serializeMember = (member: Member): string =>\n isInnerList(member)\n ? `(${member.items.map(serializeItem).join(\" \")})${serializeParams(member.params)}`\n : serializeItem(member);\n\n/** This function tells if a text is a key of a structured field: a label of a signature, for example. */\nconst isKey = (text: string): boolean => /^[a-z*][a-z0-9_\\-.*]*$/.test(text);\n\n// ── keys ──\n\nconst base64url = (bytes: Uint8Array): string => Buffer.from(bytes).toString(\"base64url\");\n\nconst isKeyObject = (key: KeyInput): key is KeyObject =>\n typeof key === \"object\" && key !== null && \"asymmetricKeyType\" in key && \"export\" in key;\n\n/** This function reads a private key. Its errors do not hold the key. */\nconst privateKeyOf = (input: KeyInput): KeyObject => {\n let key: KeyObject;\n try {\n if (isKeyObject(input)) key = input;\n else if (typeof input === \"string\")\n key = input.trim().startsWith(\"{\")\n ? createPrivateKey({ key: JSON.parse(input) as JwkInput, format: \"jwk\" })\n : createPrivateKey(input);\n else if (input instanceof Uint8Array)\n key = createPrivateKey({ key: Buffer.from(input), format: \"der\", type: \"pkcs8\" });\n else key = createPrivateKey({ key: input, format: \"jwk\" });\n } catch (cause) {\n throw new Error(\n \"the private key of the signer does not parse: give an Ed25519 key as PKCS#8 (PEM or DER) or as a JWK\",\n { cause },\n );\n }\n if (key.type !== \"private\")\n throw new Error(\"the key of the signer is not a private key: give the Ed25519 private key\");\n if (key.asymmetricKeyType !== \"ed25519\")\n throw new Error(\n `the key of the signer is ${key.asymmetricKeyType ?? \"not an asymmetric key\"}, not ed25519: Web Bot Auth here uses Ed25519 keys`,\n );\n return key;\n};\n\n/** This function reads a public key, or the public part of a private key. Its errors do not hold the key. */\nconst publicKeyOf = (input: KeyInput): KeyObject => {\n let key: KeyObject;\n try {\n if (isKeyObject(input)) key = input.type === \"private\" ? createPublicKey(input) : input;\n else if (typeof input === \"string\")\n key = input.trim().startsWith(\"{\")\n ? createPublicKey({ key: JSON.parse(input) as JwkInput, format: \"jwk\" })\n : createPublicKey(input);\n else if (input instanceof Uint8Array) {\n const bytes = Buffer.from(input);\n try {\n key = createPublicKey({ key: bytes, format: \"der\", type: \"spki\" });\n } catch {\n key = createPublicKey(createPrivateKey({ key: bytes, format: \"der\", type: \"pkcs8\" }));\n }\n } else key = createPublicKey({ key: input, format: \"jwk\" });\n } catch (cause) {\n throw new Error(\"a key of the directory does not parse: give an Ed25519 key\", { cause });\n }\n if (key.asymmetricKeyType !== \"ed25519\")\n throw new Error(\n `a key of the directory is ${key.asymmetricKeyType ?? \"not an asymmetric key\"}, not ed25519`,\n );\n return key;\n};\n\n/** This function gives the JWK thumbprint of an Ed25519 public key (RFC 7638, RFC 8037 appendix A.3). */\nconst thumbprintOf = (x: string): string =>\n base64url(\n createHash(\"sha256\")\n .update(JSON.stringify({ crv: \"Ed25519\", kty: \"OKP\", x }), \"utf8\")\n .digest(),\n );\n\nconst directoryKeyOf = (key: KeyObject): DirectoryKey => {\n const jwk = key.export({ format: \"jwk\" });\n const x = typeof jwk.x === \"string\" ? jwk.x : \"\";\n return { kty: \"OKP\", crv: \"Ed25519\", kid: thumbprintOf(x), x, use: \"sig\" };\n};\n\n/**\n * This function makes the key directory for the operator to host: a JSON Web Key Set with each\n * public key and its thumbprint as `kid`. It takes public keys, or private keys, and it writes only\n * their public parts. Serve the JSON at `KEY_DIRECTORY_PATH` with the type `KEY_DIRECTORY_TYPE`.\n */\nexport const keyDirectory = (publicKeys: readonly KeyInput[]): KeyDirectory => {\n const keys = new Map<string, DirectoryKey>();\n for (const input of publicKeys) {\n const key = directoryKeyOf(publicKeyOf(input));\n keys.set(key.kid, key);\n }\n return { keys: [...keys.values()] };\n};\n\n// ── signing ──\n\n/** The parts of one signature. The signer and the tests use this function. */\nexport interface SignatureParts {\n /** The value of `@authority`: the host and a port that is not the default port, in lowercase. */\n readonly authority: string;\n readonly label: string;\n /** The key of the member of `Signature-Agent`. The draft uses the label of the signature. */\n readonly agentKey: string;\n /** The serialized value of the member: a string, and its parameters. */\n readonly agentValue: string;\n readonly keyId: string;\n readonly created: number;\n readonly expires: number;\n readonly nonce?: string | undefined;\n}\n\n/** This function gives the `@signature-params` value: the covered components and the parameters, in the order of the draft. */\nconst signatureParams = (parts: SignatureParts): string =>\n `(\"@authority\" \"signature-agent\";key=${serializeBare(parts.agentKey)});created=${String(parts.created)};keyid=${serializeBare(parts.keyId)};alg=\"ed25519\";expires=${String(parts.expires)}${parts.nonce === undefined ? \"\" : `;nonce=${serializeBare(parts.nonce)}`};tag=\"${WEB_BOT_AUTH_TAG}\"`;\n\n/** This function gives the signature base of RFC 9421, section 2.5, for the components of Web Bot Auth. */\nexport const signatureBase = (parts: SignatureParts): string =>\n [\n `\"@authority\": ${parts.authority}`,\n `\"signature-agent\";key=${serializeBare(parts.agentKey)}: ${parts.agentValue}`,\n `\"@signature-params\": ${signatureParams(parts)}`,\n ].join(\"\\n\");\n\n/** This function signs the parts with an Ed25519 key, and gives the three headers. */\nexport const signParts = (\n parts: SignatureParts,\n key: KeyObject,\n): {\n readonly \"signature-agent\": string;\n readonly \"signature-input\": string;\n readonly signature: string;\n} => {\n const signature = signBytes(null, Buffer.from(signatureBase(parts), \"utf8\"), key);\n return {\n \"signature-agent\": `${parts.agentKey}=${parts.agentValue}`,\n \"signature-input\": `${parts.label}=${signatureParams(parts)}`,\n signature: `${parts.label}=:${signature.toString(\"base64\")}:`,\n };\n};\n\nconst DEFAULT_LIFETIME_MS = 5 * 60_000;\nconst MAX_LIFETIME_MS = 24 * 60 * 60_000;\n\n/** This function checks the URL of `Signature-Agent`, and gives the URL and the serialized value of the member. */\nconst agentOf = (\n url: string,\n type: SignatureAgentType,\n): { readonly url: string; readonly value: string } => {\n let target: URL;\n try {\n target = new URL(url);\n } catch {\n throw new Error(\n `signatureAgent \"${url}\" is not a URL: give the https URL of the key directory`,\n );\n }\n if (target.protocol !== \"https:\")\n throw new Error(`signatureAgent \"${url}\" is not an https URL: the draft permits only https`);\n if (target.username !== \"\" || target.password !== \"\" || target.hash !== \"\")\n throw new Error(`signatureAgent \"${url}\" has a user name, a password or a fragment`);\n if (type === \"directory\") {\n if (target.pathname !== \"/\" || target.search !== \"\")\n throw new Error(\n `signatureAgent \"${url}\" is not an origin: the type directory takes only an origin, such as https://bot.example.com. For a key set on a path, set agentType to jwks_uri`,\n );\n return { url: target.origin, value: serializeBare(target.origin) };\n }\n return { url: target.href, value: `${serializeBare(target.href)};type=${type}` };\n};\n\n/**\n * This function makes a signer for Web Bot Auth with an Ed25519 key. Each signature covers\n * `@authority` and the member of `Signature-Agent`. It has a new random nonce, and it expires after\n * `lifetimeMs`. The signer keeps the private key in a closure: no property of the signer holds it.\n */\nexport const createWebBotAuthSigner = (options: WebBotAuthOptions): WebBotAuthSigner => {\n const key = privateKeyOf(options.privateKey);\n const publicKey = directoryKeyOf(createPublicKey(key));\n if (options.keyId !== undefined && options.keyId !== publicKey.kid)\n throw new Error(\n `keyId \"${options.keyId}\" is not the JWK thumbprint of the key (${publicKey.kid}): Web Bot Auth uses the thumbprint as keyid`,\n );\n const agent = agentOf(options.signatureAgent, options.agentType ?? \"directory\");\n const label = options.label ?? \"sig1\";\n if (!isKey(label))\n throw new Error(\n `label \"${label}\" is not a key of a structured field: use lowercase letters, digits, \"_\", \"-\", \".\" and \"*\"`,\n );\n const lifetimeMs = options.lifetimeMs ?? DEFAULT_LIFETIME_MS;\n if (!Number.isFinite(lifetimeMs) || lifetimeMs < 1000 || lifetimeMs > MAX_LIFETIME_MS)\n throw new RangeError(\n `lifetimeMs must be from 1000 (1 second) to ${String(MAX_LIFETIME_MS)} (24 hours), not ${String(lifetimeMs)}`,\n );\n const { kty, crv, x } = publicKey;\n return {\n keyId: publicKey.kid,\n signatureAgent: agent.url,\n publicKey: { kty, crv, x },\n sign(url, now) {\n const created = Math.floor(now / 1000);\n return signParts(\n {\n authority: url.host.toLowerCase(),\n label,\n agentKey: label,\n agentValue: agent.value,\n keyId: publicKey.kid,\n created,\n expires: created + Math.ceil(lifetimeMs / 1000),\n nonce: randomBytes(32).toString(\"base64\"),\n },\n key,\n );\n },\n };\n};\n\n/** This function tells if a value is a request signer, not the options of one. */\nexport const isRequestSigner = (value: RequestSigner | WebBotAuthOptions): value is RequestSigner =>\n typeof (value as Partial<RequestSigner>).sign === \"function\";\n\n/** This function gives a request signer: the signer itself, or a new signer from its options. */\nexport const signerOf = (\n value: RequestSigner | WebBotAuthOptions | undefined,\n): RequestSigner | undefined =>\n value === undefined ? undefined : isRequestSigner(value) ? value : createWebBotAuthSigner(value);\n\n// ── verification ──\n\n/** A signature can start this long before the clock of the verifier, for a small difference of the clocks. */\nconst CLOCK_SKEW_S = 60;\n\nconst headerLines = (\n headers: SignedRequest[\"headers\"],\n name: string,\n): readonly string[] | undefined => {\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== name || value === undefined) continue;\n return typeof value === \"string\" ? [value] : value;\n }\n return undefined;\n};\n\n/** This function gives the value of a header as RFC 9421 section 2.1 combines it: each line without its outer spaces, with \", \" between. */\nconst fieldValue = (headers: SignedRequest[\"headers\"], name: string): string | undefined =>\n headerLines(headers, name)\n ?.map((line) => line.replace(/\\r?\\n[ \\t]+/g, \" \").trim())\n .join(\", \");\n\n/** This function gives the value of one component of a request, or an error text. */\nconst componentValue = (\n component: Item,\n request: SignedRequest,\n target: URL,\n): { readonly value: string } | { readonly error: string } => {\n const name = component.value;\n if (typeof name !== \"string\") return { error: \"a component is not a string\" };\n const params = new Map(component.params);\n if (name.startsWith(\"@\")) {\n if (params.size > 0) return { error: `the parameters of ${name} are not supported` };\n switch (name) {\n case \"@method\":\n return { value: (request.method ?? \"GET\").toUpperCase() };\n case \"@authority\":\n return { value: target.host.toLowerCase() };\n case \"@scheme\":\n return { value: target.protocol.slice(0, -1).toLowerCase() };\n case \"@target-uri\":\n return { value: target.href };\n case \"@path\":\n return { value: target.pathname === \"\" ? \"/\" : target.pathname };\n case \"@query\":\n return { value: target.search === \"\" ? \"?\" : target.search };\n case \"@request-target\":\n return { value: `${target.pathname}${target.search}` };\n default:\n return { error: `the component ${name} is not supported` };\n }\n }\n const value = fieldValue(request.headers, name);\n if (value === undefined) return { error: `the request has no ${name} header` };\n if ([...params.keys()].some((param) => param !== \"key\"))\n return { error: `the parameters of ${name} are not supported` };\n const key = params.get(\"key\");\n if (key === undefined) return { value };\n if (typeof key !== \"string\") return { error: `the key of ${name} is not a string` };\n let member: Member | undefined;\n try {\n member = new FieldParser(value).dictionary().get(key);\n } catch (error) {\n return { error: `the ${name} header is not a dictionary: ${(error as Error).message}` };\n }\n if (member === undefined) return { error: `the ${name} header has no member ${key}` };\n return { value: serializeMember(member) };\n};\n\nconst failed = (\n reason: string,\n facts: { label?: string | null; keyId?: string | null; agent?: string | null } = {},\n): SignatureCheck => ({\n verified: false,\n label: facts.label ?? null,\n keyId: facts.keyId ?? null,\n agent: facts.agent ?? null,\n reason,\n});\n\n/**\n * This function verifies the Web Bot Auth signatures of a request with the keys of a directory. It\n * applies RFC 9421 section 3.2 and the profile of the draft. The profile asks for the tag\n * `web-bot-auth`, `created`, `expires` and `@authority`. It also asks for the member of\n * `Signature-Agent`, and a key with the thumbprint of `keyid`. The tests use it, and an operator can check a setup with it. It sends no request.\n */\nexport const verifySignature = (\n request: SignedRequest,\n directory: KeyDirectory,\n options: { readonly now?: number | undefined } = {},\n): SignatureCheck => {\n let target: URL;\n try {\n target = new URL(request.url);\n } catch {\n return failed(\"the URL of the request is not a URL\");\n }\n const inputText = fieldValue(request.headers, \"signature-input\");\n const signatureText = fieldValue(request.headers, \"signature\");\n if (inputText === undefined || signatureText === undefined)\n return failed(\"the request has no Signature-Input or no Signature header\");\n let inputs: Map<string, Member>;\n let signatures: Map<string, Member>;\n try {\n inputs = new FieldParser(inputText).dictionary();\n signatures = new FieldParser(signatureText).dictionary();\n } catch (error) {\n return failed(`a signature header does not parse: ${(error as Error).message}`);\n }\n const now = Math.floor((options.now ?? Date.now()) / 1000);\n const keys = new Map<string, KeyObject>();\n for (const entry of directory.keys) {\n try {\n const key = createPublicKey({\n key: { kty: entry.kty, crv: entry.crv, x: entry.x },\n format: \"jwk\",\n });\n keys.set(thumbprintOf(entry.x), key);\n } catch {\n // a key that does not parse verifies nothing\n }\n }\n let last: SignatureCheck = failed(\"the request has no Web Bot Auth signature\");\n for (const [label, input] of inputs) {\n if (!isInnerList(input)) continue;\n const params = new Map(input.params);\n if (params.get(\"tag\") !== WEB_BOT_AUTH_TAG) continue;\n const keyId = typeof params.get(\"keyid\") === \"string\" ? (params.get(\"keyid\") as string) : null;\n const covered = input.items;\n const names = covered.map((item) => serializeItem(item));\n const agentItem = covered.find((item) => item.value === \"signature-agent\");\n let agent: string | null = null;\n if (agentItem !== undefined) {\n const value = componentValue(agentItem, request, target);\n if (\"value\" in value) {\n try {\n const member = new FieldParser(value.value).wholeItem().value;\n agent = typeof member === \"string\" ? member : null;\n } catch {\n agent = null;\n }\n }\n }\n const facts = { label, keyId, agent };\n if (new Set(names).size !== names.length) {\n last = failed(\"a component is covered two times\", facts);\n continue;\n }\n if (!names.includes('\"@authority\"') && !names.includes('\"@target-uri\"')) {\n last = failed(\"the signature does not cover @authority or @target-uri\", facts);\n continue;\n }\n if (agentItem === undefined) {\n last = failed(\"the signature does not cover Signature-Agent\", facts);\n continue;\n }\n const created = params.get(\"created\");\n const expires = params.get(\"expires\");\n if (typeof created !== \"number\" || typeof expires !== \"number\") {\n last = failed(\"the signature has no created or no expires\", facts);\n continue;\n }\n if (expires <= now) {\n last = failed(\"the signature expired\", facts);\n continue;\n }\n if (created > now + CLOCK_SKEW_S) {\n last = failed(\"the signature was created in the future\", facts);\n continue;\n }\n const alg = params.get(\"alg\");\n if (alg !== undefined && alg !== \"ed25519\") {\n last = failed(`the algorithm ${serializeBare(alg)} is not ed25519`, facts);\n continue;\n }\n const key = keyId === null ? undefined : keys.get(keyId);\n if (key === undefined) {\n last = failed(\"no key of the directory has the thumbprint of keyid\", facts);\n continue;\n }\n const lines: string[] = [];\n let error: string | undefined;\n for (const item of covered) {\n const value = componentValue(item, request, target);\n if (\"error\" in value) {\n error = value.error;\n break;\n }\n lines.push(`${serializeItem(item)}: ${value.value}`);\n }\n if (error !== undefined) {\n last = failed(error, facts);\n continue;\n }\n lines.push(`\"@signature-params\": ${serializeMember(input)}`);\n const signature = signatures.get(label);\n if (\n signature === undefined ||\n isInnerList(signature) ||\n !(signature.value instanceof Uint8Array)\n ) {\n last = failed(`the Signature header has no byte sequence for ${label}`, facts);\n continue;\n }\n const good = verifyBytes(null, Buffer.from(lines.join(\"\\n\"), \"utf8\"), key, signature.value);\n if (good) return { verified: true, label, keyId, agent, reason: null };\n last = failed(\"the signature does not match the signature base\", facts);\n }\n return last;\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "polite-crawler",
4
- "version": "0.1.4",
5
- "mcpName": "io.github.mark1russell7/polite-crawler",
6
- "description": "A polite web crawler for research and AI agents. It fetches a source once, keeps it, and reads it many times: robots.txt obeyed, one request per site, full-text search, an MCP server.",
4
+ "version": "0.2.1",
5
+ "description": "A polite web crawler for research and AI agents. It fetches a source once, keeps it, and reads it many times: robots.txt obeyed, one request per site, full-text search. The MCP server is the package polite-crawler-mcp.",
7
6
  "keywords": [
8
7
  "crawler",
9
8
  "web-crawler",
@@ -15,7 +14,6 @@
15
14
  "fetch",
16
15
  "cache",
17
16
  "mcp",
18
- "mcp-server",
19
17
  "model-context-protocol",
20
18
  "claude",
21
19
  "ai-agent",
@@ -50,10 +48,6 @@
50
48
  "types": "./build/index.d.mts",
51
49
  "default": "./build/index.mjs"
52
50
  },
53
- "./mcp": {
54
- "types": "./build/mcp.d.mts",
55
- "default": "./build/mcp.mjs"
56
- },
57
51
  "./robots": {
58
52
  "types": "./build/robots.d.mts",
59
53
  "default": "./build/robots.mjs"
@@ -95,12 +89,10 @@
95
89
  "node": ">=22.19"
96
90
  },
97
91
  "dependencies": {
98
- "@modelcontextprotocol/sdk": "^1.32.1",
99
92
  "@mozilla/readability": "^0.6.0",
100
93
  "linkedom": "^0.18.13",
101
94
  "undici": "^8.11.2",
102
- "unpdf": "^1.8.1",
103
- "zod": "^4.6.6"
95
+ "unpdf": "^1.8.1"
104
96
  },
105
97
  "peerDependencies": {
106
98
  "@huggingface/transformers": "^3.0.0 || ^4.0.0",
@@ -117,6 +109,8 @@
117
109
  "devDependencies": {
118
110
  "@huggingface/transformers": "^4.3.1",
119
111
  "@mark1russell7/cue": "github:mark1russell7/cue",
112
+ "@microsoft/api-extractor": "^7.59.3",
113
+ "@modelcontextprotocol/sdk": "^1.32.1",
120
114
  "@types/node": "^22.19.0",
121
115
  "@types/turndown": "^5.0.6",
122
116
  "@vitest/coverage-v8": "5.0.3",
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser-CKRNbIDT.mjs","names":[],"sources":["../src/browser.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport type { Renderer } from \"./render.ts\";\nimport { NO_PLAYWRIGHT, RenderRefused } from \"./render.ts\";\n\n/** The text of the note when `playwright-core` is installed, but its browser is not. */\nexport const NO_BROWSER =\n \"rendering needs a browser: npx playwright-core install chromium-headless-shell\";\n\n/** This is the part of `playwright-core` that the check uses. */\ninterface Chromium {\n executablePath(): string;\n}\n\n/**\n * This function tells if the default renderer can start a browser. It gives no value when it can,\n * and the reason when it cannot. The command line and the MCP server use it one time at start.\n * Then a missing browser costs no wait and no error capture for each page that needs JavaScript.\n *\n * The check does not start the browser. It finds the full Chromium build or the headless shell of\n * the same revision on the disk. When the check cannot find the answer, it gives no value, and the\n * renderer then tries.\n */\nexport const browserMissing = async (\n load: () => Promise<{ readonly chromium: Chromium }> = () => import(\"playwright-core\"),\n): Promise<string | undefined> => {\n let chromium: Chromium;\n try {\n ({ chromium } = await load());\n } catch {\n return NO_PLAYWRIGHT;\n }\n try {\n const executable = chromium.executablePath();\n if (existsSync(executable)) return undefined;\n // the headless shell is in a folder next to the folder of the full build, with the same revision\n let folder = dirname(executable);\n while (dirname(folder) !== folder && !/^chromium-\\d+$/.test(basename(folder)))\n folder = dirname(folder);\n const revision = /^chromium-(\\d+)$/.exec(basename(folder))?.[1];\n if (revision === undefined) return undefined;\n return existsSync(join(dirname(folder), `chromium_headless_shell-${revision}`))\n ? undefined\n : NO_BROWSER;\n } catch {\n return undefined;\n }\n};\n\n/**\n * This function gives a renderer that checks one time, before its first page, that the browser is\n * on the disk (`browserMissing`). With no browser, each page gets a `RenderRefused` with the reason\n * `browser` at once, and the renderer opens nothing. `createRuntime` puts its default renderer in\n * it, so a library with no browser costs no wait and no error capture.\n */\nexport const checkedRenderer = (\n renderer: Renderer,\n check: () => Promise<string | undefined> = () => browserMissing(),\n): Renderer => {\n let missing: Promise<string | undefined> | undefined;\n return {\n async render(url, options) {\n missing ??= check().catch(() => undefined);\n const reason = await missing;\n if (reason !== undefined) throw new RenderRefused(\"browser\", reason);\n return renderer.render(url, options);\n },\n close: () => renderer.close(),\n };\n};\n"],"mappings":";;;;;;;;;;;AAMA,MAAa,aACX;;;;;;;;;;AAgBF,MAAa,iBAAiB,OAC5B,aAA6D,OAAO,uBACpC;CAChC,IAAI;CACJ,IAAI;EACF,CAAC,CAAE,YAAa,MAAM,KAAK;CAC7B,QAAQ;EACN,OAAO;CACT;CACA,IAAI;EACF,MAAM,aAAa,SAAS,eAAe;EAC3C,IAAI,WAAW,UAAU,GAAG,OAAO,KAAA;EAEnC,IAAI,SAAS,QAAQ,UAAU;EAC/B,OAAO,QAAQ,MAAM,MAAM,UAAU,CAAC,iBAAiB,KAAK,SAAS,MAAM,CAAC,GAC1E,SAAS,QAAQ,MAAM;EACzB,MAAM,WAAW,mBAAmB,KAAK,SAAS,MAAM,CAAC,CAAC,GAAG;EAC7D,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;EACnC,OAAO,WAAW,KAAK,QAAQ,MAAM,GAAG,2BAA2B,UAAU,CAAC,IAC1E,KAAA,IACA;CACN,QAAQ;EACN;CACF;AACF;;;;;;;AAQA,MAAa,mBACX,UACA,cAAiD,eAAe,MACnD;CACb,IAAI;CACJ,OAAO;EACL,MAAM,OAAO,KAAK,SAAS;GACzB,YAAY,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;GACzC,MAAM,SAAS,MAAM;GACrB,IAAI,WAAW,KAAA,GAAW,MAAM,IAAI,cAAc,WAAW,MAAM;GACnE,OAAO,SAAS,OAAO,KAAK,OAAO;EACrC;EACA,aAAa,SAAS,MAAM;CAC9B;AACF"}
@@ -1,51 +0,0 @@
1
- //#region src/classify.d.ts
2
- /**
3
- * The outcome tells what a response means for the crawl. The crawler decides it before it keeps
4
- * the body as content. The crawler sees a refusal and stops. It does not try again with a
5
- * different identity.
6
- *
7
- * - `challenge`: a bot challenge page. Cloudflare marks it with `cf-mitigated: challenge`. AWS WAF
8
- * answers 202 (challenge) or 405 (CAPTCHA) with an `x-amzn-waf-action` header. The host stops
9
- * for 24 hours.
10
- * - `payment-required` (402): a site that wants payment or a license for a crawl. The crawler
11
- * records the price and does not pay. The host stops for 24 hours.
12
- * - `blocked` (401, 403): RFC 9110 tells a client not to send the request again automatically.
13
- * The host stops for 1 hour.
14
- * - `rate-limited` (429) and `unavailable` (502, 503, 504): the host waits until the time of
15
- * Retry-After, or for a backoff with jitter. A busy gateway is a reason to wait, not a failure.
16
- * - `error`: a different status, or no answer. A 5xx or no answer counts toward the stop of the
17
- * host. A different 4xx (a 400 or a 422) is a problem of the request, and it does not count.
18
- *
19
- * The politeness policy (`PolitenessPolicy`) sets each time of a stop.
20
- */
21
- type Outcome = "ok" | "not-modified" | "redirect" | "challenge" | "payment-required" | "blocked" | "not-found" | "rate-limited" | "unavailable" | "error";
22
- interface Classified {
23
- readonly outcome: Outcome;
24
- /** This value is the wait that the server asked for, when it asked for one. */
25
- readonly retryAfterMs: number | undefined;
26
- readonly note: string | undefined;
27
- }
28
- /**
29
- * This function reads an HTTP date in the three forms of RFC 9110 §5.6.7: the IMF-fixdate, the
30
- * RFC 850 form and the asctime form. It gives milliseconds since the epoch, or no value for a text
31
- * in a different form. It does not use `Date.parse`, which reads many texts that are not dates.
32
- */
33
- declare const parseHttpDate: (value: string | undefined) => number | undefined;
34
- /**
35
- * Retry-After is either delta-seconds or an HTTP date (RFC 9110 §10.2.3). The function reads only
36
- * these two forms. A different text, for example `1.5` or `-5`, gives no value, so the backoff
37
- * applies.
38
- */
39
- declare const parseRetryAfter: (value: string | undefined, now: number) => number | undefined;
40
- /** After these outcomes, the crawler does not ask the host again until the stop ends. */
41
- declare const STOPS_HOST: ReadonlySet<Outcome>;
42
- /**
43
- * This function tells if a status is a client error with no meaning for the host. Such a status
44
- * is a 4xx other than 401, 402, 403, 404, 408, 410 and 429. The request was wrong, and a
45
- * different request can succeed. Thus the failure count of the host does not change.
46
- */
47
- declare const isClientError: (status: number) => boolean;
48
- declare const classify: (status: number, header: (name: string) => string | undefined, now: number) => Classified;
49
- //#endregion
50
- export { isClientError as a, classify as i, Outcome as n, parseHttpDate as o, STOPS_HOST as r, parseRetryAfter as s, Classified as t };
51
- //# sourceMappingURL=classify-CrfyXcwo.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"daemon-DK6U51_v.mjs","names":[],"sources":["../src/daemon.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n appendFileSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { browserMissing } from \"./browser.ts\";\nimport type { Duration } from \"./duration.ts\";\nimport { durationMs } from \"./duration.ts\";\nimport type { OpenOptions, OpenedCrawler } from \"./open.ts\";\nimport { openCrawler } from \"./open.ts\";\nimport type { Clock } from \"./politeness.ts\";\nimport { realClock } from \"./politeness.ts\";\nimport { VERSION } from \"./version.ts\";\nimport type { WatchEntry, WatchEvent, WatchList, WatchSummary } from \"./watch.ts\";\n\n/*\n * The daemon is a local process that runs for a long time. It does these jobs:\n *\n * - It checks the watch list on a schedule (`scheduleWatches`). Each entry that is due gets its\n * check at its time, with each polite rule of `watch.run`.\n * - It writes each change as one JSON line, and it can start a command of the operator for it.\n * - It can serve the MCP tools over HTTP, with the long tools as MCP tasks. This part loads the MCP\n * SDK only when the daemon serves MCP, through a small seam (`DaemonMcpHost`).\n * - It writes a small state file, which `polite-crawler daemon status` reads.\n */\n\n/** These are the defaults of the daemon. */\nexport const DAEMON_DEFAULTS = {\n /** The longest wait between two ticks: 1 minute. A tick comes sooner when an entry is due sooner. */\n tickMs: 60_000,\n /** The most random time that the daemon adds to each wait: 10 seconds. */\n jitterMs: 10_000,\n /** The budget of the checks of one tick: 10 minutes. No request starts after it. */\n budgetMs: 600_000,\n /** The most checks of one tick. */\n maxPages: 100,\n /** At a stop, the checks in progress get this time to end: 10 seconds. Then they abort. */\n graceMs: 10_000,\n /** The shortest wait between two ticks: 1 second. */\n minGapMs: 1_000,\n /** The shortest tick: 1 second. */\n minTickMs: 1_000,\n /** The status lists this number of the next due entries. */\n dueShown: 10,\n /** An on-change command that runs longer than this time stops: 1 minute. */\n hookTimeoutMs: 60_000,\n} as const;\n\n/** This value is the version of the format of the state file. */\nexport const DAEMON_STATE_SCHEMA = 1;\n\n/** This is a watch entry that is due soon, as the status shows it. */\nexport interface DaemonDue {\n readonly url: string;\n /** This time (ISO 8601) is the time when the entry is due. */\n readonly next_at: string;\n readonly every_ms: number;\n /** This value is the kind of the last check, or null before the first check. */\n readonly last_kind: string | null;\n}\n\n/** This is one tick of the schedule: one run of the watch list. */\nexport interface DaemonTick {\n /** This time (ISO 8601) is the start of the run. */\n readonly started_at: string;\n /** This time (ISO 8601) is the end of the run. */\n readonly ended_at: string;\n /** This value is the summary of the run, or null when the run failed. */\n readonly summary: WatchSummary | null;\n /** This text tells why the run failed, or null. */\n readonly error: string | null;\n /** This time (ISO 8601) is the time of the next tick, or null when the schedule stops. */\n readonly next_at: string | null;\n /** These entries are the next due entries after the run, the first due first. */\n readonly due: readonly DaemonDue[];\n}\n\n/** These options control the schedule of the watch list. */\nexport interface ScheduleOptions {\n /** This value is the longest wait between two ticks. The default is `1m`, and the floor is `1s`. */\n readonly tick?: Duration | undefined;\n /** This value is the most random time that the schedule adds to each wait. The default is `10s`. */\n readonly jitter?: Duration | undefined;\n /** This value is the budget of the checks of one tick. The default is `10m`. */\n readonly budget?: Duration | undefined;\n /** This value is the most checks of one tick. The default is 100. */\n readonly maxPages?: number | undefined;\n /** This clock gives the time and the waits. Give the clock of the crawler. The default is the real clock. */\n readonly clock?: Clock | undefined;\n /** This function gives the random numbers of the jitter. The default is `Math.random`. */\n readonly random?: (() => number) | undefined;\n /** This function gets each event of each check. */\n readonly onEvent?: ((event: WatchEvent) => void) | undefined;\n /** This function gets each tick when its run ends. */\n readonly onTick?: ((tick: DaemonTick) => void) | undefined;\n /** This function gets the error of a run that failed. The schedule goes on at the next tick. */\n readonly onError?: ((error: Error) => void) | undefined;\n}\n\n/** This is the result of a schedule: the counts so far, and the last tick. */\nexport interface ScheduleResult {\n readonly ticks: number;\n /** This value counts the checks that gave an event. */\n readonly checked: number;\n /** This value counts the confirmed changes. */\n readonly changes: number;\n readonly last: DaemonTick | null;\n}\n\n/** A schedule that runs. `stop` ends it, and `done` resolves when it ends. */\nexport interface WatchSchedule {\n /** This promise resolves when the schedule ends. It does not reject. */\n readonly done: Promise<ScheduleResult>;\n /**\n * This method stops the schedule. The run in progress starts no new check. The checks in\n * progress get `grace` (10 seconds by default) to end and write their state. Then they abort,\n * and the run releases their leases. A second call with a shorter grace shortens it.\n */\n stop(grace?: Duration): Promise<ScheduleResult>;\n /** This method gives the counts so far. */\n result(): ScheduleResult;\n}\n\nconst iso = (ms: number): string => new Date(ms).toISOString();\n\n/** This function reads a duration of an option, with a floor. A bad value throws a `RangeError`. */\nconst msOf = (name: string, value: Duration, floor: number): number => {\n let ms: number;\n try {\n ms = durationMs(value);\n } catch (error) {\n throw new RangeError(`${name}: ${(error as Error).message}`, { cause: error });\n }\n if (ms < floor)\n throw new RangeError(`${name} must be ${String(floor)} ms or more, not ${String(ms)}`);\n return ms;\n};\n\nconst pagesOf = (value: number | undefined): number => {\n const pages = value ?? DAEMON_DEFAULTS.maxPages;\n if (!Number.isInteger(pages) || pages < 1)\n throw new RangeError(`maxPages must be an integer of 1 or more, not ${String(pages)}`);\n return pages;\n};\n\nconst errorOf = (error: unknown): Error =>\n error instanceof Error ? error : new Error(String(error));\n\n/** This function gives the next due entries of a list, as the status shows them. */\nconst dueOf = (entries: readonly WatchEntry[]): DaemonDue[] =>\n [...entries]\n .sort((a, b) => Date.parse(a.next_at) - Date.parse(b.next_at) || a.url.localeCompare(b.url))\n .slice(0, DAEMON_DEFAULTS.dueShown)\n .map((entry) => ({\n url: entry.url,\n next_at: entry.next_at,\n every_ms: entry.every_ms,\n last_kind: entry.last_kind,\n }));\n\n/**\n * This function gives the time of the next tick. It is the time when the first entry is due, or\n * the end of its lease. It is not later than one tick, and not sooner than `minGapMs`. A random\n * jitter from 0 to `jitterMs` comes after it, so a check does not come before its time.\n *\n * When the budget or `maxPages` of a run left due entries, they wait for the next tick, so one\n * tick does not exceed its budget. A run that failed also waits one tick.\n */\nexport const nextTickAt = (\n entries: readonly WatchEntry[],\n summary: WatchSummary | null,\n now: number,\n options: { readonly tickMs: number; readonly jitterMs: number; readonly random: () => number },\n): number => {\n let next = now + options.tickMs;\n const leftover = summary === null || summary.stopped !== \"done\";\n for (const entry of entries) {\n const lease = entry.leased_until === null ? 0 : Date.parse(entry.leased_until);\n const due = Math.max(Date.parse(entry.next_at), lease);\n if (due <= now && leftover) continue;\n next = Math.min(next, Math.max(due, now + DAEMON_DEFAULTS.minGapMs));\n }\n const random = Math.min(Math.max(options.random(), 0), 1);\n return next + Math.min(options.jitterMs, Math.floor(random * options.jitterMs));\n};\n\n/**\n * This function checks a watch list on a schedule. The first tick starts at once. Each tick is one\n * `watch.run` with the budget and `maxPages` of a tick. Thus each polite rule of the run applies:\n * one request at a time to each host, the stops of a host, `Retry-After` and robots.txt.\n *\n * After a run, the schedule waits for the next tick (`nextTickAt`). An error of a run goes to\n * `onError`, and the schedule goes on.\n */\nexport const scheduleWatches = (watch: WatchList, options: ScheduleOptions = {}): WatchSchedule => {\n const tickMs = msOf(\"tick\", options.tick ?? DAEMON_DEFAULTS.tickMs, DAEMON_DEFAULTS.minTickMs);\n const jitterMs = msOf(\"jitter\", options.jitter ?? DAEMON_DEFAULTS.jitterMs, 0);\n const budgetMs = msOf(\"budget\", options.budget ?? DAEMON_DEFAULTS.budgetMs, 0);\n const maxPages = pagesOf(options.maxPages);\n const clock = options.clock ?? realClock;\n const random = options.random ?? Math.random;\n // the drain starts no new check, the cancel aborts the checks in progress, the wake ends a wait\n const drain = new AbortController();\n const cancel = new AbortController();\n const wake = new AbortController();\n let stopping = false;\n const counts = { ticks: 0, checked: 0, changes: 0, last: null as DaemonTick | null };\n const result = (): ScheduleResult => ({ ...counts });\n const report = (error: unknown): void => {\n try {\n options.onError?.(errorOf(error));\n } catch {\n // an error of the error handler stops nothing\n }\n };\n const safely = (work: () => void): void => {\n try {\n work();\n } catch (error) {\n report(error);\n }\n };\n\n const loop = async (): Promise<void> => {\n while (!stopping) {\n const started = clock.now();\n let summary: WatchSummary | null = null;\n let failure: string | null = null;\n try {\n const run = watch.run({\n budgetMs,\n maxPages,\n signal: cancel.signal,\n drain: drain.signal,\n });\n for await (const event of run) {\n counts.checked += 1;\n if (event.confirmed) counts.changes += 1;\n safely(() => options.onEvent?.(event));\n }\n summary = await run.summary;\n } catch (error) {\n failure = errorOf(error).message;\n report(error);\n }\n counts.ticks += 1;\n // the list gives the next due entries to the status, also at the last tick\n let entries: WatchEntry[] = [];\n try {\n entries = await watch.list();\n } catch (error) {\n report(error);\n }\n const next = stopping\n ? null\n : nextTickAt(entries, summary, clock.now(), { tickMs, jitterMs, random });\n const tick: DaemonTick = {\n started_at: iso(started),\n ended_at: iso(clock.now()),\n summary,\n error: failure,\n next_at: next === null ? null : iso(next),\n due: dueOf(entries),\n };\n counts.last = tick;\n safely(() => options.onTick?.(tick));\n if (next === null || stopping) break;\n await clock.sleep(Math.max(0, next - clock.now()), wake.signal);\n }\n };\n\n const done = loop().then(result, (error: unknown) => {\n report(error);\n return result();\n });\n return {\n done,\n result,\n stop(grace = DAEMON_DEFAULTS.graceMs) {\n const graceMs = msOf(\"grace\", grace, 0);\n stopping = true;\n drain.abort(new Error(\"the schedule stops\"));\n wake.abort(new Error(\"the schedule stops\"));\n if (graceMs === 0) cancel.abort(new Error(\"the schedule stopped at once\"));\n else {\n // a real timer: the grace is time of the process, not of the clock of the crawler\n const timer = setTimeout(\n () => cancel.abort(new Error(\"the grace time of the stop ended\")),\n graceMs,\n );\n void done.finally(() => clearTimeout(timer));\n }\n return done;\n },\n };\n};\n\n// ── the state file ──────────────────────────────────────────────────────────\n\n/** This is a task of the MCP server, as the status shows it. */\nexport interface DaemonTask {\n readonly id: string;\n /** This value is the name of the tool, for example `crawl_site`. */\n readonly tool: string;\n /** This value is `working`, `input_required`, `completed`, `failed` or `cancelled`. */\n readonly status: string;\n /** This text tells the progress, for example `3 of 10 done`, or null. */\n readonly message: string | null;\n readonly created_at: string;\n readonly updated_at: string;\n}\n\n/** This is the status of a daemon. The state file (`<store>/daemon/state.json`) holds the same JSON. */\nexport interface DaemonStatus {\n /** This value is the version of the format (`DAEMON_STATE_SCHEMA`). */\n readonly schema: number;\n /** This value is the version of polite-crawler. */\n readonly version: string;\n /** This value is the id of the process of the daemon. */\n readonly pid: number;\n readonly store: string;\n /** This value is false after the daemon stopped. */\n readonly running: boolean;\n readonly started_at: string;\n readonly updated_at: string;\n readonly stopped_at: string | null;\n readonly tick_ms: number;\n readonly jitter_ms: number;\n readonly budget_ms: number;\n readonly max_pages: number;\n /** This value counts the ticks. */\n readonly ticks: number;\n /** This value counts the checks. */\n readonly checked: number;\n /** This value counts the confirmed changes. */\n readonly changes: number;\n readonly last_tick: DaemonTick | null;\n /** This time (ISO 8601) is the time of the next tick, or null. */\n readonly next_tick_at: string | null;\n /** These entries are the next due entries at the last tick, the first due first. */\n readonly next_due: readonly DaemonDue[];\n /** These tasks of the MCP server did not end. */\n readonly tasks: readonly DaemonTask[];\n /** This value is the MCP endpoint and the file of its token, or null with no MCP server. */\n readonly mcp: { readonly url: string; readonly token_file: string | null } | null;\n /** This value is the last error: of a tick, of the event log, of an on-change hook or of the MCP server. */\n readonly last_error: { readonly at: string; readonly message: string } | null;\n}\n\n/** This is a status that `readDaemonState` read from a state file. */\nexport interface DaemonState extends DaemonStatus {\n /** This value is true when the daemon runs: the state says so, and its process is alive. */\n readonly alive: boolean;\n}\n\n/** These state files belong to a daemon that runs in this process. */\nconst RUNNING = new Set<string>();\n\n/** This function tells if a process is alive. A process of a different user is alive too. */\nconst processAlive = (pid: number): boolean => {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as { code?: unknown }).code === \"EPERM\";\n }\n};\n\n/** This function gives the path of the state file of a store. */\nexport const daemonStateFile = (dir: string): string => join(dir, \"daemon\", \"state.json\");\n\n/**\n * This function reads the state file of a daemon. It gives undefined when the file is not there,\n * or when it is not a state file. `alive` tells if the daemon runs.\n */\nexport const readDaemonState = (\n dir: string,\n file: string = daemonStateFile(dir),\n): DaemonState | undefined => {\n let state: DaemonStatus;\n try {\n state = JSON.parse(readFileSync(file, \"utf8\")) as DaemonStatus;\n } catch {\n return undefined;\n }\n if (typeof state !== \"object\" || state === null || typeof state.pid !== \"number\")\n return undefined;\n const alive =\n state.running === true &&\n (state.pid === process.pid ? RUNNING.has(file) : processAlive(state.pid));\n return { ...state, alive };\n};\n\n/** This function writes a state file for the owner only. A rename makes the change atomic where the system can. */\nconst writeState = (file: string, state: DaemonStatus): void => {\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 });\n const text = `${JSON.stringify(state, null, 2)}\\n`;\n const temp = `${file}.${String(process.pid)}.tmp`;\n writeFileSync(temp, text, { mode: 0o600 });\n try {\n renameSync(temp, file);\n } catch {\n // Windows can refuse the rename while a reader has the file open\n writeFileSync(file, text, { mode: 0o600 });\n try {\n unlinkSync(temp);\n } catch {\n // the next write replaces it\n }\n }\n};\n\n/** This function writes a new random token to a file for the owner only, and gives the token. */\nconst writeToken = (file: string): string => {\n const token = randomBytes(32).toString(\"base64url\");\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 });\n try {\n // a new file gets the mode: an old file can have a wider mode\n unlinkSync(file);\n } catch {\n // no old file\n }\n writeFileSync(file, `${token}\\n`, { mode: 0o600, flag: \"wx\" });\n return token;\n};\n\n// ── the on-change command ───────────────────────────────────────────────────\n\n/** These options start an on-change command. */\nexport interface ChangeCommandOptions {\n /** A command that runs longer than this time stops. The default is `1m`. */\n readonly timeout?: Duration | undefined;\n /** This folder is the working folder of the command. The default is the working folder of the daemon. */\n readonly cwd?: string | undefined;\n}\n\n/**\n * This function gives an `onChange` hook that starts a command of the operator for each change.\n * The command operates in the shell of the system, as the user of the daemon. It gets the event as\n * one JSON line on its standard input, not in its command line. Thus no text of a page goes into\n * the command line. The output of the command goes nowhere. A command that exits with a code that\n * is not 0, or that runs too long, rejects with the end of its standard error.\n *\n * The command is the decision of the operator. Do not take it from a page or from an assistant.\n */\nexport const changeCommand = (\n command: string,\n options: ChangeCommandOptions = {},\n): ((event: WatchEvent) => Promise<void>) => {\n if (command.trim() === \"\") throw new RangeError(\"the on-change command is empty\");\n const timeoutMs = msOf(\"timeout\", options.timeout ?? DAEMON_DEFAULTS.hookTimeoutMs, 1);\n return (event) =>\n new Promise<void>((resolve, reject) => {\n const child = spawn(command, {\n shell: true,\n stdio: [\"pipe\", \"ignore\", \"pipe\"],\n windowsHide: true,\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n });\n let tail = \"\";\n let late = false;\n const timer = setTimeout(() => {\n late = true;\n child.kill();\n }, timeoutMs);\n child.stderr.setEncoding(\"utf8\");\n child.stderr.on(\"data\", (text: string) => {\n tail = (tail + text).slice(-2000);\n });\n // a command that does not read its input closes the pipe early\n child.stdin.on(\"error\", () => undefined);\n child.on(\"error\", (error) => {\n clearTimeout(timer);\n reject(\n new Error(`the on-change command did not start: ${error.message}`, { cause: error }),\n );\n });\n child.on(\"close\", (code, signal) => {\n clearTimeout(timer);\n const said = tail.trim() === \"\" ? \"\" : `: ${tail.trim().slice(-300)}`;\n if (late) {\n reject(\n new Error(\n `the on-change command ran for more than ${String(timeoutMs)} ms, so the daemon stopped it`,\n ),\n );\n } else if (code === 0) resolve();\n else\n reject(\n new Error(\n `the on-change command exited with ${code === null ? String(signal) : `code ${String(code)}`}${said}`,\n ),\n );\n });\n child.stdin.end(`${JSON.stringify(event)}\\n`);\n });\n};\n\n// ── the daemon ──────────────────────────────────────────────────────────────\n\n/** These options serve the MCP tools over HTTP. */\nexport interface DaemonMcpOptions {\n /** This value is the port. The default is 9307. The value 0 takes a free port. */\n readonly port?: number | undefined;\n /** This value is the host to bind. The default is `127.0.0.1`. A host that is not loopback needs `allowRemote`. */\n readonly host?: string | undefined;\n /** The value true permits a host that is not loopback. Read the warning in the README first. */\n readonly allowRemote?: boolean | undefined;\n /** This value is the bearer token. The default makes a new token at each start, in `<store>/daemon/mcp-token`. */\n readonly token?: string | undefined;\n /** This file holds the bearer token of the operator. The daemon reads it and writes nothing. */\n readonly tokenFile?: string | undefined;\n /** These tokens get their robots.txt rules applied to each fetch of a tool. The default is `[\"Claude-User\"]`. */\n readonly onBehalfOf?: readonly string[] | undefined;\n /** The value true lets `user_provided` release held-back text when the client cannot ask the user. */\n readonly allowUserProvided?: boolean | undefined;\n /** This text tells why the server renders no page. */\n readonly renderOff?: string | undefined;\n}\n\n/** These functions connect an MCP host to the daemon. */\nexport interface DaemonMcpHooks {\n /** This function gives the status of the daemon, for the status route. */\n readonly status: () => DaemonStatus;\n /** This function operates when a task starts, changes or ends. */\n readonly onTasks: () => void;\n /** This function writes a message of the server. */\n readonly log: (line: string) => void;\n}\n\n/** This is the MCP server of the daemon, as the MCP host gives it. */\nexport interface DaemonMcpService {\n /** This URL is the MCP endpoint. */\n readonly url: string;\n /** This method gives the tasks that did not end. */\n tasks(): readonly DaemonTask[];\n /** This method stops the server and cancels its tasks. */\n close(): Promise<void>;\n}\n\n/**\n * This is the seam between the daemon and its MCP server. The default host (`mcpHttpHost` of\n * `polite-crawler/mcp`) serves the tools over Streamable HTTP. A different MCP host can replace it.\n */\nexport interface DaemonMcpHost {\n start(\n crawler: OpenedCrawler,\n options: DaemonMcpOptions,\n hooks: DaemonMcpHooks,\n ): Promise<DaemonMcpService>;\n}\n\n/** These options start a daemon on a store. Only `dir` is necessary. */\nexport interface DaemonOptions extends OpenOptions {\n /** This value is the longest wait between two ticks. The default is `1m`, and the floor is `1s`. */\n readonly tick?: Duration | undefined;\n /** This value is the most random time that the daemon adds to each wait. The default is `10s`. */\n readonly jitter?: Duration | undefined;\n /** This value is the budget of the checks of one tick. The default is `10m`. */\n readonly budget?: Duration | undefined;\n /** This value is the most checks of one tick. The default is 100. */\n readonly maxPages?: number | undefined;\n /** At a stop, the checks in progress get this time to end. The default is `10s`. */\n readonly grace?: Duration | undefined;\n /**\n * The daemon writes each change event as one JSON line here: a file, a function, or the standard\n * output (the default). The value false writes no line. A change is an event with `confirmed: true`.\n */\n readonly events?: string | ((line: string) => void) | false | undefined;\n /** This function gets each change event. The daemon starts it in order, one at a time, and logs its error. */\n readonly onChange?: ((event: WatchEvent) => void | Promise<void>) | undefined;\n /** These options serve the MCP tools over HTTP. Without them, the daemon serves no MCP. */\n readonly mcp?: DaemonMcpOptions | undefined;\n /** This host serves the MCP tools. The default serves them over Streamable HTTP. */\n readonly mcpHost?: DaemonMcpHost | undefined;\n /** The value false installs no handler of `SIGINT` and `SIGTERM`. The default is true. */\n readonly signals?: boolean | undefined;\n /** This file keeps the state of the daemon. The default is `<store>/daemon/state.json`. */\n readonly stateFile?: string | undefined;\n /** This function writes the messages of the daemon. The default writes them to the standard error. */\n readonly log?: ((line: string) => void) | undefined;\n}\n\n/** This is a daemon that runs. */\nexport interface Daemon {\n /** This crawler is the crawler of the daemon. The daemon closes it when it stops. */\n readonly crawler: OpenedCrawler;\n /** This file keeps the state of the daemon. */\n readonly stateFile: string;\n /** This value is the MCP server, or undefined. */\n readonly mcp: DaemonMcpService | undefined;\n /** This method gives the status at this time. */\n status(): DaemonStatus;\n /**\n * This method stops the daemon. The checks in progress get the grace time to end, then they\n * abort and the run releases their leases. The MCP server stops and cancels its tasks. A second\n * call with `0` aborts the checks at once.\n */\n stop(grace?: Duration): Promise<DaemonStatus>;\n /** This promise resolves with the last status when the daemon stopped. */\n readonly done: Promise<DaemonStatus>;\n}\n\n/** The default MCP host loads the MCP SDK only when the daemon serves MCP. */\nconst defaultHost: DaemonMcpHost = {\n async start(crawler, options, hooks) {\n const { mcpHttpHost } = await import(\"./mcp.ts\");\n return mcpHttpHost.start(crawler, options, hooks);\n },\n};\n\n/** This function gives the writer of the change events. */\nconst eventWriter = (events: DaemonOptions[\"events\"]): ((line: string) => void) | undefined => {\n if (events === false) return undefined;\n if (typeof events === \"function\") return events;\n if (events === undefined) return (line) => void process.stdout.write(`${line}\\n`);\n mkdirSync(dirname(events), { recursive: true });\n return (line) => appendFileSync(events, `${line}\\n`, { mode: 0o600 });\n};\n\n/**\n * This function starts a daemon on a store. It opens the crawler, serves MCP when `mcp` is given,\n * and checks the watch list on a schedule (`scheduleWatches`). It writes each change event as one\n * JSON line, and it gives each change to `onChange`. The state file tells `daemon status` what runs.\n *\n * Only one daemon operates on a store: a second start rejects. At `SIGINT` or `SIGTERM`, the daemon\n * stops cleanly (`stop`). A second signal aborts the checks at once.\n */\nexport const startDaemon = async (options: DaemonOptions): Promise<Daemon> => {\n const {\n tick,\n jitter,\n budget,\n maxPages,\n grace,\n events,\n onChange,\n mcp,\n mcpHost,\n signals = true,\n stateFile: givenState,\n log = (line: string) => void process.stderr.write(`${line}\\n`),\n ...open\n } = options;\n const tickMs = msOf(\"tick\", tick ?? DAEMON_DEFAULTS.tickMs, DAEMON_DEFAULTS.minTickMs);\n const jitterMs = msOf(\"jitter\", jitter ?? DAEMON_DEFAULTS.jitterMs, 0);\n const budgetMs = msOf(\"budget\", budget ?? DAEMON_DEFAULTS.budgetMs, 0);\n const pages = pagesOf(maxPages);\n const graceMs = msOf(\"grace\", grace ?? DAEMON_DEFAULTS.graceMs, 0);\n const stateFile = givenState ?? daemonStateFile(open.dir);\n const before = readDaemonState(open.dir, stateFile);\n if (before?.alive === true) {\n throw new Error(\n `a daemon runs on the store ${open.dir} already (process ${String(before.pid)}): stop it first, or remove ${stateFile} when no daemon runs`,\n );\n }\n const writeEvent = eventWriter(events);\n // a missing browser turns rendering off one time, at start\n const renderOff = open.renderer === undefined ? await browserMissing() : undefined;\n const crawler = openCrawler({\n ...open,\n ...(renderOff === undefined ? {} : { renderer: false as const }),\n });\n const clock = open.clock ?? realClock;\n const startedAt = iso(clock.now());\n let lastError: DaemonStatus[\"last_error\"] = null;\n let service: DaemonMcpService | undefined;\n let tokenFile: string | null = null;\n let schedule: WatchSchedule | undefined;\n let nextDue: readonly DaemonDue[] = [];\n let stoppedAt: string | null = null;\n\n const status = (): DaemonStatus => {\n const counts = schedule?.result() ?? { ticks: 0, checked: 0, changes: 0, last: null };\n return {\n schema: DAEMON_STATE_SCHEMA,\n version: VERSION,\n pid: process.pid,\n store: open.dir,\n running: stoppedAt === null,\n started_at: startedAt,\n updated_at: iso(clock.now()),\n stopped_at: stoppedAt,\n tick_ms: tickMs,\n jitter_ms: jitterMs,\n budget_ms: budgetMs,\n max_pages: pages,\n ticks: counts.ticks,\n checked: counts.checked,\n changes: counts.changes,\n last_tick: counts.last,\n next_tick_at: stoppedAt === null ? (counts.last?.next_at ?? startedAt) : null,\n next_due: nextDue,\n tasks: service?.tasks() ?? [],\n mcp: service === undefined ? null : { url: service.url, token_file: tokenFile },\n last_error: lastError,\n };\n };\n const save = (): void => {\n try {\n writeState(stateFile, status());\n } catch (error) {\n log(`polite-crawler: the state file ${stateFile} does not write: ${errorOf(error).message}`);\n }\n };\n const fail = (error: unknown, where: string): void => {\n const message = `${where}: ${errorOf(error).message}`;\n lastError = { at: iso(clock.now()), message };\n log(`polite-crawler: ${message}`);\n };\n\n if (mcp !== undefined) {\n let given: DaemonMcpOptions = { ...mcp, renderOff: mcp.renderOff ?? renderOff };\n try {\n if (mcp.token === undefined && mcp.tokenFile === undefined) {\n // a new token at each start: a token that leaked ends at the next start\n tokenFile = join(dirname(stateFile), \"mcp-token\");\n given = { ...given, token: writeToken(tokenFile) };\n } else tokenFile = mcp.tokenFile ?? null;\n service = await (mcpHost ?? defaultHost).start(crawler, given, {\n status,\n onTasks: save,\n log,\n });\n } catch (error) {\n await crawler.close();\n throw error;\n }\n }\n\n // the hooks run in order, one at a time, so a slow hook does not start many processes\n let hooks: Promise<void> = Promise.resolve();\n const changed = (event: WatchEvent): void => {\n if (writeEvent !== undefined) {\n try {\n writeEvent(JSON.stringify(event));\n } catch (error) {\n fail(error, \"the event log\");\n }\n }\n if (onChange !== undefined) {\n hooks = hooks\n .then(() => onChange(event))\n .catch((error: unknown) => fail(error, \"the on-change hook\"));\n }\n };\n\n RUNNING.add(stateFile);\n const running = scheduleWatches(crawler.watch, {\n tick: tickMs,\n jitter: jitterMs,\n budget: budgetMs,\n maxPages: pages,\n clock,\n random: open.random,\n onEvent: (event) => {\n if (event.confirmed) changed(event);\n },\n onTick: (done) => {\n nextDue = done.due;\n // a run that failed went to onError\n if (done.summary !== null && done.summary.checked > 0) {\n const summary = done.summary;\n log(\n `polite-crawler: ${String(summary.checked)} checked: ${String(summary.confirmed)} changes, ${String(summary.suspected)} suspected, ${String(summary.refused)} refused, ${String(summary.error)} errors. Next tick at ${done.next_at ?? \"the stop\"}.`,\n );\n }\n save();\n },\n onError: (error) => fail(error, \"a tick\"),\n });\n schedule = running;\n save();\n log(\n `polite-crawler ${VERSION}: the daemon runs on the store ${open.dir}, a tick each ${String(tickMs)} ms${service === undefined ? \"\" : `, MCP at ${service.url}${tokenFile === null ? \"\" : ` (the token is in ${tokenFile})`}`}`,\n );\n if (renderOff !== undefined) log(`polite-crawler: rendering is off: ${renderOff}`);\n\n let resolveDone: (final: DaemonStatus) => void = () => undefined;\n const done = new Promise<DaemonStatus>((resolve) => {\n resolveDone = resolve;\n });\n let stopping: Promise<DaemonStatus> | undefined;\n let signalled = 0;\n const onSignal = (signal: NodeJS.Signals): void => {\n signalled += 1;\n log(\n signalled > 1\n ? `polite-crawler: ${signal} again: the checks in progress abort now`\n : `polite-crawler: ${signal}: the daemon stops, and the checks in progress get ${String(graceMs)} ms`,\n );\n void stop(signalled > 1 ? 0 : graceMs);\n };\n const stop = (stopGrace: Duration = graceMs): Promise<DaemonStatus> => {\n const ms = msOf(\"grace\", stopGrace, 0);\n void running.stop(ms);\n stopping ??= (async () => {\n const closing = service?.close().catch((error: unknown) => fail(error, \"the MCP server\"));\n await running.done;\n // the hooks in the queue get the grace too\n let timer: ReturnType<typeof setTimeout> | undefined;\n await Promise.race([\n hooks,\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, ms);\n }),\n ]);\n clearTimeout(timer);\n await closing;\n stoppedAt = iso(clock.now());\n save();\n RUNNING.delete(stateFile);\n await crawler.close();\n if (signals) {\n process.removeListener(\"SIGINT\", onSignal);\n process.removeListener(\"SIGTERM\", onSignal);\n }\n log(\"polite-crawler: the daemon stopped\");\n const final = status();\n resolveDone(final);\n return final;\n })();\n return stopping;\n };\n if (signals) {\n process.on(\"SIGINT\", onSignal);\n process.on(\"SIGTERM\", onSignal);\n }\n return { crawler, stateFile, mcp: service, status, stop, done };\n};\n"],"mappings":"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"}