{
  "nodes": {
    "crates/lanrurugi-plugin/dispatcher/dispatcher.ts": {
      "imports": [
        {
          "importedName": "PluginErrorException",
          "jsDoc": {
            "doc": "LANrurugi plugin dispatcher (constitution Principle IV, contracts/plugin-protocol.md).\n\nOne dispatcher process is dedicated to exactly one plugin namespace (started with that\nplugin's own declared Deno permissions — see lanrurugi-plugin::pool). It reads\nnewline-delimited JSON requests from stdin and dynamically import()s the plugin module on\ndemand rather than being started fresh per call (research.md §7).\n\nRequests are dispatched without awaiting the previous one to finish, so multiple in-flight\nrequests (e.g. metadata lookups for several archives at once) run concurrently within this one\nprocess — real concurrency for I/O-bound plugin work without needing multiple OS processes per\nplugin.\n\nNo external module imports (not even Deno's own std streams helpers) so that a plugin's\n`--allow-net` grant is never implicitly widened by the dispatcher's own startup needs. Same\nreasoning is why `legacyCompat`/`PluginErrorException` below are plain global assignments rather\nthan something a plugin would need to `import`: a plugin file lives under `plugins_dir`, which\nhas no fixed relative path to this file's own directory (both are independent, runtime-supplied\nCLI args — `--plugins-dir`/`--temp-dir`) — a plugin file's own top-level `import` specifier has\nto be a static string, so it can't reach into a path it won't know until runtime. A global needs\nno such path and no extra `--allow-read` grant at all, since it's set in this same\nalready-trusted process before the plugin module loads.\n\n`PluginErrorException` itself is `import`ed for real from `plugin-sdk.ts` right below (needing\nits own `--allow-read` grant — `lanrurugi_plugin::pool::Worker::spawn` grants `plugin-sdk.ts`'s\npath, written as `dispatcher_path`'s own sibling by every `DISPATCHER_SCRIPT` write site) since\n*this* import's specifier can be a static relative path (`dispatcher.ts` and `plugin-sdk.ts`\nalways share one directory, `temp_dir`) — only a *plugin* file's own import of it would have the\nunresolvable-path problem above. Re-exposed as a global (`globalThis.PluginErrorException`\nbelow) purely so every plugin file can `throw new PluginErrorException(...)` without its own\nimport at all (issue #86) — every plugin file still gets the exact same class reference this\nprocess's `catch` below checks with `instanceof`, since both paths ultimately go through this\none already-imported module.\n\nThe `/// <reference types=\"../../../plugins/legacy-globals.d.ts\" />` directive at the top of this\nfile is a\n*type-checker-only* hint — it costs nothing at runtime (erased entirely by the time `deno run`\nexecutes this file, same as any other `.d.ts`-style ambient declaration). It's what lets\n`plugins/legacy-globals.d.ts` (the canonical home for `Legacy*`/`legacyCompat`/\n`PluginErrorException`'s own ambient types — see that file's own docs) actually type-check this\nfile's `globalThis.legacyCompat = {...}`/`globalThis.PluginErrorException = ...` assignments\nbelow. Every plugin file under `plugins/` sees the same declarations with no reference line of\nits own at all (`plugins/tsconfig.json` makes that file ambiently visible project-wide) — this\nfile needs its own explicit line only because it lives outside `plugins/`, so that project\nboundary doesn't cover it."
          },
          "originalName": "PluginErrorException",
          "src": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts"
        }
      ],
      "symbols": []
    },
    "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts": {
      "module_doc": {
        "doc": "# LANrurugi Plugin SDK\n\nA plugin is a single `.ts` file dropped under `plugins/<category>/` (or uploaded through\n`POST /plugins/upload`, which lands in `plugins/custom/<category>/` instead — see\n`lanrurugi-api::plugins::upload_plugin`). It runs inside its own Deno subprocess\n(`crates/lanrurugi-plugin/src/pool.rs`), started with exactly the `--allow-net`/`--allow-read`\ngrant its own {@linkcode PluginInfoResult.declared_permissions} asked for — nothing broader\n(constitution Principle IV). This module documents that contract as real, type-checked TS\n(generate a browsable copy with `mise run plugin-sdk-docs`) rather than free-hand prose, so it\ncan't drift from what the host (`lanrurugi-api::plugins`) and the dispatcher\n(`dispatcher.ts`, doc'd alongside this file) actually do.\n\nEvery plugin file must export exactly two things:\n\n1. `pluginInfo()`, returning {@linkcode PluginInfoResult} — synchronous, no side effects. Called by\n   itself in a disposable zero-permission subprocess just to read a plugin's own metadata\n   (`lanrurugi_plugin::pool::PluginPool::plugin_info`) — must not assume `fetch`/file access\n   are available even if `declared_permissions` asks for them.\n2. One `kind`-dependent entry point, matching {@linkcode PluginInfoResult.type}:\n   - `\"metadata\"` → `execMetadata(hostArgs)`, resolving to {@linkcode MetadataResult}\n   - `\"login\"` → `execLogin(hostArgs)`, resolving to {@linkcode LoginResult}\n   - `\"download\"` → `execDownload(hostArgs)`, resolving to {@linkcode DownloadResult}\n   - `\"script\"` → `runScript(hostArgs): Promise<unknown>` — legacy's own three Scripts plugins\n     (FolderToCat/SourceFinder/nHentaiSourceConverter) are reimplemented as native Rust\n     endpoints instead (`lanrurugi-api::scripts`) rather than shipped as real `.ts` files under\n     `plugins/script/`, so this entry point currently has no worked example in this repo — a\n     third-party script plugin uploaded via `POST /plugins/upload` would still be dispatched\n     through this same generic path, though.\n\nA `\"download\"`-type plugin MAY additionally export `pluginOptions()`, returning\n{@linkcode PluginOptionsResult} — synchronous, no side effects, same zero-permission-subprocess call\npattern as `pluginInfo()`. Omit it entirely if the plugin has no configurable\nconcurrency/rate-limit/bundling defaults to declare.\n\n`lanrurugi-plugin-converter` generates both from a legacy `.pm` file's `plugin_info`/\n`get_tags`/`do_login`/`provide_url` subs automatically — see\n`crates/lanrurugi-plugin-converter/src/render.rs`'s own docs, or just run\n`mise run convert-plugins -- <path-to-legacy-LANraragi-checkout>` (see `scripts/convert-plugins.sh`).\nEverything below is what that generated code targets; write it by hand only for a genuinely new\n(non-legacy-derived) plugin.\n\nThe host-provided globals a plugin body can call (`legacyCompat.*`, `LegacyUserAgent`, ...) are\ndeclared in a sibling file, `plugins/legacy-globals.d.ts` — deliberately *not* here, and\ndeliberately *not* an `export`ed symbol from this module (issue #86). `plugins/legacy-globals.d.ts`\nis a pure global script (no top-level `import`/`export` in it at all), which is what lets\n`plugins/tsconfig.json` (covering every `.ts` file under `plugins/`) make it ambiently visible\nto every plugin file with zero boilerplate of its own — no `/// <reference>` line, no import,\nnothing (this replaced 31 files' worth of copy-pasted `declare global` boilerplate that used to\nlive inline in each one, then briefly a one-line reference directive per file, before landing on\nthis fully zero-touch shape). `dispatcher.ts` (the real runtime home of\n`globalThis.legacyCompat = {...}`) sits outside `plugins/` and so isn't covered by that\nproject-wide config — it references `plugins/legacy-globals.d.ts` via its own explicit\n`/// <reference types=\"...\" />` line instead, same as before.\n\nThe same zero-boilerplate constraint is *also* why {@linkcode MetadataHostArgs},\n{@linkcode LoginHostArgs}, {@linkcode ScriptHostArgs}, and {@linkcode DownloadHostArgs} —\ndespite being genuinely part of this SDK's protocol contract, not host-implementation\nplumbing — live in `plugins/legacy-globals.d.ts` too, not here (issue #86's later type-\nunification pass). `lanrurugi-plugin-converter`'s generated code narrows each entry point's own\nlocal info-hash interface with `extends Pick<AuthoritativeInterface, \"key1\" | \"key2\" | ...>`\nagainst these exact four (`render.rs`'s `authoritative_host_args_interface`) — a real symbol\nreference a generated plugin file needs to resolve with zero `import`/reference of its own, the\nsame constraint that keeps `Legacy*`/`legacyCompat` out of this `export`-bearing module.\n\n{@linkcode DownloadResult} and the types it contains are mirrored there for the same reason\n(as `DownloadResultShape`/`DownloadRequestResult`/`VersionHistoryEntryResult`/\n`PluginErrorResult`): a download plugin annotates its own `execDownload` return type with them,\nwhich needs a name resolvable without an import. That annotation is what makes a mistyped key a\ncompile error instead of a field the host's deserializer silently drops — `version_history`\n(issue #107) is exactly the kind of optional field that failure mode would hide. The two copies\nare kept in sync by hand; this module stays the authoritative source the Rust side is verified\nagainst.\n\nEvery *other* protocol type below (`PluginInfoResult`, permissions, `MetadataResult`, ...) has no\nsuch constraint — nothing needs to reference them by name inside a plugin file's own source, only\nthis module's own callers (`dispatcher.ts`, `deno doc`, a future Rust-side `protocol.rs`\ncomparison) do, so they stay right here as ordinary `export`s.\n\n`deno doc` this file together with `dispatcher.ts` (exactly what `mise run plugin-sdk-docs`\ndoes) to get the protocol-type half in one place — `plugins/legacy-globals.d.ts`'s own host\nglobals aren't currently included in that generated doc output (a plain `deno doc` invocation\nagainst a global script produces nothing useful; folding it in would need its own follow-up).\n`deno doc --json` (same two input files) is also confirmed to work, producing structured\nper-symbol output (JSDoc text, resolved `tsType`, source `location`) that's a more natural fit\nfor a future documentation site's own Markdown generation (issue #52) than the fixed\npre-rendered HTML bundle `--html` produces — the one thing to know before wiring that up:\n`--json`'s `nodes` keys and every symbol's `location.filename` are absolute `file://` paths on\nwhatever machine generated them, so a conversion step needs to strip/remap those before the\noutput is portable to another machine or CI.\n",
        "tags": [
          {
            "kind": "module"
          }
        ]
      },
      "symbols": [
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "tsType": {
                  "kind": "union",
                  "value": [
                    {
                      "kind": "literal",
                      "repr": "metadata",
                      "value": {
                        "kind": "string",
                        "string": "metadata"
                      }
                    },
                    {
                      "kind": "literal",
                      "repr": "login",
                      "value": {
                        "kind": "string",
                        "string": "login"
                      }
                    },
                    {
                      "kind": "literal",
                      "repr": "download",
                      "value": {
                        "kind": "string",
                        "string": "download"
                      }
                    },
                    {
                      "kind": "literal",
                      "repr": "script",
                      "value": {
                        "kind": "string",
                        "string": "script"
                      }
                    }
                  ]
                }
              },
              "jsDoc": {
                "doc": "The four plugin categories the host recognizes (`lanrurugi-api::plugins::PLUGIN_CATEGORIES`) —\nalso the fixed set of subdirectories under `plugins/` (`metadata/`, `login/`, `download/`,\n`script/`), and the value `upload_plugin` trusts *only* from a freshly-uploaded plugin's own\n`pluginInfo()` response (never a client-supplied category) to decide where to file it."
              },
              "kind": "typeAlias",
              "location": {
                "byteIndex": 7403,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 99
              }
            }
          ],
          "name": "PluginKind"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 8349,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 111
                    },
                    "name": "name",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 8365,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 112
                    },
                    "name": "description",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 8388,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 113
                    },
                    "name": "required",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "boolean",
                      "value": "boolean"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Matches legacy's own `param.type` (`~/LANraragi/templates/plugins.html.tt2`'s `SWITCH\nparam.type` — `'string'` → a text input, `'bool'` → a checkbox/switch, `'int'` → a number\ninput). Missing entirely before this — every converted plugin's parameters rendered as a\nplain, unstyled text input regardless of their real semantics, including boolean toggles like\n`plugins/metadata/mems.ts`'s own 3 params (real legacy `MEMS.pm` declares all 3 as `type =>\n'bool'`, rendered as ON/OFF switches — a plain blank text box here was a real, visible\nmismatch). Legacy's own unmatched-`SWITCH` fallback renders a `type=\"color\"` picker (its\nsource literally comments this default \"ayy lmao\" — an unintentional quirk, not a real 4th\nparameter kind any real plugin in this corpus uses); this SDK deliberately defaults absent\n`type` to `'string'` instead, since every one of this corpus's 31 plugins with declared\nparameters had none of this field before now and were already correctly rendered/used as plain\ntext inputs — matching legacy's silly color-picker default would have turned every one of\nthose already-working parameters into a broken control instead of leaving them unaffected."
                    },
                    "location": {
                      "byteIndex": 9668,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 127
                    },
                    "name": "type",
                    "optional": true,
                    "tsType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "literal",
                          "repr": "string",
                          "value": {
                            "kind": "string",
                            "string": "string"
                          }
                        },
                        {
                          "kind": "literal",
                          "repr": "bool",
                          "value": {
                            "kind": "string",
                            "string": "bool"
                          }
                        },
                        {
                          "kind": "literal",
                          "repr": "int",
                          "value": {
                            "kind": "string",
                            "string": "int"
                          }
                        }
                      ]
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "One entry in {@linkcode PluginInfoResult.parameters} — a user-configurable setting shown in\nthe plugin's settings UI. The host persists one value per declared parameter (`GET`/\n`PUT /plugins/settings?namespace=...`, `lanrurugi-api::plugins::plugin_settings_key`, Redis\nfield `customargs`) and passes the whole array back on every call, positionally matching this\narray (`parameters[0]`'s saved value is `hostArgs.customargs[0]`, etc. — see\n{@linkcode MetadataHostArgs.customargs}), each element already in its own declared {@linkcode\ntype} — a `bool` parameter's value arrives as a real `boolean`, never a string a plugin has to\nparse back out itself. Distinct from {@linkcode PluginInfoResult.oneshot_arg}, which is a single\nvalue typed fresh into a \"run once\" dialog each time, not persisted here."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 8312,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 110
              }
            }
          ],
          "name": "PluginParameter"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "Exact hostnames this plugin's `fetch()` calls are allowed to reach — verify every one\nagainst a real URL literal in the plugin's own source before shipping it; an empty array\nmeans no network access at all."
                    },
                    "location": {
                      "byteIndex": 10550,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 141
                    },
                    "name": "net",
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "keyword",
                        "repr": "string",
                        "value": "string"
                      }
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 10567,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 142
                    },
                    "name": "read",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "boolean",
                      "value": "boolean"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 10584,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 143
                    },
                    "name": "write",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "boolean",
                      "value": "boolean"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "What a plugin is allowed to touch — enforced by starting its Deno subprocess with exactly the\nmatching `--allow-net`/`--allow-read`/`--allow-write` flags and nothing wider\n(`lanrurugi_plugin::pool::Worker::spawn`), constitution Principle IV. Declaring less than a\nplugin actually needs fails loudly at the call site (a denied `fetch`/file op throws); there is\nno way to request \"read access to one specific file\" here beyond {@linkcode\nPluginInfoResult.sidecar_files}, which the host resolves itself rather than granting real\nfilesystem access for."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 10281,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 137
              }
            }
          ],
          "name": "DeclaredPermissions"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "Stable machine identifier — also this plugin's filename without the `.ts` extension once\ninstalled (`metadata/ehentai.ts` → namespace `metadata/ehentai`), and the Redis key suffix\nits one configurable `arg` value is stored under."
                    },
                    "location": {
                      "byteIndex": 11301,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 155
                    },
                    "name": "namespace",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 11322,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 156
                    },
                    "name": "type",
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "PluginKind",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "PluginKind"
                      }
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 11342,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 157
                    },
                    "name": "parameters",
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "PluginParameter",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "PluginParameter"
                        }
                      }
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 11375,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 158
                    },
                    "name": "declared_permissions",
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "DeclaredPermissions",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "DeclaredPermissions"
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Namespace of another `\"login\"`-type plugin to run *fresh before every single call* to this\none (never a cached session) — mirrors legacy's `exec_login_plugin`\n(`~/LANraragi/lib/LANraragi/Model/Plugins.pm:107-135`). Omit entirely for a plugin that needs\nno login, or that is itself a `\"login\"`-type plugin (which never chases another login). A\nfailed login attempt only logs a warning; this plugin's own call still goes ahead, just\nwithout `hostArgs.user_agent_cookies` populated."
                    },
                    "location": {
                      "byteIndex": 11936,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 165
                    },
                    "name": "login_from",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 11959,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 166
                    },
                    "name": "name",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 11975,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 167
                    },
                    "name": "author",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 11993,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 168
                    },
                    "name": "description",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 12016,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 169
                    },
                    "name": "version",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 12035,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 170
                    },
                    "name": "icon",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Prompt text for the single free-text value a user types into this plugin's \"run once\"\ndialog (as opposed to a saved settings-page {@linkcode PluginParameter}) — e.g. a URL or\ngallery ID for a one-shot \"copy tags from this other archive\" action. Omit if this plugin has\nno such one-shot action."
                    },
                    "location": {
                      "byteIndex": 12372,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 175
                    },
                    "name": "oneshot_arg",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "A regex (JS `RegExp` source, no delimiters, e.g. `\"pixiv\\\\.net\"`) matched case-insensitively\nagainst a full candidate URL to decide whether this plugin should handle it — the precise\ntrigger condition for a real download/metadata fetch (the Upload page's own URL-queue\ngrouping, the host's own `find_matching_plugin`). NOT what a domain-ownership lookup should\nmatch against — see {@linkcode PluginInfoResult.domain_match} for that. Omit if this plugin\nhas no meaningful URL-based routing (e.g. a script/login plugin, or a metadata/download\nplugin with no single well-known source domain)."
                    },
                    "location": {
                      "byteIndex": 13029,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 183
                    },
                    "name": "url_pattern",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Bare domains (no scheme, no path, e.g. `[\"e-hentai.org\", \"exhentai.org\"]`) this plugin\nconsiders itself the owner of — only for domain-ownership lookups (Upload page's \"fetch\nmetadata\" button, the AI wizard's coverage check), never for real dispatch (still\n{@linkcode PluginInfoResult.url_pattern}'s job — keep declaring that too, it should stay a\nprecise trigger condition). Omit only when `url_pattern` is already a bare domain-level\npattern; declare this explicitly whenever `url_pattern` is narrower than the domain itself\n(e.g. requires a specific path segment)."
                    },
                    "location": {
                      "byteIndex": 13664,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 191
                    },
                    "name": "domain_match",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "keyword",
                        "repr": "string",
                        "value": "string"
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Sidecar metadata filenames this plugin wants read out of the archive it's currently\nprocessing (basename suffixes, e.g. `\"ComicInfo.xml\"`, `\"info.json\"`) — `lanrurugi-\nplugin-converter` populates this automatically from every `is_file_in_archive(...)` call it\nfinds in a converted plugin's source. The host resolves each name against the current archive\n*before* calling `execMetadata` and hands back whatever it found as {@linkcode\nMetadataHostArgs.sidecar_files}: file *content*, never a path — this plugin itself never gets\nreal filesystem access for this (constitution Principle IV)."
                    },
                    "location": {
                      "byteIndex": 14322,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 199
                    },
                    "name": "sidecar_files",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "keyword",
                        "repr": "string",
                        "value": "string"
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`true` only for a plugin whose source was produced by the AI plugin creation wizard\n(`specs/006-ai-plugin-wizard`, FR-026) — a wizard-generated draft always sets this literally\nin its own `pluginInfo()` return value (the wizard's generation prompt instructs the model to\ninclude it), so it survives independently of any wizard session once the file is saved to\ndisk. Omit entirely for a hand-written/manually-uploaded plugin; the host never infers this\nfrom anything other than the plugin's own declared value."
                    },
                    "location": {
                      "byteIndex": 14897,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 206
                    },
                    "name": "generated_by_wizard",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "boolean",
                      "value": "boolean"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`pluginInfo()`'s return shape — matches `lanrurugi_plugin::protocol::PluginInfo` field for\nfield (that's the Rust-side deserialization target every one of these fields is verified\nagainst). Fields below `login_from` are display-only, answering `GET /plugins/{type}` with the\nshape existing UI/tooling expects; they're not part of `contracts/plugin-protocol.md`'s minimal\nwire contract."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 11010,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 151
              }
            }
          ],
          "name": "PluginInfoResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 15747,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 219
                    },
                    "name": "error_code",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Interpolation params for the translated string — only strings/numbers (filenames, URLs,\nother messages, page counts, HTTP status codes: everything this SDK's real plugin corpus\nactually needs to interpolate), never a richer/nested value."
                    },
                    "location": {
                      "byteIndex": 16028,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 223
                    },
                    "name": "data",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "union",
                            "value": [
                              {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              },
                              {
                                "kind": "keyword",
                                "repr": "number",
                                "value": "number"
                              }
                            ]
                          }
                        ]
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "A plugin-authored error — `error_code` doubles as an i18n lookup key (the frontend's\n`apps/frontend/src/i18n/locales/*.json` use literal English sentences as keys, not symbolic\ncodes; a missing translation falls back to rendering `error_code` itself verbatim, i18next's\nown default behavior). Write `error_code` as a natural, stable English phrase that does NOT\nembed any dynamic value (a file path, a URL, an HTTP status code, ...) — those go in\n{@linkcode data} instead, so the same `error_code` can be looked up and translated regardless\nof which specific file/URL/status triggered it. E.g. for \"Could not open foo.json!\", use\n`error_code: \"Could not open file\"` + `data: {filepath: \"foo.json\"}`, not the interpolated\nstring itself as the code."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 15714,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 218
              }
            }
          ],
          "name": "PluginError"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "constructors": [
                  {
                    "hasBody": true,
                    "location": {
                      "byteIndex": 16891,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 235
                    },
                    "name": "constructor",
                    "params": [
                      {
                        "accessibility": "public",
                        "kind": "identifier",
                        "name": "error_code",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      },
                      {
                        "accessibility": "public",
                        "kind": "identifier",
                        "name": "data",
                        "optional": true,
                        "tsType": {
                          "kind": "typeRef",
                          "repr": "Record",
                          "value": {
                            "typeName": "Record",
                            "typeParams": [
                              {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              },
                              {
                                "kind": "union",
                                "value": [
                                  {
                                    "kind": "keyword",
                                    "repr": "string",
                                    "value": "string"
                                  },
                                  {
                                    "kind": "keyword",
                                    "repr": "number",
                                    "value": "number"
                                  }
                                ]
                              }
                            ]
                          }
                        }
                      }
                    ]
                  }
                ],
                "extends": "Error"
              },
              "jsDoc": {
                "doc": "Throw this (instead of a plain `throw new Error(...)`) to report a {@linkcode PluginError}\nthrough an exception rather than a `return {error}` — used for the exact same \"expected,\nsemantic failure\" cases {@linkcode MetadataResult.error} documents, just from a code path\nthat's more naturally a `throw` (deep inside a helper, a caught-and-rethrown parse failure,\netc.) than a `return`. The dispatcher (`dispatcher.ts`'s `handleRequest`) special-cases this\nclass specifically — an unexpected `throw` of anything else (a genuine bug, a network library's\nown exception type) still degrades to today's generic unstructured-fault handling, since an\narbitrary caught value has no stable translatable shape to extract `error_code`/`data` from."
              },
              "kind": "class",
              "location": {
                "byteIndex": 16839,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 234
              }
            }
          ],
          "name": "PluginErrorException"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 18035,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 255
                    },
                    "name": "tags",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 18052,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 256
                    },
                    "name": "title",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Present instead of `tags`/`title` on failure (e.g. a filename plugin whose regex didn't\nmatch) — a structured {@linkcode PluginError}, not thrown, so the host can report it without\nthe whole `use_plugin` call failing."
                    },
                    "location": {
                      "byteIndex": 18309,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 260
                    },
                    "name": "error",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "PluginError",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "PluginError"
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`execMetadata`'s return shape. This is the real, verified-against-every-converted-plugin\ncontract: every one of this repo's 21 converted `Metadata/*.pm` plugins (and every legacy `.pm`\nsource they came from) returns exactly `(tags => ..., title => ...)` from its own `get_tags`\nsub, and `lanrurugi-api::plugins::use_plugin_sync`/`use_plugin_async` currently forward a\nplugin's JSON response to the API's own `data` field completely unchanged — no field renaming\nhappens on the host side today. (Legacy's own `~/LANraragi/lib/LANraragi/Model/Plugins.pm` does\nrename a plugin's `tags` to `new_tags` before its real HTTP API sees it, matching\n`~/LANraragi/tools/openapi.yaml`'s documented response shape — this repo deliberately doesn't\nmirror that rename, since nothing on the host or frontend side depends on the `new_tags` name;\n`crates/lanrurugi-plugin/samples/sample-metadata-plugin.ts` and every real call site use `tags`\nto match.)"
              },
              "kind": "interface",
              "location": {
                "byteIndex": 17999,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 254
              }
            }
          ],
          "name": "MetadataResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 18981,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 271
                    },
                    "name": "cookies",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "LegacyCookie",
                        "value": {
                          "typeName": "LegacyCookie"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Any header/token-based credential this login plugin wants a downstream metadata/download\ncall to send (e.g. `{ \"Authorization\": \"Key <api_key>\" }`, a bearer token, a custom\nsigned-request header — whatever the target site's own auth scheme needs). Independent of\n`cookies` and of {@linkcode LegacyUserAgent} entirely: a plugin using\n`legacyCompat.userAgent()` can hand back its own `ua.headers` snapshot here (see that\nproperty's own docs), but nothing requires going through `userAgent()` at all — a plugin that\nauthenticates with a plain `fetch()` call and holds the resulting token in a local variable\ncan just `return { headers: { Authorization: `Bearer ${token}` } }` directly. For a site\nauthenticating via a header/token rather than a cookie, this is the *only* way that credential\nactually reaches a downstream call; `cookies` alone is empty for such a login plugin (issue\n#78/#93: `nhapiauth`'s `Authorization` header used to have no way to cross this boundary at\nall before this field existed)."
                    },
                    "location": {
                      "byteIndex": 20082,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 284
                    },
                    "name": "headers",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          }
                        ]
                      }
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 20118,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 285
                    },
                    "name": "error",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "PluginError",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "PluginError"
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`execLogin`'s return shape. Only `cookies`/`headers` are ever read by the host\n(`lanrurugi-api::plugins::with_login_cookies`), which folds them into the *next*\nmetadata/download call's {@linkcode MetadataHostArgs.user_agent_cookies}/\n`user_agent_headers` — every other property a real `legacyCompat.userAgent()` instance exposes\nis a function, which `JSON.stringify` silently drops crossing the dispatcher's\nnewline-delimited-JSON boundary (see `dispatcher.ts`'s own `LegacyUserAgent.cookies` docs), so\nreturning the whole `ua` object works, but only these two plain-data fields survive."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 18948,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 270
              }
            }
          ],
          "name": "LoginResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "`1` if a match was found, `0` otherwise (`urlfinder`)."
                    },
                    "location": {
                      "byteIndex": 20647,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 295
                    },
                    "name": "total",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The matching archive's ID, when {@linkcode total} is `1` (`urlfinder`)."
                    },
                    "location": {
                      "byteIndex": 20746,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 297
                    },
                    "name": "id",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 20761,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 298
                    },
                    "name": "error",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "PluginError",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "PluginError"
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Count of archives this call changed (`nhsrcconv`)."
                    },
                    "location": {
                      "byteIndex": 20844,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 300
                    },
                    "name": "modified",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "This call's intended tag rewrites — the host applies each one (same shape as\n{@linkcode ScriptHostArgs.archives}) after `runScript` returns; a plugin never writes storage\ndirectly (`nhsrcconv`)."
                    },
                    "location": {
                      "byteIndex": 21081,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 304
                    },
                    "name": "updates",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "ScriptArchiveTags",
                        "value": {
                          "typeName": "ScriptArchiveTags"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "One static Category to create per discovered subfolder — the host creates each one after\n`runScript` returns, same read-compute-return-write-to-host split as {@linkcode updates}\n(`foldertocat`)."
                    },
                    "location": {
                      "byteIndex": 21330,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 308
                    },
                    "name": "categories_to_create",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeLiteral",
                        "value": {
                          "properties": [
                            {
                              "location": {
                                "byteIndex": 21355,
                                "col": 27,
                                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                                "line": 308
                              },
                              "name": "name",
                              "tsType": {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              }
                            },
                            {
                              "location": {
                                "byteIndex": 21369,
                                "col": 41,
                                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                                "line": 308
                              },
                              "name": "archive_ids",
                              "tsType": {
                                "kind": "array",
                                "value": {
                                  "kind": "keyword",
                                  "repr": "string",
                                  "value": "string"
                                }
                              }
                            }
                          ]
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Whether to delete every existing static (non-dynamic) category before creating the new ones\n— echoes back this call's own `customargs[0]`, since the host applies the deletion itself\n(`foldertocat`)."
                    },
                    "location": {
                      "byteIndex": 21618,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 312
                    },
                    "name": "delete_old_categories",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "boolean",
                      "value": "boolean"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Wall-clock milliseconds `runScript` itself spent walking the directory tree and computing\ngroupings — reported back for a direct comparison against the equivalent native Rust endpoint\n(`POST /database/scripts/subfolders-to-categories`'s own `elapsed_ms`), which does the\nidentical job. Not a generic field every script plugin is expected to fill in (`foldertocat`\nonly)."
                    },
                    "location": {
                      "byteIndex": 22055,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 318
                    },
                    "name": "elapsed_ms",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`runScript`'s return shape — deliberately a loose bag of fields (mirrors legacy's own\n`run_script` subs, each of which returns a different ad-hoc hash with no shared shape across the\nthree real Scripts plugins) rather than one rigid interface every script must conform to.\n`total`/`id`/`error` are `SourceFinder.pm`'s own fields; `modified`/`updates` are\n`nHentaiSourceConverter.pm`'s."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 20549,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 293
              }
            }
          ],
          "name": "ScriptResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "The real, directly-fetchable resource URL."
                    },
                    "location": {
                      "byteIndex": 22409,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 326
                    },
                    "name": "url",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Defaults to `\"GET\"` when absent — every real corpus plugin (and legacy LANraragi's own\n`Model::Upload.pm::download_url`) only ever uses GET, but other methods remain representable\nfor a future plugin that needs one."
                    },
                    "location": {
                      "byteIndex": 22661,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 330
                    },
                    "name": "method",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Extra request headers for this specific resource (e.g. Pixiv's anti-hotlink `Referer`\nheader) — absent means no extra headers beyond whatever the host's own downloader sends by\ndefault."
                    },
                    "location": {
                      "byteIndex": 22887,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 334
                    },
                    "name": "headers",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "A plugin-suggested filename, used only as a fallback when the real HTTP response has no\n(or an unparseable) `Content-Disposition` header."
                    },
                    "location": {
                      "byteIndex": 23075,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 337
                    },
                    "name": "filename_hint",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "One resource to fetch, as part of {@linkcode DownloadResult.downloads} — see that field's own\ndocs. `specs/005-download-plugin-progress/contracts/plugin-download-protocol.md` is the wire\ncontract this mirrors field-for-field."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 22320,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 324
              }
            }
          ],
          "name": "DownloadRequest"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "One element = a single-file download (e.g. Chaika/EHentai-style plugins, which resolve one\nreal download URL); more than one element = a multi-resource download (e.g. Pixiv's per-page\nimages), assembled per the plugin's {@linkcode PluginOptionsResult.bundle_as_archive}\nsetting."
                    },
                    "location": {
                      "byteIndex": 24105,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 353
                    },
                    "name": "downloads",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "DownloadRequest",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "DownloadRequest"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Pre-existing fallback escape hatch: a plugin that already downloaded/wrote a file itself and\nhands back a local path. Mutually exclusive with {@linkcode downloads}; does not receive\nprogress/concurrency/rate-limit treatment since the transfer already happened entirely inside\nthe plugin process by the time this returns."
                    },
                    "location": {
                      "byteIndex": 24483,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 358
                    },
                    "name": "file_path",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The full known version-history of this download's series, as this plugin's site exposes it —\nevery source URL the plugin can enumerate, each with the timestamp the site reports for it.\n\n\"Every node\" is a real requirement, not a best effort: the host can only detect a relationship\nto a revision that appears in this list, so an omitted node means an older download silently\npasses as unrelated. Mind the traversal when a site only exposes upward links. E-Hentai is the\nworked example: a gallery names its `parent_gid`, never its children, and `current_gid` jumps\nstraight to the chain tip — so walking outward from the requested gallery (up its parents, then\none hop to the tip) skips every revision in between. Jumping to the tip first and then\nfollowing `parent_gid` down to the root is what actually visits all of them.\n\nThe plugin performs NO comparison against the library and reaches NO conclusion — it just\nreports what the site says exists. The host walks this list against its own catalogue to decide\nwhether/what to overwrite (see {@linkcode PluginOptionsResult.relative_newer_policy}/\n{@linkcode PluginOptionsResult.relative_older_policy}). Keeping the judgment host-side means\none tested implementation shared by every plugin, rather than each (especially AI-generated)\nplugin reinventing a multi-hop reachability walk with its own edge-case bugs.\n\nEvery `source` MUST already be passed through this plugin's own {@linkcode canonicalizeSource}\n— the host compares these against stored `source:` tags by plain string equality, so an\nun-normalized entry silently fails to match with no error surfaced anywhere. Include the\njust-downloaded item itself as one of the entries; the host locates it by matching this\ndownload's own source URL. Order does not matter — the host sorts by `posted_at`.\n\nAbsent when the plugin's site has no version-history concept, or this particular download has\nnone (e.g. a first-ever upload with no parent and no later revision)."
                    },
                    "location": {
                      "byteIndex": 26613,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 385
                    },
                    "name": "version_history",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "VersionHistoryEntry",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "VersionHistoryEntry"
                        }
                      }
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 26656,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 386
                    },
                    "name": "error",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "PluginError",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "PluginError"
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`execDownload`'s return shape — verified against every real legacy `Download/*.pm`'s own\n`provide_url` sub. The host performs the real byte-level HTTP transfer itself now (Rust-side\nstreaming download — see `specs/005-download-plugin-progress/plan.md`'s Summary for why: this\nis what makes real progress reporting, per-domain concurrency limiting, and rate limiting\npossible at all), rather than treating a plugin's result as an opaque already-downloaded blob.\n\nExactly one of {@linkcode downloads}, {@linkcode file_path}, or {@linkcode error} is present in\na given result; a `downloads` array, if present, has at least one element."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 23766,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 348
              }
            }
          ],
          "name": "DownloadResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "This revision's source URL, already normalized through this plugin's own\n{@linkcode canonicalizeSource} (see {@linkcode DownloadResult.version_history}'s docs — an\nun-normalized value silently fails to match a stored `source:` tag)."
                    },
                    "location": {
                      "byteIndex": 27124,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 395
                    },
                    "name": "source",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "ISO 8601 timestamp the site reports for this revision. The plugin converts from whatever the\nsite's native format is (e-hentai's `gdata` reports `posted` as a unix-seconds *string*). Sole\ndeterminer of revision order: there is deliberately no separate \"this one is current\" flag,\nsince a second source of truth could contradict the sort and leave the host no way to know\nwhich to believe."
                    },
                    "location": {
                      "byteIndex": 27560,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 401
                    },
                    "name": "posted_at",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "One node of a {@linkcode DownloadResult.version_history} chain — a single known revision of the\nseries, as the plugin's site reports it."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 26829,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 391
              }
            }
          ],
          "name": "VersionHistoryEntry"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "A creator/uploader to follow."
                    },
                    "location": {
                      "byteIndex": 28047,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 412
                    },
                    "name": "creator",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Tags that scope the subscription."
                    },
                    "location": {
                      "byteIndex": 28110,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 414
                    },
                    "name": "tags",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "keyword",
                        "repr": "string",
                        "value": "string"
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "A feed or index page the user supplied, for sources whose extension cannot search unaided."
                    },
                    "location": {
                      "byteIndex": 28229,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 416
                    },
                    "name": "listing_url",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "How many listing pages to read at most, supplied by the host.\n\nAn extension that pages MUST respect it rather than walking the whole result set: a tag search can\nrun to tens of thousands of works. Reading only the first page is not enough either — an existing\nwork edited to carry a tag enters the result set *in the middle*, ordered by its original\npublication date, so it never appears on page one."
                    },
                    "location": {
                      "byteIndex": 28689,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 423
                    },
                    "name": "max_pages",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "What a subscription is looking for, handed to {@linkcode discover} (issue #55).\n\nExactly one shape drives a given call: an extension that can search its own site is given\n`creator`/`tags`; one pointed at a feed or index page is given `listing_url`. Both paths return\nthe same thing — candidate works — which is why they share one capability rather than being two."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 27969,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 410
              }
            }
          ],
          "name": "DiscoveryCriteria"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "This work's source URL, already normalised through this plugin's own\n{@linkcode canonicalizeSource} so the host's duplicate check is a plain comparison. An\nun-normalised value silently fails to match an identical work already held, with nothing\nreporting the mismatch."
                    },
                    "location": {
                      "byteIndex": 29081,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 432
                    },
                    "name": "source",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The work's title, by language. Keys are language tags (`\"ja\"`, `\"en\"`, …) plus `\"origin\"` for\nthe title as the source itself writes it.\n\nA map rather than one string because the same work legitimately has several titles, and a reader\nof Japanese wants a different one from a reader of English. Naming the languages in the contract\n(`title_jpn` and friends) would mean changing it again for the next source, so the source decides\nwhich keys it can fill.\n\nA bare string is still accepted and read as `{ origin: \"...\" }`."
                    },
                    "location": {
                      "byteIndex": 29941,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 445
                    },
                    "name": "title",
                    "optional": true,
                    "tsType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        },
                        {
                          "kind": "typeRef",
                          "repr": "Record",
                          "value": {
                            "typeName": "Record",
                            "typeParams": [
                              {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              },
                              {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              }
                            ]
                          }
                        }
                      ]
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "When the source published it, as the listing states it.\n\nMay be a date string (`\"YYYY-MM-DD HH:MM\"`, RFC 3339, ...), a numeric string, Unix seconds, a\nJavaScript millisecond timestamp, or a `Date`. The dispatcher normalises numeric values and\n`Date` objects to a Unix-seconds string before the host receives them; date strings are parsed\nhost-side. Declaring it here is what lets the host offer \"published more than N hours ago\" as a\nfilter on *any* source carrying a date, rather than on the ones it was taught about\nindividually. Omit it rather than guessing: a wrong value silently holds a work back, or\nreleases one early."
                    },
                    "location": {
                      "byteIndex": 30659,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 455
                    },
                    "name": "posted_at",
                    "optional": true,
                    "tsType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        },
                        {
                          "kind": "keyword",
                          "repr": "number",
                          "value": "number"
                        },
                        {
                          "kind": "typeRef",
                          "repr": "Date",
                          "value": {
                            "typeName": "Date"
                          }
                        }
                      ]
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "0–5. Absent means *not yet rated*, which is not the same as rated zero — a work published\nminutes ago usually has no rating at all, and the host treats the two differently rather than\njudging it at the moment the answer is least knowable."
                    },
                    "location": {
                      "byteIndex": 30959,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 459
                    },
                    "name": "rating",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Namespaced as the source writes them (`artist:foo`), not stripped — the host's tag rules compare\nagainst these verbatim."
                    },
                    "location": {
                      "byteIndex": 31115,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 462
                    },
                    "name": "tags",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "keyword",
                        "repr": "string",
                        "value": "string"
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The source's own classification (`\"Doujinshi\"`, `\"Manga\"`), for the excluded-category rule."
                    },
                    "location": {
                      "byteIndex": 31235,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 464
                    },
                    "name": "category",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Who posted it — what a creator-scoped subscription matches on."
                    },
                    "location": {
                      "byteIndex": 31330,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 466
                    },
                    "name": "uploader",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Length, so the host can show and filter on download cost before anything is spent."
                    },
                    "location": {
                      "byteIndex": 31443,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 468
                    },
                    "name": "pages",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "One work a listing offered."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 28747,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 427
              }
            }
          ],
          "name": "DiscoveredCandidate"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 31967,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 479
                    },
                    "name": "candidates",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "DiscoveredCandidate",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "DiscoveredCandidate"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "**Load-bearing.** `true` when this listing was read without the sign-in it needed.\n\nMany sources answer a signed-out search with a *smaller result set* rather than an error — the\ncall succeeds, it simply saw less. Staying silent about that would be indistinguishable from\n\"nothing new\", and the host would record works it never had access to as handled, hiding them\npermanently with no error anywhere to reveal it. Set this whenever the sign-in was missing,\nexpired, or rejected, even though the listing itself parsed fine."
                    },
                    "location": {
                      "byteIndex": 32569,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 487
                    },
                    "name": "degraded",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "boolean",
                      "value": "boolean"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Why the result is degraded, or why nothing could be read at all."
                    },
                    "location": {
                      "byteIndex": 32665,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 489
                    },
                    "name": "error",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "PluginError",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "PluginError"
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`discover`'s return shape.\n\nNote what this does NOT contain: any judgment about whether a candidate is worth downloading.\nThe extension reports what the site says exists; the host compares that against its own library\nand the user's rules. Keeping the judgment host-side means one tested implementation shared by\nevery source rather than each extension reinventing it — the same split {@linkcode\nDownloadResult.version_history} already uses."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 31930,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 478
              }
            }
          ],
          "name": "DiscoveryResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "tsType": {
                  "kind": "fnOrConstructor",
                  "value": {
                    "constructor": false,
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "criteria",
                        "optional": false,
                        "tsType": {
                          "kind": "typeRef",
                          "repr": "DiscoveryCriteria",
                          "value": {
                            "resolution": {
                              "kind": "local"
                            },
                            "typeName": "DiscoveryCriteria"
                          }
                        }
                      }
                    ],
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Promise",
                      "value": {
                        "typeName": "Promise",
                        "typeParams": [
                          {
                            "kind": "typeRef",
                            "repr": "DiscoveryResult",
                            "value": {
                              "resolution": {
                                "kind": "local"
                              },
                              "typeName": "DiscoveryResult"
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              },
              "jsDoc": {
                "doc": "Optional export, parallel to {@linkcode pluginOptions}. Given a subscription's criteria (or a\nlisting URL), returns the candidate works this source currently offers.\n\nAbsence is normal: a plugin without this export simply cannot back a subscription, and the host\nexcludes it from the sources a user may pick — stating why, rather than accepting the\nsubscription and failing at its first check.\n\nThis is the only capability that *finds* things. {@linkcode execMetadata} and\n{@linkcode execDownload} both act on one already-known item, which is why a subscription needs\nsomething new rather than a new way of calling them.\n\nDeclare it as `export function discover(criteria: DiscoveryCriteria): Promise<DiscoveryResult>`."
              },
              "kind": "typeAlias",
              "location": {
                "byteIndex": 33448,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 505
              }
            }
          ],
          "name": "Discover"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "An exact hostname (`\"example.com\"`) or a wildcard covering subdomains (`\"*.example.com\"`).\nCase-insensitive; no port/scheme component. Omit entirely (or use `\"*\"`) for a general,\nnon-domain-specific fallback rule. An exact-hostname match always takes precedence over a\nwildcard match, which always takes precedence over the general fallback."
                    },
                    "location": {
                      "byteIndex": 34227,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 516
                    },
                    "name": "pattern",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Maximum simultaneous downloads permitted against a domain matching `pattern`. Absent = no\nconcurrency limit from this rule."
                    },
                    "location": {
                      "byteIndex": 34385,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 519
                    },
                    "name": "max_concurrent",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Maximum transfer speed (bytes/sec) permitted against a domain matching `pattern`. Absent =\nno rate limit from this rule."
                    },
                    "location": {
                      "byteIndex": 34547,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 522
                    },
                    "name": "max_bytes_per_sec",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Human-readable explanation shown in the settings UI."
                    },
                    "location": {
                      "byteIndex": 34639,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 524
                    },
                    "name": "description",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "One per-domain concurrency/rate-limit rule, as declared by a plugin's own {@linkcode\nPluginOptionsResult.domain_rules} default, or as a user's persisted override\n(`GET`/`PUT /api/plugins/{namespace}/options`) — `specs/005-download-plugin-progress/\ndata-model.md`'s `Domain Rule`."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 33829,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 511
              }
            }
          ],
          "name": "DomainRule"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "The plugin's own declared default {@linkcode DomainRule}s for the domain(s) it targets.\nAbsent/empty means the plugin declares no concurrency/rate-limit defaults of its own — that\ndomain is unmanaged (unlimited) unless a user override exists (spec FR-017: the system itself\nimposes no default)."
                    },
                    "location": {
                      "byteIndex": 35562,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 538
                    },
                    "name": "domain_rules",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "DomainRule",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "DomainRule"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Only meaningful for a plugin whose `execDownload` can return more than one\n{@linkcode DownloadResult.downloads} element. Absent for a single-resource-only plugin (no\nsuch setting shown at all)."
                    },
                    "location": {
                      "byteIndex": 35806,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 542
                    },
                    "name": "bundle_as_archive",
                    "optional": true,
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "properties": [
                          {
                            "jsDoc": {
                              "doc": "The plugin's own declared default (e.g. Pixiv: `true` — its multi-page downloads must ship\nas one manga archive, not one archive per page)."
                            },
                            "location": {
                              "byteIndex": 35992,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 545
                            },
                            "name": "default",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "boolean",
                              "value": "boolean"
                            }
                          },
                          {
                            "jsDoc": {
                              "doc": "Human-readable explanation shown in the settings UI."
                            },
                            "location": {
                              "byteIndex": 36078,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 547
                            },
                            "name": "description",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Whether starting a download for a URL this plugin handles should overwrite an existing\narchive that collides by content hash or destination filename, rather than the default safe\nbehavior (reject the new file, keep the existing archive untouched). Omit if this plugin has\nno opinion — the effective value then falls back to the global `replacedupe` setting."
                    },
                    "location": {
                      "byteIndex": 36490,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 553
                    },
                    "name": "overwrite_on_duplicate",
                    "optional": true,
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "properties": [
                          {
                            "location": {
                              "byteIndex": 36521,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 554
                            },
                            "name": "default",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "boolean",
                              "value": "boolean"
                            }
                          },
                          {
                            "jsDoc": {
                              "doc": "Human-readable explanation shown in the settings UI."
                            },
                            "location": {
                              "byteIndex": 36607,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 556
                            },
                            "name": "description",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "How to handle a download the host determines (via {@linkcode DownloadResult.version_history})\nis a NEWER revision of an already-catalogued archive.\n\nDeclaring this is entirely optional even for a plugin that does populate `version_history`:\nthe host detects `version_history` support by static analysis of the plugin's own source (a\n`plugin_introspect` dispatcher call), so the settings UI offers both policies to the user\nregardless, falling back to {@linkcode DEFAULT_RELATIVE_NEWER_POLICY} when the plugin states no\npreference. Declare it only to express a *different* default than that."
                    },
                    "location": {
                      "byteIndex": 37269,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 566
                    },
                    "name": "relative_newer_policy",
                    "optional": true,
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "properties": [
                          {
                            "location": {
                              "byteIndex": 37299,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 567
                            },
                            "name": "default",
                            "tsType": {
                              "kind": "typeRef",
                              "repr": "RelativeNewerPolicy",
                              "value": {
                                "resolution": {
                                  "kind": "local"
                                },
                                "typeName": "RelativeNewerPolicy"
                              }
                            }
                          },
                          {
                            "jsDoc": {
                              "doc": "Human-readable explanation shown in the settings UI."
                            },
                            "location": {
                              "byteIndex": 37397,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 569
                            },
                            "name": "description",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "How to handle a download the host determines is an OLDER revision of an already-catalogued\narchive. Optional on exactly the same terms as\n{@linkcode PluginOptionsResult.relative_newer_policy}; defaults to\n{@linkcode DEFAULT_RELATIVE_OLDER_POLICY}."
                    },
                    "location": {
                      "byteIndex": 37697,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 575
                    },
                    "name": "relative_older_policy",
                    "optional": true,
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "properties": [
                          {
                            "location": {
                              "byteIndex": 37727,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 576
                            },
                            "name": "default",
                            "tsType": {
                              "kind": "typeRef",
                              "repr": "RelativeOlderPolicy",
                              "value": {
                                "resolution": {
                                  "kind": "local"
                                },
                                "typeName": "RelativeOlderPolicy"
                              }
                            }
                          },
                          {
                            "jsDoc": {
                              "doc": "Human-readable explanation shown in the settings UI."
                            },
                            "location": {
                              "byteIndex": 37825,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 578
                            },
                            "name": "description",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "How often a subscription against this source may check (issue #55). Only meaningful for a\nplugin that also exports {@linkcode Discover}.\n\nTwo numbers because two parties hold different knowledge: this plugin knows what its site\ntolerates (rate limits, ban risk), while the user knows how closely they want to follow a given\ncreator. `suggested_secs` is a starting point the user may change; `minimum_secs` is a floor the\nhost enforces and the user cannot go under — being rate-limited or banned is not a consequence\nthe user should have to discover by trial."
                    },
                    "location": {
                      "byteIndex": 38457,
                      "col": 2,
                      "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                      "line": 588
                    },
                    "name": "check_interval",
                    "optional": true,
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "properties": [
                          {
                            "location": {
                              "byteIndex": 38480,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 589
                            },
                            "name": "suggested_secs",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "number",
                              "value": "number"
                            }
                          },
                          {
                            "location": {
                              "byteIndex": 38508,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 590
                            },
                            "name": "minimum_secs",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "number",
                              "value": "number"
                            }
                          },
                          {
                            "jsDoc": {
                              "doc": "Human-readable explanation shown in the settings UI."
                            },
                            "location": {
                              "byteIndex": 38598,
                              "col": 4,
                              "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                              "line": 592
                            },
                            "name": "description",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          }
                        ]
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`pluginOptions()`'s return shape — a plain additional `export function`, parallel to the\nmandatory `pluginInfo()` (see this module's own top-level docs). Omitting `pluginOptions()`\nentirely is valid, and is the expected shape for every non-download plugin, and for a download\nplugin with nothing to configure — the host then shows no settings UI for that plugin at all\n(spec FR-015). `specs/005-download-plugin-progress/contracts/plugin-download-protocol.md` is the\nwire contract this mirrors field-for-field."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 35200,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 533
              }
            }
          ],
          "name": "PluginOptionsResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "tsType": {
                  "kind": "union",
                  "value": [
                    {
                      "kind": "literal",
                      "repr": "always_overwrite",
                      "value": {
                        "kind": "string",
                        "string": "always_overwrite"
                      }
                    },
                    {
                      "kind": "literal",
                      "repr": "overwrite_if_same_name",
                      "value": {
                        "kind": "string",
                        "string": "overwrite_if_same_name"
                      }
                    }
                  ]
                }
              },
              "jsDoc": {
                "doc": "What to do when the incoming download is a newer revision of something already in the library.\n- `\"always_overwrite\"`: delete the old archive unconditionally, regardless of filename.\n- `\"overwrite_if_same_name\"`: only overwrite when destination filenames actually collide;\n  otherwise keep both as separate archives."
              },
              "kind": "typeAlias",
              "location": {
                "byteIndex": 38960,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 600
              }
            }
          ],
          "name": "RelativeNewerPolicy"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "tsType": {
                  "kind": "union",
                  "value": [
                    {
                      "kind": "literal",
                      "repr": "block",
                      "value": {
                        "kind": "string",
                        "string": "block"
                      }
                    },
                    {
                      "kind": "literal",
                      "repr": "warn_then_conflict_menu",
                      "value": {
                        "kind": "string",
                        "string": "warn_then_conflict_menu"
                      }
                    },
                    {
                      "kind": "literal",
                      "repr": "silent_then_conflict_menu",
                      "value": {
                        "kind": "string",
                        "string": "silent_then_conflict_menu"
                      }
                    }
                  ]
                }
              },
              "jsDoc": {
                "doc": "What to do when the incoming download is an *older* revision than something already in library.\n- `\"block\"`: refuse the download outright before any bytes transfer, surfacing which in-library\n  archive is newer as the failure reason.\n- `\"warn_then_conflict_menu\"`: hold the queue item for explicit user confirmation before\n  starting the transfer; afterwards the existing filename-collision conflict menu still applies\n  if (and only if) the filename actually collides.\n- `\"silent_then_conflict_menu\"`: no upfront prompt, the download just proceeds; same\n  filename-collision-gated conflict menu afterwards."
              },
              "kind": "typeAlias",
              "location": {
                "byteIndex": 39678,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 610
              }
            }
          ],
          "name": "RelativeOlderPolicy"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "kind": "const",
                "tsType": {
                  "kind": "typeRef",
                  "repr": "RelativeNewerPolicy",
                  "value": {
                    "resolution": {
                      "kind": "local"
                    },
                    "typeName": "RelativeNewerPolicy"
                  }
                }
              },
              "jsDoc": {
                "doc": "Applied when neither the user nor the plugin states a preference. A download the site itself\nmarks as a later revision of something already in the library is normally meant to replace it;\nkeeping both would leave a superseded duplicate for the user to find and delete by hand. The old\narchive is only removed after the new revision is safely catalogued, so this default never risks\nending up with neither."
              },
              "kind": "variable",
              "location": {
                "byteIndex": 40226,
                "col": 13,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 620
              }
            }
          ],
          "name": "DEFAULT_RELATIVE_NEWER_POLICY"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "kind": "const",
                "tsType": {
                  "kind": "typeRef",
                  "repr": "RelativeOlderPolicy",
                  "value": {
                    "resolution": {
                      "kind": "local"
                    },
                    "typeName": "RelativeOlderPolicy"
                  }
                }
              },
              "jsDoc": {
                "doc": "Applied when neither the user nor the plugin states a preference. Asking first is the safe\ndefault for the genuinely surprising case — the user asked for a URL that turns out to be older\nthan what they already have."
              },
              "kind": "variable",
              "location": {
                "byteIndex": 40544,
                "col": 13,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 625
              }
            }
          ],
          "name": "DEFAULT_RELATIVE_OLDER_POLICY"
        },
        {
          "declarations": [
            {
              "declarationKind": "export",
              "def": {
                "tsType": {
                  "kind": "fnOrConstructor",
                  "value": {
                    "constructor": false,
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "url",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ],
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  }
                }
              },
              "jsDoc": {
                "doc": "Optional export, parallel to {@linkcode pluginOptions}. Given a URL this plugin's site might\nexpose (any domain alias, with or without query string/trailing slash/scheme), returns the\ncanonical form the host should use for matching against stored `source:` tags — e.g. e-hentai\nfolding `exhentai.org`/`g.e-hentai.org`/`forums.e-hentai.org` all down to a single\n`e-hentai.org/g/{gid}/{token}` form. The host calls this before every `source:` tag comparison\n(both for {@linkcode DownloadResult.version_history} matching and for\n`plugins/script/sourcefinder.ts`'s existing lookup) instead of maintaining its own per-site\ndomain-alias table, so this plugin's own domain-alias knowledge lives in the one place that\nactually has it.\n\nCONTRACT (required, not just a suggestion): this function MUST be idempotent and MUST produce\nbyte-identical output for every URL form that refers to the same underlying resource — including\na URL already stored in an archive's `source:` tag from a PAST version of this plugin (or from\nthe host's own generic `trim_url()`), a freshly-fetched URL this plugin just resolved via\n`execDownload`, AND every entry this plugin puts in `DownloadResult.version_history[].source`.\nThe host calls this function on both sides of every comparison and does a plain string equality\ncheck on the results — if this function ever normalizes the same real-world resource into two\ndifferent strings depending on which historical form of the URL it was given, the comparison\nsilently fails to match with no error surfaced anywhere, which is far harder to diagnose than an\nexception. When in doubt, normalize toward the most stable identifier the site guarantees never\nchanges (e.g. e-hentai's numeric `gid`), not toward whatever the most recently observed URL\nshape happens to look like.\n\nReturn the input unchanged if this plugin has no additional normalization beyond the host's\ngeneric scheme/www/query/trailing-slash trimming. Omitting this export entirely is valid — the\nhost then applies only its own generic `trim_url()`.\n\nThis declaration exists purely to document the contract; a plugin declares its own\n`export function canonicalizeSource(url: string): string` rather than importing this type."
              },
              "kind": "typeAlias",
              "location": {
                "byteIndex": 42933,
                "col": 0,
                "filename": "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts",
                "line": 657
              }
            }
          ],
          "name": "CanonicalizeSource"
        }
      ]
    },
    "plugins/legacy-globals.d.ts": {
      "symbols": [
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 3170,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 41
                    },
                    "name": "name",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 3186,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 42
                    },
                    "name": "value",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 3203,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 43
                    },
                    "name": "domain",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 3221,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 44
                    },
                    "name": "path",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "A cookie exactly as `Mojo::Cookie::Response->new(name => ..., value => ..., domain => ...,\npath => ...)` shapes it in the legacy Perl source this was converted from —\n`lanrurugi-plugin-converter` renders that legacy constructor call straight to an object literal\nof this shape (see `render.rs::try_match_legacy_http_constructor`), so `cookie_jar` below takes\nit as plain data rather than a real class instance."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 3143,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 40
              }
            }
          ],
          "name": "LegacyCookie"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 3269,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 48
                    },
                    "name": "body",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 3285,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 49
                    },
                    "name": "code",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`$res->dom` (`Mojo::Message::Body`'s own DOM-parsing shortcut) — parses `body` as HTML\non first access. A getter (not parsed eagerly at fetch time) since most call sites only ever\nread `.body` as plain text and never touch `.dom` at all."
                    },
                    "location": {
                      "byteIndex": 3560,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 53
                    },
                    "name": "dom",
                    "readonly": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "LegacyDomNode",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "LegacyDomNode"
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`$res->json` (`Mojo::Message::Body`'s own JSON-decoding shortcut) — parses `body` as JSON on\nfirst access. Real `Mojo::Message::Body::json` returns `undef` on a parse failure rather than\nthrowing (legacy code routinely checks `if (defined $res->json)` or wraps the whole call in\n`eval {}` expecting exactly that); matched here by returning `undefined` instead of letting\n`JSON.parse` throw."
                    },
                    "location": {
                      "byteIndex": 4013,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 59
                    },
                    "name": "json",
                    "readonly": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "unknown",
                      "value": "unknown"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`$res->is_error` (`Mojo::Message`'s own status-class check) — true for any 4xx/5xx HTTP\nstatus."
                    },
                    "location": {
                      "byteIndex": 4151,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 62
                    },
                    "name": "is_error",
                    "readonly": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "boolean",
                      "value": "boolean"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`$res->headers` — not a general `Mojo::Headers` shim, just the one accessor\n(`->location`, read when manually following a 301/302 redirect with `max_redirects(0)`) any\nreal call site in the legacy plugin corpus actually reads off a response's headers."
                    },
                    "location": {
                      "byteIndex": 4454,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 66
                    },
                    "name": "headers",
                    "readonly": true,
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "properties": [
                          {
                            "location": {
                              "byteIndex": 4474,
                              "col": 22,
                              "filename": "plugins/legacy-globals.d.ts",
                              "line": 66
                            },
                            "name": "location",
                            "tsType": {
                              "kind": "union",
                              "value": [
                                {
                                  "kind": "keyword",
                                  "repr": "string",
                                  "value": "string"
                                },
                                {
                                  "kind": "keyword",
                                  "repr": "undefined",
                                  "value": "undefined"
                                }
                              ]
                            }
                          }
                        ]
                      }
                    }
                  }
                ]
              },
              "kind": "interface",
              "location": {
                "byteIndex": 3238,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 47
              }
            }
          ],
          "name": "LegacyHttpResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "methods": [
                  {
                    "kind": "method",
                    "location": {
                      "byteIndex": 5478,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 81
                    },
                    "name": "attr",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "name",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        },
                        {
                          "kind": "keyword",
                          "repr": "undefined",
                          "value": "undefined"
                        }
                      ]
                    }
                  },
                  {
                    "kind": "method",
                    "location": {
                      "byteIndex": 5557,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 83
                    },
                    "name": "at",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "selector",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "typeRef",
                          "repr": "LegacyDomNode",
                          "value": {
                            "resolution": {
                              "kind": "local"
                            },
                            "typeName": "LegacyDomNode"
                          }
                        },
                        {
                          "kind": "keyword",
                          "repr": "undefined",
                          "value": "undefined"
                        }
                      ]
                    }
                  },
                  {
                    "kind": "method",
                    "location": {
                      "byteIndex": 5608,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 84
                    },
                    "name": "find",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "selector",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "intersection",
                      "value": [
                        {
                          "kind": "array",
                          "value": {
                            "kind": "typeRef",
                            "repr": "LegacyDomNode",
                            "value": {
                              "resolution": {
                                "kind": "local"
                              },
                              "typeName": "LegacyDomNode"
                            }
                          }
                        },
                        {
                          "kind": "typeLiteral",
                          "value": {
                            "methods": [
                              {
                                "kind": "method",
                                "location": {
                                  "byteIndex": 5652,
                                  "col": 46,
                                  "filename": "plugins/legacy-globals.d.ts",
                                  "line": 84
                                },
                                "name": "each",
                                "params": [
                                  {
                                    "kind": "identifier",
                                    "name": "fn",
                                    "optional": false,
                                    "tsType": {
                                      "kind": "fnOrConstructor",
                                      "value": {
                                        "constructor": false,
                                        "params": [
                                          {
                                            "kind": "identifier",
                                            "name": "node",
                                            "optional": false,
                                            "tsType": {
                                              "kind": "typeRef",
                                              "repr": "LegacyDomNode",
                                              "value": {
                                                "resolution": {
                                                  "kind": "local"
                                                },
                                                "typeName": "LegacyDomNode"
                                              }
                                            }
                                          },
                                          {
                                            "kind": "identifier",
                                            "name": "index",
                                            "optional": false,
                                            "tsType": {
                                              "kind": "keyword",
                                              "repr": "number",
                                              "value": "number"
                                            }
                                          }
                                        ],
                                        "tsType": {
                                          "kind": "typeRef",
                                          "repr": "T",
                                          "value": {
                                            "resolution": {
                                              "kind": "typeParam"
                                            },
                                            "typeName": "T"
                                          }
                                        }
                                      }
                                    }
                                  }
                                ],
                                "returnType": {
                                  "kind": "array",
                                  "value": {
                                    "kind": "typeRef",
                                    "repr": "T",
                                    "value": {
                                      "resolution": {
                                        "kind": "typeParam"
                                      },
                                      "typeName": "T"
                                    }
                                  }
                                },
                                "typeParams": [
                                  {
                                    "name": "T"
                                  }
                                ]
                              }
                            ]
                          }
                        }
                      ]
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`->children(selector?)` (`Mojo::DOM::children`) — direct children only (optionally filtered\nby `selector`), distinct from `find`, which also matches nested descendants."
                    },
                    "kind": "method",
                    "location": {
                      "byteIndex": 5902,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 87
                    },
                    "name": "children",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "selector",
                        "optional": true,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "intersection",
                      "value": [
                        {
                          "kind": "array",
                          "value": {
                            "kind": "typeRef",
                            "repr": "LegacyDomNode",
                            "value": {
                              "resolution": {
                                "kind": "local"
                              },
                              "typeName": "LegacyDomNode"
                            }
                          }
                        },
                        {
                          "kind": "typeLiteral",
                          "value": {
                            "methods": [
                              {
                                "kind": "method",
                                "location": {
                                  "byteIndex": 5951,
                                  "col": 51,
                                  "filename": "plugins/legacy-globals.d.ts",
                                  "line": 87
                                },
                                "name": "each",
                                "params": [
                                  {
                                    "kind": "identifier",
                                    "name": "fn",
                                    "optional": false,
                                    "tsType": {
                                      "kind": "fnOrConstructor",
                                      "value": {
                                        "constructor": false,
                                        "params": [
                                          {
                                            "kind": "identifier",
                                            "name": "node",
                                            "optional": false,
                                            "tsType": {
                                              "kind": "typeRef",
                                              "repr": "LegacyDomNode",
                                              "value": {
                                                "resolution": {
                                                  "kind": "local"
                                                },
                                                "typeName": "LegacyDomNode"
                                              }
                                            }
                                          },
                                          {
                                            "kind": "identifier",
                                            "name": "index",
                                            "optional": false,
                                            "tsType": {
                                              "kind": "keyword",
                                              "repr": "number",
                                              "value": "number"
                                            }
                                          }
                                        ],
                                        "tsType": {
                                          "kind": "typeRef",
                                          "repr": "T",
                                          "value": {
                                            "resolution": {
                                              "kind": "typeParam"
                                            },
                                            "typeName": "T"
                                          }
                                        }
                                      }
                                    }
                                  }
                                ],
                                "returnType": {
                                  "kind": "array",
                                  "value": {
                                    "kind": "typeRef",
                                    "repr": "T",
                                    "value": {
                                      "resolution": {
                                        "kind": "typeParam"
                                      },
                                      "typeName": "T"
                                    }
                                  }
                                },
                                "typeParams": [
                                  {
                                    "name": "T"
                                  }
                                ]
                              }
                            ]
                          }
                        }
                      ]
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`->to_string` — real `Mojo::DOM` re-serializes the (possibly-modified) tree back to markup;\nthis shim is read-only (no `->remove`/mutation support), so the original source markup never\nactually changes after parsing, and returning that original string verbatim is equivalent to\na real serialization for every real call site in the legacy plugin corpus (both existing uses\n— `EHentai.pm`/`Pixiv.pm` — only ever call it on the *root* `Mojo::DOM->new(...)`/`->dom`\nresult to substring-search the whole page, never on a sub-node after `->at()`/`->find()`)."
                    },
                    "kind": "method",
                    "location": {
                      "byteIndex": 6609,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 94
                    },
                    "name": "toString",
                    "params": [],
                    "returnType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  }
                ],
                "properties": [
                  {
                    "location": {
                      "byteIndex": 5462,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 80
                    },
                    "name": "text",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 5520,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 82
                    },
                    "name": "parent",
                    "tsType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "typeRef",
                          "repr": "LegacyDomNode",
                          "value": {
                            "resolution": {
                              "kind": "local"
                            },
                            "typeName": "LegacyDomNode"
                          }
                        },
                        {
                          "kind": "keyword",
                          "repr": "undefined",
                          "value": "undefined"
                        }
                      ]
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`Mojo::DOM`'s return shape for `->at(selector)`/`->find(selector)` results — enough of its\nchainable node API (`->text`, `->attr(name)`, `->parent`, `->find(selector)->each`) to cover\nevery call site across the legacy plugin corpus (verified: every `->at(...)`/`->find(...)`\nselector string across `~/LANraragi/lib/LANraragi/Plugin/**\\/*.pm` is a single simple selector —\na tag name, `.class`, `#id`, or `[attr=val]`/`[attr^=val]`, never a descendant combinator or\npseudo-class). Deliberately not a real DOM (no `querySelector`, no live tree, no `Node`\ninterface) and not backed by a third-party HTML-parsing library (`dispatcher.ts`'s own docs:\nno external module imports at all, so a plugin's `--allow-net`/`--allow-read` grant is never\nimplicitly widened by the dispatcher's own needs) — a small hand-rolled parser and selector\nmatcher, sized to exactly what this corpus exercises."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 5434,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 79
              }
            }
          ],
          "name": "LegacyDomNode"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 7053,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 102
                    },
                    "name": "req",
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "properties": [
                          {
                            "location": {
                              "byteIndex": 7060,
                              "col": 9,
                              "filename": "plugins/legacy-globals.d.ts",
                              "line": 102
                            },
                            "name": "headers",
                            "tsType": {
                              "kind": "typeLiteral",
                              "value": {
                                "methods": [
                                  {
                                    "kind": "method",
                                    "location": {
                                      "byteIndex": 7071,
                                      "col": 20,
                                      "filename": "plugins/legacy-globals.d.ts",
                                      "line": 102
                                    },
                                    "name": "header",
                                    "params": [
                                      {
                                        "kind": "identifier",
                                        "name": "name",
                                        "optional": false,
                                        "tsType": {
                                          "kind": "keyword",
                                          "repr": "string",
                                          "value": "string"
                                        }
                                      },
                                      {
                                        "kind": "identifier",
                                        "name": "value",
                                        "optional": false,
                                        "tsType": {
                                          "kind": "keyword",
                                          "repr": "string",
                                          "value": "string"
                                        }
                                      }
                                    ],
                                    "returnType": {
                                      "kind": "keyword",
                                      "repr": "void",
                                      "value": "void"
                                    }
                                  }
                                ]
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`$tx->req->headers->header(name => value)` — the one piece of `Mojo::Transaction` surface an\n`->on('start', sub ($ua, $tx) {...})` handler (see `LegacyUserAgent.on` below) actually reaches\ninto across the legacy plugin corpus (always to set a static request header, never to read\nanything back off `$tx`), so this is intentionally not a general `Mojo::Transaction` shim."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 7021,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 101
              }
            }
          ],
          "name": "LegacyTransaction"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "methods": [
                  {
                    "kind": "method",
                    "location": {
                      "byteIndex": 7730,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 113
                    },
                    "name": "max_redirects",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "n",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "number",
                          "value": "number"
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "typeRef",
                      "repr": "LegacyUserAgent",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "LegacyUserAgent"
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`$ua->on(start => sub ($ua, $tx) {...})` — real `Mojo::UserAgent` fires this before *every*\nrequest; every real call site in the legacy plugin corpus only uses it to set one or more\nstatic headers via `$tx->req->headers->header(...)`, never anything request-specific (e.g.\ninspecting `$tx->req->url`), so it's faithfully enough modeled by running `handler` once,\nimmediately, against a synthetic `tx` whose header setter writes into this instance's default\nheaders (applied to every `get`/`post` call from then on) rather than actually re-invoking it\nper request. Only the `\"start\"` event is recognized — legacy's other events\n(`error`/`finish`/etc.) have no real use in this corpus and aren't modeled."
                    },
                    "kind": "method",
                    "location": {
                      "byteIndex": 8717,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 125
                    },
                    "name": "on",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "event",
                        "optional": false,
                        "tsType": {
                          "kind": "literal",
                          "repr": "start",
                          "value": {
                            "kind": "string",
                            "string": "start"
                          }
                        }
                      },
                      {
                        "kind": "identifier",
                        "name": "handler",
                        "optional": false,
                        "tsType": {
                          "kind": "fnOrConstructor",
                          "value": {
                            "constructor": false,
                            "params": [
                              {
                                "kind": "identifier",
                                "name": "ua",
                                "optional": false,
                                "tsType": {
                                  "kind": "typeRef",
                                  "repr": "LegacyUserAgent",
                                  "value": {
                                    "resolution": {
                                      "kind": "local"
                                    },
                                    "typeName": "LegacyUserAgent"
                                  }
                                }
                              },
                              {
                                "kind": "identifier",
                                "name": "tx",
                                "optional": false,
                                "tsType": {
                                  "kind": "typeRef",
                                  "repr": "LegacyTransaction",
                                  "value": {
                                    "resolution": {
                                      "kind": "local"
                                    },
                                    "typeName": "LegacyTransaction"
                                  }
                                }
                              }
                            ],
                            "tsType": {
                              "kind": "keyword",
                              "repr": "void",
                              "value": "void"
                            }
                          }
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "keyword",
                      "repr": "void",
                      "value": "void"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`headers`, when given, are merged on top of this instance's own default headers (set via\n{@linkcode transactor}/{@linkcode on}) for this one request only — mirrors real\n`Mojo::UserAgent`'s own `->get($url => {'Header-Name' => 'value'})` calling convention."
                    },
                    "kind": "method",
                    "location": {
                      "byteIndex": 9086,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 129
                    },
                    "name": "get",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "url",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      },
                      {
                        "kind": "identifier",
                        "name": "headers",
                        "optional": true,
                        "tsType": {
                          "kind": "typeRef",
                          "repr": "Record",
                          "value": {
                            "typeName": "Record",
                            "typeParams": [
                              {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              },
                              {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              }
                            ]
                          }
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "typeRef",
                      "repr": "Promise",
                      "value": {
                        "typeName": "Promise",
                        "typeParams": [
                          {
                            "kind": "typeLiteral",
                            "value": {
                              "properties": [
                                {
                                  "location": {
                                    "byteIndex": 9148,
                                    "col": 64,
                                    "filename": "plugins/legacy-globals.d.ts",
                                    "line": 129
                                  },
                                  "name": "result",
                                  "tsType": {
                                    "kind": "typeRef",
                                    "repr": "LegacyHttpResult",
                                    "value": {
                                      "resolution": {
                                        "kind": "local"
                                      },
                                      "typeName": "LegacyHttpResult"
                                    }
                                  }
                                }
                              ]
                            }
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`kind` mirrors Mojo::UserAgent's own `->post($url => form => {...})` /\n`->post($url => json => {...})` calling convention — `form` URL-encodes `data` as\n`application/x-www-form-urlencoded` (string values only, matching real HTML form semantics);\n`json` sends it as a `application/json` body (`data` may be any JSON-serializable value —\nverified against the one real `json`-kind call site in the corpus, which passes a nested\nobject with an array value, not just flat strings)."
                    },
                    "kind": "method",
                    "location": {
                      "byteIndex": 9694,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 136
                    },
                    "name": "post",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "url",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      },
                      {
                        "kind": "identifier",
                        "name": "kind",
                        "optional": false,
                        "tsType": {
                          "kind": "union",
                          "value": [
                            {
                              "kind": "literal",
                              "repr": "form",
                              "value": {
                                "kind": "string",
                                "string": "form"
                              }
                            },
                            {
                              "kind": "literal",
                              "repr": "json",
                              "value": {
                                "kind": "string",
                                "string": "json"
                              }
                            }
                          ]
                        }
                      },
                      {
                        "kind": "identifier",
                        "name": "data",
                        "optional": false,
                        "tsType": {
                          "kind": "union",
                          "value": [
                            {
                              "kind": "typeRef",
                              "repr": "Record",
                              "value": {
                                "typeName": "Record",
                                "typeParams": [
                                  {
                                    "kind": "keyword",
                                    "repr": "string",
                                    "value": "string"
                                  },
                                  {
                                    "kind": "keyword",
                                    "repr": "string",
                                    "value": "string"
                                  }
                                ]
                              }
                            },
                            {
                              "kind": "typeRef",
                              "repr": "Record",
                              "value": {
                                "typeName": "Record",
                                "typeParams": [
                                  {
                                    "kind": "keyword",
                                    "repr": "string",
                                    "value": "string"
                                  },
                                  {
                                    "kind": "keyword",
                                    "repr": "unknown",
                                    "value": "unknown"
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "typeRef",
                      "repr": "Promise",
                      "value": {
                        "typeName": "Promise",
                        "typeParams": [
                          {
                            "kind": "typeLiteral",
                            "value": {
                              "properties": [
                                {
                                  "location": {
                                    "byteIndex": 9819,
                                    "col": 15,
                                    "filename": "plugins/legacy-globals.d.ts",
                                    "line": 140
                                  },
                                  "name": "result",
                                  "tsType": {
                                    "kind": "typeRef",
                                    "repr": "LegacyHttpResult",
                                    "value": {
                                      "resolution": {
                                        "kind": "local"
                                      },
                                      "typeName": "LegacyHttpResult"
                                    }
                                  }
                                }
                              ]
                            }
                          }
                        ]
                      }
                    }
                  }
                ],
                "properties": [
                  {
                    "location": {
                      "byteIndex": 7679,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 112
                    },
                    "name": "cookie_jar",
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "methods": [
                          {
                            "kind": "method",
                            "location": {
                              "byteIndex": 7693,
                              "col": 16,
                              "filename": "plugins/legacy-globals.d.ts",
                              "line": 112
                            },
                            "name": "add",
                            "params": [
                              {
                                "kind": "identifier",
                                "name": "cookie",
                                "optional": false,
                                "tsType": {
                                  "kind": "typeRef",
                                  "repr": "LegacyCookie",
                                  "value": {
                                    "resolution": {
                                      "kind": "local"
                                    },
                                    "typeName": "LegacyCookie"
                                  }
                                }
                              }
                            ],
                            "returnType": {
                              "kind": "keyword",
                              "repr": "void",
                              "value": "void"
                            }
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`$ua->transactor->name($value)` — sets the default `User-Agent` header every subsequent\n`get`/`post` call on this instance sends."
                    },
                    "location": {
                      "byteIndex": 7921,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 116
                    },
                    "name": "transactor",
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "methods": [
                          {
                            "kind": "method",
                            "location": {
                              "byteIndex": 7935,
                              "col": 16,
                              "filename": "plugins/legacy-globals.d.ts",
                              "line": 116
                            },
                            "name": "name",
                            "params": [
                              {
                                "kind": "identifier",
                                "name": "value",
                                "optional": false,
                                "tsType": {
                                  "kind": "keyword",
                                  "repr": "string",
                                  "value": "string"
                                }
                              }
                            ],
                            "returnType": {
                              "kind": "keyword",
                              "repr": "void",
                              "value": "void"
                            }
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "A plain, JSON-serializable snapshot of this instance's cookie jar (the *same* live array the\n`get`/`post`/`cookie_jar.add` closures above mutate, not a copy) — every other property here\nis a function, which `JSON.stringify` silently drops, so a `do_login`-derived plugin's\n`return ua;` would otherwise cross `handleRequest`'s `writeLine` (below) as an empty object.\nThe host (`lanrurugi-api::plugins::with_login_cookies`) reads this off `exec_login`'s result\nand threads it back into a later `exec_metadata`/`exec_download`/`exec_script` call as\n`hostArgs.user_agent_cookies`, which `lanrurugi-plugin-converter`'s generated entry points\nrehydrate into a fresh `userAgent()` instance (see `render.rs`'s\n`render_user_agent_hydration_preamble`) — a live object with closures can't itself cross that\nsame JSON boundary, so this plain-array snapshot is what actually gets to do the traveling."
                    },
                    "location": {
                      "byteIndex": 10796,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 151
                    },
                    "name": "cookies",
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "LegacyCookie",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "LegacyCookie"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "A plain, JSON-serializable snapshot of every default header set on this instance via\n{@linkcode transactor}/{@linkcode on} so far — same \"only plain data survives `JSON.stringify`\"\nreasoning as {@linkcode cookies}, just for header/token-based auth (e.g. `Authorization: Key\n<api_key>`) instead of cookie-based auth. The host reads this off `exec_login`'s result and\nthreads it back as `hostArgs.user_agent_headers`, rehydrated the same way `user_agent_cookies`\nis (issue #78/#93 — before this field existed, a login plugin authenticating via a header\nrather than a cookie had no way to pass that credential to a downstream metadata/download call\nat all: `nhapiauth`'s `Authorization` header was silently dropped crossing this exact\nboundary)."
                    },
                    "location": {
                      "byteIndex": 11619,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 161
                    },
                    "name": "headers",
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          }
                        ]
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`legacyCompat.userAgent()`'s return shape — enough of `Mojo::UserAgent`'s own chainable\nsurface (`->cookie_jar->add(...)`, `->max_redirects(n)->get(url)->result->body`) to cover what\nthe legacy plugin corpus actually calls, backed by `fetch()` instead of a real CPAN HTTP\nclient. Not a general Mojo::UserAgent reimplementation — no proxy/auth/multipart support, no\n`Mojo::Message`-family object model, just the one GET/POST-with-cookies-and-redirects idiom\nevery login/download plugin so far has used."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 7649,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 111
              }
            }
          ],
          "name": "LegacyUserAgent"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "methods": [
                  {
                    "kind": "method",
                    "location": {
                      "byteIndex": 12169,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 170
                    },
                    "name": "debug",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "msg",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "keyword",
                      "repr": "void",
                      "value": "void"
                    }
                  },
                  {
                    "kind": "method",
                    "location": {
                      "byteIndex": 12197,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 171
                    },
                    "name": "info",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "msg",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "keyword",
                      "repr": "void",
                      "value": "void"
                    }
                  },
                  {
                    "kind": "method",
                    "location": {
                      "byteIndex": 12224,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 172
                    },
                    "name": "warn",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "msg",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "keyword",
                      "repr": "void",
                      "value": "void"
                    }
                  },
                  {
                    "kind": "method",
                    "location": {
                      "byteIndex": 12251,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 173
                    },
                    "name": "error",
                    "params": [
                      {
                        "kind": "identifier",
                        "name": "msg",
                        "optional": false,
                        "tsType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ],
                    "returnType": {
                      "kind": "keyword",
                      "repr": "void",
                      "value": "void"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`get_logger($name, $category)`/`get_plugin_logger()`'s return shape. The legacy versions write\nto a real rotating log file on disk (`LANraragi::Utils::Logging`); most converted plugins don't\nget filesystem write access under `declared_permissions` (constitution Principle IV), so this\nlogs to stderr instead — safe because `dispatcher.ts`'s own stdout is reserved for its\nnewline-delimited JSON protocol (see `writeLine` below) and stderr doesn't share that stream."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 12142,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 169
              }
            }
          ],
          "name": "LegacyLogger"
        },
        {
          "declarations": [
            {
              "declarationKind": "declare",
              "def": {
                "kind": "var",
                "tsType": {
                  "kind": "typeLiteral",
                  "value": {
                    "methods": [
                      {
                        "jsDoc": {
                          "doc": "Perl's `reverse(@list)` returns a *new* list; JS's `Array.prototype.reverse` mutates its\nreceiver in place. Spreads into a fresh array first so converted code doesn't pick up a\nmutation side effect the original Perl never had."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 12583,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 181
                        },
                        "name": "reverse",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "list",
                            "optional": false,
                            "tsType": {
                              "kind": "typeOperator",
                              "value": {
                                "operator": "readonly",
                                "tsType": {
                                  "kind": "array",
                                  "value": {
                                    "kind": "typeRef",
                                    "repr": "T",
                                    "value": {
                                      "resolution": {
                                        "kind": "typeParam"
                                      },
                                      "typeName": "T"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "array",
                          "value": {
                            "kind": "typeRef",
                            "repr": "T",
                            "value": {
                              "resolution": {
                                "kind": "typeParam"
                              },
                              "typeName": "T"
                            }
                          }
                        },
                        "typeParams": [
                          {
                            "name": "T"
                          }
                        ]
                      },
                      {
                        "jsDoc": {
                          "doc": "Perl's `chomp($x)` mutates `$x` in place, stripping exactly one trailing `\"\\n\"` (Perl's\ndefault `$/` record separator — not `\"\\r\\n\"`). JS can't mutate a caller's local binding\nthrough a function call, so converted code must reassign the result itself:\n`x = legacyCompat.chomp(x)`, mirroring `chomp $x;`'s own mutating spirit as closely as a\npure function can."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 13013,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 187
                        },
                        "name": "chomp",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "s",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "A minimal `sprintf` covering the format-spec subset actually used across the legacy\nplugin corpus (`%s`, `%d`, `%f` with optional width/precision) — not a full reimplementation\nof Perl's considerably larger `sprintf` grammar."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 13288,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 191
                        },
                        "name": "sprintf",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "format",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          },
                          {
                            "arg": {
                              "kind": "identifier",
                              "name": "args",
                              "optional": false
                            },
                            "kind": "rest",
                            "tsType": {
                              "kind": "array",
                              "value": {
                                "kind": "keyword",
                                "repr": "unknown",
                                "value": "unknown"
                              }
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "`Mojo::UserAgent->new` — see `LegacyUserAgent`'s own docs for exactly what's covered. Every\nplugin using this must still declare its own real target host(s) in `declared_permissions`\n(constitution Principle IV); this shim runs inside that same already-permission-scoped\nprocess, so it's bound by whatever `--allow-net` grant the plugin itself was started with,\nsame as a plugin calling `fetch()` directly would be."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 13789,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 197
                        },
                        "name": "userAgent",
                        "params": [],
                        "returnType": {
                          "kind": "typeRef",
                          "repr": "LegacyUserAgent",
                          "value": {
                            "resolution": {
                              "kind": "local"
                            },
                            "typeName": "LegacyUserAgent"
                          }
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "`get_logger($name, $category)` — see `LegacyLogger`'s own docs for exactly what's covered."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 13923,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 199
                        },
                        "name": "getLogger",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "name",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          },
                          {
                            "kind": "identifier",
                            "name": "category",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "typeRef",
                          "repr": "LegacyLogger",
                          "value": {
                            "resolution": {
                              "kind": "local"
                            },
                            "typeName": "LegacyLogger"
                          }
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "`Mojo::Util::html_unescape` — decodes HTML entities (named: `&amp;`/`&lt;`/`&quot;`/etc.,\nand numeric: `&#39;`/`&#x27;`). No native JS equivalent outside a real DOM (a `textarea`\nelement's `.value` trick, unavailable in this Deno sandbox — there's no `document`), so\nreimplemented directly against the HTML5 entity table's most common names rather than\npulling in a full HTML entity library for what the legacy plugin corpus only ever uses on\nscraped titles/summaries (a handful of the same few entities in practice)."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 14538,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 206
                        },
                        "name": "htmlUnescape",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "s",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "`Mojo::DOM->new(html)` / `Mojo::DOM->new->xml(1)->parse(xml)` — parses markup and returns\nits root node. `xml` toggles case-sensitive tag matching (real XML/custom elements like\n`<Genre>` in a ComicInfo.xml file) vs HTML's traditionally case-insensitive tag names; see\n`LegacyDomNode`'s own docs for what's covered."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 14915,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 211
                        },
                        "name": "parseHtml",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "markup",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          },
                          {
                            "kind": "identifier",
                            "name": "xml",
                            "optional": true,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "boolean",
                              "value": "boolean"
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "typeRef",
                          "repr": "LegacyDomNode",
                          "value": {
                            "resolution": {
                              "kind": "local"
                            },
                            "typeName": "LegacyDomNode"
                          }
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "Perl's `sleep($seconds)` blocks synchronously; JS has no synchronous sleep, so this is a\n`setTimeout`-backed `Promise` instead — every call site must `await` it (the converter's\nown `sleep` mapping in `render.rs` always emits the `await`, forcing the enclosing function\n`async` the same way an awaited `->get()`/`->post()` Mojo call already does)."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 15348,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 216
                        },
                        "name": "sleep",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "seconds",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "number",
                              "value": "number"
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "typeRef",
                          "repr": "Promise",
                          "value": {
                            "typeName": "Promise",
                            "typeParams": [
                              {
                                "kind": "keyword",
                                "repr": "void",
                                "value": "void"
                              }
                            ]
                          }
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "`LANraragi::Utils::Generic::get_version` — legacy reads this straight out of its own\n`package.json` (`version`/`homepage` fields) once and caches it; this returns the\nequivalent static values for this project instead of trying to read a Rust crate's\n`Cargo.toml` from within the Deno sandbox. Update these two literals if\n`Cargo.toml`'s `[workspace.package]` `version`/`repository` ever changes."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 15816,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 222
                        },
                        "name": "getVersion",
                        "params": [],
                        "returnType": {
                          "kind": "typeLiteral",
                          "value": {
                            "properties": [
                              {
                                "location": {
                                  "byteIndex": 15832,
                                  "col": 18,
                                  "filename": "plugins/legacy-globals.d.ts",
                                  "line": 222
                                },
                                "name": "version",
                                "tsType": {
                                  "kind": "keyword",
                                  "repr": "string",
                                  "value": "string"
                                }
                              },
                              {
                                "location": {
                                  "byteIndex": 15849,
                                  "col": 35,
                                  "filename": "plugins/legacy-globals.d.ts",
                                  "line": 222
                                },
                                "name": "homepage",
                                "tsType": {
                                  "kind": "keyword",
                                  "repr": "string",
                                  "value": "string"
                                }
                              }
                            ]
                          }
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "Perl's `ref($x)` builtin — returns `\"HASH\"`/`\"ARRAY\"` for a hashref/arrayref, or `\"\"` for\nanything else (a plain scalar, `undef`, etc.), matching Perl's own string-truthiness rule\nclosely enough that `ref($x)` used as a bare boolean condition (`return if ref $x;`) works\nunmodified once translated. Only hashref/arrayref detection — real Perl `ref()` also\nrecognizes `CODE`/`SCALAR`/blessed-object-class-name, none of which the legacy plugin\ncorpus's own use of `ref()` ever tests for."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 16395,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 229
                        },
                        "name": "refType",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "x",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "unknown",
                              "value": "unknown"
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "`LANraragi::Utils::String::trim` — strips leading/trailing whitespace, returning `\"\"` for\n`null`/`undefined` instead of throwing (unlike JS's own `String.prototype.trim`, which\nrequires a real string receiver)."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 16658,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 233
                        },
                        "name": "trim",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "s",
                            "optional": false,
                            "tsType": {
                              "kind": "union",
                              "value": [
                                {
                                  "kind": "keyword",
                                  "repr": "string",
                                  "value": "string"
                                },
                                {
                                  "kind": "keyword",
                                  "repr": "null",
                                  "value": "null"
                                },
                                {
                                  "kind": "keyword",
                                  "repr": "undefined",
                                  "value": "undefined"
                                }
                              ]
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "`File::Basename::fileparse($path, $suffix_pattern)` — every real call site in the legacy\nplugin corpus passes the same \"match a trailing dot plus any run of non-dot characters\"\nsuffix pattern (i.e. \"the last extension\"), so the second argument is accepted but ignored\nrather than genuinely interpreting an arbitrary suffix pattern. Returns\n`[name-without-extension, directory-with-trailing-slash, extension-with-leading-dot]`,\nmatching Perl's own return order."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 17201,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 240
                        },
                        "name": "fileparse",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "path",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          },
                          {
                            "kind": "identifier",
                            "name": "suffixPattern",
                            "optional": true,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "unknown",
                              "value": "unknown"
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "tuple",
                          "value": [
                            {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            },
                            {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            },
                            {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          ]
                        }
                      },
                      {
                        "jsDoc": {
                          "doc": "`LANraragi::Utils::Redis::redis_decode` — legacy re-decodes a value read back from Redis\nas UTF-8, since Perl's own Redis client hands back raw bytes. Deno's Redis client (and\nevery string this dispatcher otherwise touches) is already a proper UTF-8 JS string by the\ntime a plugin sees it, so this is a pure pass-through."
                        },
                        "kind": "method",
                        "location": {
                          "byteIndex": 17627,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 245
                        },
                        "name": "redis_decode",
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "s",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        }
                      }
                    ]
                  }
                }
              },
              "kind": "variable",
              "location": {
                "byteIndex": 12319,
                "col": 12,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 177
              }
            }
          ],
          "name": "legacyCompat"
        },
        {
          "declarations": [
            {
              "declarationKind": "declare",
              "def": {
                "kind": "var",
                "tsType": {
                  "kind": "typeLiteral",
                  "value": {
                    "constructors": [
                      {
                        "location": {
                          "byteIndex": 19061,
                          "col": 2,
                          "filename": "plugins/legacy-globals.d.ts",
                          "line": 266
                        },
                        "params": [
                          {
                            "kind": "identifier",
                            "name": "error_code",
                            "optional": false,
                            "tsType": {
                              "kind": "keyword",
                              "repr": "string",
                              "value": "string"
                            }
                          },
                          {
                            "kind": "identifier",
                            "name": "data",
                            "optional": true,
                            "tsType": {
                              "kind": "typeRef",
                              "repr": "Record",
                              "value": {
                                "typeName": "Record",
                                "typeParams": [
                                  {
                                    "kind": "keyword",
                                    "repr": "string",
                                    "value": "string"
                                  },
                                  {
                                    "kind": "union",
                                    "value": [
                                      {
                                        "kind": "keyword",
                                        "repr": "string",
                                        "value": "string"
                                      },
                                      {
                                        "kind": "keyword",
                                        "repr": "number",
                                        "value": "number"
                                      }
                                    ]
                                  }
                                ]
                              }
                            }
                          }
                        ],
                        "returnType": {
                          "kind": "intersection",
                          "value": [
                            {
                              "kind": "typeRef",
                              "repr": "Error",
                              "value": {
                                "typeName": "Error"
                              }
                            },
                            {
                              "kind": "typeLiteral",
                              "value": {
                                "properties": [
                                  {
                                    "location": {
                                      "byteIndex": 19141,
                                      "col": 4,
                                      "filename": "plugins/legacy-globals.d.ts",
                                      "line": 267
                                    },
                                    "name": "error_code",
                                    "tsType": {
                                      "kind": "keyword",
                                      "repr": "string",
                                      "value": "string"
                                    }
                                  },
                                  {
                                    "location": {
                                      "byteIndex": 19165,
                                      "col": 4,
                                      "filename": "plugins/legacy-globals.d.ts",
                                      "line": 268
                                    },
                                    "name": "data",
                                    "optional": true,
                                    "tsType": {
                                      "kind": "typeRef",
                                      "repr": "Record",
                                      "value": {
                                        "typeName": "Record",
                                        "typeParams": [
                                          {
                                            "kind": "keyword",
                                            "repr": "string",
                                            "value": "string"
                                          },
                                          {
                                            "kind": "union",
                                            "value": [
                                              {
                                                "kind": "keyword",
                                                "repr": "string",
                                                "value": "string"
                                              },
                                              {
                                                "kind": "keyword",
                                                "repr": "number",
                                                "value": "number"
                                              }
                                            ]
                                          }
                                        ]
                                      }
                                    }
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      }
                    ]
                  }
                }
              },
              "jsDoc": {
                "doc": "A plugin-authored, structured error — throw this (instead of a plain `throw new Error(...)`)\nto report `{error_code, data}` through an exception rather than a `return {error}`. `error_code`\ndoubles as an i18n lookup key (see `crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts`'s\n`PluginError` for the full field-meaning docs — this class's fields mirror that interface\nexactly). The dispatcher's own `catch` (`dispatcher.ts`'s `handleRequest`) uses real\n`instanceof` against this exact class to extract `error_code`/`data` for the Rust host.\n\nThe *real* class lives in `plugin-sdk.ts` and is exposed here only as a global\n(`globalThis.PluginErrorException = PluginErrorException` in `dispatcher.ts`) so every plugin\nfile can use it with zero `import` of its own — a plugin file's static `import` specifier can't\nreach `plugin-sdk.ts` at all, since `plugins_dir` and `plugin-sdk.ts`'s own directory\n(`temp_dir`) have no fixed relative path between them (both are independent runtime CLI args).\n`declare class` here (rather than `declare var ...: PluginError`) is what lets a plugin file\nboth `throw new PluginErrorException(...)` (construct) and `catch` it with `instanceof`\n(matches the *value* `dispatcher.ts` actually assigns, a real ES class, not just its instance\nshape)."
              },
              "kind": "variable",
              "location": {
                "byteIndex": 19035,
                "col": 12,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 265
              }
            }
          ],
          "name": "PluginErrorException"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "tsType": {
                  "kind": "union",
                  "value": [
                    {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    },
                    {
                      "kind": "keyword",
                      "repr": "boolean",
                      "value": "boolean"
                    },
                    {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  ]
                }
              },
              "jsDoc": {
                "doc": "One `customargs` element's real type — `PluginParameter.type` (`plugin-sdk.ts`) decides which:\n`\"bool\"` is a real `boolean`, `\"int\"` a real `number`, `\"string\"` (or absent) a `string`. The\nwhole chain (settings-page form → `PUT /plugins/settings` → Redis → `exec_*` call) carries the\nreal type end to end — never a stringly-typed `\"1\"`/`\"true\"` a plugin has to parse back out\nitself, a real, repeatedly-observed source of bugs when left to each plugin's own judgment."
              },
              "kind": "typeAlias",
              "location": {
                "byteIndex": 20695,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 287
              }
            }
          ],
          "name": "CustomArgValue"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "Present when the call was made against a real archive (`GET /plugins/use_plugin?id=...`);\nabsent for a settings-page \"test this plugin\" dry run."
                    },
                    "location": {
                      "byteIndex": 21309,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 296
                    },
                    "name": "archive_id",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The archive's current stored `tags` string (comma-separated, possibly empty) — matches\nlegacy's own `exec_metadata_plugin` (`Model/Plugins.pm`), whose `%infohash` always includes\n`existing_tags`. Several real converted plugins (`chaika`/`ehentai`/`fakku`/`hitomi`/`mems`/\n`nhentai`) parse this for an existing `source:`-style tag as a fallback when no `arg`/oneshot\nURL was given (e.g. every auto-run/watcher-triggered call, which never has one) — present\n(possibly `\"\"`) whenever `archive_id` is, absent only for a settings-page dry run with no real\narchive at all."
                    },
                    "location": {
                      "byteIndex": 21942,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 304
                    },
                    "name": "existing_tags",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The archive's current stored `title` — same legacy `%infohash` field (`archive_title`).\nSeveral real converted plugins (`chaika`/`ehentai`/`fakku`/`mems`/`pixiv`) fall back to parsing\nan embedded ID out of this when neither `arg` nor `existing_tags` yielded one. Same presence\nrule as {@linkcode existing_tags}."
                    },
                    "location": {
                      "byteIndex": 22306,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 309
                    },
                    "name": "archive_title",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "A metadata plugin's cached thumbnail-image content hash, used by a handful of real converted\nplugins (e.g. `chaika.ts`'s \"look up by thumbnail\" fallback path) to search a source site by\nimage rather than by title/ID. Currently always `\"\"` in practice (`lanrurugi-api::plugins`\nhas no real thumbnail-hashing step wired up yet) — present in the wire contract so those\nplugins' own reads still type-check and don't crash on `undefined`, not because a real value\nis ever supplied today."
                    },
                    "location": {
                      "byteIndex": 22851,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 316
                    },
                    "name": "thumbnail_hash",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The single free-text \"run once\" value (see `PluginInfoResult.oneshot_arg` in `plugin-sdk.ts`)\n— `undefined`/absent for any call that isn't a manual one-shot run. Distinct from {@linkcode\ncustomargs}."
                    },
                    "location": {
                      "byteIndex": 23099,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 320
                    },
                    "name": "arg",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Exact duplicate of {@linkcode arg}, sent under legacy's own field name\n(`$lrr_info->{oneshot_param}` — every legacy Perl plugin source reads it under this name, not\n`arg`) alongside it so a converted plugin's untouched, unmodified-since-conversion field\naccess still works verbatim. A hand-written (non-converted) plugin should prefer {@linkcode\narg} — this field exists purely for source fidelity with the legacy corpus, not because the\ntwo ever carry different values."
                    },
                    "location": {
                      "byteIndex": 23624,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 327
                    },
                    "name": "oneshot_param",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "This plugin's own persisted per-parameter values, positionally matching `PluginInfoResult.\nparameters` in `plugin-sdk.ts` (`customargs[0]` is `parameters[0]`'s saved value, etc.) —\nalways present, one entry per declared parameter, `\"\"` for any never configured."
                    },
                    "location": {
                      "byteIndex": 23933,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 331
                    },
                    "name": "customargs",
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "CustomArgValue",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "CustomArgValue"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The archive's on-disk path — fetched by the host once per call\n(`lanrurugi-api::plugins::use_plugin_sync`) so a filename-deriving plugin (e.g. \"Filename\nParsing\") doesn't need its own round trip back into the API just to resolve an ID to a path."
                    },
                    "location": {
                      "byteIndex": 24232,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 335
                    },
                    "name": "file_path",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "`file_path`'s own last-modified time (Unix seconds) — resolved host-side (a plain `stat`\ncall) alongside `file_path` itself so a plugin needing this (currently only\n`plugins/metadata/dateadded.ts`) doesn't need its own filesystem read permission just for\nthis one call. Absent when `file_path` itself is absent, or the `stat` call failed."
                    },
                    "location": {
                      "byteIndex": 24619,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 340
                    },
                    "name": "file_modified_time",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "A small, deliberately narrow subset of the server's global settings (`LRR_CONFIG`) a\nmetadata plugin might need to consult — currently just `usedateadded`/`usedatemodified`\n(`plugins/metadata/dateadded.ts`'s own settings-driven defaults). Not the *entire* settings\nhash (constitution Principle IV: no broader access than a plugin actually needs)."
                    },
                    "location": {
                      "byteIndex": 25023,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 345
                    },
                    "name": "settings",
                    "optional": true,
                    "tsType": {
                      "kind": "typeLiteral",
                      "value": {
                        "properties": [
                          {
                            "location": {
                              "byteIndex": 25036,
                              "col": 15,
                              "filename": "plugins/legacy-globals.d.ts",
                              "line": 345
                            },
                            "name": "usedateadded",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "boolean",
                              "value": "boolean"
                            }
                          },
                          {
                            "location": {
                              "byteIndex": 25059,
                              "col": 38,
                              "filename": "plugins/legacy-globals.d.ts",
                              "line": 345
                            },
                            "name": "usedatemodified",
                            "tsType": {
                              "kind": "keyword",
                              "repr": "boolean",
                              "value": "boolean"
                            }
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Another archive's stored `tags` string, resolved host-side by extracting a 40-character\narchive ID out of this call's own {@linkcode arg} — currently only computed (and only\nnon-null) for `plugins/metadata/copyarchivetags.ts`, which has no other way to read a\n*different* archive's metadata from inside the Deno sandbox (no direct storage access).\n`null` when `arg` contains no valid ID, or that ID doesn't resolve to a real archive."
                    },
                    "location": {
                      "byteIndex": 25554,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 351
                    },
                    "name": "other_archive_tags",
                    "optional": true,
                    "tsType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        },
                        {
                          "kind": "keyword",
                          "repr": "null",
                          "value": "null"
                        }
                      ]
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Content of every file listed in this plugin's own `PluginInfoResult.sidecar_files`\n(`plugin-sdk.ts`), keyed by that same filename — present (possibly empty) only when\n`sidecar_files` was non-empty; a missing/unreadable/non-UTF-8 file is just absent from this\nmap rather than failing the whole call."
                    },
                    "location": {
                      "byteIndex": 25917,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 356
                    },
                    "name": "sidecar_files",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Populated only when this plugin (or the one it declares via `PluginInfoResult.login_from`)\nneeds a logged-in session — see `plugin-sdk.ts`'s `LoginResult` for where this comes from. A\nconverted plugin's entry point rehydrates this into a real `legacyCompat.userAgent()` instance\nbefore its body runs (`render.rs`'s `render_user_agent_hydration_preamble`)."
                    },
                    "location": {
                      "byteIndex": 26341,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 361
                    },
                    "name": "user_agent_cookies",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "LegacyCookie",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "LegacyCookie"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Same origin/lifecycle as {@linkcode user_agent_cookies} (from the declared `login_from`\nplugin's own `LoginResult.headers`), but for header/token-based auth instead of cookie-based\nauth — e.g. an `Authorization: Key <api_key>` header a site expects on every request rather\nthan a session cookie. A hand-written plugin reads this directly (`hostArgs.user_agent_headers\n?.Authorization`); a converted plugin's rehydration preamble folds it into the same\n`legacyCompat.userAgent()` instance `user_agent_cookies` rehydrates into (issue #78/#93)."
                    },
                    "location": {
                      "byteIndex": 26958,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 368
                    },
                    "name": "user_agent_headers",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          }
                        ]
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`hostArgs` for `execMetadata`. In practice a converted plugin's generated entry point binds\nthe *whole* object to one variable (`let lrr_info = hostArgs as Record<string, any>;`) and reads\nindividual fields off it by name — this interface exists so a hand-written plugin (or\n`plugin-sdk.ts`'s own docs) has something concrete to check field names against."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 21119,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 293
              }
            }
          ],
          "name": "MetadataHostArgs"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 27475,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 377
                    },
                    "name": "customargs",
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "CustomArgValue",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "CustomArgValue"
                        }
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`hostArgs` for `execLogin` — the plugin's own persisted parameter values (credentials,\ntypically — see {@linkcode MetadataHostArgs.customargs}'s own docs, same shape), never an\n`archive_id`/`file_path` (a login plugin never runs against a specific archive). Re-sent on\n*every* call to whatever declares this plugin as its `login_from`, never a cached session, so\ncredentials always reflect the current saved settings."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 27447,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 376
              }
            }
          ],
          "name": "LoginHostArgs"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 27841,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 384
                    },
                    "name": "id",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 27855,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 385
                    },
                    "name": "tags",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "One archive as passed into a `\"script\"`-type plugin that needs to inspect/rewrite tags across\nthe whole library (`ScriptHostArgs.archives`) — deliberately just the two fields any such script\nplugin in this corpus actually needs, not the full archive record (constitution Principle IV)."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 27809,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 383
              }
            }
          ],
          "name": "ScriptArchiveTags"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "The single free-text \"run once\" value — same meaning as {@linkcode MetadataHostArgs.arg},\nnamed to match legacy's own `$lrr_info->{oneshot_param}` field exactly (`SourceFinder.pm`'s\nonly real input)."
                    },
                    "location": {
                      "byteIndex": 29141,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 403
                    },
                    "name": "oneshot_param",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "This plugin's own persisted per-parameter values — same meaning as {@linkcode\nMetadataHostArgs.customargs}."
                    },
                    "location": {
                      "byteIndex": 29291,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 406
                    },
                    "name": "customargs",
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "CustomArgValue",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "CustomArgValue"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Every archive's `id`/`tags`, host-fetched once before the call — present (possibly empty)\nonly for a script plugin whose namespace the host recognizes as needing it (currently just\n`script/nhentaisourceconverter`); `undefined` for every other script plugin, including a\nthird-party one uploaded via `POST /plugins/upload` (no way to know it needs this without a\ndeclarative opt-in this SDK doesn't have yet — such a plugin can still request a narrower,\nper-archive equivalent through the same mechanism {@linkcode MetadataHostArgs.other_archive_tags}\nuses, if a future need arises)."
                    },
                    "location": {
                      "byteIndex": 29949,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 414
                    },
                    "name": "archives",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "ScriptArchiveTags",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "ScriptArchiveTags"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Host-resolved archive ID whose `source:` tag matches {@linkcode oneshot_param} (after\ntrimming and the E-Hentai/ExHentai domain-alias special case) — present (possibly `null`) only\nfor `script/urlfinder`, `undefined` for every other script plugin."
                    },
                    "location": {
                      "byteIndex": 30252,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 418
                    },
                    "name": "existing_archive_id",
                    "optional": true,
                    "tsType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        },
                        {
                          "kind": "keyword",
                          "repr": "null",
                          "value": "null"
                        }
                      ]
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The library's real archive-directory root, absolute path — present only for\n`script/foldertocat`, which (unlike every other plugin in this corpus) needs genuine, broad\nfilesystem read access to walk the whole content tree itself via `Deno.readDir`\n(`declared_permissions.read: true`, an unrestricted `--allow-read` grant — see\n`lanrurugi_plugin::pool::Worker::spawn`), matching legacy's own `FolderToCat.pm::run_script`\n(`File::Find` over `LANraragi::Model::Config->get_userdir`) exactly rather than having the\nhost pre-walk it and hand over a file list, which would make a since-requested Rust-vs-TS\nperformance comparison meaningless (the I/O would happen only once, on the Rust side, either\nway). `undefined` for every other script plugin."
                    },
                    "location": {
                      "byteIndex": 31087,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 428
                    },
                    "name": "library_path",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Maps each already-catalogued archive's on-disk path to its ID — present only for\n`script/foldertocat`, needed to resolve the files `Deno.readDir` finds back to real archive\nIDs before returning its computed category groupings (a plugin has no direct storage access\nto look this up itself). `undefined` for every other script plugin."
                    },
                    "location": {
                      "byteIndex": 31471,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 433
                    },
                    "name": "archive_id_by_path",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          }
                        ]
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`hostArgs` for `runScript` — legacy's three Scripts plugins operate library-wide (not against\none archive), so unlike {@linkcode MetadataHostArgs} there's no `archive_id`/`file_path` here\neither. A Deno-sandboxed plugin has no direct Redis/archive-storage access, so any script that\nneeds to *read* every archive's tags (`nhsrcconv`) gets them pre-fetched into {@linkcode\narchives} rather than reaching for storage itself; a script that needs to *write* tags returns\nits intended changes in `ScriptResult.updates` (`plugin-sdk.ts`) instead of writing them\ndirectly — the host applies them after the call returns (same shape both ways, so a plugin's\nown transform logic is the only real content). `urlfinder`'s one host-computed lookup\n(`Model::Stats::is_url_recorded`, a `LRR_URLMAP` hash read this repo doesn't maintain — resolved\nhost-side as a full tag-scan instead, mirroring `GET /database/stats`'s own same simplification)\nis exposed as {@linkcode existing_archive_id}."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 28891,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 399
              }
            }
          ],
          "name": "ScriptHostArgs"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "Mirrors legacy's own `%infohash` shape (`~/LANraragi/lib/LANraragi/Model/Plugins.pm:171-175`,\n`url => $input`) — every real download plugin in this corpus reads `hostArgs.url`."
                    },
                    "location": {
                      "byteIndex": 32064,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 443
                    },
                    "name": "url",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 32079,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 444
                    },
                    "name": "category",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 32100,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 445
                    },
                    "name": "user_agent_cookies",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "LegacyCookie",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "LegacyCookie"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "See {@linkcode MetadataHostArgs.user_agent_headers}'s own docs — same origin/lifecycle, just\nfor `execDownload` instead of `execMetadata`."
                    },
                    "location": {
                      "byteIndex": 32294,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 448
                    },
                    "name": "user_agent_headers",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "This plugin's own persisted per-parameter values — same meaning as {@linkcode\nMetadataHostArgs.customargs}. Legacy passes the equivalent (`%settings`, converted from its\nown persisted array into a keyed hash) as a *separate* positional argument to\n`provide_url($lrr_info, %params)` (`~/LANraragi/lib/LANraragi/Model/Plugins.pm:163-179`)\nrather than folding it into the info hash the way this repo does — e.g. `ehentai.ts`'s own\n`forceresampled` toggle reads `hostArgs.customargs[0]` exactly like a metadata plugin would."
                    },
                    "location": {
                      "byteIndex": 32900,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 455
                    },
                    "name": "customargs",
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "CustomArgValue",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "CustomArgValue"
                        }
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`hostArgs` for `execDownload` — the user-supplied URL/ID to download, plus an optional target\ncategory (`POST /download_url?url=...&catid=...`). Unlike `execMetadata` there's no\n`archive_id`/`file_path`: nothing has been catalogued yet at this point, that's the whole job\nthis entry point exists to do."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 31840,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 440
              }
            }
          ],
          "name": "DownloadHostArgs"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 33350,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 463
                    },
                    "name": "error_code",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 33372,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 464
                    },
                    "name": "data",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "union",
                            "value": [
                              {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              },
                              {
                                "kind": "keyword",
                                "repr": "number",
                                "value": "number"
                              }
                            ]
                          }
                        ]
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "A plugin-authored error, structurally identical to `plugin-sdk.ts`'s own `PluginError` — see\nthat interface for the full `error_code`-as-i18n-key convention. Declared here (not only there)\nfor the same zero-boilerplate reason as {@linkcode DownloadHostArgs}: it appears in the return\ntype a plugin file annotates, and a plugin file can never `import` the SDK module."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 33318,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 462
              }
            }
          ],
          "name": "PluginErrorResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 33542,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 469
                    },
                    "name": "url",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 33557,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 470
                    },
                    "name": "method",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 33576,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 471
                    },
                    "name": "headers",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "Record",
                      "value": {
                        "typeName": "Record",
                        "typeParams": [
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          },
                          {
                            "kind": "keyword",
                            "repr": "string",
                            "value": "string"
                          }
                        ]
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Fallback filename, used only when the real response has no usable `Content-Disposition`."
                    },
                    "location": {
                      "byteIndex": 33710,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 473
                    },
                    "name": "filename_hint",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "One resource for the host to fetch — mirrors `plugin-sdk.ts`'s `DownloadRequest`."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 33506,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 468
              }
            }
          ],
          "name": "DownloadRequestResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "Already normalized through this plugin's own `canonicalizeSource` — the host compares these\nagainst stored `source:` tags by plain string equality."
                    },
                    "location": {
                      "byteIndex": 34037,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 480
                    },
                    "name": "source",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "ISO 8601. Sole determiner of revision order; there is deliberately no \"is current\" flag."
                    },
                    "location": {
                      "byteIndex": 34153,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 482
                    },
                    "name": "posted_at",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "One revision of a download's series — mirrors `plugin-sdk.ts`'s `VersionHistoryEntry`."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 33833,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 477
              }
            }
          ],
          "name": "VersionHistoryEntryResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 34507,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 489
                    },
                    "name": "creator",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 34527,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 490
                    },
                    "name": "tags",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "keyword",
                        "repr": "string",
                        "value": "string"
                      }
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 34546,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 491
                    },
                    "name": "listing_url",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "How many listing pages to read at most. The host supplies it; an extension that pages MUST stop\nat this many rather than walking the whole result set — a tag search can run to tens of thousands\nof works, and reading all of it on every check would be both slow and a good way to get banned.\n\nReading only the first page is not enough on its own: an existing work edited to carry a tag\nenters the result set *in the middle*, ordered by its original publication date, so it is never\nvisible from page one."
                    },
                    "location": {
                      "byteIndex": 35113,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 499
                    },
                    "name": "max_pages",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "What a subscription is looking for — mirrors `plugin-sdk.ts`'s `DiscoveryCriteria`. Declared here\ntoo, for the same zero-import reason as the host-args types: a discovery extension annotates its\nown `discover` function with it, and a plugin file can never `import` the SDK module."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 34471,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 488
              }
            }
          ],
          "name": "DiscoveryCriteriaArgs"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "jsDoc": {
                      "doc": "Must already be normalised through this plugin's own `canonicalizeSource`."
                    },
                    "location": {
                      "byteIndex": 35347,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 505
                    },
                    "name": "source",
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The work's title, by language. Keys are language tags (`\"ja\"`, `\"en\"`, …) plus `\"origin\"` for\nthe title as the source itself writes it.\n\nA map rather than one string because the same work legitimately has several titles, and a reader\nof Japanese wants a different one from a reader of English. Naming the languages in the contract\n(`title_jpn` and friends) would mean changing it again for the next source, so the source decides\nwhich keys it can fill.\n\nA bare string is still accepted and read as `{ origin: \"...\" }`."
                    },
                    "location": {
                      "byteIndex": 35933,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 515
                    },
                    "name": "title",
                    "optional": true,
                    "tsType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        },
                        {
                          "kind": "typeRef",
                          "repr": "Record",
                          "value": {
                            "typeName": "Record",
                            "typeParams": [
                              {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              },
                              {
                                "kind": "keyword",
                                "repr": "string",
                                "value": "string"
                              }
                            ]
                          }
                        }
                      ]
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 35976,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 516
                    },
                    "name": "posted_at",
                    "optional": true,
                    "tsType": {
                      "kind": "union",
                      "value": [
                        {
                          "kind": "keyword",
                          "repr": "string",
                          "value": "string"
                        },
                        {
                          "kind": "keyword",
                          "repr": "number",
                          "value": "number"
                        },
                        {
                          "kind": "typeRef",
                          "repr": "Date",
                          "value": {
                            "typeName": "Date"
                          }
                        }
                      ]
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 36014,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 517
                    },
                    "name": "rating",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 36033,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 518
                    },
                    "name": "tags",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "keyword",
                        "repr": "string",
                        "value": "string"
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "The source's own classification (e.g. \"Doujinshi\"), for the excluded-category rule."
                    },
                    "location": {
                      "byteIndex": 36145,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 520
                    },
                    "name": "category",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Who posted it — what a creator-scoped subscription matches on."
                    },
                    "location": {
                      "byteIndex": 36240,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 522
                    },
                    "name": "uploader",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Length, so download cost can be judged before approving."
                    },
                    "location": {
                      "byteIndex": 36327,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 524
                    },
                    "name": "pages",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "number",
                      "value": "number"
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "One work a listing offered — mirrors `plugin-sdk.ts`'s `DiscoveredCandidate`."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 35223,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 503
              }
            }
          ],
          "name": "DiscoveredCandidateResult"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 36799,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 534
                    },
                    "name": "candidates",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "DiscoveredCandidateResult",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "DiscoveredCandidateResult"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Set whenever the sign-in was missing, expired, or rejected — even if the listing parsed fine."
                    },
                    "location": {
                      "byteIndex": 36948,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 536
                    },
                    "name": "degraded",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "boolean",
                      "value": "boolean"
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 36970,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 537
                    },
                    "name": "error",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "PluginErrorResult",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "PluginErrorResult"
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`discover`'s return shape — mirrors `plugin-sdk.ts`'s `DiscoveryResult`.\n\nAnnotate `discover` with this so a mistyped key is a compile error rather than a field the host's\ndeserializer silently drops. `degraded` is the one most worth getting right: forgetting to set it\nturns \"I could not see everything\" into \"there is nothing new\", and the host will then record\nworks it never saw as handled."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 36764,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 533
              }
            }
          ],
          "name": "DiscoveryResultShape"
        },
        {
          "declarations": [
            {
              "declarationKind": "private",
              "def": {
                "properties": [
                  {
                    "location": {
                      "byteIndex": 37521,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 547
                    },
                    "name": "downloads",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "DownloadRequestResult",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "DownloadRequestResult"
                        }
                      }
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Escape hatch: the plugin already wrote the file itself and hands back a local path."
                    },
                    "location": {
                      "byteIndex": 37653,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 549
                    },
                    "name": "file_path",
                    "optional": true,
                    "tsType": {
                      "kind": "keyword",
                      "repr": "string",
                      "value": "string"
                    }
                  },
                  {
                    "jsDoc": {
                      "doc": "Every revision of this download's series the site exposes, including the one being downloaded.\nThe plugin reports only; the host decides what supersedes what (issue #107)."
                    },
                    "location": {
                      "byteIndex": 37861,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 552
                    },
                    "name": "version_history",
                    "optional": true,
                    "tsType": {
                      "kind": "array",
                      "value": {
                        "kind": "typeRef",
                        "repr": "VersionHistoryEntryResult",
                        "value": {
                          "resolution": {
                            "kind": "local"
                          },
                          "typeName": "VersionHistoryEntryResult"
                        }
                      }
                    }
                  },
                  {
                    "location": {
                      "byteIndex": 37910,
                      "col": 2,
                      "filename": "plugins/legacy-globals.d.ts",
                      "line": 553
                    },
                    "name": "error",
                    "optional": true,
                    "tsType": {
                      "kind": "typeRef",
                      "repr": "PluginErrorResult",
                      "value": {
                        "resolution": {
                          "kind": "local"
                        },
                        "typeName": "PluginErrorResult"
                      }
                    }
                  }
                ]
              },
              "jsDoc": {
                "doc": "`execDownload`'s return shape — mirrors `plugin-sdk.ts`'s `DownloadResult` field-for-field.\n\nAnnotate `execDownload` with this (`): Promise<DownloadResultShape>`) so a typo in a field name\nis a compile error rather than a silently-ignored property: the host deserializes this JSON into\n`lanrurugi_plugin::protocol::DownloadResult`, which skips anything it doesn't recognize, so an\nunannotated return value has nothing checking it against the real contract at all."
              },
              "kind": "interface",
              "location": {
                "byteIndex": 37487,
                "col": 0,
                "filename": "plugins/legacy-globals.d.ts",
                "line": 546
              }
            }
          ],
          "name": "DownloadResultShape"
        }
      ]
    }
  },
  "version": 2
}
