From 50fd043a2d55be0dc733ef3a5f0b966ab1ac0c63 Mon Sep 17 00:00:00 2001 From: yehongyu Date: Mon, 20 Jul 2026 14:40:17 +0800 Subject: [PATCH] first commit --- .env.example | 30 + .gitattributes | 1 + .gitignore | 16 + .prettierignore | 7 + .vscode/extensions.json | 7 + .vscode/launch.json | 22 + .vscode/settings.json | 8 + .vscode/toolkit.code-snippets | 45 + LICENSE | 661 +++++ addon/bootstrap.js | 58 + addon/content/chatpapers.css | 140 + addon/content/icons/chat.svg | 6 + addon/content/icons/favicon.png | Bin 0 -> 677 bytes addon/content/icons/favicon@0.5x.png | Bin 0 -> 836 bytes addon/content/preferences.xhtml | 127 + addon/content/zoteroPane.css | 3 + addon/locale/en-US/addon.ftl | 24 + addon/locale/en-US/mainWindow.ftl | 1 + addon/locale/en-US/preferences.ftl | 29 + addon/locale/zh-CN/addon.ftl | 24 + addon/locale/zh-CN/mainWindow.ftl | 1 + addon/locale/zh-CN/preferences.ftl | 29 + addon/manifest.json | 20 + addon/prefs.js | 15 + docs/development.md | 411 +++ docs/requirements.md | 344 +++ eslint.config.mjs | 16 + package-lock.json | 3774 ++++++++++++++++++++++++++ package.json | 56 + readme.md | 41 + src/addon.ts | 35 + src/hooks.ts | 58 + src/index.ts | 25 + src/modules/llm/client.ts | 238 ++ src/modules/llm/prompts.ts | 29 + src/modules/llm/providers.ts | 71 + src/modules/llm/types.ts | 54 + src/modules/llm/url.ts | 22 + src/modules/pdf/context.ts | 77 + src/modules/pdf/extractor.ts | 136 + src/modules/storage/sessions.ts | 71 + src/modules/ui/chatView.ts | 312 +++ src/modules/ui/prefs.ts | 102 + src/modules/ui/readerPane.ts | 76 + src/modules/zotero/notes.ts | 100 + src/utils/abort.ts | 102 + src/utils/locale.ts | 96 + src/utils/prefs.ts | 36 + src/utils/window.ts | 10 + src/utils/ztoolkit.ts | 48 + tsconfig.json | 5 + typings/global.d.ts | 37 + typings/i10n.d.ts | 55 + typings/prefs.d.ts | 27 + zotero-plugin.config.ts | 48 + 55 files changed, 7786 insertions(+) create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/toolkit.code-snippets create mode 100644 LICENSE create mode 100644 addon/bootstrap.js create mode 100644 addon/content/chatpapers.css create mode 100644 addon/content/icons/chat.svg create mode 100644 addon/content/icons/favicon.png create mode 100644 addon/content/icons/favicon@0.5x.png create mode 100644 addon/content/preferences.xhtml create mode 100644 addon/content/zoteroPane.css create mode 100644 addon/locale/en-US/addon.ftl create mode 100644 addon/locale/en-US/mainWindow.ftl create mode 100644 addon/locale/en-US/preferences.ftl create mode 100644 addon/locale/zh-CN/addon.ftl create mode 100644 addon/locale/zh-CN/mainWindow.ftl create mode 100644 addon/locale/zh-CN/preferences.ftl create mode 100644 addon/manifest.json create mode 100644 addon/prefs.js create mode 100644 docs/development.md create mode 100644 docs/requirements.md create mode 100644 eslint.config.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 readme.md create mode 100644 src/addon.ts create mode 100644 src/hooks.ts create mode 100644 src/index.ts create mode 100644 src/modules/llm/client.ts create mode 100644 src/modules/llm/prompts.ts create mode 100644 src/modules/llm/providers.ts create mode 100644 src/modules/llm/types.ts create mode 100644 src/modules/llm/url.ts create mode 100644 src/modules/pdf/context.ts create mode 100644 src/modules/pdf/extractor.ts create mode 100644 src/modules/storage/sessions.ts create mode 100644 src/modules/ui/chatView.ts create mode 100644 src/modules/ui/prefs.ts create mode 100644 src/modules/ui/readerPane.ts create mode 100644 src/modules/zotero/notes.ts create mode 100644 src/utils/abort.ts create mode 100644 src/utils/locale.ts create mode 100644 src/utils/prefs.ts create mode 100644 src/utils/window.ts create mode 100644 src/utils/ztoolkit.ts create mode 100644 tsconfig.json create mode 100644 typings/global.d.ts create mode 100644 typings/i10n.d.ts create mode 100644 typings/prefs.d.ts create mode 100644 zotero-plugin.config.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b81c110 --- /dev/null +++ b/.env.example @@ -0,0 +1,30 @@ +# Usage: +# Copy this file as `.env` and fill in the variables below as instructed. + +# If you are developing more than one plugin, you can store the bin path and +# profile path in the system environment variables, which can be omitted here. + +# The path of the Zotero binary file. +# The path delimiter should be escaped as `\\` for win32. +# The path is `*/Zotero.app/Contents/MacOS/zotero` for MacOS. +ZOTERO_PLUGIN_ZOTERO_BIN_PATH = /path/to/zotero.exe + +# The path of the profile used for development. +# Start the profile manager by `/path/to/zotero.exe -p` to create a profile for development. +# @see https://www.zotero.org/support/kb/profile_directory +ZOTERO_PLUGIN_PROFILE_PATH = /path/to/profile + +# The directory where the database is located. +# If this field is kept empty, Zotero will start with the default data. +# @see https://www.zotero.org/support/zotero_data +ZOTERO_PLUGIN_DATA_DIR = + +# Custom commands to kill Zotero processes. +# Commands for different platforms are already built into zotero-plugin, +# if the built-in commands are not suitable for your needs, please modify this variable. +# ZOTERO_PLUGIN_KILL_COMMAND = + +# GitHub Token +# For scaffold auto create release and upload assets. +# Fill in this variable if you are publishing locally instead of CI. +# GITHUB_TOKEN = \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60612be --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# dot files +.DS_Store + +# Node.js +node_modules +pnpm-lock.yaml +yarn.lock + +# TSC +tsconfig.tsbuildinfo + +# Scaffold +.env +.scaffold +build +logs \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6411ee4 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +.vscode +build +logs +node_modules +package-lock.json +yarn.lock +pnpm-lock.yaml diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..7ecea32 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "macabeus.vscode-fluent" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8b7f88b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Start", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "start"] + }, + { + "type": "node", + "request": "launch", + "name": "Build", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "build"] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2e76555 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.formatOnType": false, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/.vscode/toolkit.code-snippets b/.vscode/toolkit.code-snippets new file mode 100644 index 0000000..425b7dd --- /dev/null +++ b/.vscode/toolkit.code-snippets @@ -0,0 +1,45 @@ +{ + "appendElement - full": { + "scope": "javascript,typescript", + "prefix": "appendElement", + "body": [ + "appendElement({", + "\ttag: '${1:div}',", + "\tid: '${2:id}',", + "\tnamespace: '${3:html}',", + "\tclassList: ['${4:class}'],", + "\tstyles: {${5:style}: '$6'},", + "\tproperties: {},", + "\tattributes: {},", + "\t[{ '${7:onload}', (e: Event) => $8, ${9:false} }],", + "\tcheckExistanceParent: ${10:HTMLElement},", + "\tignoreIfExists: ${11:true},", + "\tskipIfExists: ${12:true},", + "\tremoveIfExists: ${13:true},", + "\tcustomCheck: (doc: Document, options: ElementOptions) => ${14:true},", + "\tchildren: [$15]", + "}, ${16:container});" + ] + }, + "appendElement - minimum": { + "scope": "javascript,typescript", + "prefix": "appendElement", + "body": "appendElement({ tag: '$1' }, $2);" + }, + "register Notifier": { + "scope": "javascript,typescript", + "prefix": "registerObserver", + "body": [ + "registerObserver({", + "\t notify: (", + "\t\tevent: _ZoteroTypes.Notifier.Event,", + "\t\ttype: _ZoteroTypes.Notifier.Type,", + "\t\tids: string[],", + "\t\textraData: _ZoteroTypes.anyObj", + "\t) => {", + "\t\t$0", + "\t}", + "});" + ] + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dbbe355 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/addon/bootstrap.js b/addon/bootstrap.js new file mode 100644 index 0000000..7b50767 --- /dev/null +++ b/addon/bootstrap.js @@ -0,0 +1,58 @@ +/** + * Most of this code is from Zotero team's official Make It Red example[1] + * or the Zotero 7 documentation[2]. + * [1] https://github.com/zotero/make-it-red + * [2] https://www.zotero.org/support/dev/zotero_7_for_developers + */ + +var chromeHandle; + +function install(data, reason) {} + +async function startup({ id, version, resourceURI, rootURI }, reason) { + var aomStartup = Components.classes[ + "@mozilla.org/addons/addon-manager-startup;1" + ].getService(Components.interfaces.amIAddonManagerStartup); + var manifestURI = Services.io.newURI(rootURI + "manifest.json"); + chromeHandle = aomStartup.registerChrome(manifestURI, [ + ["content", "__addonRef__", rootURI + "content/"], + ]); + + /** + * Global variables for plugin code. + * The `_globalThis` is the global root variable of the plugin sandbox environment + * and all child variables assigned to it is globally accessible. + * See `src/index.ts` for details. + */ + const ctx = { rootURI }; + ctx._globalThis = ctx; + + Services.scriptloader.loadSubScript( + `${rootURI}/content/scripts/__addonRef__.js`, + ctx, + ); + await Zotero.__addonInstance__.hooks.onStartup(); +} + +async function onMainWindowLoad({ window }, reason) { + await Zotero.__addonInstance__?.hooks.onMainWindowLoad(window); +} + +async function onMainWindowUnload({ window }, reason) { + await Zotero.__addonInstance__?.hooks.onMainWindowUnload(window); +} + +async function shutdown({ id, version, resourceURI, rootURI }, reason) { + if (reason === APP_SHUTDOWN) { + return; + } + + await Zotero.__addonInstance__?.hooks.onShutdown(); + + if (chromeHandle) { + chromeHandle.destruct(); + chromeHandle = null; + } +} + +async function uninstall(data, reason) {} diff --git a/addon/content/chatpapers.css b/addon/content/chatpapers.css new file mode 100644 index 0000000..56e610e --- /dev/null +++ b/addon/content/chatpapers.css @@ -0,0 +1,140 @@ +.chatpapers-root { + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px; + min-height: 320px; + box-sizing: border-box; + font-size: 13px; +} + +.chatpapers-header { + display: flex; + flex-direction: column; + gap: 2px; +} + +.chatpapers-title { + font-weight: 600; + line-height: 1.3; +} + +.chatpapers-meta { + opacity: 0.7; + font-size: 12px; +} + +.chatpapers-messages { + flex: 1; + min-height: 180px; + max-height: 420px; + overflow: auto; + border: 1px solid color-mix(in srgb, currentColor 18%, transparent); + border-radius: 8px; + padding: 8px; + display: flex; + flex-direction: column; + gap: 8px; + background: color-mix(in srgb, currentColor 4%, transparent); +} + +.chatpapers-bubble { + padding: 8px 10px; + border-radius: 8px; + white-space: pre-wrap; + word-break: break-word; +} + +.chatpapers-user { + background: color-mix(in srgb, #3b82f6 18%, transparent); + align-self: flex-end; + max-width: 92%; +} + +.chatpapers-assistant { + background: color-mix(in srgb, currentColor 8%, transparent); + align-self: flex-start; + max-width: 96%; +} + +.chatpapers-role { + font-size: 11px; + opacity: 0.65; + margin-bottom: 4px; + font-weight: 600; +} + +.chatpapers-status { + min-height: 1.2em; + font-size: 12px; + opacity: 0.85; +} + +.chatpapers-status[data-kind="warn"] { + color: #b45309; +} + +.chatpapers-status[data-kind="error"] { + color: #b91c1c; +} + +.chatpapers-toolbar, +.chatpapers-actions { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.chatpapers-btn { + appearance: none; + border: 1px solid color-mix(in srgb, currentColor 22%, transparent); + background: color-mix(in srgb, currentColor 6%, transparent); + border-radius: 6px; + padding: 4px 10px; + cursor: pointer; + font: inherit; +} + +.chatpapers-btn:hover:not(:disabled) { + background: color-mix(in srgb, currentColor 12%, transparent); +} + +.chatpapers-btn:disabled { + opacity: 0.5; + cursor: default; +} + +.chatpapers-primary { + background: #2563eb; + border-color: #2563eb; + color: #fff; +} + +.chatpapers-primary:hover:not(:disabled) { + background: #1d4ed8; +} + +.chatpapers-input { + width: 100%; + box-sizing: border-box; + resize: vertical; + min-height: 64px; + border-radius: 8px; + border: 1px solid color-mix(in srgb, currentColor 22%, transparent); + padding: 8px; + font: inherit; + background: transparent; + color: inherit; +} + +.chatpapers-loading { + padding: 12px; + opacity: 0.7; +} + +.chatpapers-pref-hint { + opacity: 0.8; + font-size: 12px; + margin: 4px 0 10px; + max-width: 52em; +} diff --git a/addon/content/icons/chat.svg b/addon/content/icons/chat.svg new file mode 100644 index 0000000..67bd58e --- /dev/null +++ b/addon/content/icons/chat.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/addon/content/icons/favicon.png b/addon/content/icons/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8a57dede77060aa159836686f7e8eff9fb0f5899 GIT binary patch literal 677 zcmV;W0$TlvP)7yO1}aNMxo=Ss1!z zqRbsAPtbNC0}JI1ig*C8wpzMs$T)@ro^4n$T&0MG}3 zQ`W-2c#vt>Wf2fr5dgppA2)Qa@UYEX5PLd*_q2DzC2;8DT&7{)nSLQwC?AgwcJGMH8%3*IXAdL5D*`ueUq0sOctJB~M9vCM z4MImsRt$;M(462R zpqzEIcKTz)1DA9NjkpAq%Au6*C3zV$(%^+U0?R?WHAbgeE5-E9NkbiKuVl(eti4mu z=6PV^%hi7BQd%cOia<}x`&!*7GRrEFp-W2OAh1SN1XTC~;~p3!dC9tRxlk1WWxP)n ztV0s1v1N>nstDNfuOO1Vj4(nTd3M4^z*hYxItI~32`HnLx0oi|(N+Udl9!nKo0@fk z=J@OVph+c2ViQZlmo90g-EMdH_wl~(cFUaX))`weODz11o)R7FTTW!dQ5T8Lj)PHeAotlmf!Fd+7S?%;T6 zPG9W49}2Zz91A}Ex8pw)hByUGV!;3^1&S*C+s~J8_pMHKQ{6gJX0!0vKK=J{{_c3) zEZ%D6H|Lk_$$CU^3YZTr^}hjd7(hPQLJSjXanz#-_CJS`3+1;;-1~KVUCue+KqP=y zSOA9J`=Pf<0~658)me`$4p35s5W4Z zm>fU6%e<-zlniMs?7LgN{U6V+eg5o7aIGBBDfLhxzzpv8nS*Q}rVIac{Tsixbad|)`SSWhHyInK zQ>CGpb0+Ngc`-Q~Fm;8Yom82N+cT%w)0C@sSN(JC#2?yLp+5js0vHMo7`@>VFOAlu zyQNGT3DVam;!g|!wH`7Y(|RPhvXbaS>v4W(o&Ri>c)pwKzSYFbv8n3foBJAj+o?8$ zU==_lSZ4x!?tyI;zK}V*vX<(_)ZyF9_v||pP5#Qd4TeJ5sMV0xsv&i1A$1HBm&bR2ryn@Vnew4z@D}qyUoB}?Nncyy=y-z z8@QDOl6&|r1)`$i6@gV8@CvDfbaSG~*9*p;bnyR>{93jYXsu@e O0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addon/content/zoteroPane.css b/addon/content/zoteroPane.css new file mode 100644 index 0000000..461cee5 --- /dev/null +++ b/addon/content/zoteroPane.css @@ -0,0 +1,3 @@ +.makeItRed { + background-color: red; +} diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl new file mode 100644 index 0000000..222dbe2 --- /dev/null +++ b/addon/locale/en-US/addon.ftl @@ -0,0 +1,24 @@ +startup-begin = ChatPapers is loading +startup-finish = ChatPapers is ready +prefs-title = ChatPapers + +item-section-chat-head = ChatPapers +item-section-chat-sidenav = Chat with paper + +chat-loading = Loading ChatPapers… +chat-placeholder = Ask a question about this paper… +chat-send = Send +chat-stop = Stop +chat-summarize = Summarize +chat-add-selection = Add selection +chat-clear = Clear +chat-save-note = Save as note +chat-need-config = Configure a provider in Preferences first. +chat-thinking = Thinking… +chat-done = Done +chat-stopped = Stopped +chat-cleared = Chat cleared +chat-no-reply = No AI reply to save yet +chat-note-saved = Saved as note +chat-selection-added = Selection added to next message +chat-selection-empty = No text selected in the reader diff --git a/addon/locale/en-US/mainWindow.ftl b/addon/locale/en-US/mainWindow.ftl new file mode 100644 index 0000000..ba411f3 --- /dev/null +++ b/addon/locale/en-US/mainWindow.ftl @@ -0,0 +1 @@ +itemmenu-label = ChatPapers: Summarize diff --git a/addon/locale/en-US/preferences.ftl b/addon/locale/en-US/preferences.ftl new file mode 100644 index 0000000..77a5155 --- /dev/null +++ b/addon/locale/en-US/preferences.ftl @@ -0,0 +1,29 @@ +prefs-title = ChatPapers +prefs-section-provider = AI Provider +prefs-section-generation = Generation +prefs-section-openrouter = OpenRouter (optional) +prefs-section-prompts = Prompts +prefs-provider = Provider +prefs-reset = Reset defaults +prefs-base-url = Base URL +prefs-api-key = API Key +prefs-model = Model +prefs-refresh-models = Refresh models +prefs-temperature = Temperature +prefs-max-tokens = Max tokens +prefs-timeout = Timeout (ms) +prefs-max-context = Max context chars +prefs-answer-language = Answer language +prefs-send-metadata = + .label = Include item metadata in prompts +prefs-openrouter-referer = HTTP-Referer +prefs-openrouter-title = X-Title +prefs-system-prompt = System prompt (leave empty for default) +prefs-summary-prompt = Summary prompt (leave empty for default) +prefs-hint-empty = Choose Ollama, LM Studio, OpenRouter, or another provider. +prefs-hint-local = Local provider: requests stay on this machine. Start the server first. +prefs-hint-openrouter = Cloud aggregator: paper text is sent to OpenRouter / upstream models. +prefs-hint-cloud = Cloud provider: paper text is sent to the selected API endpoint. +prefs-models-empty = No models returned. +prefs-models-pick = Enter a model id from the list (showing up to 30): +prefs-privacy = Privacy: ChatPapers only sends content when you click Send/Summarize, and only to the endpoint you configured. API keys are stored in local Zotero preferences. diff --git a/addon/locale/zh-CN/addon.ftl b/addon/locale/zh-CN/addon.ftl new file mode 100644 index 0000000..707826f --- /dev/null +++ b/addon/locale/zh-CN/addon.ftl @@ -0,0 +1,24 @@ +startup-begin = ChatPapers 加载中 +startup-finish = ChatPapers 已就绪 +prefs-title = ChatPapers + +item-section-chat-head = ChatPapers +item-section-chat-sidenav = 论文对话 + +chat-loading = 正在加载 ChatPapers… +chat-placeholder = 就这篇论文提问… +chat-send = 发送 +chat-stop = 停止 +chat-summarize = 总结 +chat-add-selection = 添加选区 +chat-clear = 清空 +chat-save-note = 保存为笔记 +chat-need-config = 请先在偏好设置中配置 AI Provider +chat-thinking = 思考中… +chat-done = 完成 +chat-stopped = 已停止 +chat-cleared = 已清空对话 +chat-no-reply = 还没有可保存的 AI 回复 +chat-note-saved = 已保存为笔记 +chat-selection-added = 已将选区加入下一条消息 +chat-selection-empty = 阅读器中没有选中文本 diff --git a/addon/locale/zh-CN/mainWindow.ftl b/addon/locale/zh-CN/mainWindow.ftl new file mode 100644 index 0000000..ae057d0 --- /dev/null +++ b/addon/locale/zh-CN/mainWindow.ftl @@ -0,0 +1 @@ +itemmenu-label = ChatPapers:总结 diff --git a/addon/locale/zh-CN/preferences.ftl b/addon/locale/zh-CN/preferences.ftl new file mode 100644 index 0000000..4ab3ad2 --- /dev/null +++ b/addon/locale/zh-CN/preferences.ftl @@ -0,0 +1,29 @@ +prefs-title = ChatPapers +prefs-section-provider = AI 接入 +prefs-section-generation = 生成参数 +prefs-section-openrouter = OpenRouter(可选) +prefs-section-prompts = 提示词 +prefs-provider = Provider +prefs-reset = 重置为预设默认 +prefs-base-url = Base URL +prefs-api-key = API Key +prefs-model = 模型 +prefs-refresh-models = 刷新模型列表 +prefs-temperature = 温度 +prefs-max-tokens = 最大 Token +prefs-timeout = 超时(毫秒) +prefs-max-context = 最大上下文字符 +prefs-answer-language = 回答语言 +prefs-send-metadata = + .label = 在提示词中附带条目元数据 +prefs-openrouter-referer = HTTP-Referer +prefs-openrouter-title = X-Title +prefs-system-prompt = 系统提示词(留空使用默认) +prefs-summary-prompt = 总结提示词(留空使用默认) +prefs-hint-empty = 请选择 Ollama、LM Studio、OpenRouter 或其他 Provider。 +prefs-hint-local = 本地 Provider:请求发往本机。请先启动对应服务。 +prefs-hint-openrouter = 云端聚合:论文内容将发送到 OpenRouter / 上游模型。 +prefs-hint-cloud = 云端 Provider:论文内容将发送到所选 API 端点。 +prefs-models-empty = 未返回任何模型。 +prefs-models-pick = 从列表中输入模型 ID(最多显示 30 个): +prefs-privacy = 隐私说明:仅在你点击发送/总结时,向你配置的端点发送内容。API Key 仅保存在本地 Zotero 偏好设置中。 diff --git a/addon/manifest.json b/addon/manifest.json new file mode 100644 index 0000000..78d39d7 --- /dev/null +++ b/addon/manifest.json @@ -0,0 +1,20 @@ +{ + "manifest_version": 2, + "name": "__addonName__", + "version": "__buildVersion__", + "description": "__description__", + "homepage_url": "__homepage__", + "author": "__author__", + "icons": { + "48": "content/icons/favicon@0.5x.png", + "96": "content/icons/favicon.png" + }, + "applications": { + "zotero": { + "id": "__addonID__", + "update_url": "__updateURL__", + "strict_min_version": "9.0", + "strict_max_version": "9.*" + } + } +} diff --git a/addon/prefs.js b/addon/prefs.js new file mode 100644 index 0000000..05551f9 --- /dev/null +++ b/addon/prefs.js @@ -0,0 +1,15 @@ +pref("provider", ""); +pref("apiBaseUrl", ""); +pref("apiKey", ""); +pref("model", ""); +pref("openrouterReferer", "https://github.com/chatpapers/chatpapers"); +pref("openrouterTitle", "ChatPapers"); +pref("temperature", "0.3"); +pref("maxTokens", 2048); +pref("timeoutMs", 120000); +pref("maxContextChars", 80000); +pref("answerLanguage", "zh-CN"); +pref("systemPrompt", ""); +pref("summaryPrompt", ""); +pref("sendMetadata", true); +pref("verboseLog", false); diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..8b4f425 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,411 @@ +# ChatPapers 开发文档 + +> 版本:v0.2(草案) +> 更新日期:2026-07-20 +> 关联需求:[requirements.md](./requirements.md) + +--- + +## 1. 技术选型 + +### 1.1 平台与形态 + +| 项 | 选择 | 理由 | +| --- | --- | --- | +| 宿主 | Zotero 9 Desktop | 需求明确;2026 年已有多款插件 bump 至 `9.*` | +| 扩展形态 | Bootstrapped Extension | Zotero 官方推荐路径(非 WebExtension 受限 API) | +| 清单 | `manifest.json` + `bootstrap.js` | Zotero 7+ 标准;9 需注意 bootstrap 导出方式 | +| 目标版本字段 | `strict_min_version: "9.0"`,`strict_max_version: "9.*"` | 与需求一致;若兼容 8 需额外回归 | + +### 1.2 语言与工具链 + +| 项 | 选择 | 说明 | +| --- | --- | --- | +| 语言 | TypeScript | 类型安全;配合 `zotero-types` | +| 构建 | esbuild(经 `zotero-plugin-scaffold`) | 社区主流;热重载友好 | +| 脚手架 | [zotero-plugin-template](https://github.com/windingwind/zotero-plugin-template) + [zotero-plugin-scaffold](https://github.com/zotero-plugin-dev/zotero-plugin-scaffold) | 一键 build / start / release | +| UI Toolkit | `zotero-plugin-toolkit`(注意 Z9 API 迁移) | 菜单、快捷键、进度等;部分旧 API 已迁至原生 | +| 本地化 | Fluent(`.ftl`) | Zotero 未来方向;避免 DTD | +| 包管理 | pnpm 或 npm | 团队自定,文档示例用 npm | + +### 1.3 Zotero 9 开发注意点 + +根据社区插件迁移经验,落地时需特别注意: + +1. **`bootstrap.js` 导出方式** + Zotero 9 通过 `scope[method]` 解析生命周期函数。`bootstrap` 入口应产出**顶层** `function install/startup/shutdown/uninstall/...`,避免被 esbuild IIFE 包死导致钩子找不到。常见做法:对 `bootstrap.ts` **transpile 不 bundle**,或显式配置 esbuild 保留全局函数。 + +2. **废弃 `ChromeUtils.import`** + Firefox 128+ / Zotero 9 路径下优先使用 ESM / 官方推荐的模块加载方式,避免遗留 JSM import。 + +3. **UI 注册** + 优先使用 Zotero 原生 PreferencePane、菜单注入点;Toolkit 大版本升级后核对 Breaking Changes。 + +4. **无重启启停** + `shutdown` / `onMainWindowUnload` 必须移除 DOM、监听、定时器、未完成的 fetch AbortController。 + +### 1.4 参考文档 + +- [Zotero 7 for Developers](https://www.zotero.org/support/dev/zotero_7_for_developers)(基础仍适用,9 在此之上演进) +- [Zotero Plugin Dev Docs](https://windingwind.github.io/doc-for-zotero-plugin-dev/) +- [Zotero Plugin Scaffold](https://zotero-plugin.dev/zotero-plugin-scaffold/) +- 官方示例:[make-it-red](https://github.com/zotero/make-it-red) + +--- + +## 2. 工程结构(目标) + +以 template 为起点,建议收敛为以下结构: + +```text +chatpapers/ +├── addon/ # 打包进 xpi 的静态资源 +│ ├── bootstrap.js # 由 bootstrap.ts 转译生成(顶层函数) +│ ├── manifest.json +│ ├── prefs.js # 默认偏好 +│ ├── locale/ +│ │ ├── en-US/chatpapers.ftl +│ │ └── zh-CN/chatpapers.ftl +│ └── content/ # 图标、样式、偏好 XHTML 等 +├── src/ +│ ├── index.ts # 插件主入口(bundle) +│ ├── addon.ts # Addon 单例、生命周期编排 +│ ├── hooks.ts # startup/shutdown/window hooks 业务 +│ ├── modules/ +│ │ ├── ui/ +│ │ │ ├── readerPane.ts # PDF Reader 侧边栏注入 +│ │ │ ├── chatView.ts # 对话 UI +│ │ │ └── prefs.ts # 偏好面板 +│ │ ├── pdf/ +│ │ │ ├── extractor.ts # 文本提取适配层 +│ │ │ └── context.ts # 上下文裁剪 / 分块 +│ │ ├── llm/ +│ │ │ ├── client.ts # OpenAI 兼容客户端(stream / models) +│ │ │ ├── providers.ts # Ollama / LM Studio / OpenRouter 等预设 +│ │ │ ├── prompts.ts # 系统提示与总结模板 +│ │ │ └── types.ts +│ │ ├── storage/ +│ │ │ ├── prefs.ts # Preference 读写封装 +│ │ │ └── sessions.ts # 会话持久化 +│ │ └── zotero/ +│ │ ├── notes.ts # 创建/追加 Note +│ │ └── items.ts # 元数据读取 +│ └── utils/ +│ ├── logger.ts +│ └── abort.ts +├── docs/ +│ ├── requirements.md +│ └── development.md +├── zotero-plugin.config.ts +├── package.json +├── tsconfig.json +└── readme.md +``` + +**命名约定** + +- 插件 ID:`yehongyu@njau.edu.cn` +- Pref 前缀:`extensions.chatpapers.*` +- Fluent 文件:`chatpapers.ftl`(避免与其它插件冲突) +- 日志前缀:`[ChatPapers]` + +--- + +## 3. 架构设计 + +### 3.1 分层 + +```text +┌─────────────────────────────────────────┐ +│ UI Layer(Reader Pane / Prefs / Menus) │ +├─────────────────────────────────────────┤ +│ Application Services │ +│ ChatSession · Summarize · SaveNote │ +├─────────────────────────────────────────┤ +│ Adapters │ +│ PdfExtractor · LlmClient · PrefStore │ +├─────────────────────────────────────────┤ +│ Zotero Platform APIs │ +└─────────────────────────────────────────┘ +``` + +原则: + +- UI 不直接 `fetch`;统一经 `LlmClient` +- PDF 提取细节隔离在 `PdfExtractor`,便于 Z9 API 变更时单点替换 +- 所有偏好读写经 `PrefStore`,禁止散落魔法字符串 + +### 3.2 核心时序:提问 + +```text +User 发送 + → ChatView 收集:input + selection? + session history + → ContextBuilder:metadata + pdfText(截断) + messages + → LlmClient.chatStream(messages, AbortSignal) + → 逐 chunk 更新 UI + → 完成:写入 SessionStore +``` + +### 3.3 核心时序:保存笔记 + +```text +User 点击「保存为笔记」 + → 取当前 Assistant 消息(或总结全文) + → Notes.createChildNote(item, { title, bodyHtmlOrMd }) + → Toast / 状态提示成功 +``` + +Markdown → Zotero Note:优先转为简单 HTML(标题、段落、列表),保持可读。 + +--- + +## 4. 关键模块设计要点 + +### 4.1 Reader 侧边栏 + +- 在 Reader 窗口加载后注册侧边栏 Tab(具体 DOM/API 以 Zotero 9 Reader 为准,实现前用 DevTools 探测现网结构) +- Pane 与 `itemID` / `attachmentKey` 绑定;切换 PDF 时重置或切换会话 +- 卸载时移除 Tab 与事件,防止重复注入 + +### 4.2 PDF 文本提取 + +实现顺序建议: + +1. **优先**:使用 Zotero 已索引全文 / Reader 内可获取的文本 API(若可用) +2. **次选**:通过 PDF.js / 附件本地路径做页面级提取 +3. **失败**:UI 提示「无文本层」,不发起无效大请求 + +`ContextBuilder` 策略(MVP): + +- 若 `len(text) <= maxContextChars`:全文注入 +- 否则:标题页元数据 + 前 N 字 + 后 M 字,或「先让模型做分段摘要」二选一(由需求待决策项决定) + +### 4.3 LLM Client + +MVP 以 **单一 OpenAI 兼容客户端** 覆盖 Ollama、LM Studio、OpenRouter 及各类云端;差异用 **Provider 预设表** 表达,而不是为每个厂商写一套协议。 + +```http +POST {baseUrl}/chat/completions +Authorization: Bearer {apiKey} # 本地预设可省略 +Content-Type: application/json + +{ + "model": "...", + "messages": [...], + "stream": true, + "temperature": 0.3, + "max_tokens": 2048 +} +``` + +#### Provider 预设(与需求 §4.4 对齐) + +| 预设 | 默认 Base URL | Key | 额外行为 | +| --- | --- | --- | --- | +| Ollama | `http://127.0.0.1:11434/v1` | 可空 | 可选 `GET /v1/models` 刷新列表;连接失败提示启动 Ollama | +| LM Studio | `http://127.0.0.1:1234/v1` | 可空 | 可选拉取 models;提示开启 Local Server | +| OpenRouter | `https://openrouter.ai/api/v1` | 必填 | 附加可选头 `HTTP-Referer`、`X-Title` | +| OpenAI / DeepSeek / 硅基流动 | 各官方 `/v1` | 必填 | 标准 Bearer | +| 自定义 | 用户输入 | 按需 | 无额外头 | + +建议代码结构: + +```text +src/modules/llm/ + ├── client.ts # 统一 chatStream / listModels + ├── providers.ts # 预设常量:id → { baseUrl, needsKey, headers? } + ├── url.ts # Base URL 规范化(补 /v1、去尾斜杠) + ├── prompts.ts + └── types.ts +``` + +要求: + +- 解析 SSE `data: {...}`;处理 `[DONE]` +- `AbortController` 支持停止生成 +- 统一错误类型:`AuthError` / `RateLimitError` / `NetworkError` / `LocalServerDown` / `ProviderError` +- **禁止**将 API Key 打进日志;verbose 模式下对 Header 脱敏 +- Base URL 规范化:兼容 `http://127.0.0.1:11434` 与 `.../v1` +- 切换预设时:写入默认 Base URL,**不覆盖**用户已改过的自定义 URL(可用「重置为预设默认」按钮) + +本地联调建议: + +- Ollama:`ollama pull qwen2.5` → Base URL 默认 → model 填实际名 +- LM Studio:加载模型并 Start Server → 端口若非 1234 则改 URL +- OpenRouter:填 Key + 模型如 `openai/gpt-4o-mini` + +### 4.4 会话存储 + +MVP 可选方案: + +| 方案 | 优点 | 缺点 | +| --- | --- | --- | +| A. Preferences 存 JSON | 实现快 | 容量与性能差 | +| B. 插件数据目录 JSON 文件 | 清晰、易清理 | 需处理路径与并发 | +| C. IndexedDB / SQLite | 可扩展 | 实现成本高 | + +**建议 MVP 用 B**,路径形如:`{profile}/chatpapers/sessions/{itemKey}-{attachmentKey}.json`。 + +### 4.5 偏好面板 + +使用 `Zotero.PreferencePanes.register`(Z9 推荐原生 API)注册: + +- **Provider**:下拉预设(Ollama / LM Studio / OpenRouter / … / 自定义)+「重置为预设默认」 +- 基本:Base URL、Key(本地可空)、Model、「刷新模型列表」 +- 生成:temperature、maxTokens、timeout、maxContextChars +- OpenRouter 高级:Referer / Title(可折叠) +- 提示词:system / summary 可编辑文本框 +- 隐私说明:明确当前预设是本地还是云端 + +--- + +## 5. 开发环境搭建 + +### 5.1 前置依赖 + +- Node.js 20+(LTS) +- 已安装 **Zotero 9** 桌面版 +- Git + +### 5.2 初始化(计划命令) + +需求确认后,推荐从官方模板生成: + +```bash +# 示例:使用 zotero-plugin-template 克隆后改名 +npx tiged windingwind/zotero-plugin-template chatpapers +cd chatpapers +npm install +``` + +随后修改: + +- `package.json` → `addonName` / `addonID` / `addonRef` +- `addon/manifest.json` → `strict_*_version` 设为 Zotero 9 +- 清理 demo 代码,按本文结构迁入模块 + +### 5.3 本地调试 + +典型脚本(以 scaffold 为准): + +```bash +npm run start # 启动 Zotero + 加载未打包插件 + 热重载 +npm run build # 产出 build/ 与 xpi +npm run lint +``` + +调试技巧: + +1. 在 Zotero 中打开错误控制台(查看 `[ChatPapers]` 日志) +2. 对 Reader 窗口使用独立 DevTools(若可用)检查侧边栏 DOM +3. 用错误 Base URL 验证错误提示路径 +4. 用本地 Ollama(`http://127.0.0.1:11434/v1`)验证兼容协议 + +### 5.4 环境变量 + +Scaffold 通常需要本机 Zotero 可执行文件路径,例如: + +```bash +# Windows 示例(按实际安装路径调整) +set ZOTERO_PLUGIN_ZOTERO_CMD=C:\Program Files\Zotero\zotero.exe +``` + +具体变量名以当前 `zotero-plugin-scaffold` 文档为准。 + +--- + +## 6. 编码规范 + +1. **只改任务相关代码**;不顺手大重构模板无关文件 +2. TypeScript `strict`;避免无必要 `any` +3. 异步:统一 `async/await`;外部请求必须可 Abort +4. UI 字符串全部走 Fluent,禁止硬编码中文/英文(开发期临时除外,合并前清掉) +5. 公共工具放 `utils/`;业务不互相深层耦合 +6. 每个模块顶部保持短注释(「做什么」),避免长篇叙述 + +### 6.1 建议的 npm scripts + +```json +{ + "scripts": { + "start": "zotero-plugin serve", + "build": "zotero-plugin build", + "lint": "eslint .", + "release": "zotero-plugin release" + } +} +``` + +(实际命令以模板生成结果为准。) + +--- + +## 7. 测试策略 + +| 层级 | 内容 | 时机 | +| --- | --- | --- | +| 手工冒烟 | 安装、侧边栏、提问、总结、存笔记、禁用清理 | 每个里程碑 | +| 单元测试 | `ContextBuilder` 截断、URL 规范化、错误映射 | M2+ | +| 集成(可选) | scaffold + Mocha 在真实 Zotero 中跑 | M4 | + +最低验收清单见需求文档里程碑 M1–M4。 + +**手工测试用例(MVP)** + +1. 未配置 API Key → 发送时友好提示 +2. 错误 Key → 401 可读错误 +3. 正常 PDF → 流式回复(分别用 Ollama / OpenRouter 各测一次) +4. 选中文本提问 → 上下文包含选区 +5. 超长 PDF → 截断提示仍可回答 +6. 无文本 PDF → 明确失败原因 +7. Ollama 未启动 → `LocalServerDown` 友好提示 +8. 保存笔记 → 条目下可见 Note +9. 禁用插件 → 侧边栏与菜单消失,无报错 + +--- + +## 8. 发布流程 + +1. 更新版本号(`package.json` / `manifest.json`) +2. `npm run build` 生成 `.xpi` +3. GitHub Release 上传 `.xpi` +4. 配置 `update_url` 指向 `updates.json`(Scaffold 可生成) +5. 在干净 Zotero 9 Profile 中验证安装与更新 + +版本号遵循 SemVer:`MAJOR.MINOR.PATCH`。 + +--- + +## 9. 实施计划(与需求里程碑对齐) + +| 阶段 | 工程任务 | 预估 | +| --- | --- | --- | +| M0 | 确认需求待决策项;冻结插件 ID / 协议 | 0.5d | +| M1 | 从 template 初始化;偏好页;Reader 占位 Pane | 2–3d | +| M2 | PDF 提取 + 非流式/流式 Client + 单轮对话 | 3–5d | +| M3 | 多轮 UI、总结模板、存 Note、Abort | 3–4d | +| M4 | 会话持久化、i18n、日志脱敏、打包与 README 使用说明 | 2–3d | + +以上为单人全职粗估,随 Reader API 熟悉度波动。 + +--- + +## 10. 目录外约定 + +- 密钥与真实 API Key **不得**提交仓库;提供 `.env.example`(若需要)仅含路径类配置 +- `docs/` 变更随功能演进;重大行为变更先更新需求再改代码 +- Issue / PR 标题建议前缀:`feat:` / `fix:` / `docs:` / `chore:` + +--- + +## 11. 下一步(工程开工清单) + +需求方确认 [requirements.md §10 待决策事项](./requirements.md#10-待决策事项需产品确认) 后,按序执行: + +1. 从 `zotero-plugin-template` 初始化仓库代码 +2. 改名与 Zotero 9 manifest +3. 落地 `PrefStore` + 空 Reader Pane +4. 实现 `LlmClient` 与最小 Chat UI +5. 接通 PDF 文本与「保存为笔记」 + +如需,可在确认待决策项后直接进入 M1 脚手架初始化。 diff --git a/docs/requirements.md b/docs/requirements.md new file mode 100644 index 0000000..bdcad7c --- /dev/null +++ b/docs/requirements.md @@ -0,0 +1,344 @@ +# ChatPapers 需求文档 + +> 版本:v0.2(草案) +> 更新日期:2026-07-20 +> 状态:待确认(Provider 预设范围已按产品意见更新) +> 关联:基于仓库根目录初步需求整理 + +--- + +## 1. 背景与目标 + +### 1.1 背景 + +研究人员日常在 Zotero 中管理大量 PDF 文献。现有 AI 阅读工具多为独立网页或外部应用,存在以下摩擦: + +- 需反复上传 PDF,上下文与 Zotero 条目割裂 +- 对话结果难以回流到文献库(笔记、标签、批注) +- 工作流在「文献管理器 ↔ AI 工具」之间频繁切换 + +### 1.2 产品目标 + +在 **Zotero 9** 内提供原生体验的 AI 论文助手,使用户能够: + +1. **对着当前 PDF 提问**(选中文本 / 全文上下文) +2. **一键生成结构化总结**(摘要、贡献、方法、局限等) +3. **把有价值的 AI 输出沉淀为 Zotero Note**,与条目长期绑定 + +### 1.3 成功标准(MVP) + +| 指标 | 目标 | +| --- | --- | +| 安装 | 用户可通过 `.xpi` 安装到 Zotero 9,启用后无需重启即可使用 | +| 可用性 | 打开带 PDF 附件的条目后,≤ 3 次点击进入对话 | +| 核心路径 | 「打开 PDF → 提问 → 得到带引用页码的回答 → 存为笔记」可完整走通 | +| 配置 | 一键切换常用 Provider 预设(含 Ollama / LM Studio / OpenRouter),并可自定义端点;错误提示可读、可排查 | +| 隐私 | API Key 仅存本地;本地 Provider 默认不外传;云端仅向用户所选端点发送 | + +--- + +## 2. 用户与场景 + +### 2.1 目标用户 + +| 角色 | 描述 | 核心诉求 | +| --- | --- | --- | +| 研究生 / 科研人员 | 高频阅读、需要快速把握论文要点 | 总结、方法/实验追问 | +| 文献综述作者 | 批量阅读、需要可比的结构化输出 | 固定模板总结、笔记归档 | +| 隐私敏感用户 | 不愿使用封闭云端上传产品 | 自备 API / 本地模型兼容端点 | + +### 2.2 核心用户故事 + +1. **作为**科研人员,**我希望**在阅读 PDF 时直接向 AI 提问选中段落,**以便**快速理解术语与论证。 +2. **作为**科研人员,**我希望**一键生成论文结构化摘要并写入笔记,**以便**后续检索与写作引用。 +3. **作为**用户,**我希望**使用自己的 API Key 与自定义 Base URL,**以便**接入公司代理、国产大模型或任意 OpenAI 兼容服务。 +4. **作为**用户,**我希望**一键选择 Ollama / LM Studio / OpenRouter 等常用接入,**以便**本地离线或聚合云端模型都能立刻用上。 +5. **作为**用户,**我希望**对话历史按「条目 / PDF」隔离保存,**以便**下次打开同一篇论文可续聊。 +6. **作为**用户,**我希望**回答尽量标注依据页码或原文片段,**以便**回读核验,降低幻觉风险。 + +--- + +## 3. 范围定义 + +### 3.1 范围内(In Scope)— MVP + +- Zotero 9 Bootstrapped 插件安装与生命周期管理 +- PDF Reader 侧边栏 Chat UI(多轮对话) +- 当前打开 PDF 的文本提取与上下文组装 +- 选中文本作为「引用上下文」提问 +- 一键总结(可配置 Prompt 模板) +- OpenAI Chat Completions 兼容协议(含流式输出)作为统一接入层 +- **常用 Provider 预设(MVP 必做)**:Ollama、LM Studio、OpenRouter,以及 OpenAI / DeepSeek 等(见 §4.4) +- 偏好设置页:Provider 预设、API Key、Base URL、Model、温度、最大 Token、语言、Prompt;本地端点可「探测可用模型」 +- 将选中回复 / 总结保存为当前 Item 的子 Note +- 中英双语界面(Fluent:`zh-CN` / `en-US`) + +### 3.2 范围内 — 后续版本(Post-MVP) + +| 阶段 | 能力 | +| --- | --- | +| v0.2 | 多 PDF / 多条目联合对话;从主窗口条目菜单发起 Chat | +| v0.3 | 批注(Highlight)联动:基于高亮内容追问;回复一键生成高亮建议 | +| v0.4 | RAG:长文分块 + 向量检索(可插拔 Embedding Provider) | +| v0.5 | 本地服务健康检查增强、模型拉取引导、离线模式全局提示 | +| v0.6 | 批量总结 Collection;导出 Markdown / 结构化 JSON | +| 探索 | MCP Server;Anthropic / Gemini 原生协议;图表/公式 OCR | + +### 3.3 范围外(Out of Scope)— 当前明确不做 + +- 不替代 Zotero 原生 PDF 阅读器 +- 不做独立桌面端或 Web SaaS +- 不自建账号体系与云端同步对话(对话仅本地) +- 不提供付费模型转售 / 内置官方 Key +- 不保证扫描版纯图片 PDF 的 OCR(可作为后续可选引擎) +- 不兼容 Zotero 6 / 7 / 8(以 Zotero 9 为主;若成本低可放宽 `strict_min_version`) + +--- + +## 4. 功能需求 + +### 4.1 安装与入口 + +| ID | 需求 | 优先级 | +| --- | --- | --- | +| F-01 | 提供可安装的 `.xpi`;`manifest.json` 声明兼容 Zotero 9 | P0 | +| F-02 | 插件启用/禁用无需重启 Zotero;`shutdown` 清理 UI 与监听 | P0 | +| F-03 | PDF Reader 右侧边栏提供 ChatPapers Tab / Pane | P0 | +| F-04 | 主窗口「工具 → ChatPapers 设置」或偏好面板入口 | P0 | +| F-05 | 条目右键菜单:「用 ChatPapers 总结」(Post-MVP 可升为 P0) | P1 | + +### 4.2 上下文与 PDF + +| ID | 需求 | 优先级 | +| --- | --- | --- | +| F-10 | 识别当前 Reader 打开的 PDF Attachment,绑定 Item Key | P0 | +| F-11 | 提取 PDF 纯文本(优先 Zotero / PDF.js 已有全文能力) | P0 | +| F-12 | 超长文本按策略截断或分块(见 5.2);UI 提示「已截断」 | P0 | +| F-13 | 支持将 Reader 中选中文本注入本轮用户消息 | P0 | +| F-14 | 可选:附带条目标题、作者、年份、DOI、摘要到 System Prompt | P1 | +| F-15 | 扫描版 / 无文本层 PDF:明确提示无法提取,引导用户处理 | P1 | + +### 4.3 对话与总结 + +| ID | 需求 | 优先级 | +| --- | --- | --- | +| F-20 | 多轮对话;支持停止生成(Abort) | P0 | +| F-21 | 流式展示 Token(SSE / stream) | P0 | +| F-22 | 内置「总结」动作:使用可编辑模板生成结构化摘要 | P0 | +| F-23 | 预设快捷指令:术语解释、方法复述、局限与质疑、相关工作 | P1 | +| F-24 | Markdown 渲染回复(标题、列表、代码块、引用) | P0 | +| F-25 | 复制回复;清空当前会话;新建会话 | P0 | +| F-26 | 会话按 `itemKey + attachmentKey` 本地持久化 | P1 | +| F-27 | 回答尽量要求模型标注页码 / 引用片段(Prompt 约束 + UI 展示) | P1 | + +### 4.4 AI Provider + +**设计原则**:不绑定单一厂商。MVP 以 **OpenAI Chat Completions 兼容协议** 为统一客户端;各服务通过「预设 + 可编辑 Base URL / Model / Key」接入。Ollama、LM Studio、OpenRouter 均为 **P0**。 + +| ID | 需求 | 优先级 | +| --- | --- | --- | +| F-30 | 统一客户端支持 OpenAI 兼容 `POST .../chat/completions`(含 stream) | P0 | +| F-31 | 可配置 Base URL、API Key、Model、Timeout;切换预设时自动填充默认值且允许覆写 | P0 | +| F-32 | 内置 Provider 预设表(见下方);含 **Ollama / LM Studio / OpenRouter** 及常用云端 | P0 | +| F-33 | 请求失败:展示 HTTP 状态、可理解错误与重试;本地服务未启动时给出启动提示 | P0 | +| F-34 | 本地 Provider(Ollama / LM Studio):可选「刷新模型列表」(调用兼容 `/models` 或等价接口) | P1 | +| F-35 | 本地 Provider:API Key 可为空;UI 不强制填写 | P0 | +| F-36 | OpenRouter:支持可选 `HTTP-Referer` / `X-Title` 等推荐头(可配置) | P1 | +| F-37 | 可选记录最近一次请求的 Token 用量(若 API 返回) | P2 | +| F-38 | Anthropic Messages / Gemini 原生协议(非兼容层) | P2(Post-MVP) | + +#### MVP 内置 Provider 预设 + +| 预设 ID | 显示名 | 默认 Base URL | API Key | 说明 | +| --- | --- | --- | --- | --- | +| `ollama` | Ollama | `http://127.0.0.1:11434/v1` | 可空 | 本机本地模型;需用户先 `ollama serve` | +| `lmstudio` | LM Studio | `http://127.0.0.1:1234/v1` | 可空 | 本机 Local Server;端口以用户设置为准 | +| `openrouter` | OpenRouter | `https://openrouter.ai/api/v1` | 必填 | 聚合多模型;模型名形如 `vendor/model` | +| `openai` | OpenAI | `https://api.openai.com/v1` | 必填 | 官方 API | +| `deepseek` | DeepSeek | `https://api.deepseek.com/v1` | 必填 | 常用国产云端 | +| `siliconflow` | 硅基流动 | `https://api.siliconflow.cn/v1` | 必填 | 常用聚合 | +| `custom` | 自定义 | 用户填写 | 按服务要求 | 任意 OpenAI 兼容网关 / 代理 | + +> 说明:上述默认端口/路径以各产品当前常见配置为准;设置页需提示「若改过端口请手动改 Base URL」。 + +### 4.5 与 Zotero 数据联动 + +| ID | 需求 | 优先级 | +| --- | --- | --- | +| F-40 | 「保存为笔记」:创建子 Note,标题可配置(默认含日期) | P0 | +| F-41 | 可选将总结追加到已有 Note,而非总是新建 | P1 | +| F-42 | 不自动修改条目元数据(标题、标签等),除非用户显式操作 | P0 | +| F-43 | (后续)根据 AI 建议创建 Highlight Annotation | P2 | + +### 4.6 设置与安全 + +| ID | 需求 | 优先级 | +| --- | --- | --- | +| F-50 | 偏好面板完整配置项(见附录 A) | P0 | +| F-51 | API Key 使用 Zotero Preferences / 安全存储惯例,不明文写入日志 | P0 | +| F-52 | 默认不上传全文以外的库数据;设置页说明「将发送哪些内容」 | P0 | +| F-53 | 「发送前预览上下文」开关(调试用) | P2 | + +--- + +## 5. 非功能需求 + +### 5.1 兼容性 + +- 目标:Zotero 9(发布于 2026 年) +- `applications.zotero.strict_max_version`:`9.*` +- `strict_min_version`:建议 `9.0`(若需覆盖 8.x,需单独验证后下调) +- OS:Windows / macOS / Linux(随 Zotero 官方桌面端) + +### 5.2 性能与限制 + +| 项 | 建议默认值 | 说明 | +| --- | --- | --- | +| 单次上下文最大字符 | 80,000(可配置) | 超出则截断或分块摘要后再问 | +| 流式首字延迟 | 受模型与网络影响;UI 需有 loading | — | +| 并发请求 | 单会话同时仅 1 个进行中请求 | 新请求可取消旧请求 | +| 会话历史条数 | 默认保留最近 50 轮 | 可清理 | + +### 5.3 可靠性 + +- 网络超时可配置(默认 120s) +- Provider 4xx/5xx 不导致插件崩溃 +- PDF 提取失败有明确降级提示 + +### 5.4 可维护性 + +- TypeScript 源码 + ESLint +- 模块边界清晰:UI / PDF / LLM / Storage / Prefs +- 关键日志带命名空间前缀,可开关 verbose + +### 5.5 国际化与无障碍 + +- Fluent 本地化,至少中英 +- 侧边栏控件具备可访问名称;支持键盘发送(Enter / Ctrl+Enter 策略可配置) + +### 5.6 许可 + +- 建议开源协议:AGPL-3.0 或 MIT(待决策) +- 第三方依赖遵循各自许可,在 NOTICE 中声明 + +--- + +## 6. 信息架构与交互草案 + +### 6.1 主要界面 + +1. **PDF Reader 侧边栏 — Chat** + - 顶部:当前论文短标题、模型名、设置入口 + - 中部:消息列表(User / Assistant) + - 底部:输入框、附加选区按钮、发送、停止、快捷动作(总结) +2. **偏好面板 — ChatPapers** + - Provider、凭据、模型、生成参数、Prompt 模板、隐私说明 +3. **(可选)独立对话窗口**:侧边栏空间不足时弹出(P2) + +### 6.2 关键流程(MVP 主路径) + +```text +安装插件 → 配置 API → 打开 PDF + → 打开 ChatPapers 面板 + →(可选)选中文本「添加到上下文」 + → 输入问题 / 点击「总结」 + → 流式显示回答 + →「保存为笔记」→ 条目下新增 Note +``` + +--- + +## 7. 数据与隐私 + +### 7.1 本地数据 + +| 数据 | 存储位置 | 说明 | +| --- | --- | --- | +| Provider 预设、API Key、模型等 | Zotero Preferences | 前缀如 `extensions.chatpapers.*` | +| 会话历史 | 插件本地存储(prefs JSON / SQLite / 文件,待技术选型) | 按条目隔离 | +| 笔记内容 | Zotero 数据库(用户主动保存时) | 标准 Note 条目 | + +### 7.2 外发数据 + +仅在用户点击发送 / 总结时,向**用户当前所选** API 端点发送: + +- System Prompt(含模板与元数据,按设置) +- 组装后的 PDF 文本片段 / 选区 +- 对话历史(按窗口策略) + +行为约定: + +- 选择 **Ollama / LM Studio** 时,请求发往本机,不经过第三方云(除非用户改成远程 Base URL) +- 选择 **OpenRouter / OpenAI 等云端** 时,内容发往对应服务商,设置页需显著提示 +- **不默认发送**:整个 Zotero 库、未打开条目、其他附件 + +--- + +## 8. 里程碑 + +| 里程碑 | 交付物 | 验收要点 | +| --- | --- | --- | +| M0 | 需求/开发文档定稿 | 本文档与开发文档评审通过 | +| M1 | 插件骨架可安装 | Zotero 9 加载、侧边栏占位、偏好页 | +| M2 | PDF 文本 + 单轮问答 | 能对当前 PDF 提问并显示回复 | +| M3 | 流式 + 总结 + 存笔记 | MVP 主路径闭环 | +| M4 | 会话持久化 + i18n + 打包发布 | 可分发 `.xpi` 与更新清单 | +| M5+ | Post-MVP 能力 | 按优先级排期 | + +--- + +## 9. 风险与依赖 + +| 风险 | 影响 | 缓解 | +| --- | --- | --- | +| Zotero 9 Reader / PDF API 变动 | 文本提取或侧边栏注入失败 | 跟进官方 changelog;抽象 Reader Adapter | +| 超长论文超出模型上下文 | 回答质量下降 | 分块摘要、RAG、截断提示 | +| 扫描版 PDF 无文本 | 核心功能不可用 | 明确提示;后续可选 OCR | +| API 费用与密钥泄露 | 用户损失 | 本地存储、日志脱敏、用量提示 | +| 竞品功能重叠(PapersGPT 等) | 差异化不足 | 聚焦「轻量、可自托管、笔记回流」 | + +--- + +## 10. 待决策事项(需产品确认) + +请在开工前确认以下选项: + +1. **开源协议**:AGPL-3.0 vs MIT? +2. **是否兼容 Zotero 8**:仅 9,还是 `8.*`–`9.*`? +3. **MVP 是否必须流式输出**:可先非流式降低复杂度? +4. **长文策略**:仅截断 / 先分块摘要再问答 / 直接上简单 RAG? +5. **默认 Provider 与默认模型**:建议默认 `openrouter` 或 `ollama`?各预设的默认 model 字符串?(当前草案:无强制默认云,首次打开引导选择) +6. **品牌名是否确定为 ChatPapers**(插件 ID、命名空间将据此固定)? +7. **对话历史是否需要跨设备同步**(当前默认:不同步)? +8. ~~Ollama / LM Studio / OpenRouter 是否进 MVP~~ → **已确认进 MVP(P0)** + +--- + +## 附录 A. 偏好项清单(草案) + +| Preference Key | 类型 | 默认 | 说明 | +| --- | --- | --- | --- | +| `extensions.chatpapers.provider` | string | `""`(首次引导) | 预设 ID:`ollama` / `lmstudio` / `openrouter` / … / `custom` | +| `extensions.chatpapers.apiBaseUrl` | string | 随预设 | API 根路径(可覆写) | +| `extensions.chatpapers.apiKey` | string | `""` | API Key;本地预设可空 | +| `extensions.chatpapers.model` | string | 随预设 | 模型名 | +| `extensions.chatpapers.openrouterReferer` | string | `https://github.com/chatpapers` | OpenRouter 可选 Referer | +| `extensions.chatpapers.openrouterTitle` | string | `ChatPapers` | OpenRouter 可选 X-Title | +| `extensions.chatpapers.temperature` | number | `0.3` | 温度 | +| `extensions.chatpapers.maxTokens` | number | `2048` | 最大生成 Token | +| `extensions.chatpapers.timeoutMs` | number | `120000` | 超时(本地大模型可调大) | +| `extensions.chatpapers.maxContextChars` | number | `80000` | 上下文上限 | +| `extensions.chatpapers.answerLanguage` | string | `zh-CN` | 回答语言偏好 | +| `extensions.chatpapers.systemPrompt` | string | (内置模板) | 可覆盖 | +| `extensions.chatpapers.summaryPrompt` | string | (内置模板) | 总结模板 | +| `extensions.chatpapers.sendMetadata` | bool | `true` | 是否附带条目元数据 | +| `extensions.chatpapers.verboseLog` | bool | `false` | 调试日志 | + +--- + +## 附录 B. 竞品参考(仅作范围对照,非抄袭目标) + +- PapersGPT、Zotero-easyGPT、llm-for-zotero:侧边栏对话 / 多模型 +- Zotero MCP 类插件:对外暴露库能力给 Cursor/Claude + +ChatPapers MVP:单 PDF 对话 + 总结 + 笔记回流 + **多 Provider 预设(Ollama / LM Studio / OpenRouter 等)**。 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..15c3281 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,16 @@ +// @ts-check Let TS check this config file + +import zotero from "@zotero-plugin/eslint-config"; + +export default zotero({ + overrides: [ + { + files: ["**/*.ts"], + rules: { + // We disable this rule here because the template + // contains some unused examples and variables + "@typescript-eslint/no-unused-vars": "off", + }, + }, + ], +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..22aa03e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3774 @@ +{ + "name": "chatpapers", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "chatpapers", + "version": "0.1.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "zotero-plugin-toolkit": "^5.1.0-beta.13" + }, + "devDependencies": { + "@types/node": "^24.10.0", + "@zotero-plugin/eslint-config": "^0.6.7", + "eslint": "^9.39.2", + "prettier": "^3.7.4", + "typescript": "^5.9.3", + "zotero-plugin-scaffold": "^0.8.2", + "zotero-types": "^4.1.0-beta.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.3.1.tgz", + "integrity": "sha512-k8MHony59I5EPic6EQTCNOuPoVBnoYXkP+20xvwFjN7t0qI3ImyvyBgg+hIVPwC8JaxVjjUZld+cLfBLFDLucg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.40 || 9" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fluent/syntax": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@fluent/syntax/-/syntax-0.19.0.tgz", + "integrity": "sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/app": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-16.1.2.tgz", + "integrity": "sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^8.1.2", + "@octokit/auth-unauthenticated": "^7.0.3", + "@octokit/core": "^7.0.6", + "@octokit/oauth-app": "^8.0.3", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/types": "^16.0.0", + "@octokit/webhooks": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-app": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.1.2.tgz", + "integrity": "sha512-db8VO0PqXxfzI6GdjtgEFHY9tzqUql5xMFXYA12juq8TeTgPAuiiP3zid4h50lwlIP457p5+56PnJOgd2GGBuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.3", + "@octokit/auth-oauth-user": "^6.0.2", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "toad-cache": "^3.7.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.3.tgz", + "integrity": "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.3", + "@octokit/auth-oauth-user": "^6.0.2", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.3.tgz", + "integrity": "sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^6.0.2", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.2.tgz", + "integrity": "sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.3", + "@octokit/oauth-methods": "^6.0.2", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.3.tgz", + "integrity": "sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", + "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-8.0.3.tgz", + "integrity": "sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.2", + "@octokit/auth-oauth-user": "^6.0.1", + "@octokit/auth-unauthenticated": "^7.0.2", + "@octokit/core": "^7.0.5", + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/oauth-methods": "^6.0.1", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", + "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.2.tgz", + "integrity": "sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/openapi-webhooks-types": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.0.3.tgz", + "integrity": "sha512-90MF5LVHjBedwoHyJsgmaFhEN1uzXyBDRLEBe7jlTYx/fEhPAk3P3DAJsfZwC54m8hAIryosJOL+UuZHB3K3yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz", + "integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz", + "integrity": "sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=7" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", + "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": "^7.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.6.tgz", + "integrity": "sha512-FO+UgZCUu+pPnZAR+iKdUt64kPE7QW7ciqpldaMXaNzixz5Jld8dJ31LAUewk0cfSRkNSRKyqG438ba9c/qDlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.2.tgz", + "integrity": "sha512-U8piOROoQQUyExw5c6dTkU3GKxts5/ERRThIauNL7yaRoeXW0q/5bgHWT7JfWBw1UyrbK8ERId2wVkcB32n0uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/webhooks": { + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-14.1.3.tgz", + "integrity": "sha512-gcK4FNaROM9NjA0mvyfXl0KPusk7a1BeA8ITlYEZVQCXF5gcETTd4yhAU0Kjzd8mXwYHppzJBWgdBVpIR9wUcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-webhooks-types": "12.0.3", + "@octokit/request-error": "^7.0.0", + "@octokit/webhooks-methods": "^6.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz", + "integrity": "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@swc/core": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.1.tgz", + "integrity": "sha512-s9GN3M2jA32k+StvuS9uGe4ztf5KVGBdlJMMC6LR6Ah23Lq/CWKVcC3WeQi8qaAcLd+DiddoNCNMUWymLv+wWQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.25" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.1", + "@swc/core-darwin-x64": "1.15.1", + "@swc/core-linux-arm-gnueabihf": "1.15.1", + "@swc/core-linux-arm64-gnu": "1.15.1", + "@swc/core-linux-arm64-musl": "1.15.1", + "@swc/core-linux-x64-gnu": "1.15.1", + "@swc/core-linux-x64-musl": "1.15.1", + "@swc/core-win32-arm64-msvc": "1.15.1", + "@swc/core-win32-ia32-msvc": "1.15.1", + "@swc/core-win32-x64-msvc": "1.15.1" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.1.tgz", + "integrity": "sha512-vEPrVxegWIjKEz+1VCVuKRY89jhokhSmQ/YXBWLnmLj9cI08G61RTZJvdsIcjYUjjTu7NgZlYVK+b2y0fbh11g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.1.tgz", + "integrity": "sha512-z9QguKxE3aldvwKHHDg5OlKehasbJBF1lacn5CnN6SlrHbdwokXHFA3nIoO3Bh1Tw7bCgFtdIR4jKlTTn3kBZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.1.tgz", + "integrity": "sha512-yS2FHA8E4YeiPG9YeYk/6mKiCWuXR5RdYlCmtlGzKcjWbI4GXUVe7+p9C0M6myRt3zdj3M1knmJxk52MQA9EZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.1.tgz", + "integrity": "sha512-IFrjDu7+5Y61jLsUqBVXlXutDoPBX10eEeNTjW6C1yzm+cSTE7ayiKXMIFri4gEZ4VpXS6MUgkwjxtDpIXTh+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.1.tgz", + "integrity": "sha512-fKzP9mRQGbhc5QhJPIsqKNNX/jyWrZgBxmo3Nz1SPaepfCUc7RFmtcJQI5q8xAun3XabXjh90wqcY/OVyg2+Kg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.1.tgz", + "integrity": "sha512-ZLjMi138uTJxb+1wzo4cB8mIbJbAsSLWRNeHc1g1pMvkERPWOGlem+LEYkkzaFzCNv1J8aKcL653Vtw8INHQeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.1.tgz", + "integrity": "sha512-jvSI1IdsIYey5kOITzyajjofXOOySVitmLxb45OPUjoNojql4sDojvlW5zoHXXFePdA6qAX4Y6KbzAOV3T3ctA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.1.tgz", + "integrity": "sha512-X/FcDtNrDdY9r4FcXHt9QxUqC/2FbQdvZobCKHlHe8vTSKhUHOilWl5EBtkFVfsEs4D5/yAri9e3bJbwyBhhBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.1.tgz", + "integrity": "sha512-vfheiWBux8PpC87oy1cshcqzgH7alWYpnVq5jWe7xuVkjqjGGDbBUKuS84eJCdsWcVaB5EXIWLKt+11W3/BOwA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.1.tgz", + "integrity": "sha512-n3Ppn0LSov/IdlANq+8kxHqENuJRX5XtwQqPgQsgwKIcFq22u17NKfDs9vL5PwRsEHY6Xd67pnOqQX0h4AvbuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.157", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.157.tgz", + "integrity": "sha512-ofjcRCO1N7tMZDSO11u5bFHPDfUFD3Q9YK9g4S4w8UDKuG3CNlw2lNK1sd3Itdo7JORygZmG4h9ZykS8dlXvMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/bluebird": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.42.tgz", + "integrity": "sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz", + "integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/type-utils": "8.39.0", + "@typescript-eslint/utils": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.39.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz", + "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", + "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.39.0", + "@typescript-eslint/types": "^8.39.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", + "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", + "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz", + "integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz", + "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", + "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.39.0", + "@typescript-eslint/tsconfig-utils": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz", + "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", + "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@zotero-plugin/eslint-config": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@zotero-plugin/eslint-config/-/eslint-config-0.6.7.tgz", + "integrity": "sha512-VPuH+G9CClw1rq29JjYFJPwxTWqgeo4qQJneIf0uSsUNwkQ9H99SbRFR4K0PRTZRtZsSbZQdGaTfaE4ROlKo/g==", + "dev": true, + "license": "AGPL-3.0-or-later", + "dependencies": { + "@eslint/js": "^9.28.0", + "eslint-config-flat-gitignore": "^2.1.0", + "eslint-plugin-chai-friendly": "^1.1.0", + "eslint-plugin-mocha": "^11.1.0", + "typescript-eslint": "^8.34.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "peerDependencies": { + "eslint": "^9.28.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/args-tokenizer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/args-tokenizer/-/args-tokenizer-0.3.0.tgz", + "integrity": "sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bumpp": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/bumpp/-/bumpp-10.3.1.tgz", + "integrity": "sha512-cOKPRFCWvHcYPJQAHN6V7Jp/wAfnyqQRXQ+2fgWIL6Gao20rpu7xQ1cGGo1APOfmbQmmHngEPg9Fy7nJ3giRkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.2.0", + "args-tokenizer": "^0.3.0", + "c12": "^3.3.0", + "cac": "^6.7.14", + "escalade": "^3.2.0", + "jsonc-parser": "^3.3.1", + "package-manager-detector": "^1.3.0", + "semver": "^7.7.2", + "tinyexec": "^1.0.1", + "tinyglobby": "^0.2.15", + "yaml": "^2.8.1" + }, + "bin": { + "bumpp": "bin/bumpp.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/c12": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.2.tgz", + "integrity": "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^17.2.3", + "exsolve": "^1.0.8", + "giget": "^2.0.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.0.0", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/epubjs": { + "version": "0.3.93", + "resolved": "git+ssh://git@github.com/zotero/epub.js.git#a6139d586b66d404f3da2b846598960c0394afc5", + "integrity": "sha512-q+MZBFYx0ibCxxKkDDMl9HlUqK0fUGotpcXRqwCncS1sJtphUKZGkmWkkykEXGrtFiuAn+7KKK6a5Ld84q76mg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "event-emitter": "^0.3.5", + "jszip": "^3.7.1", + "path-webpack": "0.0.3" + } + }, + "node_modules/es-toolkit": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.41.0.tgz", + "integrity": "sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-flat-gitignore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-flat-gitignore/-/eslint-config-flat-gitignore-2.1.0.tgz", + "integrity": "sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint/compat": "^1.2.5" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "eslint": "^9.5.0" + } + }, + "node_modules/eslint-plugin-chai-friendly": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-1.1.0.tgz", + "integrity": "sha512-+T1rClpDdXkgBAhC16vRQMI5umiWojVqkj9oUTdpma50+uByCZM/oBfxitZiOkjMRlm725mwFfz/RVgyDRvCKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "eslint": ">=3.0.0" + } + }, + "node_modules/eslint-plugin-mocha": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-11.1.0.tgz", + "integrity": "sha512-rKntVWRsQFPbf8OkSgVNRVRrcVAPaGTyEgWCEyXaPDJkTl0v5/lwu1vTk5sWiUJU8l2sxwvGUZzSNrEKdVMeQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.1", + "globals": "^15.14.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "node_modules/eslint-plugin-mocha/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/nypm": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/octokit": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-5.0.5.tgz", + "integrity": "sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/app": "^16.1.2", + "@octokit/core": "^7.0.6", + "@octokit/oauth-app": "^8.0.3", + "@octokit/plugin-paginate-graphql": "^6.0.0", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0", + "@octokit/plugin-retry": "^8.0.3", + "@octokit/plugin-throttling": "^11.0.3", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "@octokit/webhooks": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-webpack": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/path-webpack/-/path-webpack-0.0.3.tgz", + "integrity": "sha512-AmeDxedoo5svf7aB3FYqSAKqMxys014lVKBzy1o/5vv9CtU7U4wgGWL1dA2o6MOzcD53ScN4Jmiq6VbtLz1vIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pdfjs-dist": { + "resolved": "git+ssh://git@github.com/zotero-plugin-dev/zotero-pdfjs-types.git#8aa68e6e7936550ee557d530b074bc861f22247a", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semiff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semiff/-/semiff-2.0.0.tgz", + "integrity": "sha512-ox5jzQW5mbuZdaQq7erRsJHhJfzwXx6ow6Q43NI/eYBLW+6dR2KkWFRE+blTFsvpLspqR4NxTIA4vnCtZ0Ul3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tiny-update-notifier": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tiny-update-notifier/-/tiny-update-notifier-2.0.2.tgz", + "integrity": "sha512-aATRGebDaQ7A3jK3tQrJX7LrijoA2uCbLM+d3dYfpIsrXVylFcWG+Nr4XG4x5jm2H3fdQ0K329xg1a1oQdIiyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semiff": "^2.0.0" + } + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz", + "integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.39.0", + "@typescript-eslint/parser": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-github-app-jwt": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", + "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xvfb-ts": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/xvfb-ts/-/xvfb-ts-1.1.1.tgz", + "integrity": "sha512-5LWBLqKlneQMSKVh4QPkuCo+/u3Z0I8E8VdJHgeiDL1Jg3mgQbcCv3FksAJyl4b5wcfW6pvcxaYPOA1Q773ZIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17.0" + } + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zotero-plugin-scaffold": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/zotero-plugin-scaffold/-/zotero-plugin-scaffold-0.8.2.tgz", + "integrity": "sha512-HtTGQ/Rz9bt1KgmFnmSNXNG2eH4sJ8VPLBwmDmV4hjv0lCgAwp2YWYtewdu5tVLzpVnTx0v++SjEW+k8Az0Qqw==", + "dev": true, + "license": "AGPL-3.0-or-later", + "workspaces": [ + "src", + "docs" + ], + "dependencies": { + "@fluent/syntax": "^0.19.0", + "@swc/core": "^1.15.0", + "adm-zip": "^0.5.16", + "bumpp": "^10.3.1", + "c12": "^3.3.1", + "chokidar": "^4.0.3", + "commander": "^14.0.2", + "es-toolkit": "^1.41.0", + "esbuild": "^0.25.12", + "fs-extra": "^11.3.2", + "hookable": "^5.5.3", + "octokit": "^5.0.5", + "std-env": "^3.10.0", + "tiny-update-notifier": "^2.0.2", + "tinyglobby": "^0.2.15", + "xvfb-ts": "^1.1.1" + }, + "bin": { + "zotero-plugin": "bin/zotero-plugin.mjs" + }, + "engines": { + "node": ">=22.8.0" + } + }, + "node_modules/zotero-plugin-toolkit": { + "version": "5.1.0-beta.13", + "resolved": "https://registry.npmjs.org/zotero-plugin-toolkit/-/zotero-plugin-toolkit-5.1.0-beta.13.tgz", + "integrity": "sha512-/KxIlxECuugVbO4/qpjWaD98lKxVsh9tZSwN/BuLXRyngXFCzMEkeMWj9lbglgiriPzYPYULCbwr1cZgXwpwLQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/zotero-types": { + "version": "4.1.0-beta.4", + "resolved": "https://registry.npmjs.org/zotero-types/-/zotero-types-4.1.0-beta.4.tgz", + "integrity": "sha512-Dn8AKJQDeEGiDkCI8OT9+Lh1xnqa+JhBqcMBOPJcS2Ycpz40P5YH/BtHviUWzodKyvqwcseDwgOburaTVujeXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bluebird": "^3.5.42", + "@types/react": "^18.3.26", + "bumpp": "^10.3.1", + "epubjs": "github:zotero/epub.js#a6139d586b66d404f3da2b846598960c0394afc5", + "pdfjs-dist": "github:zotero-plugin-dev/zotero-pdfjs-types#8aa68e6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6e3ab1a --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "chatpapers", + "type": "module", + "version": "0.1.0", + "description": "AI paper reading and summarization plugin for Zotero 9", + "config": { + "addonName": "ChatPapers", + "addonID": "chatpapers@local", + "addonRef": "chatpapers", + "addonInstance": "ChatPapers", + "prefsPrefix": "extensions.zotero.chatpapers" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/chatpapers/chatpapers.git" + }, + "author": "ChatPapers", + "bugs": { + "url": "https://github.com/chatpapers/chatpapers/issues" + }, + "homepage": "https://github.com/chatpapers/chatpapers#readme", + "license": "AGPL-3.0-or-later", + "scripts": { + "start": "zotero-plugin serve", + "build": "zotero-plugin build && tsc --noEmit", + "lint:check": "prettier --check . && eslint .", + "lint:fix": "prettier --write . && eslint . --fix", + "release": "zotero-plugin release", + "update-deps": "npm update --save" + }, + "dependencies": { + "zotero-plugin-toolkit": "^5.1.0-beta.13" + }, + "devDependencies": { + "@types/node": "^24.10.0", + "@zotero-plugin/eslint-config": "^0.6.7", + "eslint": "^9.39.2", + "prettier": "^3.7.4", + "typescript": "^5.9.3", + "zotero-plugin-scaffold": "^0.8.2", + "zotero-types": "^4.1.0-beta.4" + }, + "prettier": { + "printWidth": 80, + "tabWidth": 2, + "endOfLine": "lf", + "overrides": [ + { + "files": ["*.xhtml"], + "options": { + "htmlWhitespaceSensitivity": "css" + } + } + ] + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..5dede0f --- /dev/null +++ b/readme.md @@ -0,0 +1,41 @@ +# ChatPapers + +面向 **Zotero 9** 的 AI 论文阅读与总结插件:在 Zotero 内对 PDF 文献对话、摘要,并把结果保存为笔记。 + +## 功能(MVP) + +- PDF / 条目侧栏 **ChatPapers** 对话面板 +- 一键总结、添加选区、流式回复、停止生成 +- 保存为 Zotero 子笔记 +- Provider 预设:**Ollama / LM Studio / OpenRouter / OpenAI / DeepSeek / 硅基流动 / 自定义** + +## 文档 + +| 文档 | 说明 | +| --- | --- | +| [需求文档](docs/requirements.md) | 产品范围与验收 | +| [开发文档](docs/development.md) | 技术架构与开发说明 | + +## 开发 + +```bash +npm install +npm run build # 产出 .scaffold/build/*.xpi +npm run start # 需本机已安装 Zotero 9,并配置 .env(见 .env.example) +``` + +## 安装 + +1. `npm run build` +2. 打开 Zotero → 工具 → 插件 → 从文件安装 +3. 选择 `.scaffold/build/chatpapers.xpi`(文件名以构建产物为准) + +## 使用 + +1. 偏好设置 → ChatPapers:选择 Provider(如 Ollama)并填写模型 +2. 打开 PDF 或选中条目,在条目面板侧栏进入 **ChatPapers** +3. 提问 / 总结 → 可将回复「保存为笔记」 + +## 许可 + +AGPL-3.0-or-later(基于 [zotero-plugin-template](https://github.com/windingwind/zotero-plugin-template)) diff --git a/src/addon.ts b/src/addon.ts new file mode 100644 index 0000000..d7cf581 --- /dev/null +++ b/src/addon.ts @@ -0,0 +1,35 @@ +import { config } from "../package.json"; +import hooks from "./hooks"; +import { createZToolkit } from "./utils/ztoolkit"; + +class Addon { + public data: { + alive: boolean; + config: typeof config; + env: "development" | "production"; + initialized?: boolean; + ztoolkit: ZToolkit; + locale?: { + current: any; + }; + prefs?: { + window: Window; + }; + }; + public hooks: typeof hooks; + public api: object; + + constructor() { + this.data = { + alive: true, + config, + env: __env__, + initialized: false, + ztoolkit: createZToolkit(), + }; + this.hooks = hooks; + this.api = {}; + } +} + +export default Addon; diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..c63e7a4 --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,58 @@ +import { initLocale } from "./utils/locale"; +import { createZToolkit } from "./utils/ztoolkit"; +import { registerChatPane, registerPrefs } from "./modules/ui/readerPane"; +import { registerPrefsScripts } from "./modules/ui/prefs"; + +async function onStartup() { + await Promise.all([ + Zotero.initializationPromise, + Zotero.unlockPromise, + Zotero.uiReadyPromise, + ]); + + initLocale(); + registerPrefs(); + registerChatPane(); + + await Promise.all( + Zotero.getMainWindows().map((win) => onMainWindowLoad(win)), + ); + + addon.data.initialized = true; +} + +async function onMainWindowLoad(win: _ZoteroTypes.MainWindow): Promise { + addon.data.ztoolkit = createZToolkit(); + win.MozXULElement.insertFTLIfNeeded( + `${addon.data.config.addonRef}-mainWindow.ftl`, + ); +} + +async function onMainWindowUnload(_win: Window): Promise { + ztoolkit.unregisterAll(); +} + +function onShutdown(): void { + ztoolkit.unregisterAll(); + addon.data.alive = false; + // @ts-expect-error - Plugin instance is not typed + delete Zotero[addon.data.config.addonInstance]; +} + +async function onPrefsEvent(type: string, data: { [key: string]: any }) { + switch (type) { + case "load": + registerPrefsScripts(data.window); + break; + default: + return; + } +} + +export default { + onStartup, + onShutdown, + onMainWindowLoad, + onMainWindowUnload, + onPrefsEvent, +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..756b450 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,25 @@ +import { BasicTool } from "zotero-plugin-toolkit"; +import Addon from "./addon"; +import { config } from "../package.json"; + +const basicTool = new BasicTool(); + +// @ts-expect-error - Plugin instance is not typed +if (!basicTool.getGlobal("Zotero")[config.addonInstance]) { + _globalThis.addon = new Addon(); + defineGlobal("ztoolkit", () => { + return _globalThis.addon.data.ztoolkit; + }); + // @ts-expect-error - Plugin instance is not typed + Zotero[config.addonInstance] = addon; +} + +function defineGlobal(name: Parameters[0]): void; +function defineGlobal(name: string, getter: () => any): void; +function defineGlobal(name: string, getter?: () => any) { + Object.defineProperty(_globalThis, name, { + get() { + return getter ? getter() : basicTool.getGlobal(name); + }, + }); +} diff --git a/src/modules/llm/client.ts b/src/modules/llm/client.ts new file mode 100644 index 0000000..66941b9 --- /dev/null +++ b/src/modules/llm/client.ts @@ -0,0 +1,238 @@ +import { getPref } from "../../utils/prefs"; +import type { AbortSignalLike } from "../../utils/abort"; +import { getProviderPreset, isLocalProvider } from "./providers"; +import type { ChatMessage, LlmRuntimeConfig } from "./types"; +import { LlmError } from "./types"; +import { joinUrl, normalizeBaseUrl } from "./url"; + +export function getRuntimeConfig(): LlmRuntimeConfig { + const provider = (getPref("provider") || "") as LlmRuntimeConfig["provider"]; + const preset = provider ? getProviderPreset(provider) : undefined; + return { + provider, + baseUrl: getPref("apiBaseUrl") || preset?.baseUrl || "", + apiKey: getPref("apiKey") || "", + model: getPref("model") || preset?.defaultModel || "", + temperature: Number(getPref("temperature") ?? 0.3), + maxTokens: Number(getPref("maxTokens") ?? 2048), + timeoutMs: Number(getPref("timeoutMs") ?? 120000), + openrouterReferer: getPref("openrouterReferer") || "", + openrouterTitle: getPref("openrouterTitle") || "ChatPapers", + }; +} + +export function assertConfig(cfg: LlmRuntimeConfig): void { + if (!cfg.baseUrl) { + throw new LlmError( + "config", + "请先在设置中选择 Provider 并配置 Base URL。", + ); + } + if (!cfg.model) { + throw new LlmError("config", "请先配置模型名称(Model)。"); + } + const preset = cfg.provider ? getProviderPreset(cfg.provider) : undefined; + if (preset?.needsKey && !cfg.apiKey) { + throw new LlmError("config", "当前 Provider 需要填写 API Key。"); + } +} + +function resolveFetch(): typeof fetch { + const candidates = [ + (globalThis as any).fetch, + typeof fetch !== "undefined" ? fetch : undefined, + ]; + try { + candidates.push(ztoolkit.getGlobal("fetch" as any)); + } catch { + // ignore + } + try { + const win = Zotero.getMainWindow?.(); + if (win) candidates.push((win as any).fetch?.bind(win)); + } catch { + // ignore + } + for (const fn of candidates) { + if (typeof fn === "function") return fn as typeof fetch; + } + throw new LlmError("network", "当前环境不支持 fetch,无法请求 AI 服务。"); +} + +function abortError(): Error { + const err = new Error("Aborted"); + err.name = "AbortError"; + return err; +} + +function throwIfAborted(signal?: AbortSignalLike) { + if (signal?.aborted) throw abortError(); +} + +function buildHeaders(cfg: LlmRuntimeConfig): Record { + const headers: Record = { + "Content-Type": "application/json", + }; + if (cfg.apiKey) { + headers.Authorization = `Bearer ${cfg.apiKey}`; + } + if (cfg.provider === "openrouter") { + if (cfg.openrouterReferer) { + headers["HTTP-Referer"] = cfg.openrouterReferer; + } + if (cfg.openrouterTitle) { + headers["X-Title"] = cfg.openrouterTitle; + } + } + return headers; +} + +function mapHttpError(status: number, body: string, cfg: LlmRuntimeConfig): LlmError { + if (status === 401 || status === 403) { + return new LlmError("auth", `认证失败 (${status}):请检查 API Key。`, status); + } + if (status === 429) { + return new LlmError("rate_limit", `触发限流 (${status}),请稍后重试。`, status); + } + if (status >= 500 && isLocalProvider(cfg.provider)) { + return new LlmError( + "local_down", + `本地服务异常 (${status})。请确认 Ollama / LM Studio 已启动。`, + status, + ); + } + return new LlmError( + "provider", + `请求失败 (${status}):${body.slice(0, 300) || "无响应内容"}`, + status, + ); +} + +export async function listModels( + signal?: AbortSignalLike, +): Promise { + const cfg = getRuntimeConfig(); + assertConfig({ ...cfg, model: cfg.model || "placeholder" }); + const url = joinUrl(cfg.baseUrl, "/models"); + const doFetch = resolveFetch(); + try { + const res = await doFetch(url, { + method: "GET", + headers: buildHeaders(cfg), + signal: signal as AbortSignal | undefined, + }); + if (!res.ok) { + const text = await res.text(); + throw mapHttpError(res.status, text, cfg); + } + const data = (await res.json()) as { + data?: Array<{ id?: string }>; + }; + return (data.data || []) + .map((m) => m.id || "") + .filter(Boolean) + .sort(); + } catch (e) { + if (e instanceof LlmError) throw e; + if (signal?.aborted) throw abortError(); + if (isLocalProvider(cfg.provider)) { + throw new LlmError( + "local_down", + `无法连接本地服务 (${normalizeBaseUrl(cfg.baseUrl)})。请先启动 Ollama / LM Studio。`, + ); + } + throw new LlmError("network", `网络错误:${String(e)}`); + } +} + +export async function chatStream(options: { + messages: ChatMessage[]; + onDelta: (text: string) => void; + signal?: AbortSignalLike; +}): Promise { + const cfg = getRuntimeConfig(); + assertConfig(cfg); + throwIfAborted(options.signal); + + const url = joinUrl(cfg.baseUrl, "/chat/completions"); + const body = { + model: cfg.model, + messages: options.messages, + stream: true, + temperature: cfg.temperature, + max_tokens: cfg.maxTokens, + }; + + const doFetch = resolveFetch(); + let res: Response; + try { + res = await doFetch(url, { + method: "POST", + headers: buildHeaders(cfg), + body: JSON.stringify(body), + signal: options.signal as AbortSignal | undefined, + }); + } catch (e) { + if (options.signal?.aborted) throw abortError(); + if (isLocalProvider(cfg.provider)) { + throw new LlmError( + "local_down", + `无法连接本地服务 (${normalizeBaseUrl(cfg.baseUrl)})。请先启动 Ollama / LM Studio。`, + ); + } + throw new LlmError("network", `网络错误:${String(e)}`); + } + + throwIfAborted(options.signal); + + if (!res.ok) { + const text = await res.text(); + throw mapHttpError(res.status, text, cfg); + } + + if (!res.body) { + const data = (await res.json()) as { + choices?: Array<{ message?: { content?: string } }>; + }; + const content = data.choices?.[0]?.message?.content || ""; + if (content) options.onDelta(content); + return content; + } + + const reader = res.body.getReader() as ReadableStreamDefaultReader; + const decoder = new TextDecoder(); + let buffer = ""; + let full = ""; + + while (true) { + throwIfAborted(options.signal); + const result = await reader.read(); + const done = result.done; + const value = result.value; + if (done) break; + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split(/\r?\n/); + buffer = lines.pop() || ""; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed.startsWith("data:")) continue; + const payload = trimmed.slice(5).trim(); + if (!payload || payload === "[DONE]") continue; + try { + const json = JSON.parse(payload) as { + choices?: Array<{ delta?: { content?: string } }>; + }; + const delta = json.choices?.[0]?.delta?.content || ""; + if (delta) { + full += delta; + options.onDelta(delta); + } + } catch { + // ignore malformed chunks + } + } + } + + return full; +} diff --git a/src/modules/llm/prompts.ts b/src/modules/llm/prompts.ts new file mode 100644 index 0000000..d65c67f --- /dev/null +++ b/src/modules/llm/prompts.ts @@ -0,0 +1,29 @@ +import { getPref } from "../../utils/prefs"; + +const DEFAULT_SYSTEM = `你是学术论文阅读助手 ChatPapers。请基于用户提供的论文文本回答问题。 +要求: +1. 回答准确、简洁,优先使用论文原文依据。 +2. 若信息不足,请明确说明,不要编造。 +3. 尽量标注页码或引用短片段(若上下文含页分隔)。 +4. 使用用户偏好的语言回答。`; + +const DEFAULT_SUMMARY = `请对以下论文做结构化总结,使用 Markdown,包含: +## 一句话概括 +## 研究问题 / 动机 +## 方法 +## 主要结果 +## 贡献与创新 +## 局限与可追问点 +## 关键术语(可选)`; + +export function getSystemPrompt(): string { + const custom = (getPref("systemPrompt") || "").trim(); + const lang = getPref("answerLanguage") || "zh-CN"; + const base = custom || DEFAULT_SYSTEM; + return `${base}\n\n回答语言偏好:${lang}`; +} + +export function getSummaryPrompt(): string { + const custom = (getPref("summaryPrompt") || "").trim(); + return custom || DEFAULT_SUMMARY; +} diff --git a/src/modules/llm/providers.ts b/src/modules/llm/providers.ts new file mode 100644 index 0000000..e085710 --- /dev/null +++ b/src/modules/llm/providers.ts @@ -0,0 +1,71 @@ +import type { ProviderId, ProviderPreset } from "./types"; + +export const PROVIDER_PRESETS: Record = { + ollama: { + id: "ollama", + label: "Ollama", + baseUrl: "http://127.0.0.1:11434/v1", + needsKey: false, + defaultModel: "qwen2.5", + }, + lmstudio: { + id: "lmstudio", + label: "LM Studio", + baseUrl: "http://127.0.0.1:1234/v1", + needsKey: false, + defaultModel: "local-model", + }, + openrouter: { + id: "openrouter", + label: "OpenRouter", + baseUrl: "https://openrouter.ai/api/v1", + needsKey: true, + defaultModel: "openai/gpt-4o-mini", + }, + openai: { + id: "openai", + label: "OpenAI", + baseUrl: "https://api.openai.com/v1", + needsKey: true, + defaultModel: "gpt-4o-mini", + }, + deepseek: { + id: "deepseek", + label: "DeepSeek", + baseUrl: "https://api.deepseek.com/v1", + needsKey: true, + defaultModel: "deepseek-chat", + }, + siliconflow: { + id: "siliconflow", + label: "SiliconFlow", + baseUrl: "https://api.siliconflow.cn/v1", + needsKey: true, + defaultModel: "deepseek-ai/DeepSeek-V3", + }, + custom: { + id: "custom", + label: "Custom", + baseUrl: "", + needsKey: true, + defaultModel: "", + }, +}; + +export const PROVIDER_ORDER: ProviderId[] = [ + "ollama", + "lmstudio", + "openrouter", + "openai", + "deepseek", + "siliconflow", + "custom", +]; + +export function getProviderPreset(id: string): ProviderPreset | undefined { + return PROVIDER_PRESETS[id as ProviderId]; +} + +export function isLocalProvider(id: string): boolean { + return id === "ollama" || id === "lmstudio"; +} diff --git a/src/modules/llm/types.ts b/src/modules/llm/types.ts new file mode 100644 index 0000000..30cbcbd --- /dev/null +++ b/src/modules/llm/types.ts @@ -0,0 +1,54 @@ +export type ProviderId = + | "ollama" + | "lmstudio" + | "openrouter" + | "openai" + | "deepseek" + | "siliconflow" + | "custom"; + +export interface ProviderPreset { + id: ProviderId; + label: string; + baseUrl: string; + needsKey: boolean; + defaultModel: string; + extraHeaders?: Record; +} + +export interface ChatMessage { + role: "system" | "user" | "assistant"; + content: string; +} + +export interface LlmRuntimeConfig { + provider: ProviderId | ""; + baseUrl: string; + apiKey: string; + model: string; + temperature: number; + maxTokens: number; + timeoutMs: number; + openrouterReferer: string; + openrouterTitle: string; +} + +export type LlmErrorCode = + | "auth" + | "rate_limit" + | "network" + | "local_down" + | "provider" + | "config"; + +export class LlmError extends Error { + code: LlmErrorCode; + status?: number; + + constructor(code: LlmErrorCode, message: string, status?: number) { + super(message); + this.name = "LlmError"; + this.code = code; + this.status = status; + } +} diff --git a/src/modules/llm/url.ts b/src/modules/llm/url.ts new file mode 100644 index 0000000..65fcb3b --- /dev/null +++ b/src/modules/llm/url.ts @@ -0,0 +1,22 @@ +/** + * Normalize OpenAI-compatible base URL. + * Accepts both `http://host:port` and `http://host:port/v1`. + */ +export function normalizeBaseUrl(input: string): string { + let url = (input || "").trim().replace(/\/+$/, ""); + if (!url) return ""; + if (!/\/v\d+$/i.test(url)) { + url = `${url}/v1`; + } + return url; +} + +export function joinUrl(baseUrl: string, path: string): string { + const base = normalizeBaseUrl(baseUrl).replace(/\/+$/, ""); + const p = path.startsWith("/") ? path : `/${path}`; + // Avoid /v1/v1/... + if (base.endsWith("/v1") && p.startsWith("/v1/")) { + return `${base}${p.slice(3)}`; + } + return `${base}${p}`; +} diff --git a/src/modules/pdf/context.ts b/src/modules/pdf/context.ts new file mode 100644 index 0000000..bba7086 --- /dev/null +++ b/src/modules/pdf/context.ts @@ -0,0 +1,77 @@ +import type { ChatMessage } from "../llm/types"; +import { getSummaryPrompt, getSystemPrompt } from "../llm/prompts"; +import { + buildMetadataBlock, + extractPdfContext, + type ExtractResult, +} from "./extractor"; + +export interface BuiltContext { + messages: ChatMessage[]; + extract: ExtractResult; + warning?: string; +} + +export async function buildChatMessages(options: { + item: Zotero.Item; + history: ChatMessage[]; + userText: string; + selection?: string; + mode?: "chat" | "summary"; +}): Promise { + const extract = await extractPdfContext(options.item); + const warnings: string[] = []; + + if (!extract.text) { + warnings.push( + "未能提取 PDF 文本(可能是扫描版或尚无文本层)。请检查附件后重试。", + ); + } else if (extract.truncated) { + warnings.push("论文较长,已按设置截断上下文。"); + } + + const meta = extract.parentItem + ? buildMetadataBlock(extract.parentItem) + : ""; + + const systemParts = [getSystemPrompt()]; + if (meta) { + systemParts.push(`论文元数据:\n${meta}`); + } + if (extract.text) { + systemParts.push( + `论文正文(页之间可能以换页符分隔):\n${extract.text}`, + ); + } + + const messages: ChatMessage[] = [ + { role: "system", content: systemParts.join("\n\n") }, + ]; + + // Keep recent history (exclude old system) + const history = options.history + .filter((m) => m.role !== "system") + .slice(-20); + messages.push(...history); + + if (options.mode === "summary") { + const summaryAsk = getSummaryPrompt(); + messages.push({ + role: "user", + content: summaryAsk, + }); + } else { + let content = options.userText.trim(); + if (options.selection?.trim()) { + content = + `【选中原文】\n${options.selection.trim()}\n\n【问题】\n${content}`; + } + messages.push({ role: "user", content }); + } + + return { + messages, + extract, + warning: warnings.length ? warnings.join(" ") : undefined, + }; +} diff --git a/src/modules/pdf/extractor.ts b/src/modules/pdf/extractor.ts new file mode 100644 index 0000000..9ab77f5 --- /dev/null +++ b/src/modules/pdf/extractor.ts @@ -0,0 +1,136 @@ +import { getPref } from "../../utils/prefs"; + +export interface ExtractResult { + text: string; + truncated: boolean; + pageCount?: number; + source: "pdfworker" | "fulltext" | "empty"; + attachment?: Zotero.Item; + parentItem?: Zotero.Item; +} + +function findPdfAttachment(item: Zotero.Item): Zotero.Item | undefined { + if (item.isAttachment() && item.attachmentContentType === "application/pdf") { + return item; + } + if (item.isRegularItem()) { + const attachments = item.getAttachments().map((id) => Zotero.Items.get(id)); + return attachments.find( + (att) => att?.isAttachment() && att.attachmentContentType === "application/pdf", + ); + } + // Child attachment of a parent + if (item.isAttachment() && item.parentItemID) { + const parent = Zotero.Items.get(item.parentItemID); + return findPdfAttachment(parent); + } + return undefined; +} + +function getParentItem(item: Zotero.Item): Zotero.Item { + if (item.isRegularItem()) return item; + if (item.parentItemID) { + return Zotero.Items.get(item.parentItemID) || item; + } + return item; +} + +async function extractViaPdfWorker(attachment: Zotero.Item): Promise { + const worker = (Zotero as any).PDFWorker; + if (!worker?.getFullText) { + throw new Error("PDFWorker unavailable"); + } + const result = await worker.getFullText(attachment.id, null, true); + return (result?.text || "").trim(); +} + +async function extractViaFulltext(attachment: Zotero.Item): Promise { + try { + const text = (attachment as any).attachmentText; + if (typeof text === "string" && text.trim()) return text.trim(); + } catch { + // ignore + } + return ""; +} + +export function truncateText( + text: string, + maxChars: number, +): { text: string; truncated: boolean } { + if (text.length <= maxChars) { + return { text, truncated: false }; + } + const head = Math.floor(maxChars * 0.7); + const tail = maxChars - head - 80; + const sliced = + text.slice(0, head) + + "\n\n[... 中间内容已截断 ...]\n\n" + + text.slice(-Math.max(tail, 0)); + return { text: sliced, truncated: true }; +} + +export async function extractPdfContext(item: Zotero.Item): Promise { + const attachment = findPdfAttachment(item); + const parentItem = getParentItem(item); + if (!attachment) { + return { + text: "", + truncated: false, + source: "empty", + parentItem, + }; + } + + let raw = ""; + let source: ExtractResult["source"] = "empty"; + try { + raw = await extractViaPdfWorker(attachment); + if (raw) source = "pdfworker"; + } catch (e) { + ztoolkit.log("PDFWorker extract failed", e); + } + + if (!raw) { + try { + raw = await extractViaFulltext(attachment); + if (raw) source = "fulltext"; + } catch (e) { + ztoolkit.log("Fulltext extract failed", e); + } + } + + const maxChars = Number(getPref("maxContextChars") ?? 80000); + const { text, truncated } = truncateText(raw, maxChars); + const pageCount = raw ? raw.split("\f").length : undefined; + + return { + text, + truncated, + pageCount, + source, + attachment, + parentItem, + }; +} + +export function buildMetadataBlock(item: Zotero.Item): string { + if (!getPref("sendMetadata")) return ""; + const title = item.getField("title") || ""; + const creators = item.getCreators() + .map((c) => [c.firstName, c.lastName].filter(Boolean).join(" ")) + .filter(Boolean) + .join(", "); + const year = (item.getField("date") || "").slice(0, 4); + const doi = item.getField("DOI") || ""; + const abstract = item.getField("abstractNote") || ""; + return [ + `标题:${title}`, + creators ? `作者:${creators}` : "", + year ? `年份:${year}` : "", + doi ? `DOI:${doi}` : "", + abstract ? `摘要:${abstract}` : "", + ] + .filter(Boolean) + .join("\n"); +} diff --git a/src/modules/storage/sessions.ts b/src/modules/storage/sessions.ts new file mode 100644 index 0000000..345383a --- /dev/null +++ b/src/modules/storage/sessions.ts @@ -0,0 +1,71 @@ +import type { ChatMessage } from "../llm/types"; + +function sessionDir(): string { + const profile = (Zotero as any).Profile?.dir || (Zotero as any).getProfileDirectory?.()?.path; + if (!profile) { + throw new Error("Cannot resolve Zotero profile directory"); + } + return PathUtils.join(profile, "chatpapers", "sessions"); +} + +function sessionPath(itemKey: string, attachmentKey: string): string { + const safe = `${itemKey}-${attachmentKey || "none"}`.replace( + /[^a-zA-Z0-9_-]/g, + "_", + ); + return PathUtils.join(sessionDir(), `${safe}.json`); +} + +export async function loadSession( + itemKey: string, + attachmentKey: string, +): Promise { + try { + const path = sessionPath(itemKey, attachmentKey); + const exists = await IOUtils.exists(path); + if (!exists) return []; + const raw = await IOUtils.readUTF8(path); + const data = JSON.parse(raw) as { messages?: ChatMessage[] }; + return (data.messages || []).filter((m) => m.role !== "system"); + } catch (e) { + ztoolkit.log("loadSession failed", e); + return []; + } +} + +export async function saveSession( + itemKey: string, + attachmentKey: string, + messages: ChatMessage[], +): Promise { + try { + const dir = sessionDir(); + await IOUtils.makeDirectory(dir, { createAncestors: true, ignoreExisting: true }); + const path = sessionPath(itemKey, attachmentKey); + const payload = JSON.stringify( + { + updatedAt: Date.now(), + messages: messages.filter((m) => m.role !== "system").slice(-50), + }, + null, + 2, + ); + await IOUtils.writeUTF8(path, payload); + } catch (e) { + ztoolkit.log("saveSession failed", e); + } +} + +export async function clearSession( + itemKey: string, + attachmentKey: string, +): Promise { + try { + const path = sessionPath(itemKey, attachmentKey); + if (await IOUtils.exists(path)) { + await IOUtils.remove(path); + } + } catch (e) { + ztoolkit.log("clearSession failed", e); + } +} diff --git a/src/modules/ui/chatView.ts b/src/modules/ui/chatView.ts new file mode 100644 index 0000000..447b893 --- /dev/null +++ b/src/modules/ui/chatView.ts @@ -0,0 +1,312 @@ +import { chatStream, getRuntimeConfig } from "../llm/client"; +import { LlmError, type ChatMessage } from "../llm/types"; +import { getProviderPreset } from "../llm/providers"; +import { buildChatMessages } from "../pdf/context"; +import { clearSession, loadSession, saveSession } from "../storage/sessions"; +import { createChildNote } from "../zotero/notes"; +import { getString } from "../../utils/locale"; +import { + createAbortHandle, + isAbortError, + type AbortHandle, +} from "../../utils/abort"; + +export class ChatView { + private body: HTMLElement; + private doc: Document; + private item: Zotero.Item; + private messages: ChatMessage[] = []; + private abort?: AbortHandle; + private selectionText = ""; + + private messagesEl!: HTMLElement; + private inputEl!: HTMLTextAreaElement; + private statusEl!: HTMLElement; + private sendBtn!: HTMLButtonElement; + private stopBtn!: HTMLButtonElement; + + constructor(doc: Document, body: HTMLElement, item: Zotero.Item) { + this.doc = doc; + this.body = body; + this.item = item; + } + + async mount(): Promise { + this.body.replaceChildren(); + this.body.classList.add("chatpapers-root"); + + const cfg = getRuntimeConfig(); + const preset = cfg.provider ? getProviderPreset(cfg.provider) : undefined; + const providerLabel = preset?.label || cfg.provider || "未配置"; + + const header = this.el("div", "chatpapers-header"); + header.append( + this.el("div", "chatpapers-title", this.item.getField("title") || "ChatPapers"), + this.el("div", "chatpapers-meta", `${providerLabel} · ${cfg.model || "无模型"}`), + ); + + this.messagesEl = this.el("div", "chatpapers-messages"); + this.statusEl = this.el("div", "chatpapers-status"); + + const toolbar = this.el("div", "chatpapers-toolbar"); + const summarizeBtn = this.btn(getString("chat-summarize"), () => + this.runSummary(), + ); + const addSelBtn = this.btn(getString("chat-add-selection"), () => + this.captureSelection(), + ); + const clearBtn = this.btn(getString("chat-clear"), () => this.clearChat()); + const saveBtn = this.btn(getString("chat-save-note"), () => this.saveLastNote()); + toolbar.append(summarizeBtn, addSelBtn, clearBtn, saveBtn); + + this.inputEl = this.doc.createElement("textarea"); + this.inputEl.className = "chatpapers-input"; + this.inputEl.rows = 3; + this.inputEl.placeholder = getString("chat-placeholder"); + + const actions = this.el("div", "chatpapers-actions"); + this.sendBtn = this.btn(getString("chat-send"), () => this.send()); + this.sendBtn.classList.add("chatpapers-primary"); + this.stopBtn = this.btn(getString("chat-stop"), () => this.stop()); + this.stopBtn.disabled = true; + actions.append(this.sendBtn, this.stopBtn); + + this.inputEl.addEventListener("keydown", (ev) => { + const kev = ev as KeyboardEvent; + if (kev.key === "Enter" && !kev.shiftKey) { + kev.preventDefault(); + void this.send(); + } + }); + + this.body.append(header, this.messagesEl, this.statusEl, toolbar, this.inputEl, actions); + + const keys = this.sessionKeys(); + this.messages = await loadSession(keys.itemKey, keys.attachmentKey); + this.renderMessages(); + if (!cfg.provider || !cfg.baseUrl) { + this.setStatus(getString("chat-need-config"), "warn"); + } + } + + destroy(): void { + this.stop(); + this.body.replaceChildren(); + } + + private sessionKeys(): { itemKey: string; attachmentKey: string } { + const parent = + this.item.isRegularItem() + ? this.item + : this.item.parentItemID + ? Zotero.Items.get(this.item.parentItemID) + : this.item; + const attachment = this.item.isAttachment() + ? this.item + : undefined; + return { + itemKey: parent.key, + attachmentKey: attachment?.key || "", + }; + } + + private el(tag: string, className?: string, text?: string): HTMLElement { + const node = this.doc.createElement(tag); + if (className) node.className = className; + if (text) node.textContent = text; + return node; + } + + private btn(label: string, onClick: () => void): HTMLButtonElement { + const b = this.doc.createElement("button"); + b.type = "button"; + b.className = "chatpapers-btn"; + b.textContent = label; + b.addEventListener("click", onClick); + return b; + } + + private setStatus(text: string, kind: "info" | "warn" | "error" | "" = "info") { + this.statusEl.textContent = text; + this.statusEl.dataset.kind = kind; + } + + private renderMessages() { + this.messagesEl.replaceChildren(); + for (const msg of this.messages) { + if (msg.role === "system") continue; + const bubble = this.el( + "div", + `chatpapers-bubble chatpapers-${msg.role}`, + ); + const role = this.el( + "div", + "chatpapers-role", + msg.role === "user" ? "You" : "AI", + ); + const content = this.el("div", "chatpapers-content", msg.content); + bubble.append(role, content); + this.messagesEl.append(bubble); + } + this.messagesEl.scrollTop = this.messagesEl.scrollHeight; + } + + private appendAssistantPlaceholder(): HTMLElement { + const bubble = this.el("div", "chatpapers-bubble chatpapers-assistant"); + bubble.append( + this.el("div", "chatpapers-role", "AI"), + this.el("div", "chatpapers-content", ""), + ); + this.messagesEl.append(bubble); + this.messagesEl.scrollTop = this.messagesEl.scrollHeight; + return bubble.querySelector(".chatpapers-content") as HTMLElement; + } + + private captureSelection() { + try { + const reader = Zotero.Reader.getByTabID?.(Zotero_Tabs.selectedID); + // @ts-expect-error internal reader APIs vary + const win = reader?._iframeWindow || reader?._internalReader?._primaryView?._iframe?.contentWindow; + const sel = win?.getSelection?.()?.toString?.() || ""; + if (!sel.trim()) { + // try main window selection as fallback + const mainSel = this.doc.defaultView?.getSelection?.()?.toString?.() || ""; + this.selectionText = mainSel.trim(); + } else { + this.selectionText = sel.trim(); + } + if (this.selectionText) { + this.setStatus( + `${getString("chat-selection-added")} (${this.selectionText.length} chars)`, + "info", + ); + } else { + this.setStatus(getString("chat-selection-empty"), "warn"); + } + } catch (e) { + ztoolkit.log(e); + this.setStatus(getString("chat-selection-empty"), "warn"); + } + } + + private async persist() { + const keys = this.sessionKeys(); + await saveSession(keys.itemKey, keys.attachmentKey, this.messages); + } + + private setBusy(busy: boolean) { + this.sendBtn.disabled = busy; + this.stopBtn.disabled = !busy; + this.inputEl.disabled = busy; + } + + private stop() { + this.abort?.abort(); + this.abort = undefined; + this.setBusy(false); + } + + private async send() { + const text = this.inputEl.value.trim(); + if (!text) return; + this.inputEl.value = ""; + await this.runChat(text, "chat"); + } + + private async runSummary() { + await this.runChat(getString("chat-summarize"), "summary"); + } + + private async runChat(userText: string, mode: "chat" | "summary") { + this.stop(); + this.abort = createAbortHandle(); + this.setBusy(true); + this.setStatus(getString("chat-thinking"), "info"); + + try { + const built = await buildChatMessages({ + item: this.item, + history: this.messages, + userText, + selection: this.selectionText, + mode, + }); + this.selectionText = ""; + + if (built.warning) { + this.setStatus(built.warning, "warn"); + } + + if (mode === "chat") { + this.messages.push({ role: "user", content: userText }); + } else { + this.messages.push({ role: "user", content: userText }); + } + this.renderMessages(); + + const contentEl = this.appendAssistantPlaceholder(); + let full = ""; + await chatStream({ + messages: built.messages, + signal: this.abort.signal, + onDelta: (delta) => { + full += delta; + contentEl.textContent = full; + this.messagesEl.scrollTop = this.messagesEl.scrollHeight; + }, + }); + + this.messages.push({ role: "assistant", content: full || "(empty)" }); + await this.persist(); + this.setStatus(getString("chat-done"), "info"); + } catch (e) { + if (isAbortError(e)) { + this.setStatus(getString("chat-stopped"), "warn"); + } else if (e instanceof LlmError) { + this.setStatus(e.message, "error"); + } else { + this.setStatus(String(e), "error"); + } + } finally { + this.setBusy(false); + this.abort = undefined; + this.renderMessages(); + } + } + + private async clearChat() { + this.messages = []; + const keys = this.sessionKeys(); + await clearSession(keys.itemKey, keys.attachmentKey); + this.renderMessages(); + this.setStatus(getString("chat-cleared"), "info"); + } + + private async saveLastNote() { + const last = [...this.messages].reverse().find((m) => m.role === "assistant"); + if (!last) { + this.setStatus(getString("chat-no-reply"), "warn"); + return; + } + const parent = + this.item.isRegularItem() + ? this.item + : this.item.parentItemID + ? Zotero.Items.get(this.item.parentItemID) + : this.item; + const title = `ChatPapers · ${new Date().toLocaleString()}`; + try { + await createChildNote({ + parentItem: parent, + title, + bodyMarkdown: last.content, + }); + this.setStatus(getString("chat-note-saved"), "info"); + new ztoolkit.ProgressWindow("ChatPapers") + .createLine({ text: getString("chat-note-saved"), type: "success" }) + .show(); + } catch (e) { + this.setStatus(String(e), "error"); + } + } +} diff --git a/src/modules/ui/prefs.ts b/src/modules/ui/prefs.ts new file mode 100644 index 0000000..94daea7 --- /dev/null +++ b/src/modules/ui/prefs.ts @@ -0,0 +1,102 @@ +import { getPref, setPref } from "../../utils/prefs"; +import { listModels } from "../llm/client"; +import { LlmError } from "../llm/types"; +import { + PROVIDER_ORDER, + PROVIDER_PRESETS, + getProviderPreset, + isLocalProvider, +} from "../llm/providers"; +import { getString } from "../../utils/locale"; + +export function registerPrefsScripts(win: Window) { + const doc = win.document; + const providerSelect = doc.getElementById( + "chatpapers-pref-provider", + ) as HTMLSelectElement | null; + const baseUrlInput = doc.getElementById( + "chatpapers-pref-apiBaseUrl", + ) as HTMLInputElement | null; + const modelInput = doc.getElementById( + "chatpapers-pref-model", + ) as HTMLInputElement | null; + const resetBtn = doc.getElementById("chatpapers-pref-reset"); + const refreshBtn = doc.getElementById("chatpapers-pref-refresh-models"); + const hint = doc.getElementById("chatpapers-pref-hint"); + + if (providerSelect && providerSelect.options.length === 0) { + for (const id of PROVIDER_ORDER) { + const opt = doc.createElement("option"); + opt.value = id; + opt.textContent = PROVIDER_PRESETS[id].label; + providerSelect.appendChild(opt); + } + const current = getPref("provider") || ""; + if (current) providerSelect.value = current; + } + + const updateHint = () => { + if (!hint) return; + const id = (providerSelect?.value || getPref("provider") || "") as string; + if (isLocalProvider(id)) { + hint.textContent = getString("prefs-hint-local"); + } else if (id === "openrouter") { + hint.textContent = getString("prefs-hint-openrouter"); + } else if (id) { + hint.textContent = getString("prefs-hint-cloud"); + } else { + hint.textContent = getString("prefs-hint-empty"); + } + }; + + providerSelect?.addEventListener("change", () => { + const id = providerSelect.value; + setPref("provider", id); + const preset = getProviderPreset(id); + if (preset) { + // Always apply preset defaults on switch; user can edit after + setPref("apiBaseUrl", preset.baseUrl); + setPref("model", preset.defaultModel); + if (baseUrlInput) baseUrlInput.value = preset.baseUrl; + if (modelInput) modelInput.value = preset.defaultModel; + } + updateHint(); + }); + + resetBtn?.addEventListener("command", () => { + const id = providerSelect?.value || getPref("provider"); + const preset = getProviderPreset(id); + if (!preset) return; + setPref("apiBaseUrl", preset.baseUrl); + setPref("model", preset.defaultModel); + if (baseUrlInput) baseUrlInput.value = preset.baseUrl; + if (modelInput) modelInput.value = preset.defaultModel; + }); + + refreshBtn?.addEventListener("command", async () => { + try { + refreshBtn.setAttribute("disabled", "true"); + const models = await listModels(); + if (!models.length) { + win.alert(getString("prefs-models-empty")); + return; + } + const picked = models[0]; + const choice = win.prompt( + `${getString("prefs-models-pick")}\n\n${models.slice(0, 30).join("\n")}`, + getPref("model") || picked, + ); + if (choice) { + setPref("model", choice); + if (modelInput) modelInput.value = choice; + } + } catch (e) { + const msg = e instanceof LlmError ? e.message : String(e); + win.alert(msg); + } finally { + refreshBtn.removeAttribute("disabled"); + } + }); + + updateHint(); +} diff --git a/src/modules/ui/readerPane.ts b/src/modules/ui/readerPane.ts new file mode 100644 index 0000000..b0f7822 --- /dev/null +++ b/src/modules/ui/readerPane.ts @@ -0,0 +1,76 @@ +import { config } from "../../../package.json"; +import { getLocaleID, getString } from "../../utils/locale"; +import { ChatView } from "./chatView"; + +const views = new WeakMap(); + +function iconURL(file: string) { + return `chrome://${config.addonRef}/content/icons/${file}`; +} + +export function registerChatPane() { + Zotero.ItemPaneManager.registerSection({ + paneID: "chatpapers-chat", + pluginID: config.addonID, + header: { + l10nID: getLocaleID("item-section-chat-head"), + icon: iconURL("chat.svg"), + }, + sidenav: { + l10nID: getLocaleID("item-section-chat-sidenav"), + icon: iconURL("chat.svg"), + }, + onInit: ({ body }) => { + const doc = body.ownerDocument; + if (doc) ensureStyles(doc); + }, + onDestroy: ({ body }) => { + views.get(body)?.destroy(); + views.delete(body); + }, + onItemChange: ({ item, setEnabled, tabType }) => { + // Enable in reader and library item pane when there is an item + setEnabled(Boolean(item)); + void tabType; + return true; + }, + onRender: ({ body }) => { + body.replaceChildren(); + const doc = body.ownerDocument; + if (!doc) return; + const loading = doc.createElement("div"); + loading.className = "chatpapers-loading"; + loading.textContent = getString("chat-loading"); + body.append(loading); + }, + onAsyncRender: async ({ body, item }) => { + if (!item) return; + const doc = body.ownerDocument; + if (!doc) return; + views.get(body)?.destroy(); + const view = new ChatView(doc, body, item); + views.set(body, view); + await view.mount(); + }, + }); +} + +export function registerPrefs() { + Zotero.PreferencePanes.register({ + pluginID: config.addonID, + src: rootURI + "content/preferences.xhtml", + label: getString("prefs-title"), + image: `chrome://${config.addonRef}/content/icons/favicon.png`, + }); +} + +function ensureStyles(doc: Document) { + const id = "chatpapers-styles"; + if (doc.getElementById(id)) return; + const link = doc.createElement("link"); + link.id = id; + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = `chrome://${config.addonRef}/content/chatpapers.css`; + doc.documentElement?.appendChild(link); +} diff --git a/src/modules/zotero/notes.ts b/src/modules/zotero/notes.ts new file mode 100644 index 0000000..d5e3431 --- /dev/null +++ b/src/modules/zotero/notes.ts @@ -0,0 +1,100 @@ +export async function createChildNote(options: { + parentItem: Zotero.Item; + title: string; + bodyMarkdown: string; +}): Promise { + const note = new Zotero.Item("note"); + note.libraryID = options.parentItem.libraryID; + note.parentID = options.parentItem.id; + + const html = markdownToSimpleHtml(options.title, options.bodyMarkdown); + note.setNote(html); + await note.saveTx(); + return note; +} + +function escapeHtml(s: string): string { + return s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +/** Minimal markdown → HTML for Zotero notes */ +export function markdownToSimpleHtml(title: string, md: string): string { + const lines = md.replace(/\r\n/g, "\n").split("\n"); + const parts: string[] = [ + `

${escapeHtml(title)}

`, + `

Generated by ChatPapers · ${new Date().toLocaleString()}

`, + ]; + + let inList = false; + let inCode = false; + const codeBuf: string[] = []; + + const closeList = () => { + if (inList) { + parts.push(""); + inList = false; + } + }; + + for (const line of lines) { + if (line.startsWith("```")) { + if (inCode) { + parts.push(`
${escapeHtml(codeBuf.join("\n"))}
`); + codeBuf.length = 0; + inCode = false; + } else { + closeList(); + inCode = true; + } + continue; + } + if (inCode) { + codeBuf.push(line); + continue; + } + + const heading = /^(#{1,3})\s+(.*)$/.exec(line); + if (heading) { + closeList(); + const level = heading[1].length; + parts.push(`${escapeHtml(heading[2])}`); + continue; + } + + const li = /^[-*]\s+(.*)$/.exec(line); + if (li) { + if (!inList) { + parts.push("
    "); + inList = true; + } + parts.push(`
  • ${inlineFormat(li[1])}
  • `); + continue; + } + + if (!line.trim()) { + closeList(); + continue; + } + + closeList(); + parts.push(`

    ${inlineFormat(line)}

    `); + } + + closeList(); + if (inCode) { + parts.push(`
    ${escapeHtml(codeBuf.join("\n"))}
    `); + } + + return parts.join("\n"); +} + +function inlineFormat(text: string): string { + let s = escapeHtml(text); + s = s.replace(/\*\*(.+?)\*\*/g, "$1"); + s = s.replace(/`([^`]+)`/g, "$1"); + return s; +} diff --git a/src/utils/abort.ts b/src/utils/abort.ts new file mode 100644 index 0000000..cd2a414 --- /dev/null +++ b/src/utils/abort.ts @@ -0,0 +1,102 @@ +/** + * Zotero plugin sandbox may not expose AbortController as a free global. + * Resolve from chrome globals, or fall back to a minimal stub. + */ + +export type AbortHandle = { + signal: AbortSignalLike; + abort: () => void; +}; + +export type AbortSignalLike = { + aborted: boolean; + reason?: unknown; + addEventListener?: ( + type: "abort", + listener: () => void, + options?: { once?: boolean }, + ) => void; + removeEventListener?: (type: "abort", listener: () => void) => void; +}; + +function resolveNativeAbortController(): typeof AbortController | undefined { + const candidates = [ + (globalThis as any).AbortController, + typeof AbortController !== "undefined" ? AbortController : undefined, + ]; + try { + candidates.push(ztoolkit.getGlobal("AbortController" as any)); + } catch { + // ignore + } + try { + const win = Zotero.getMainWindow?.(); + if (win) candidates.push((win as any).AbortController); + } catch { + // ignore + } + for (const Ctor of candidates) { + if (typeof Ctor === "function") return Ctor; + } + return undefined; +} + +class StubAbortSignal implements AbortSignalLike { + aborted = false; + reason: unknown; + private listeners: Array<() => void> = []; + + addEventListener( + type: "abort", + listener: () => void, + _options?: { once?: boolean }, + ) { + if (type === "abort") this.listeners.push(listener); + } + + removeEventListener(type: "abort", listener: () => void) { + if (type !== "abort") return; + this.listeners = this.listeners.filter((l) => l !== listener); + } + + _abort(reason?: unknown) { + if (this.aborted) return; + this.aborted = true; + this.reason = reason; + for (const l of [...this.listeners]) l(); + } +} + +class StubAbortController { + signal = new StubAbortSignal(); + abort(reason?: unknown) { + const err = new Error("Aborted"); + err.name = "AbortError"; + this.signal._abort(reason ?? err); + } +} + +export function createAbortHandle(): AbortHandle { + const Native = resolveNativeAbortController(); + if (Native) { + const ctrl = new Native(); + return { + signal: ctrl.signal as AbortSignalLike, + abort: () => ctrl.abort(), + }; + } + const stub = new StubAbortController(); + return { + signal: stub.signal, + abort: () => stub.abort(), + }; +} + +export function isAbortError(e: unknown): boolean { + if (!e || typeof e !== "object") return false; + const err = e as { name?: string; message?: string }; + return ( + err.name === "AbortError" || + String(err.message || "").toLowerCase().includes("abort") + ); +} diff --git a/src/utils/locale.ts b/src/utils/locale.ts new file mode 100644 index 0000000..f0c5200 --- /dev/null +++ b/src/utils/locale.ts @@ -0,0 +1,96 @@ +import { config } from "../../package.json"; +import { FluentMessageId } from "../../typings/i10n"; + +export { initLocale, getString, getLocaleID }; + +/** + * Initialize locale data + */ +function initLocale() { + const l10n = new ( + typeof Localization === "undefined" + ? ztoolkit.getGlobal("Localization") + : Localization + )([`${config.addonRef}-addon.ftl`], true); + addon.data.locale = { + current: l10n, + }; +} + +/** + * Get locale string, see https://firefox-source-docs.mozilla.org/l10n/fluent/tutorial.html#fluent-translation-list-ftl + * @param localString ftl key + * @param options.branch branch name + * @param options.args args + * @example + * ```ftl + * # addon.ftl + * addon-static-example = This is default branch! + * .branch-example = This is a branch under addon-static-example! + * addon-dynamic-example = + { $count -> + [one] I have { $count } apple + *[other] I have { $count } apples + } + * ``` + * ```js + * getString("addon-static-example"); // This is default branch! + * getString("addon-static-example", { branch: "branch-example" }); // This is a branch under addon-static-example! + * getString("addon-dynamic-example", { args: { count: 1 } }); // I have 1 apple + * getString("addon-dynamic-example", { args: { count: 2 } }); // I have 2 apples + * ``` + */ +function getString(localString: FluentMessageId): string; +function getString(localString: FluentMessageId, branch: string): string; +function getString( + localeString: FluentMessageId, + options: { branch?: string | undefined; args?: Record }, +): string; +function getString(...inputs: any[]) { + if (inputs.length === 1) { + return _getString(inputs[0]); + } else if (inputs.length === 2) { + if (typeof inputs[1] === "string") { + return _getString(inputs[0], { branch: inputs[1] }); + } else { + return _getString(inputs[0], inputs[1]); + } + } else { + throw new Error("Invalid arguments"); + } +} + +interface Pattern { + value: string | null; + attributes: Array<{ + name: string; + value: string; + }> | null; +} + +function _getString( + localeString: FluentMessageId, + options: { branch?: string | undefined; args?: Record } = {}, +): string { + const localStringWithPrefix = `${config.addonRef}-${localeString}`; + const { branch, args } = options; + const pattern = addon.data.locale?.current.formatMessagesSync([ + { id: localStringWithPrefix, args }, + ])[0] as Pattern; + + if (!pattern) { + return localStringWithPrefix; + } + if (branch && pattern.attributes) { + return ( + pattern.attributes.find((attr) => attr.name === branch)?.value || + localStringWithPrefix + ); + } else { + return pattern.value || localStringWithPrefix; + } +} + +function getLocaleID(id: FluentMessageId) { + return `${config.addonRef}-${id}`; +} diff --git a/src/utils/prefs.ts b/src/utils/prefs.ts new file mode 100644 index 0000000..e661baa --- /dev/null +++ b/src/utils/prefs.ts @@ -0,0 +1,36 @@ +import { config } from "../../package.json"; + +type PluginPrefsMap = _ZoteroTypes.Prefs["PluginPrefsMap"]; + +const PREFS_PREFIX = config.prefsPrefix; + +/** + * Get preference value. + * Wrapper of `Zotero.Prefs.get`. + * @param key + */ +export function getPref(key: K) { + return Zotero.Prefs.get(`${PREFS_PREFIX}.${key}`, true) as PluginPrefsMap[K]; +} + +/** + * Set preference value. + * Wrapper of `Zotero.Prefs.set`. + * @param key + * @param value + */ +export function setPref( + key: K, + value: PluginPrefsMap[K], +) { + return Zotero.Prefs.set(`${PREFS_PREFIX}.${key}`, value, true); +} + +/** + * Clear preference value. + * Wrapper of `Zotero.Prefs.clear`. + * @param key + */ +export function clearPref(key: string) { + return Zotero.Prefs.clear(`${PREFS_PREFIX}.${key}`, true); +} diff --git a/src/utils/window.ts b/src/utils/window.ts new file mode 100644 index 0000000..270631f --- /dev/null +++ b/src/utils/window.ts @@ -0,0 +1,10 @@ +export { isWindowAlive }; + +/** + * Check if the window is alive. + * Useful to prevent opening duplicate windows. + * @param win + */ +function isWindowAlive(win?: Window) { + return win && !Components.utils.isDeadWrapper(win) && !win.closed; +} diff --git a/src/utils/ztoolkit.ts b/src/utils/ztoolkit.ts new file mode 100644 index 0000000..f2acfb3 --- /dev/null +++ b/src/utils/ztoolkit.ts @@ -0,0 +1,48 @@ +import { ZoteroToolkit } from "zotero-plugin-toolkit"; +import { config } from "../../package.json"; + +export { createZToolkit }; + +function createZToolkit() { + const _ztoolkit = new ZoteroToolkit(); + /** + * Alternatively, import toolkit modules you use to minify the plugin size. + * You can add the modules under the `MyToolkit` class below and uncomment the following line. + */ + // const _ztoolkit = new MyToolkit(); + initZToolkit(_ztoolkit); + return _ztoolkit; +} + +function initZToolkit(_ztoolkit: ReturnType) { + const env = __env__; + _ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`; + _ztoolkit.basicOptions.log.disableConsole = env === "production"; + _ztoolkit.UI.basicOptions.ui.enableElementJSONLog = __env__ === "development"; + _ztoolkit.UI.basicOptions.ui.enableElementDOMLog = __env__ === "development"; + // Getting basicOptions.debug will load global modules like the debug bridge. + // since we want to deprecate it, should avoid using it unless necessary. + // _ztoolkit.basicOptions.debug.disableDebugBridgePassword = + // __env__ === "development"; + _ztoolkit.basicOptions.api.pluginID = config.addonID; + _ztoolkit.ProgressWindow.setIconURI( + "default", + `chrome://${config.addonRef}/content/icons/favicon.png`, + ); +} + +import { BasicTool, unregister } from "zotero-plugin-toolkit"; +import { UITool } from "zotero-plugin-toolkit"; + +class MyToolkit extends BasicTool { + UI: UITool; + + constructor() { + super(); + this.UI = new UITool(this); + } + + unregisterAll() { + unregister(this); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cc33c1a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "zotero-types/entries/sandbox/", + "include": ["src", "typings"], + "exclude": ["build", "addon"] +} diff --git a/typings/global.d.ts b/typings/global.d.ts new file mode 100644 index 0000000..0c84c22 --- /dev/null +++ b/typings/global.d.ts @@ -0,0 +1,37 @@ +declare const _globalThis: { + [key: string]: any; + Zotero: _ZoteroTypes.Zotero; + ztoolkit: ZToolkit; + addon: typeof addon; +}; + +declare type ZToolkit = ReturnType< + typeof import("../src/utils/ztoolkit").createZToolkit +>; + +declare const ztoolkit: ZToolkit; + +declare const rootURI: string; + +declare const addon: import("../src/addon").default; + +declare const __env__: "production" | "development"; + +declare const Zotero_Tabs: { + selectedID: string; +}; + +declare const PathUtils: { + join: (...parts: string[]) => string; +}; + +declare const IOUtils: { + exists: (path: string) => Promise; + readUTF8: (path: string) => Promise; + writeUTF8: (path: string, data: string) => Promise; + remove: (path: string) => Promise; + makeDirectory: ( + path: string, + options?: { createAncestors?: boolean; ignoreExisting?: boolean }, + ) => Promise; +}; diff --git a/typings/i10n.d.ts b/typings/i10n.d.ts new file mode 100644 index 0000000..e2ce5d1 --- /dev/null +++ b/typings/i10n.d.ts @@ -0,0 +1,55 @@ +// Generated by zotero-plugin-scaffold +/* prettier-ignore */ +/* eslint-disable */ +// @ts-nocheck +export type FluentMessageId = + | 'chat-add-selection' + | 'chat-clear' + | 'chat-cleared' + | 'chat-done' + | 'chat-loading' + | 'chat-need-config' + | 'chat-no-reply' + | 'chat-note-saved' + | 'chat-placeholder' + | 'chat-save-note' + | 'chat-selection-added' + | 'chat-selection-empty' + | 'chat-send' + | 'chat-stop' + | 'chat-stopped' + | 'chat-summarize' + | 'chat-thinking' + | 'item-section-chat-head' + | 'item-section-chat-sidenav' + | 'itemmenu-label' + | 'prefs-answer-language' + | 'prefs-api-key' + | 'prefs-base-url' + | 'prefs-hint-cloud' + | 'prefs-hint-empty' + | 'prefs-hint-local' + | 'prefs-hint-openrouter' + | 'prefs-max-context' + | 'prefs-max-tokens' + | 'prefs-model' + | 'prefs-models-empty' + | 'prefs-models-pick' + | 'prefs-openrouter-referer' + | 'prefs-openrouter-title' + | 'prefs-privacy' + | 'prefs-provider' + | 'prefs-refresh-models' + | 'prefs-reset' + | 'prefs-section-generation' + | 'prefs-section-openrouter' + | 'prefs-section-prompts' + | 'prefs-section-provider' + | 'prefs-send-metadata' + | 'prefs-summary-prompt' + | 'prefs-system-prompt' + | 'prefs-temperature' + | 'prefs-timeout' + | 'prefs-title' + | 'startup-begin' + | 'startup-finish'; diff --git a/typings/prefs.d.ts b/typings/prefs.d.ts new file mode 100644 index 0000000..1ef5465 --- /dev/null +++ b/typings/prefs.d.ts @@ -0,0 +1,27 @@ +// Generated by zotero-plugin-scaffold +/* prettier-ignore */ +/* eslint-disable */ +// @ts-nocheck + +// prettier-ignore +declare namespace _ZoteroTypes { + interface Prefs { + PluginPrefsMap: { + "provider": string; + "apiBaseUrl": string; + "apiKey": string; + "model": string; + "openrouterReferer": string; + "openrouterTitle": string; + "temperature": string; + "maxTokens": number; + "timeoutMs": number; + "maxContextChars": number; + "answerLanguage": string; + "systemPrompt": string; + "summaryPrompt": string; + "sendMetadata": boolean; + "verboseLog": boolean; + }; + } +} diff --git a/zotero-plugin.config.ts b/zotero-plugin.config.ts new file mode 100644 index 0000000..e6f514b --- /dev/null +++ b/zotero-plugin.config.ts @@ -0,0 +1,48 @@ +import { defineConfig } from "zotero-plugin-scaffold"; +import pkg from "./package.json"; + +export default defineConfig({ + source: ["src", "addon"], + dist: ".scaffold/build", + name: pkg.config.addonName, + id: pkg.config.addonID, + namespace: pkg.config.addonRef, + updateURL: `https://github.com/{{owner}}/{{repo}}/releases/download/release/${ + pkg.version.includes("-") ? "update-beta.json" : "update.json" + }`, + xpiDownloadLink: + "https://github.com/{{owner}}/{{repo}}/releases/download/v{{version}}/{{xpiName}}.xpi", + + build: { + assets: ["addon/**/*.*"], + define: { + ...pkg.config, + author: pkg.author, + description: pkg.description, + homepage: pkg.homepage, + buildVersion: pkg.version, + buildTime: "{{buildTime}}", + }, + prefs: { + prefix: pkg.config.prefsPrefix, + }, + esbuildOptions: [ + { + entryPoints: ["src/index.ts"], + define: { + __env__: `"${process.env.NODE_ENV}"`, + }, + bundle: true, + target: "firefox115", + outfile: `.scaffold/build/addon/content/scripts/${pkg.config.addonRef}.js`, + }, + ], + }, + + test: { + waitForPlugin: `() => Zotero.${pkg.config.addonInstance}.data.initialized`, + }, + + // If you need to see a more detailed log, uncomment the following line: + // logLevel: "trace", +});