├── .editorconfig ├── .esbuild.mjs ├── .githooks └── pre-commit ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── biome.yml │ ├── markdownlint.yml │ ├── obsidian-plugin-release.yml │ ├── pr-title.yml │ └── stale-bot.yml ├── .gitignore ├── .knip.jsonc ├── .markdownlint.yaml ├── .release.mjs ├── Justfile ├── LICENSE ├── README.md ├── biome.jsonc ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── accept-reject-suggestions.ts ├── main.ts ├── obsidian-undocumented-api.d.ts ├── proofread.ts ├── providers │ ├── adapter.d.ts │ ├── model-info.ts │ └── openai.ts ├── settings.ts └── utils.ts ├── styles.css ├── tsconfig.json └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | max_line_length = 100 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 3 10 | tab_width = 3 11 | trim_trailing_whitespace = true 12 | 13 | [*.{yml,yaml,cff}] 14 | indent_style = space 15 | indent_size = 2 16 | tab_width = 2 17 | 18 | [*.py] 19 | indent_style = space 20 | indent_size = 4 21 | tab_width = 4 22 | 23 | [*.md] 24 | indent_size = 4 25 | tab_width = 4 26 | trim_trailing_whitespace = false 27 | -------------------------------------------------------------------------------- /.esbuild.mjs: -------------------------------------------------------------------------------- 1 | import { appendFileSync } from "node:fs"; 2 | import builtins from "builtin-modules"; 3 | import esbuild from "esbuild"; 4 | 5 | const banner = `/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 6 | If you want to view the source, please visit the GitHub repository of this plugin. */`; 7 | 8 | const production = process.argv[2] === "production"; 9 | const analyze = process.argv[2] === "analyze"; 10 | 11 | //────────────────────────────────────────────────────────────────────────────── 12 | 13 | const result = await esbuild 14 | .build({ 15 | entryPoints: ["src/main.ts"], 16 | banner: { js: banner + "\n" }, 17 | outfile: "main.js", 18 | bundle: true, 19 | // biome-ignore format: no need to inspect this regularly 20 | external: ["obsidian", "electron", "@codemirror/autocomplete", "@codemirror/collab", "@codemirror/commands", "@codemirror/language", "@codemirror/lint", "@codemirror/search", "@codemirror/state", "@codemirror/view", "@lezer/common", "@lezer/highlight", "@lezer/lr", ...builtins], 21 | format: "cjs", 22 | target: "es2022", 23 | sourcemap: production || analyze ? false : "inline", 24 | minify: production || analyze, 25 | drop: ["debugger"], 26 | treeShaking: true, 27 | logLevel: analyze ? "silent" : "info", 28 | metafile: analyze, 29 | }) 30 | .catch(() => process.exit(1)); 31 | 32 | //────────────────────────────────────────────────────────────────────────────── 33 | 34 | // DOCS https://esbuild.github.io/api/index#metafile 35 | if (result.metafile) { 36 | const sizes = await esbuild.analyzeMetafile(result.metafile, { verbose: false }); 37 | console.info(sizes); 38 | } 39 | 40 | // FIX prevent Obsidian from removing the source map when using dev build 41 | // https://forum.obsidian.md/t/source-map-trimming-in-dev-builds/87612 42 | if (!production) appendFileSync(import.meta.dirname + "/main.js", "\n/* nosourcemap */"); 43 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | set -o errexit # block commit if there are any issues reported 3 | cd "$(git rev-parse --show-toplevel)" 4 | #─────────────────────────────────────────────────────────────────────────────── 5 | 6 | echo "Pre-Commit Hook" 7 | 8 | echo "(1/4) Biome" 9 | if [[ "$1" == "check-all" ]]; then 10 | npx biome check --write --error-on-warnings --log-kind="compact" 11 | else 12 | # `--staged` so unused things in unstaged files does not block commit 13 | npx biome check --error-on-warnings --staged --no-errors-on-unmatched --log-kind="compact" 14 | fi 15 | 16 | echo "(2/4) TypeScript" 17 | npx tsc --noEmit --skipLibCheck --strict 18 | echo "Done." 19 | 20 | echo "(3/4) Knip" 21 | npx knip 22 | test -t || echo "Done." 23 | 24 | echo "(4/4) Markdownlint" 25 | npx markdownlint --ignore="node_modules" . 26 | echo "Done." 27 | echo 28 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/displaying-a-sponsor-button-in-your-repository 2 | 3 | custom: https://www.paypal.me/ChrisGrieser 4 | ko_fi: pseudometa 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: [bug] 5 | body: 6 | - type: textarea 7 | id: bug-description 8 | attributes: 9 | label: Bug Description 10 | description: A clear and concise description of the bug. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshot 15 | attributes: 16 | label: Relevant Screenshot 17 | description: If applicable, add screenshots or a screen recording to help explain your problem. 18 | - type: textarea 19 | id: reproduction-steps 20 | attributes: 21 | label: To Reproduce 22 | description: Steps to reproduce the problem 23 | placeholder: | 24 | For example: 25 | 1. Go to '...' 26 | 2. Click on '...' 27 | 3. Scroll down to '...' 28 | - type: input 29 | id: obsi-version 30 | attributes: 31 | label: Obsidian Version 32 | description: You can find the version in the *About* tab of the settings. 33 | placeholder: 1.1.2 34 | validations: 35 | required: true 36 | - type: checkboxes 37 | id: checklist 38 | attributes: 39 | label: Checklist 40 | options: 41 | - label: I updated to the latest version of the plugin. 42 | required: true 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea 3 | title: "Feature Request: " 4 | labels: [enhancement] 5 | body: 6 | - type: checkboxes 7 | id: checklist 8 | attributes: 9 | label: Checklist 10 | options: 11 | - label: "I have read the plugin's documentation." 12 | required: true 13 | - label: The feature would be useful to more users than just me. 14 | required: true 15 | - type: textarea 16 | id: feature-requested 17 | attributes: 18 | label: Feature Requested 19 | description: A clear and concise description of the feature. 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: screenshot 24 | attributes: 25 | label: Relevant Screenshot 26 | description: If applicable, add screenshots or a screen recording to help explain the request. 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "chore(dependabot): " 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## What problem does this PR solve? 2 | 3 | ## How does the PR solve it? 4 | 5 | ## Checklist 6 | - [ ] If functionality is added or modified, also made respective changes to the 7 | `README.md` and any affected settings. 8 | -------------------------------------------------------------------------------- /.github/workflows/biome.yml: -------------------------------------------------------------------------------- 1 | name: Biome check 2 | 3 | on: 4 | # not on `push`, since that's covered by pre-commit checks 5 | pull_request: 6 | paths: 7 | - "**.ts" 8 | - "**.css" 9 | - "**.jsonc?" 10 | - "**.m?js" 11 | 12 | jobs: 13 | biome-check: 14 | name: Biome PR check 15 | runs-on: macos-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: biomejs/setup-biome@v2 19 | - run: biome ci 20 | -------------------------------------------------------------------------------- /.github/workflows/markdownlint.yml: -------------------------------------------------------------------------------- 1 | name: Markdownlint check 2 | 3 | on: 4 | # not on `push`, since that's covered by pre-commit checks 5 | pull_request: 6 | paths: ["**.md"] 7 | 8 | jobs: 9 | markdownlint-check: 10 | name: Markdownlint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: DavidAnson/markdownlint-cli2-action@v20 15 | with: 16 | globs: "**/*.md" 17 | -------------------------------------------------------------------------------- /.github/workflows/obsidian-plugin-release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ["*"] 6 | 7 | env: 8 | PLUGIN_NAME: ${{ github.event.repository.name }} 9 | 10 | #─────────────────────────────────────────────────────────────────────────────── 11 | 12 | jobs: 13 | build: 14 | runs-on: macos-latest 15 | permissions: { contents: write } 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v4 22 | with: { node-version: "22.x" } 23 | 24 | - name: Build plugin 25 | run: | 26 | npm install 27 | node .esbuild.mjs "production" 28 | mkdir ${{ env.PLUGIN_NAME }} 29 | cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }} 30 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 31 | 32 | - name: Create release notes 33 | id: release_notes 34 | uses: mikepenz/release-changelog-builder-action@v5 35 | env: 36 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 37 | with: 38 | mode: "COMMIT" 39 | configurationJson: | 40 | { 41 | "label_extractor": [{ 42 | "pattern": "^(\\w+)(\\([\\w\\-\\.]+\\))?(!)?: .+", 43 | "on_property": "title", 44 | "target": "$1" 45 | }], 46 | "categories": [ 47 | { "title": "## 🚀 New features", "labels": ["feat", "improv"] }, 48 | { "title": "## 🛠️ Fixes", "labels": ["fix"] }, 49 | { "title": "## 👾 Other", "labels": [] } 50 | ], 51 | "ignore_labels": ["release", "bump"] 52 | } 53 | 54 | - name: Release 55 | uses: softprops/action-gh-release@v2 56 | with: 57 | token: ${{ secrets.GITHUB_TOKEN }} 58 | body: ${{ steps.release_notes.outputs.changelog }} 59 | files: | 60 | ${{ env.PLUGIN_NAME }}.zip 61 | main.js 62 | manifest.json 63 | styles.css 64 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yml: -------------------------------------------------------------------------------- 1 | name: PR title 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | - reopened 10 | - ready_for_review 11 | 12 | permissions: 13 | pull-requests: read 14 | 15 | jobs: 16 | semantic-pull-request: 17 | name: Check PR title 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: amannn/action-semantic-pull-request@v5 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | with: 24 | requireScope: false 25 | subjectPattern: ^(?![A-Z]).+$ # disallow title starting with capital 26 | types: | # add `improv` to the list of allowed types 27 | improv 28 | fix 29 | feat 30 | refactor 31 | build 32 | ci 33 | style 34 | test 35 | chore 36 | perf 37 | docs 38 | break 39 | revert 40 | -------------------------------------------------------------------------------- /.github/workflows/stale-bot.yml: -------------------------------------------------------------------------------- 1 | name: Stale bot 2 | on: 3 | schedule: 4 | - cron: "18 04 * * 3" 5 | 6 | permissions: 7 | issues: write 8 | pull-requests: write 9 | 10 | jobs: 11 | stale: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Close stale issues 15 | uses: actions/stale@v9 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | # DOCS https://github.com/actions/stale#all-options 20 | days-before-stale: 180 21 | days-before-close: 7 22 | stale-issue-label: "Stale" 23 | stale-issue-message: | 24 | This issue has been automatically marked as stale. 25 | **If this issue is still affecting you, please leave any comment**, for example "bump", and it will be kept open. 26 | close-issue-message: | 27 | This issue has been closed due to inactivity, and will not be monitored. 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules 3 | 4 | # external source maps 5 | *.js.map 6 | 7 | # obsidian 8 | data.json 9 | main.js 10 | -------------------------------------------------------------------------------- /.knip.jsonc: -------------------------------------------------------------------------------- 1 | // DOCS https://knip.dev/overview/configuration 2 | { 3 | "$schema": "https://unpkg.com/knip@5/schema-jsonc.json", 4 | "entry": ["src/main.ts"], 5 | "project": ["**/*.ts"], 6 | "ignoreDependencies": [ 7 | "@biomejs/biome", // used only in pre-commit hook / task 8 | "markdownlint-cli" // used only in pre-commit hook / task 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # Defaults https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml 2 | # DOCS https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md 3 | #─────────────────────────────────────────────────────────────────────────────── 4 | 5 | # MODIFIED SETTINGS 6 | blanks-around-headings: 7 | lines_below: 0 # space waster 8 | ul-style: { style: sublist } 9 | 10 | # not autofixable 11 | ol-prefix: { style: ordered } 12 | line-length: 13 | tables: false 14 | code_blocks: false 15 | no-inline-html: 16 | allowed_elements: [img, details, summary, kbd, a, br] 17 | 18 | #───────────────────────────────────────────────────────────────────────────── 19 | # DISABLED 20 | ul-indent: false # not compatible with using tabs 21 | no-hard-tabs: false # taken care of by editorconfig 22 | blanks-around-lists: false # space waster 23 | first-line-heading: false # e.g., ignore-comments 24 | no-emphasis-as-heading: false # sometimes useful 25 | -------------------------------------------------------------------------------- /.release.mjs: -------------------------------------------------------------------------------- 1 | import { spawn } from "node:child_process"; 2 | import { readFileSync, writeFileSync } from "node:fs"; 3 | import readlinePromises from "node:readline/promises"; 4 | 5 | /** @param {string} filepath */ 6 | function readJson(filepath) { 7 | return JSON.parse(readFileSync(filepath, "utf8")); 8 | } 9 | 10 | /** @param {string} filepath @param {object} jsonObj */ 11 | function writeJson(filepath, jsonObj) { 12 | writeFileSync(filepath, JSON.stringify(jsonObj, null, "\t") + "\n"); 13 | } 14 | 15 | //────────────────────────────────────────────────────────────────────────────── 16 | // PROMPT FOR TARGET VERSION 17 | 18 | const manifest = readJson("manifest.json"); 19 | const currentVersion = manifest.version; 20 | const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout }); 21 | 22 | console.info(`current version: ${currentVersion}`); 23 | const nextVersion = await rl.question(" next version: "); 24 | console.info("───────────────────────────"); 25 | if (!nextVersion?.match(/\d+\.\d+\.\d+/) || nextVersion === currentVersion) { 26 | console.error("\x1b[1;31mInvalid target version given, aborting.\x1b[0m"); 27 | process.exit(1); 28 | } 29 | 30 | rl.close(); 31 | 32 | //────────────────────────────────────────────────────────────────────────────── 33 | // UPDATE VERSION IN VARIOUS JSONS 34 | 35 | manifest.version = nextVersion; 36 | writeJson("manifest.json", manifest); 37 | 38 | const versionsJson = readJson("versions.json"); 39 | versionsJson[nextVersion] = manifest.minAppVersion; 40 | writeJson("versions.json", versionsJson); 41 | 42 | const packageJson = readJson("package.json"); 43 | packageJson.version = nextVersion; 44 | writeJson("package.json", packageJson); 45 | 46 | const packageLock = readJson("package-lock.json"); 47 | packageLock.version = nextVersion; 48 | packageLock.packages[""].version = nextVersion; 49 | writeJson("package-lock.json", packageLock); 50 | 51 | //────────────────────────────────────────────────────────────────────────────── 52 | // UPDATE GIT REPO 53 | 54 | const gitCommands = [ 55 | "git add manifest.json versions.json package.json package-lock.json", 56 | `git commit --no-verify --message="release: ${nextVersion}"`, // skip hook, since only bumping 57 | "git pull --no-progress", 58 | "git push --no-progress", 59 | `git tag ${nextVersion}`, // tag triggers the release action 60 | "git push --no-progress origin --tags", 61 | ]; 62 | 63 | // INFO as opposed to `exec`, `spawn` does not buffer the output 64 | const gitProcess = spawn(gitCommands.join(" && "), [], { shell: true }); 65 | gitProcess.stdout.on("data", (data) => console.info(data.toString().trim())); 66 | gitProcess.stderr.on("data", (data) => console.info(data.toString().trim())); 67 | gitProcess.on("error", (_err) => process.exit(1)); 68 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | set quiet := true 2 | 3 | test_vault := "$HOME/Vaults/writing-vault/" 4 | 5 | #─────────────────────────────────────────────────────────────────────────────── 6 | 7 | [macos] 8 | build-and-reload: 9 | #!/usr/bin/env zsh 10 | node .esbuild.mjs 11 | 12 | plugin_id=$(grep '"id"' "./manifest.json" | cut -d'"' -f4) 13 | mkdir -p "{{ test_vault }}/.obsidian/plugins/$plugin_id/" 14 | cp -f "main.js" "{{ test_vault }}/.obsidian/plugins/$plugin_id/main.js" 15 | cp -f "manifest.json" "{{ test_vault }}/.obsidian/plugins/$plugin_id/manifest.json" 16 | vault_name=$(basename "{{ test_vault }}") 17 | open "obsidian://open?vault=$vault_name" 18 | 19 | # reload (REQUIRES: registering the URI manually in a helper plugin) 20 | open "obsidian://reload-plugin?id=$plugin_id&vault=$vault_name" 21 | 22 | check-all: 23 | git hook run pre-commit -- "check-all" 24 | 25 | check-tsc-qf: 26 | npx tsc --noEmit --skipLibCheck --strict && echo "Typescript OK" 27 | 28 | release: 29 | node .release.mjs 30 | 31 | analyze: 32 | node .esbuild.mjs analyze 33 | 34 | init: 35 | #!/usr/bin/env zsh 36 | git config core.hooksPath .githooks 37 | npm install 38 | node .esbuild.mjs 39 | 40 | update-deps: 41 | #!/usr/bin/env zsh 42 | npm update 43 | node .esbuild.mjs 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Christopher Grieser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proofreader 2 | ![Obsidian downloads](https://img.shields.io/badge/dynamic/json?logo=obsidian&color=%23483699&label=downloads&query=%24%5B%22proofreader%22%5D.downloads&url=https%3A%2F%2Fraw.githubusercontent.com%2Fobsidianmd%2Fobsidian-releases%2Fmaster%2Fcommunity-plugin-stats.json&style=plastic) 3 | ![GitHub download count](https://img.shields.io/github/downloads/chrisgrieser/obsidian-proofreader/total?label=GitHub%20Downloads&style=plastic) 4 | ![Last release](https://img.shields.io/github/v/release/chrisgrieser/obsidian-proofreader?label=Latest%20Release&style=plastic) 5 | 6 | AI-based proofreading and stylistic improvements for your writing. Changes are 7 | inserted as suggestions directly in the editor, similar to the suggested changes 8 | feature in word processing apps. 9 | 10 | Showcase 11 | 12 | ## Table of contents 13 | 14 | 15 | 16 | - [Features](#features) 17 | - [Installation & setup](#installation--setup) 18 | * [Plugin installation](#plugin-installation) 19 | * [Get an OpenAI API key](#get-an-openai-api-key) 20 | - [Usage](#usage) 21 | - [Visual appearance of the changes](#visual-appearance-of-the-changes) 22 | - [Testimonials](#testimonials) 23 | - [Plugin development](#plugin-development) 24 | * [General](#general) 25 | * [Adding support for new LLMs](#adding-support-for-new-llms) 26 | - [About the developer](#about-the-developer) 27 | 28 | 29 | 30 | ## Features 31 | - Suggested changes are inserted directly into the text: Additions as 32 | `==highlights==` and removals as `~~strikethroughs~~`. 33 | - Accept or reject changes with just one hotkey. 34 | - Easy to use: No complicated plugin settings and AI parameters to configure. 35 | 36 | | | Professional proofreading service | Proofreader plugin | 37 | | ---------------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------- | 38 | | Cost for English text of 10,000 words | ~ $400, depending on the service | ~ \$0.01 – $0.06[^1] | 39 | | Completion duration | up to 3 work days | about 5 minutes | 40 | | Input format | usually Microsoft Word (`.docx`) | Markdown file in Obsidian | 41 | | Method of incorporating changes | mostly mouse clicks | keyboard shortcuts | 42 | | Additional benefits | Editor makes general comments on writing style. | Plugin can also be used to quickly proofread single sentences or paragraphs. | 43 | 44 | [^1]: Estimated pricing for the [GPT 4.1 nano 45 | model](https://platform.openai.com/docs/models/) in April 2025. The plugin 46 | developer is not responsible if the actual costs differ. You can track your 47 | usage costs [on this page](https://platform.openai.com/usage). 48 | 49 | > [!NOTE] 50 | > This plugin requires an **OpenAI API key** and incurs costs at OpenAI based on 51 | > usage. Network requests are made when running the proofreading command. (PRs 52 | > [adding support for other LLMs](#adding-support-for-new-llms) are welcome.) 53 | 54 | ## Installation & setup 55 | 56 | ### Plugin installation 57 | [Install in Obsidian](https://obsidian.md/plugins?id=proofreader) 58 | 59 | ### Get an OpenAI API key 60 | 1. [Create an OpenAI account](https://auth.openai.com/create-account). 61 | 2. Go to [this site](https://platform.openai.com/api-keys), and click `Create 62 | new secret key`. 63 | 3. Copy the API key. 64 | 4. In Obsidian, go to `Settings → Proofreader` and paste your API key there. 65 | 66 | > [!TIP] 67 | > The usage costs should not be very high, nonetheless you can track them 68 | > [on this page](https://platform.openai.com/usage). 69 | 70 | ## Usage 71 | 1. Use the command `Proofread selection/paragraph` to check the selected 72 | text. If there is no selection, the command will check the current paragraph. 73 | * Alternatively, you can also check the whole document with `Proofread full 74 | document`. However, note that the quality of AI suggestions tends to 75 | decrease when proofreading too much text at once. 76 | 2. The changes are automatically inserted. 77 | 3. Accept/reject changes with the `Accept suggestions in selection/paragraph`. 78 | Same as the proofreading command, the `accept` and `reject` commands affect 79 | the current paragraph if there is no selection. Alternatively, you can also 80 | only accept/reject the next suggestion after your cursor via `Accept next 81 | suggestion`. 82 | 83 | ## Visual appearance of the changes 84 | You can add the following CSS snippet to make highlights and strikethroughs 85 | appear like suggested changes, similar to the screenshot further above. ([How 86 | to add CSS snippets.](https://help.obsidian.md/snippets)) 87 | 88 | ```css 89 | .cm-strikethrough { 90 | text-decoration-color: var(--color-red); 91 | } 92 | 93 | .cm-s-obsidian span.cm-highlight { 94 | background-color: rgba(var(--color-green-rgb), 35%); 95 | } 96 | ``` 97 | 98 | ## Testimonials 99 | 100 | > I was paying $29 a month for type.ai until today, your plugin made me cancel 101 | > the subscription, because the only feature I wanted from there was this inline 102 | > granular diffing which no other app offered, until Proofreader. 103 | > [@samwega](https://github.com/chrisgrieser/obsidian-proofreader/discussions/1#discussioncomment-12972780) 104 | 105 | ## Plugin development 106 | 107 | ### General 108 | 109 | ```bash 110 | just init # run once after cloning 111 | 112 | just format # run all formatters 113 | just build # builds the plugin 114 | just check # runs the pre-commit hook (without committing) 115 | ``` 116 | 117 | > [!NOTE] 118 | > This repo uses a pre-commit hook, which prevents commits that do not build or 119 | > do not pass the checks. 120 | 121 | ### Adding support for new LLMs 122 | 1. Create a new adapter for the LLM in 123 | [./src/providers/](./src/providers/). This should take ~50 lines of code. 124 | 2. In [./src/providers/model-info.ts](./src/providers/model-info.ts), add the 125 | adapter function to `PROVIDER_ADAPTER_MAP`, and add models for the new 126 | provider to `MODEL_SPECS`. 127 | 3. In [./src/settings.ts], add a setting for the API key to 128 | `ProofreaderSettingsMenu` and add a field to `DEFAULT_SETTINGS`. 129 | 130 | ## About the developer 131 | In my day job, I am a sociologist studying the social mechanisms underlying the 132 | digital economy. For my PhD project, I investigate the governance of the app 133 | economy and how software ecosystems manage the tension between innovation and 134 | compatibility. If you are interested in this subject, feel free to get in touch. 135 | 136 | - [Website](https://chris-grieser.de/) 137 | - [ResearchGate](https://www.researchgate.net/profile/Christopher-Grieser) 138 | - [Mastodon](https://pkm.social/@pseudometa) 139 | - [LinkedIn](https://www.linkedin.com/in/christopher-grieser-ba693b17a/) 140 | 141 | Buy Me a Coffee at ko-fi.com 144 | -------------------------------------------------------------------------------- /biome.jsonc: -------------------------------------------------------------------------------- 1 | // DOCS https://biomejs.dev/reference/configuration/ 2 | //────────────────────────────────────────────────────────────────────────────── 3 | { 4 | "linter": { 5 | "rules": { 6 | "all": true, 7 | "nursery": { 8 | "all": true, 9 | "useImportRestrictions": "off", // over-complicating stuff 10 | "noSecrets": "off" // buggy, many false positives 11 | }, 12 | "complexity": { 13 | "noExcessiveCognitiveComplexity": "info", 14 | "useSimplifiedLogicExpression": "off" // complains about negated and-conditions 15 | }, 16 | "performance": { 17 | "useTopLevelRegex": "off" // often not needed 18 | }, 19 | "suspicious": { 20 | "noConsole": { 21 | "level": "info", 22 | "options": { "allow": ["assert", "error", "info", "warn", "debug"] } // only disallow `.log` 23 | }, 24 | "noDebugger": "off" // dropped automatically by `esbuild` 25 | }, 26 | "correctness": { 27 | "noUndeclaredDependencies": "off", // incompatible with typescript's default auto-imports 28 | "useImportExtensions": "off", // incompatible with typescript's default auto-imports 29 | "noNodejsModules": "off" // Obsidian is client-site, thus requiring them 30 | }, 31 | "style": { 32 | "useBlockStatements": "off", // too much clutter 33 | "useImportType": "off", // incompatible with typescript's default auto-imports 34 | "useTemplate": "off", // too strict, simple concatenations are often fine 35 | "noParameterAssign": "off", // not useful 36 | "useNamingConvention": { "level": "info", "options": { "strictCase": false } } 37 | } 38 | } 39 | }, 40 | "javascript": { 41 | "globals": ["activeDocument", "activeWindow"] // electron 42 | }, 43 | "formatter": { 44 | "useEditorconfig": true, 45 | "formatWithErrors": true 46 | }, 47 | "files": { 48 | "ignoreUnknown": true 49 | }, 50 | "vcs": { 51 | "enabled": true, 52 | "clientKind": "git", 53 | "useIgnoreFile": true 54 | }, 55 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json" 56 | } 57 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "proofreader", 3 | "name": "Proofreader", 4 | "description": "AI-based proofreading and stylistic improvements for your writing. Changes are inserted as suggestions directly in the editor, similar to suggested changes in word processing apps.", 5 | "version": "1.2.3", 6 | "minAppVersion": "1.5.8", 7 | "isDesktopOnly": false, 8 | "author": "pseudometa (aka Chris Grieser)", 9 | "authorUrl": "https://chris-grieser.de/", 10 | "helpUrl": "https://github.com/chrisgrieser/obsidian-proofreader#readme", 11 | "fundingUrl": { 12 | "Ko-Fi": "https://ko-fi.com/pseudometa", 13 | "PayPal": "https://www.paypal.me/ChrisGrieser" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Proofreader", 3 | "version": "1.2.3", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "Proofreader", 9 | "version": "1.2.3", 10 | "license": "MIT", 11 | "dependencies": { 12 | "diff": "^7.0.0" 13 | }, 14 | "devDependencies": { 15 | "@biomejs/biome": "latest", 16 | "@codemirror/view": "latest", 17 | "@types/diff": "^7.0.2", 18 | "@types/node": "^22.5.5", 19 | "builtin-modules": "^3.2.0", 20 | "esbuild": "^0.25.1", 21 | "knip": "^5.30.2", 22 | "markdownlint-cli": "latest", 23 | "obsidian": "latest", 24 | "tslib": "2.6.2", 25 | "typescript": "^5.6.2" 26 | } 27 | }, 28 | "node_modules/@biomejs/biome": { 29 | "version": "1.9.4", 30 | "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", 31 | "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", 32 | "dev": true, 33 | "hasInstallScript": true, 34 | "license": "MIT OR Apache-2.0", 35 | "bin": { 36 | "biome": "bin/biome" 37 | }, 38 | "engines": { 39 | "node": ">=14.21.3" 40 | }, 41 | "funding": { 42 | "type": "opencollective", 43 | "url": "https://opencollective.com/biome" 44 | }, 45 | "optionalDependencies": { 46 | "@biomejs/cli-darwin-arm64": "1.9.4", 47 | "@biomejs/cli-darwin-x64": "1.9.4", 48 | "@biomejs/cli-linux-arm64": "1.9.4", 49 | "@biomejs/cli-linux-arm64-musl": "1.9.4", 50 | "@biomejs/cli-linux-x64": "1.9.4", 51 | "@biomejs/cli-linux-x64-musl": "1.9.4", 52 | "@biomejs/cli-win32-arm64": "1.9.4", 53 | "@biomejs/cli-win32-x64": "1.9.4" 54 | } 55 | }, 56 | "node_modules/@biomejs/cli-darwin-arm64": { 57 | "version": "1.9.4", 58 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", 59 | "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", 60 | "cpu": [ 61 | "arm64" 62 | ], 63 | "dev": true, 64 | "license": "MIT OR Apache-2.0", 65 | "optional": true, 66 | "os": [ 67 | "darwin" 68 | ], 69 | "engines": { 70 | "node": ">=14.21.3" 71 | } 72 | }, 73 | "node_modules/@biomejs/cli-darwin-x64": { 74 | "version": "1.9.4", 75 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", 76 | "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", 77 | "cpu": [ 78 | "x64" 79 | ], 80 | "dev": true, 81 | "license": "MIT OR Apache-2.0", 82 | "optional": true, 83 | "os": [ 84 | "darwin" 85 | ], 86 | "engines": { 87 | "node": ">=14.21.3" 88 | } 89 | }, 90 | "node_modules/@biomejs/cli-linux-arm64": { 91 | "version": "1.9.4", 92 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", 93 | "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", 94 | "cpu": [ 95 | "arm64" 96 | ], 97 | "dev": true, 98 | "license": "MIT OR Apache-2.0", 99 | "optional": true, 100 | "os": [ 101 | "linux" 102 | ], 103 | "engines": { 104 | "node": ">=14.21.3" 105 | } 106 | }, 107 | "node_modules/@biomejs/cli-linux-arm64-musl": { 108 | "version": "1.9.4", 109 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", 110 | "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", 111 | "cpu": [ 112 | "arm64" 113 | ], 114 | "dev": true, 115 | "license": "MIT OR Apache-2.0", 116 | "optional": true, 117 | "os": [ 118 | "linux" 119 | ], 120 | "engines": { 121 | "node": ">=14.21.3" 122 | } 123 | }, 124 | "node_modules/@biomejs/cli-linux-x64": { 125 | "version": "1.9.4", 126 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", 127 | "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", 128 | "cpu": [ 129 | "x64" 130 | ], 131 | "dev": true, 132 | "license": "MIT OR Apache-2.0", 133 | "optional": true, 134 | "os": [ 135 | "linux" 136 | ], 137 | "engines": { 138 | "node": ">=14.21.3" 139 | } 140 | }, 141 | "node_modules/@biomejs/cli-linux-x64-musl": { 142 | "version": "1.9.4", 143 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", 144 | "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", 145 | "cpu": [ 146 | "x64" 147 | ], 148 | "dev": true, 149 | "license": "MIT OR Apache-2.0", 150 | "optional": true, 151 | "os": [ 152 | "linux" 153 | ], 154 | "engines": { 155 | "node": ">=14.21.3" 156 | } 157 | }, 158 | "node_modules/@biomejs/cli-win32-arm64": { 159 | "version": "1.9.4", 160 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", 161 | "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", 162 | "cpu": [ 163 | "arm64" 164 | ], 165 | "dev": true, 166 | "license": "MIT OR Apache-2.0", 167 | "optional": true, 168 | "os": [ 169 | "win32" 170 | ], 171 | "engines": { 172 | "node": ">=14.21.3" 173 | } 174 | }, 175 | "node_modules/@biomejs/cli-win32-x64": { 176 | "version": "1.9.4", 177 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", 178 | "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", 179 | "cpu": [ 180 | "x64" 181 | ], 182 | "dev": true, 183 | "license": "MIT OR Apache-2.0", 184 | "optional": true, 185 | "os": [ 186 | "win32" 187 | ], 188 | "engines": { 189 | "node": ">=14.21.3" 190 | } 191 | }, 192 | "node_modules/@codemirror/state": { 193 | "version": "6.5.2", 194 | "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", 195 | "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", 196 | "dev": true, 197 | "license": "MIT", 198 | "dependencies": { 199 | "@marijn/find-cluster-break": "^1.0.0" 200 | } 201 | }, 202 | "node_modules/@codemirror/view": { 203 | "version": "6.37.1", 204 | "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.1.tgz", 205 | "integrity": "sha512-Qy4CAUwngy/VQkEz0XzMKVRcckQuqLYWKqVpDDDghBe5FSXSqfVrJn49nw3ePZHxRUz4nRmb05Lgi+9csWo4eg==", 206 | "dev": true, 207 | "license": "MIT", 208 | "dependencies": { 209 | "@codemirror/state": "^6.5.0", 210 | "crelt": "^1.0.6", 211 | "style-mod": "^4.1.0", 212 | "w3c-keyname": "^2.2.4" 213 | } 214 | }, 215 | "node_modules/@emnapi/core": { 216 | "version": "1.4.3", 217 | "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", 218 | "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", 219 | "dev": true, 220 | "license": "MIT", 221 | "optional": true, 222 | "dependencies": { 223 | "@emnapi/wasi-threads": "1.0.2", 224 | "tslib": "^2.4.0" 225 | } 226 | }, 227 | "node_modules/@emnapi/runtime": { 228 | "version": "1.4.3", 229 | "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", 230 | "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", 231 | "dev": true, 232 | "license": "MIT", 233 | "optional": true, 234 | "dependencies": { 235 | "tslib": "^2.4.0" 236 | } 237 | }, 238 | "node_modules/@emnapi/wasi-threads": { 239 | "version": "1.0.2", 240 | "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", 241 | "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", 242 | "dev": true, 243 | "license": "MIT", 244 | "optional": true, 245 | "dependencies": { 246 | "tslib": "^2.4.0" 247 | } 248 | }, 249 | "node_modules/@esbuild/aix-ppc64": { 250 | "version": "0.25.5", 251 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", 252 | "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", 253 | "cpu": [ 254 | "ppc64" 255 | ], 256 | "dev": true, 257 | "license": "MIT", 258 | "optional": true, 259 | "os": [ 260 | "aix" 261 | ], 262 | "engines": { 263 | "node": ">=18" 264 | } 265 | }, 266 | "node_modules/@esbuild/android-arm": { 267 | "version": "0.25.5", 268 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", 269 | "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", 270 | "cpu": [ 271 | "arm" 272 | ], 273 | "dev": true, 274 | "license": "MIT", 275 | "optional": true, 276 | "os": [ 277 | "android" 278 | ], 279 | "engines": { 280 | "node": ">=18" 281 | } 282 | }, 283 | "node_modules/@esbuild/android-arm64": { 284 | "version": "0.25.5", 285 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", 286 | "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", 287 | "cpu": [ 288 | "arm64" 289 | ], 290 | "dev": true, 291 | "license": "MIT", 292 | "optional": true, 293 | "os": [ 294 | "android" 295 | ], 296 | "engines": { 297 | "node": ">=18" 298 | } 299 | }, 300 | "node_modules/@esbuild/android-x64": { 301 | "version": "0.25.5", 302 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", 303 | "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", 304 | "cpu": [ 305 | "x64" 306 | ], 307 | "dev": true, 308 | "license": "MIT", 309 | "optional": true, 310 | "os": [ 311 | "android" 312 | ], 313 | "engines": { 314 | "node": ">=18" 315 | } 316 | }, 317 | "node_modules/@esbuild/darwin-arm64": { 318 | "version": "0.25.5", 319 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", 320 | "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", 321 | "cpu": [ 322 | "arm64" 323 | ], 324 | "dev": true, 325 | "license": "MIT", 326 | "optional": true, 327 | "os": [ 328 | "darwin" 329 | ], 330 | "engines": { 331 | "node": ">=18" 332 | } 333 | }, 334 | "node_modules/@esbuild/darwin-x64": { 335 | "version": "0.25.5", 336 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", 337 | "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", 338 | "cpu": [ 339 | "x64" 340 | ], 341 | "dev": true, 342 | "license": "MIT", 343 | "optional": true, 344 | "os": [ 345 | "darwin" 346 | ], 347 | "engines": { 348 | "node": ">=18" 349 | } 350 | }, 351 | "node_modules/@esbuild/freebsd-arm64": { 352 | "version": "0.25.5", 353 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", 354 | "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", 355 | "cpu": [ 356 | "arm64" 357 | ], 358 | "dev": true, 359 | "license": "MIT", 360 | "optional": true, 361 | "os": [ 362 | "freebsd" 363 | ], 364 | "engines": { 365 | "node": ">=18" 366 | } 367 | }, 368 | "node_modules/@esbuild/freebsd-x64": { 369 | "version": "0.25.5", 370 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", 371 | "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", 372 | "cpu": [ 373 | "x64" 374 | ], 375 | "dev": true, 376 | "license": "MIT", 377 | "optional": true, 378 | "os": [ 379 | "freebsd" 380 | ], 381 | "engines": { 382 | "node": ">=18" 383 | } 384 | }, 385 | "node_modules/@esbuild/linux-arm": { 386 | "version": "0.25.5", 387 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", 388 | "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", 389 | "cpu": [ 390 | "arm" 391 | ], 392 | "dev": true, 393 | "license": "MIT", 394 | "optional": true, 395 | "os": [ 396 | "linux" 397 | ], 398 | "engines": { 399 | "node": ">=18" 400 | } 401 | }, 402 | "node_modules/@esbuild/linux-arm64": { 403 | "version": "0.25.5", 404 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", 405 | "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", 406 | "cpu": [ 407 | "arm64" 408 | ], 409 | "dev": true, 410 | "license": "MIT", 411 | "optional": true, 412 | "os": [ 413 | "linux" 414 | ], 415 | "engines": { 416 | "node": ">=18" 417 | } 418 | }, 419 | "node_modules/@esbuild/linux-ia32": { 420 | "version": "0.25.5", 421 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", 422 | "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", 423 | "cpu": [ 424 | "ia32" 425 | ], 426 | "dev": true, 427 | "license": "MIT", 428 | "optional": true, 429 | "os": [ 430 | "linux" 431 | ], 432 | "engines": { 433 | "node": ">=18" 434 | } 435 | }, 436 | "node_modules/@esbuild/linux-loong64": { 437 | "version": "0.25.5", 438 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", 439 | "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", 440 | "cpu": [ 441 | "loong64" 442 | ], 443 | "dev": true, 444 | "license": "MIT", 445 | "optional": true, 446 | "os": [ 447 | "linux" 448 | ], 449 | "engines": { 450 | "node": ">=18" 451 | } 452 | }, 453 | "node_modules/@esbuild/linux-mips64el": { 454 | "version": "0.25.5", 455 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", 456 | "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", 457 | "cpu": [ 458 | "mips64el" 459 | ], 460 | "dev": true, 461 | "license": "MIT", 462 | "optional": true, 463 | "os": [ 464 | "linux" 465 | ], 466 | "engines": { 467 | "node": ">=18" 468 | } 469 | }, 470 | "node_modules/@esbuild/linux-ppc64": { 471 | "version": "0.25.5", 472 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", 473 | "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", 474 | "cpu": [ 475 | "ppc64" 476 | ], 477 | "dev": true, 478 | "license": "MIT", 479 | "optional": true, 480 | "os": [ 481 | "linux" 482 | ], 483 | "engines": { 484 | "node": ">=18" 485 | } 486 | }, 487 | "node_modules/@esbuild/linux-riscv64": { 488 | "version": "0.25.5", 489 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", 490 | "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", 491 | "cpu": [ 492 | "riscv64" 493 | ], 494 | "dev": true, 495 | "license": "MIT", 496 | "optional": true, 497 | "os": [ 498 | "linux" 499 | ], 500 | "engines": { 501 | "node": ">=18" 502 | } 503 | }, 504 | "node_modules/@esbuild/linux-s390x": { 505 | "version": "0.25.5", 506 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", 507 | "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", 508 | "cpu": [ 509 | "s390x" 510 | ], 511 | "dev": true, 512 | "license": "MIT", 513 | "optional": true, 514 | "os": [ 515 | "linux" 516 | ], 517 | "engines": { 518 | "node": ">=18" 519 | } 520 | }, 521 | "node_modules/@esbuild/linux-x64": { 522 | "version": "0.25.5", 523 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", 524 | "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", 525 | "cpu": [ 526 | "x64" 527 | ], 528 | "dev": true, 529 | "license": "MIT", 530 | "optional": true, 531 | "os": [ 532 | "linux" 533 | ], 534 | "engines": { 535 | "node": ">=18" 536 | } 537 | }, 538 | "node_modules/@esbuild/netbsd-arm64": { 539 | "version": "0.25.5", 540 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", 541 | "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", 542 | "cpu": [ 543 | "arm64" 544 | ], 545 | "dev": true, 546 | "license": "MIT", 547 | "optional": true, 548 | "os": [ 549 | "netbsd" 550 | ], 551 | "engines": { 552 | "node": ">=18" 553 | } 554 | }, 555 | "node_modules/@esbuild/netbsd-x64": { 556 | "version": "0.25.5", 557 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", 558 | "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", 559 | "cpu": [ 560 | "x64" 561 | ], 562 | "dev": true, 563 | "license": "MIT", 564 | "optional": true, 565 | "os": [ 566 | "netbsd" 567 | ], 568 | "engines": { 569 | "node": ">=18" 570 | } 571 | }, 572 | "node_modules/@esbuild/openbsd-arm64": { 573 | "version": "0.25.5", 574 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", 575 | "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", 576 | "cpu": [ 577 | "arm64" 578 | ], 579 | "dev": true, 580 | "license": "MIT", 581 | "optional": true, 582 | "os": [ 583 | "openbsd" 584 | ], 585 | "engines": { 586 | "node": ">=18" 587 | } 588 | }, 589 | "node_modules/@esbuild/openbsd-x64": { 590 | "version": "0.25.5", 591 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", 592 | "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", 593 | "cpu": [ 594 | "x64" 595 | ], 596 | "dev": true, 597 | "license": "MIT", 598 | "optional": true, 599 | "os": [ 600 | "openbsd" 601 | ], 602 | "engines": { 603 | "node": ">=18" 604 | } 605 | }, 606 | "node_modules/@esbuild/sunos-x64": { 607 | "version": "0.25.5", 608 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", 609 | "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", 610 | "cpu": [ 611 | "x64" 612 | ], 613 | "dev": true, 614 | "license": "MIT", 615 | "optional": true, 616 | "os": [ 617 | "sunos" 618 | ], 619 | "engines": { 620 | "node": ">=18" 621 | } 622 | }, 623 | "node_modules/@esbuild/win32-arm64": { 624 | "version": "0.25.5", 625 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", 626 | "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", 627 | "cpu": [ 628 | "arm64" 629 | ], 630 | "dev": true, 631 | "license": "MIT", 632 | "optional": true, 633 | "os": [ 634 | "win32" 635 | ], 636 | "engines": { 637 | "node": ">=18" 638 | } 639 | }, 640 | "node_modules/@esbuild/win32-ia32": { 641 | "version": "0.25.5", 642 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", 643 | "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", 644 | "cpu": [ 645 | "ia32" 646 | ], 647 | "dev": true, 648 | "license": "MIT", 649 | "optional": true, 650 | "os": [ 651 | "win32" 652 | ], 653 | "engines": { 654 | "node": ">=18" 655 | } 656 | }, 657 | "node_modules/@esbuild/win32-x64": { 658 | "version": "0.25.5", 659 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", 660 | "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", 661 | "cpu": [ 662 | "x64" 663 | ], 664 | "dev": true, 665 | "license": "MIT", 666 | "optional": true, 667 | "os": [ 668 | "win32" 669 | ], 670 | "engines": { 671 | "node": ">=18" 672 | } 673 | }, 674 | "node_modules/@isaacs/cliui": { 675 | "version": "8.0.2", 676 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 677 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 678 | "dev": true, 679 | "license": "ISC", 680 | "dependencies": { 681 | "string-width": "^5.1.2", 682 | "string-width-cjs": "npm:string-width@^4.2.0", 683 | "strip-ansi": "^7.0.1", 684 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 685 | "wrap-ansi": "^8.1.0", 686 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 687 | }, 688 | "engines": { 689 | "node": ">=12" 690 | } 691 | }, 692 | "node_modules/@marijn/find-cluster-break": { 693 | "version": "1.0.2", 694 | "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", 695 | "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", 696 | "dev": true, 697 | "license": "MIT" 698 | }, 699 | "node_modules/@napi-rs/wasm-runtime": { 700 | "version": "0.2.10", 701 | "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz", 702 | "integrity": "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==", 703 | "dev": true, 704 | "license": "MIT", 705 | "optional": true, 706 | "dependencies": { 707 | "@emnapi/core": "^1.4.3", 708 | "@emnapi/runtime": "^1.4.3", 709 | "@tybys/wasm-util": "^0.9.0" 710 | } 711 | }, 712 | "node_modules/@nodelib/fs.scandir": { 713 | "version": "2.1.5", 714 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 715 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 716 | "dev": true, 717 | "license": "MIT", 718 | "dependencies": { 719 | "@nodelib/fs.stat": "2.0.5", 720 | "run-parallel": "^1.1.9" 721 | }, 722 | "engines": { 723 | "node": ">= 8" 724 | } 725 | }, 726 | "node_modules/@nodelib/fs.stat": { 727 | "version": "2.0.5", 728 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 729 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 730 | "dev": true, 731 | "license": "MIT", 732 | "engines": { 733 | "node": ">= 8" 734 | } 735 | }, 736 | "node_modules/@nodelib/fs.walk": { 737 | "version": "1.2.8", 738 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 739 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 740 | "dev": true, 741 | "license": "MIT", 742 | "dependencies": { 743 | "@nodelib/fs.scandir": "2.1.5", 744 | "fastq": "^1.6.0" 745 | }, 746 | "engines": { 747 | "node": ">= 8" 748 | } 749 | }, 750 | "node_modules/@oxc-resolver/binding-darwin-arm64": { 751 | "version": "11.1.0", 752 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.1.0.tgz", 753 | "integrity": "sha512-n9y3Lb1+BwsOtm3BmXSUPu3iDtTq7Sf0gX4e+izFTfNrj+u6uTKqbmlq8ggV8CRdg1zGUaCvKNvg/9q3C/19gg==", 754 | "cpu": [ 755 | "arm64" 756 | ], 757 | "dev": true, 758 | "license": "MIT", 759 | "optional": true, 760 | "os": [ 761 | "darwin" 762 | ] 763 | }, 764 | "node_modules/@oxc-resolver/binding-darwin-x64": { 765 | "version": "11.1.0", 766 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.1.0.tgz", 767 | "integrity": "sha512-2aJTPN9/lTmq0xw1YYsy5GDPkTyp92EoYRtw9nVgGErwMvA87duuLnIdoztYk66LGa3g5y4RgOaEapZbK7132A==", 768 | "cpu": [ 769 | "x64" 770 | ], 771 | "dev": true, 772 | "license": "MIT", 773 | "optional": true, 774 | "os": [ 775 | "darwin" 776 | ] 777 | }, 778 | "node_modules/@oxc-resolver/binding-freebsd-x64": { 779 | "version": "11.1.0", 780 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.1.0.tgz", 781 | "integrity": "sha512-GoPEd9GvEyuS1YyqvAhAlccZeBEyHFkrHPEhS/+UTPcrzDzZ16ckJSmZtwOPhci5FWHK/th4L6NPiOnDLGFrqQ==", 782 | "cpu": [ 783 | "x64" 784 | ], 785 | "dev": true, 786 | "license": "MIT", 787 | "optional": true, 788 | "os": [ 789 | "freebsd" 790 | ] 791 | }, 792 | "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { 793 | "version": "11.1.0", 794 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.1.0.tgz", 795 | "integrity": "sha512-mQdQDTbw2/RcJKvMi8RAmDECuEC4waM5jeUBn8Cz1pLVddH8MfYJgKbZJUATBNNaHjw/u+Sq9Q1tcJbm8dhpYQ==", 796 | "cpu": [ 797 | "arm" 798 | ], 799 | "dev": true, 800 | "license": "MIT", 801 | "optional": true, 802 | "os": [ 803 | "linux" 804 | ] 805 | }, 806 | "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { 807 | "version": "11.1.0", 808 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.1.0.tgz", 809 | "integrity": "sha512-HDFQiPl7cX2DVXFlulWOinjqXa5Rj4ydFY9xJCwWAHGx2LmqwLDD8MI0UrHVUaHhLLWn54vjGtwsJK94dtkCwg==", 810 | "cpu": [ 811 | "arm64" 812 | ], 813 | "dev": true, 814 | "license": "MIT", 815 | "optional": true, 816 | "os": [ 817 | "linux" 818 | ] 819 | }, 820 | "node_modules/@oxc-resolver/binding-linux-arm64-musl": { 821 | "version": "11.1.0", 822 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.1.0.tgz", 823 | "integrity": "sha512-0TFcZSVUQPV1r6sFUf7U2fz0mFCaqh5qMlb2zCioZj0C+xUJghC8bz88/qQUc5SA5K4gqg0WEOXzdqz/mXCLLA==", 824 | "cpu": [ 825 | "arm64" 826 | ], 827 | "dev": true, 828 | "license": "MIT", 829 | "optional": true, 830 | "os": [ 831 | "linux" 832 | ] 833 | }, 834 | "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { 835 | "version": "11.1.0", 836 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.1.0.tgz", 837 | "integrity": "sha512-crG0iy5U9ac99Xkt9trWo5YvtCoSpPUrNZMeUVDkIy1qy1znfv66CveOgCm0G5TwooIIWLJrtFUqi0AkazS3fw==", 838 | "cpu": [ 839 | "riscv64" 840 | ], 841 | "dev": true, 842 | "license": "MIT", 843 | "optional": true, 844 | "os": [ 845 | "linux" 846 | ] 847 | }, 848 | "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { 849 | "version": "11.1.0", 850 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.1.0.tgz", 851 | "integrity": "sha512-aPemnsn/FXADFu7/VnSprO8uVb9UhNVdBdrIlAREh3s7LoW1QksKyP8/DlFe0o2E79MRQ3XF1ONOgW5zLcUmzA==", 852 | "cpu": [ 853 | "s390x" 854 | ], 855 | "dev": true, 856 | "license": "MIT", 857 | "optional": true, 858 | "os": [ 859 | "linux" 860 | ] 861 | }, 862 | "node_modules/@oxc-resolver/binding-linux-x64-gnu": { 863 | "version": "11.1.0", 864 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.1.0.tgz", 865 | "integrity": "sha512-eMQ0Iue4Bs0jabCIHiEJbZMPoczdx1oBGOiNS/ykCE76Oos/Hb5uD1FB+Vw4agP2cAxzcp8zHO7MpEW450yswg==", 866 | "cpu": [ 867 | "x64" 868 | ], 869 | "dev": true, 870 | "license": "MIT", 871 | "optional": true, 872 | "os": [ 873 | "linux" 874 | ] 875 | }, 876 | "node_modules/@oxc-resolver/binding-linux-x64-musl": { 877 | "version": "11.1.0", 878 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.1.0.tgz", 879 | "integrity": "sha512-5IjxRv0vWiGb102QmwF+ljutUWA1+BZbdW+58lFOVzVVo29L+m5PrEtijY5kK0FMTDvwb/xFXpGq3/vQx+bpSg==", 880 | "cpu": [ 881 | "x64" 882 | ], 883 | "dev": true, 884 | "license": "MIT", 885 | "optional": true, 886 | "os": [ 887 | "linux" 888 | ] 889 | }, 890 | "node_modules/@oxc-resolver/binding-wasm32-wasi": { 891 | "version": "11.1.0", 892 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.1.0.tgz", 893 | "integrity": "sha512-+yz7LYHKW1GK+fJoHh9JibgIWDeBHf5wiu1tgDD92y5eLFEBxP+CjJ2caTZnVRREH74l03twOfcTR9EaLsEidQ==", 894 | "cpu": [ 895 | "wasm32" 896 | ], 897 | "dev": true, 898 | "license": "MIT", 899 | "optional": true, 900 | "dependencies": { 901 | "@napi-rs/wasm-runtime": "^0.2.10" 902 | }, 903 | "engines": { 904 | "node": ">=14.0.0" 905 | } 906 | }, 907 | "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { 908 | "version": "11.1.0", 909 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.1.0.tgz", 910 | "integrity": "sha512-aTF/1TIq9v86Qy3++YFhKJVKXYSTO54yRRWIXwzpgGvZu41acjN/UsNOG7C2QFy/xdkitrZf1awYgawSqNox3g==", 911 | "cpu": [ 912 | "arm64" 913 | ], 914 | "dev": true, 915 | "license": "MIT", 916 | "optional": true, 917 | "os": [ 918 | "win32" 919 | ] 920 | }, 921 | "node_modules/@oxc-resolver/binding-win32-x64-msvc": { 922 | "version": "11.1.0", 923 | "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.1.0.tgz", 924 | "integrity": "sha512-CxalsPMU4oSoZviLMaw01RhLglyN7jrUUhTDRv4pYGcsRxxt5S7e/wO9P/lm5BYgAAq4TtP5MkGuGuMrm//a0g==", 925 | "cpu": [ 926 | "x64" 927 | ], 928 | "dev": true, 929 | "license": "MIT", 930 | "optional": true, 931 | "os": [ 932 | "win32" 933 | ] 934 | }, 935 | "node_modules/@tybys/wasm-util": { 936 | "version": "0.9.0", 937 | "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", 938 | "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", 939 | "dev": true, 940 | "license": "MIT", 941 | "optional": true, 942 | "dependencies": { 943 | "tslib": "^2.4.0" 944 | } 945 | }, 946 | "node_modules/@types/codemirror": { 947 | "version": "5.60.8", 948 | "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", 949 | "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", 950 | "dev": true, 951 | "license": "MIT", 952 | "dependencies": { 953 | "@types/tern": "*" 954 | } 955 | }, 956 | "node_modules/@types/debug": { 957 | "version": "4.1.12", 958 | "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", 959 | "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", 960 | "dev": true, 961 | "license": "MIT", 962 | "dependencies": { 963 | "@types/ms": "*" 964 | } 965 | }, 966 | "node_modules/@types/diff": { 967 | "version": "7.0.2", 968 | "resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.2.tgz", 969 | "integrity": "sha512-JSWRMozjFKsGlEjiiKajUjIJVKuKdE3oVy2DNtK+fUo8q82nhFZ2CPQwicAIkXrofahDXrWJ7mjelvZphMS98Q==", 970 | "dev": true, 971 | "license": "MIT" 972 | }, 973 | "node_modules/@types/estree": { 974 | "version": "1.0.7", 975 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", 976 | "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", 977 | "dev": true, 978 | "license": "MIT" 979 | }, 980 | "node_modules/@types/katex": { 981 | "version": "0.16.7", 982 | "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", 983 | "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", 984 | "dev": true, 985 | "license": "MIT" 986 | }, 987 | "node_modules/@types/ms": { 988 | "version": "2.1.0", 989 | "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", 990 | "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", 991 | "dev": true, 992 | "license": "MIT" 993 | }, 994 | "node_modules/@types/node": { 995 | "version": "22.15.29", 996 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", 997 | "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", 998 | "dev": true, 999 | "license": "MIT", 1000 | "dependencies": { 1001 | "undici-types": "~6.21.0" 1002 | } 1003 | }, 1004 | "node_modules/@types/tern": { 1005 | "version": "0.23.9", 1006 | "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", 1007 | "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", 1008 | "dev": true, 1009 | "license": "MIT", 1010 | "dependencies": { 1011 | "@types/estree": "*" 1012 | } 1013 | }, 1014 | "node_modules/@types/unist": { 1015 | "version": "2.0.11", 1016 | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", 1017 | "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", 1018 | "dev": true, 1019 | "license": "MIT" 1020 | }, 1021 | "node_modules/ansi-regex": { 1022 | "version": "6.1.0", 1023 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", 1024 | "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", 1025 | "dev": true, 1026 | "license": "MIT", 1027 | "engines": { 1028 | "node": ">=12" 1029 | }, 1030 | "funding": { 1031 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 1032 | } 1033 | }, 1034 | "node_modules/ansi-styles": { 1035 | "version": "6.2.1", 1036 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 1037 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 1038 | "dev": true, 1039 | "license": "MIT", 1040 | "engines": { 1041 | "node": ">=12" 1042 | }, 1043 | "funding": { 1044 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1045 | } 1046 | }, 1047 | "node_modules/argparse": { 1048 | "version": "2.0.1", 1049 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1050 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1051 | "dev": true, 1052 | "license": "Python-2.0" 1053 | }, 1054 | "node_modules/balanced-match": { 1055 | "version": "1.0.2", 1056 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1057 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1058 | "dev": true, 1059 | "license": "MIT" 1060 | }, 1061 | "node_modules/brace-expansion": { 1062 | "version": "2.0.1", 1063 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 1064 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 1065 | "dev": true, 1066 | "license": "MIT", 1067 | "dependencies": { 1068 | "balanced-match": "^1.0.0" 1069 | } 1070 | }, 1071 | "node_modules/braces": { 1072 | "version": "3.0.3", 1073 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 1074 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 1075 | "dev": true, 1076 | "license": "MIT", 1077 | "dependencies": { 1078 | "fill-range": "^7.1.1" 1079 | }, 1080 | "engines": { 1081 | "node": ">=8" 1082 | } 1083 | }, 1084 | "node_modules/builtin-modules": { 1085 | "version": "3.3.0", 1086 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", 1087 | "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", 1088 | "dev": true, 1089 | "license": "MIT", 1090 | "engines": { 1091 | "node": ">=6" 1092 | }, 1093 | "funding": { 1094 | "url": "https://github.com/sponsors/sindresorhus" 1095 | } 1096 | }, 1097 | "node_modules/character-entities": { 1098 | "version": "2.0.2", 1099 | "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", 1100 | "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", 1101 | "dev": true, 1102 | "license": "MIT", 1103 | "funding": { 1104 | "type": "github", 1105 | "url": "https://github.com/sponsors/wooorm" 1106 | } 1107 | }, 1108 | "node_modules/character-entities-legacy": { 1109 | "version": "3.0.0", 1110 | "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", 1111 | "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", 1112 | "dev": true, 1113 | "license": "MIT", 1114 | "funding": { 1115 | "type": "github", 1116 | "url": "https://github.com/sponsors/wooorm" 1117 | } 1118 | }, 1119 | "node_modules/character-reference-invalid": { 1120 | "version": "2.0.1", 1121 | "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", 1122 | "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", 1123 | "dev": true, 1124 | "license": "MIT", 1125 | "funding": { 1126 | "type": "github", 1127 | "url": "https://github.com/sponsors/wooorm" 1128 | } 1129 | }, 1130 | "node_modules/color-convert": { 1131 | "version": "2.0.1", 1132 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1133 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1134 | "dev": true, 1135 | "license": "MIT", 1136 | "dependencies": { 1137 | "color-name": "~1.1.4" 1138 | }, 1139 | "engines": { 1140 | "node": ">=7.0.0" 1141 | } 1142 | }, 1143 | "node_modules/color-name": { 1144 | "version": "1.1.4", 1145 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1146 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1147 | "dev": true, 1148 | "license": "MIT" 1149 | }, 1150 | "node_modules/commander": { 1151 | "version": "13.1.0", 1152 | "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", 1153 | "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", 1154 | "dev": true, 1155 | "license": "MIT", 1156 | "engines": { 1157 | "node": ">=18" 1158 | } 1159 | }, 1160 | "node_modules/crelt": { 1161 | "version": "1.0.6", 1162 | "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", 1163 | "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", 1164 | "dev": true, 1165 | "license": "MIT" 1166 | }, 1167 | "node_modules/cross-spawn": { 1168 | "version": "7.0.6", 1169 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 1170 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 1171 | "dev": true, 1172 | "license": "MIT", 1173 | "dependencies": { 1174 | "path-key": "^3.1.0", 1175 | "shebang-command": "^2.0.0", 1176 | "which": "^2.0.1" 1177 | }, 1178 | "engines": { 1179 | "node": ">= 8" 1180 | } 1181 | }, 1182 | "node_modules/debug": { 1183 | "version": "4.4.1", 1184 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", 1185 | "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", 1186 | "dev": true, 1187 | "license": "MIT", 1188 | "dependencies": { 1189 | "ms": "^2.1.3" 1190 | }, 1191 | "engines": { 1192 | "node": ">=6.0" 1193 | }, 1194 | "peerDependenciesMeta": { 1195 | "supports-color": { 1196 | "optional": true 1197 | } 1198 | } 1199 | }, 1200 | "node_modules/decode-named-character-reference": { 1201 | "version": "1.1.0", 1202 | "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", 1203 | "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", 1204 | "dev": true, 1205 | "license": "MIT", 1206 | "dependencies": { 1207 | "character-entities": "^2.0.0" 1208 | }, 1209 | "funding": { 1210 | "type": "github", 1211 | "url": "https://github.com/sponsors/wooorm" 1212 | } 1213 | }, 1214 | "node_modules/deep-extend": { 1215 | "version": "0.6.0", 1216 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 1217 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 1218 | "dev": true, 1219 | "license": "MIT", 1220 | "engines": { 1221 | "node": ">=4.0.0" 1222 | } 1223 | }, 1224 | "node_modules/dequal": { 1225 | "version": "2.0.3", 1226 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", 1227 | "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", 1228 | "dev": true, 1229 | "license": "MIT", 1230 | "engines": { 1231 | "node": ">=6" 1232 | } 1233 | }, 1234 | "node_modules/devlop": { 1235 | "version": "1.1.0", 1236 | "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", 1237 | "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", 1238 | "dev": true, 1239 | "license": "MIT", 1240 | "dependencies": { 1241 | "dequal": "^2.0.0" 1242 | }, 1243 | "funding": { 1244 | "type": "github", 1245 | "url": "https://github.com/sponsors/wooorm" 1246 | } 1247 | }, 1248 | "node_modules/diff": { 1249 | "version": "7.0.0", 1250 | "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", 1251 | "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", 1252 | "license": "BSD-3-Clause", 1253 | "engines": { 1254 | "node": ">=0.3.1" 1255 | } 1256 | }, 1257 | "node_modules/eastasianwidth": { 1258 | "version": "0.2.0", 1259 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 1260 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 1261 | "dev": true, 1262 | "license": "MIT" 1263 | }, 1264 | "node_modules/emoji-regex": { 1265 | "version": "9.2.2", 1266 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 1267 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 1268 | "dev": true, 1269 | "license": "MIT" 1270 | }, 1271 | "node_modules/entities": { 1272 | "version": "4.5.0", 1273 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 1274 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 1275 | "dev": true, 1276 | "license": "BSD-2-Clause", 1277 | "engines": { 1278 | "node": ">=0.12" 1279 | }, 1280 | "funding": { 1281 | "url": "https://github.com/fb55/entities?sponsor=1" 1282 | } 1283 | }, 1284 | "node_modules/esbuild": { 1285 | "version": "0.25.5", 1286 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", 1287 | "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", 1288 | "dev": true, 1289 | "hasInstallScript": true, 1290 | "license": "MIT", 1291 | "bin": { 1292 | "esbuild": "bin/esbuild" 1293 | }, 1294 | "engines": { 1295 | "node": ">=18" 1296 | }, 1297 | "optionalDependencies": { 1298 | "@esbuild/aix-ppc64": "0.25.5", 1299 | "@esbuild/android-arm": "0.25.5", 1300 | "@esbuild/android-arm64": "0.25.5", 1301 | "@esbuild/android-x64": "0.25.5", 1302 | "@esbuild/darwin-arm64": "0.25.5", 1303 | "@esbuild/darwin-x64": "0.25.5", 1304 | "@esbuild/freebsd-arm64": "0.25.5", 1305 | "@esbuild/freebsd-x64": "0.25.5", 1306 | "@esbuild/linux-arm": "0.25.5", 1307 | "@esbuild/linux-arm64": "0.25.5", 1308 | "@esbuild/linux-ia32": "0.25.5", 1309 | "@esbuild/linux-loong64": "0.25.5", 1310 | "@esbuild/linux-mips64el": "0.25.5", 1311 | "@esbuild/linux-ppc64": "0.25.5", 1312 | "@esbuild/linux-riscv64": "0.25.5", 1313 | "@esbuild/linux-s390x": "0.25.5", 1314 | "@esbuild/linux-x64": "0.25.5", 1315 | "@esbuild/netbsd-arm64": "0.25.5", 1316 | "@esbuild/netbsd-x64": "0.25.5", 1317 | "@esbuild/openbsd-arm64": "0.25.5", 1318 | "@esbuild/openbsd-x64": "0.25.5", 1319 | "@esbuild/sunos-x64": "0.25.5", 1320 | "@esbuild/win32-arm64": "0.25.5", 1321 | "@esbuild/win32-ia32": "0.25.5", 1322 | "@esbuild/win32-x64": "0.25.5" 1323 | } 1324 | }, 1325 | "node_modules/fast-glob": { 1326 | "version": "3.3.3", 1327 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 1328 | "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 1329 | "dev": true, 1330 | "license": "MIT", 1331 | "dependencies": { 1332 | "@nodelib/fs.stat": "^2.0.2", 1333 | "@nodelib/fs.walk": "^1.2.3", 1334 | "glob-parent": "^5.1.2", 1335 | "merge2": "^1.3.0", 1336 | "micromatch": "^4.0.8" 1337 | }, 1338 | "engines": { 1339 | "node": ">=8.6.0" 1340 | } 1341 | }, 1342 | "node_modules/fastq": { 1343 | "version": "1.19.1", 1344 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 1345 | "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 1346 | "dev": true, 1347 | "license": "ISC", 1348 | "dependencies": { 1349 | "reusify": "^1.0.4" 1350 | } 1351 | }, 1352 | "node_modules/fd-package-json": { 1353 | "version": "2.0.0", 1354 | "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", 1355 | "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", 1356 | "dev": true, 1357 | "license": "MIT", 1358 | "dependencies": { 1359 | "walk-up-path": "^4.0.0" 1360 | } 1361 | }, 1362 | "node_modules/fill-range": { 1363 | "version": "7.1.1", 1364 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 1365 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 1366 | "dev": true, 1367 | "license": "MIT", 1368 | "dependencies": { 1369 | "to-regex-range": "^5.0.1" 1370 | }, 1371 | "engines": { 1372 | "node": ">=8" 1373 | } 1374 | }, 1375 | "node_modules/foreground-child": { 1376 | "version": "3.3.1", 1377 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", 1378 | "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", 1379 | "dev": true, 1380 | "license": "ISC", 1381 | "dependencies": { 1382 | "cross-spawn": "^7.0.6", 1383 | "signal-exit": "^4.0.1" 1384 | }, 1385 | "engines": { 1386 | "node": ">=14" 1387 | }, 1388 | "funding": { 1389 | "url": "https://github.com/sponsors/isaacs" 1390 | } 1391 | }, 1392 | "node_modules/formatly": { 1393 | "version": "0.2.4", 1394 | "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.2.4.tgz", 1395 | "integrity": "sha512-lIN7GpcvX/l/i24r/L9bnJ0I8Qn01qijWpQpDDvTLL29nKqSaJJu4h20+7VJ6m2CAhQ2/En/GbxDiHCzq/0MyA==", 1396 | "dev": true, 1397 | "license": "MIT", 1398 | "dependencies": { 1399 | "fd-package-json": "^2.0.0" 1400 | }, 1401 | "bin": { 1402 | "formatly": "bin/index.mjs" 1403 | }, 1404 | "engines": { 1405 | "node": ">=18.3.0" 1406 | } 1407 | }, 1408 | "node_modules/glob": { 1409 | "version": "11.0.2", 1410 | "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", 1411 | "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", 1412 | "dev": true, 1413 | "license": "ISC", 1414 | "dependencies": { 1415 | "foreground-child": "^3.1.0", 1416 | "jackspeak": "^4.0.1", 1417 | "minimatch": "^10.0.0", 1418 | "minipass": "^7.1.2", 1419 | "package-json-from-dist": "^1.0.0", 1420 | "path-scurry": "^2.0.0" 1421 | }, 1422 | "bin": { 1423 | "glob": "dist/esm/bin.mjs" 1424 | }, 1425 | "engines": { 1426 | "node": "20 || >=22" 1427 | }, 1428 | "funding": { 1429 | "url": "https://github.com/sponsors/isaacs" 1430 | } 1431 | }, 1432 | "node_modules/glob-parent": { 1433 | "version": "5.1.2", 1434 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1435 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1436 | "dev": true, 1437 | "license": "ISC", 1438 | "dependencies": { 1439 | "is-glob": "^4.0.1" 1440 | }, 1441 | "engines": { 1442 | "node": ">= 6" 1443 | } 1444 | }, 1445 | "node_modules/ignore": { 1446 | "version": "7.0.5", 1447 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", 1448 | "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", 1449 | "dev": true, 1450 | "license": "MIT", 1451 | "engines": { 1452 | "node": ">= 4" 1453 | } 1454 | }, 1455 | "node_modules/ini": { 1456 | "version": "4.1.3", 1457 | "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", 1458 | "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", 1459 | "dev": true, 1460 | "license": "ISC", 1461 | "engines": { 1462 | "node": "^14.17.0 || ^16.13.0 || >=18.0.0" 1463 | } 1464 | }, 1465 | "node_modules/is-alphabetical": { 1466 | "version": "2.0.1", 1467 | "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", 1468 | "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", 1469 | "dev": true, 1470 | "license": "MIT", 1471 | "funding": { 1472 | "type": "github", 1473 | "url": "https://github.com/sponsors/wooorm" 1474 | } 1475 | }, 1476 | "node_modules/is-alphanumerical": { 1477 | "version": "2.0.1", 1478 | "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", 1479 | "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", 1480 | "dev": true, 1481 | "license": "MIT", 1482 | "dependencies": { 1483 | "is-alphabetical": "^2.0.0", 1484 | "is-decimal": "^2.0.0" 1485 | }, 1486 | "funding": { 1487 | "type": "github", 1488 | "url": "https://github.com/sponsors/wooorm" 1489 | } 1490 | }, 1491 | "node_modules/is-decimal": { 1492 | "version": "2.0.1", 1493 | "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", 1494 | "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", 1495 | "dev": true, 1496 | "license": "MIT", 1497 | "funding": { 1498 | "type": "github", 1499 | "url": "https://github.com/sponsors/wooorm" 1500 | } 1501 | }, 1502 | "node_modules/is-extglob": { 1503 | "version": "2.1.1", 1504 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1505 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1506 | "dev": true, 1507 | "license": "MIT", 1508 | "engines": { 1509 | "node": ">=0.10.0" 1510 | } 1511 | }, 1512 | "node_modules/is-fullwidth-code-point": { 1513 | "version": "3.0.0", 1514 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1515 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1516 | "dev": true, 1517 | "license": "MIT", 1518 | "engines": { 1519 | "node": ">=8" 1520 | } 1521 | }, 1522 | "node_modules/is-glob": { 1523 | "version": "4.0.3", 1524 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1525 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1526 | "dev": true, 1527 | "license": "MIT", 1528 | "dependencies": { 1529 | "is-extglob": "^2.1.1" 1530 | }, 1531 | "engines": { 1532 | "node": ">=0.10.0" 1533 | } 1534 | }, 1535 | "node_modules/is-hexadecimal": { 1536 | "version": "2.0.1", 1537 | "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", 1538 | "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", 1539 | "dev": true, 1540 | "license": "MIT", 1541 | "funding": { 1542 | "type": "github", 1543 | "url": "https://github.com/sponsors/wooorm" 1544 | } 1545 | }, 1546 | "node_modules/is-number": { 1547 | "version": "7.0.0", 1548 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1549 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1550 | "dev": true, 1551 | "license": "MIT", 1552 | "engines": { 1553 | "node": ">=0.12.0" 1554 | } 1555 | }, 1556 | "node_modules/isexe": { 1557 | "version": "2.0.0", 1558 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1559 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1560 | "dev": true, 1561 | "license": "ISC" 1562 | }, 1563 | "node_modules/jackspeak": { 1564 | "version": "4.1.1", 1565 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", 1566 | "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", 1567 | "dev": true, 1568 | "license": "BlueOak-1.0.0", 1569 | "dependencies": { 1570 | "@isaacs/cliui": "^8.0.2" 1571 | }, 1572 | "engines": { 1573 | "node": "20 || >=22" 1574 | }, 1575 | "funding": { 1576 | "url": "https://github.com/sponsors/isaacs" 1577 | } 1578 | }, 1579 | "node_modules/jiti": { 1580 | "version": "2.4.2", 1581 | "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", 1582 | "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", 1583 | "dev": true, 1584 | "license": "MIT", 1585 | "bin": { 1586 | "jiti": "lib/jiti-cli.mjs" 1587 | } 1588 | }, 1589 | "node_modules/js-yaml": { 1590 | "version": "4.1.0", 1591 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1592 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1593 | "dev": true, 1594 | "license": "MIT", 1595 | "dependencies": { 1596 | "argparse": "^2.0.1" 1597 | }, 1598 | "bin": { 1599 | "js-yaml": "bin/js-yaml.js" 1600 | } 1601 | }, 1602 | "node_modules/jsonc-parser": { 1603 | "version": "3.3.1", 1604 | "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", 1605 | "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", 1606 | "dev": true, 1607 | "license": "MIT" 1608 | }, 1609 | "node_modules/jsonpointer": { 1610 | "version": "5.0.1", 1611 | "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", 1612 | "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", 1613 | "dev": true, 1614 | "license": "MIT", 1615 | "engines": { 1616 | "node": ">=0.10.0" 1617 | } 1618 | }, 1619 | "node_modules/katex": { 1620 | "version": "0.16.22", 1621 | "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", 1622 | "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", 1623 | "dev": true, 1624 | "funding": [ 1625 | "https://opencollective.com/katex", 1626 | "https://github.com/sponsors/katex" 1627 | ], 1628 | "license": "MIT", 1629 | "dependencies": { 1630 | "commander": "^8.3.0" 1631 | }, 1632 | "bin": { 1633 | "katex": "cli.js" 1634 | } 1635 | }, 1636 | "node_modules/katex/node_modules/commander": { 1637 | "version": "8.3.0", 1638 | "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", 1639 | "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", 1640 | "dev": true, 1641 | "license": "MIT", 1642 | "engines": { 1643 | "node": ">= 12" 1644 | } 1645 | }, 1646 | "node_modules/knip": { 1647 | "version": "5.60.0", 1648 | "resolved": "https://registry.npmjs.org/knip/-/knip-5.60.0.tgz", 1649 | "integrity": "sha512-r6oIbaV0Ztz/7DKe1voxg2O5IRhLi9Q0GjhplfRqUZ1gvTChew6ywmLzehuaXIHVKkPs8LF5UKOxFlc93RKzow==", 1650 | "dev": true, 1651 | "funding": [ 1652 | { 1653 | "type": "github", 1654 | "url": "https://github.com/sponsors/webpro" 1655 | }, 1656 | { 1657 | "type": "opencollective", 1658 | "url": "https://opencollective.com/knip" 1659 | }, 1660 | { 1661 | "type": "polar", 1662 | "url": "https://polar.sh/webpro-nl" 1663 | } 1664 | ], 1665 | "license": "ISC", 1666 | "dependencies": { 1667 | "@nodelib/fs.walk": "^1.2.3", 1668 | "fast-glob": "^3.3.3", 1669 | "formatly": "^0.2.4", 1670 | "jiti": "^2.4.2", 1671 | "js-yaml": "^4.1.0", 1672 | "minimist": "^1.2.8", 1673 | "oxc-resolver": "^11.1.0", 1674 | "picocolors": "^1.1.1", 1675 | "picomatch": "^4.0.1", 1676 | "smol-toml": "^1.3.4", 1677 | "strip-json-comments": "5.0.2", 1678 | "zod": "^3.22.4", 1679 | "zod-validation-error": "^3.0.3" 1680 | }, 1681 | "bin": { 1682 | "knip": "bin/knip.js", 1683 | "knip-bun": "bin/knip-bun.js" 1684 | }, 1685 | "engines": { 1686 | "node": ">=18.18.0" 1687 | }, 1688 | "peerDependencies": { 1689 | "@types/node": ">=18", 1690 | "typescript": ">=5.0.4" 1691 | } 1692 | }, 1693 | "node_modules/linkify-it": { 1694 | "version": "5.0.0", 1695 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", 1696 | "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", 1697 | "dev": true, 1698 | "license": "MIT", 1699 | "dependencies": { 1700 | "uc.micro": "^2.0.0" 1701 | } 1702 | }, 1703 | "node_modules/lru-cache": { 1704 | "version": "11.1.0", 1705 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", 1706 | "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", 1707 | "dev": true, 1708 | "license": "ISC", 1709 | "engines": { 1710 | "node": "20 || >=22" 1711 | } 1712 | }, 1713 | "node_modules/markdown-it": { 1714 | "version": "14.1.0", 1715 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", 1716 | "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", 1717 | "dev": true, 1718 | "license": "MIT", 1719 | "dependencies": { 1720 | "argparse": "^2.0.1", 1721 | "entities": "^4.4.0", 1722 | "linkify-it": "^5.0.0", 1723 | "mdurl": "^2.0.0", 1724 | "punycode.js": "^2.3.1", 1725 | "uc.micro": "^2.1.0" 1726 | }, 1727 | "bin": { 1728 | "markdown-it": "bin/markdown-it.mjs" 1729 | } 1730 | }, 1731 | "node_modules/markdownlint": { 1732 | "version": "0.38.0", 1733 | "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.38.0.tgz", 1734 | "integrity": "sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ==", 1735 | "dev": true, 1736 | "license": "MIT", 1737 | "dependencies": { 1738 | "micromark": "4.0.2", 1739 | "micromark-core-commonmark": "2.0.3", 1740 | "micromark-extension-directive": "4.0.0", 1741 | "micromark-extension-gfm-autolink-literal": "2.1.0", 1742 | "micromark-extension-gfm-footnote": "2.1.0", 1743 | "micromark-extension-gfm-table": "2.1.1", 1744 | "micromark-extension-math": "3.1.0", 1745 | "micromark-util-types": "2.0.2" 1746 | }, 1747 | "engines": { 1748 | "node": ">=20" 1749 | }, 1750 | "funding": { 1751 | "url": "https://github.com/sponsors/DavidAnson" 1752 | } 1753 | }, 1754 | "node_modules/markdownlint-cli": { 1755 | "version": "0.45.0", 1756 | "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.45.0.tgz", 1757 | "integrity": "sha512-GiWr7GfJLVfcopL3t3pLumXCYs8sgWppjIA1F/Cc3zIMgD3tmkpyZ1xkm1Tej8mw53B93JsDjgA3KOftuYcfOw==", 1758 | "dev": true, 1759 | "license": "MIT", 1760 | "dependencies": { 1761 | "commander": "~13.1.0", 1762 | "glob": "~11.0.2", 1763 | "ignore": "~7.0.4", 1764 | "js-yaml": "~4.1.0", 1765 | "jsonc-parser": "~3.3.1", 1766 | "jsonpointer": "~5.0.1", 1767 | "markdown-it": "~14.1.0", 1768 | "markdownlint": "~0.38.0", 1769 | "minimatch": "~10.0.1", 1770 | "run-con": "~1.3.2", 1771 | "smol-toml": "~1.3.4" 1772 | }, 1773 | "bin": { 1774 | "markdownlint": "markdownlint.js" 1775 | }, 1776 | "engines": { 1777 | "node": ">=20" 1778 | } 1779 | }, 1780 | "node_modules/mdurl": { 1781 | "version": "2.0.0", 1782 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", 1783 | "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", 1784 | "dev": true, 1785 | "license": "MIT" 1786 | }, 1787 | "node_modules/merge2": { 1788 | "version": "1.4.1", 1789 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1790 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1791 | "dev": true, 1792 | "license": "MIT", 1793 | "engines": { 1794 | "node": ">= 8" 1795 | } 1796 | }, 1797 | "node_modules/micromark": { 1798 | "version": "4.0.2", 1799 | "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", 1800 | "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", 1801 | "dev": true, 1802 | "funding": [ 1803 | { 1804 | "type": "GitHub Sponsors", 1805 | "url": "https://github.com/sponsors/unifiedjs" 1806 | }, 1807 | { 1808 | "type": "OpenCollective", 1809 | "url": "https://opencollective.com/unified" 1810 | } 1811 | ], 1812 | "license": "MIT", 1813 | "dependencies": { 1814 | "@types/debug": "^4.0.0", 1815 | "debug": "^4.0.0", 1816 | "decode-named-character-reference": "^1.0.0", 1817 | "devlop": "^1.0.0", 1818 | "micromark-core-commonmark": "^2.0.0", 1819 | "micromark-factory-space": "^2.0.0", 1820 | "micromark-util-character": "^2.0.0", 1821 | "micromark-util-chunked": "^2.0.0", 1822 | "micromark-util-combine-extensions": "^2.0.0", 1823 | "micromark-util-decode-numeric-character-reference": "^2.0.0", 1824 | "micromark-util-encode": "^2.0.0", 1825 | "micromark-util-normalize-identifier": "^2.0.0", 1826 | "micromark-util-resolve-all": "^2.0.0", 1827 | "micromark-util-sanitize-uri": "^2.0.0", 1828 | "micromark-util-subtokenize": "^2.0.0", 1829 | "micromark-util-symbol": "^2.0.0", 1830 | "micromark-util-types": "^2.0.0" 1831 | } 1832 | }, 1833 | "node_modules/micromark-core-commonmark": { 1834 | "version": "2.0.3", 1835 | "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", 1836 | "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", 1837 | "dev": true, 1838 | "funding": [ 1839 | { 1840 | "type": "GitHub Sponsors", 1841 | "url": "https://github.com/sponsors/unifiedjs" 1842 | }, 1843 | { 1844 | "type": "OpenCollective", 1845 | "url": "https://opencollective.com/unified" 1846 | } 1847 | ], 1848 | "license": "MIT", 1849 | "dependencies": { 1850 | "decode-named-character-reference": "^1.0.0", 1851 | "devlop": "^1.0.0", 1852 | "micromark-factory-destination": "^2.0.0", 1853 | "micromark-factory-label": "^2.0.0", 1854 | "micromark-factory-space": "^2.0.0", 1855 | "micromark-factory-title": "^2.0.0", 1856 | "micromark-factory-whitespace": "^2.0.0", 1857 | "micromark-util-character": "^2.0.0", 1858 | "micromark-util-chunked": "^2.0.0", 1859 | "micromark-util-classify-character": "^2.0.0", 1860 | "micromark-util-html-tag-name": "^2.0.0", 1861 | "micromark-util-normalize-identifier": "^2.0.0", 1862 | "micromark-util-resolve-all": "^2.0.0", 1863 | "micromark-util-subtokenize": "^2.0.0", 1864 | "micromark-util-symbol": "^2.0.0", 1865 | "micromark-util-types": "^2.0.0" 1866 | } 1867 | }, 1868 | "node_modules/micromark-extension-directive": { 1869 | "version": "4.0.0", 1870 | "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", 1871 | "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", 1872 | "dev": true, 1873 | "license": "MIT", 1874 | "dependencies": { 1875 | "devlop": "^1.0.0", 1876 | "micromark-factory-space": "^2.0.0", 1877 | "micromark-factory-whitespace": "^2.0.0", 1878 | "micromark-util-character": "^2.0.0", 1879 | "micromark-util-symbol": "^2.0.0", 1880 | "micromark-util-types": "^2.0.0", 1881 | "parse-entities": "^4.0.0" 1882 | }, 1883 | "funding": { 1884 | "type": "opencollective", 1885 | "url": "https://opencollective.com/unified" 1886 | } 1887 | }, 1888 | "node_modules/micromark-extension-gfm-autolink-literal": { 1889 | "version": "2.1.0", 1890 | "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", 1891 | "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", 1892 | "dev": true, 1893 | "license": "MIT", 1894 | "dependencies": { 1895 | "micromark-util-character": "^2.0.0", 1896 | "micromark-util-sanitize-uri": "^2.0.0", 1897 | "micromark-util-symbol": "^2.0.0", 1898 | "micromark-util-types": "^2.0.0" 1899 | }, 1900 | "funding": { 1901 | "type": "opencollective", 1902 | "url": "https://opencollective.com/unified" 1903 | } 1904 | }, 1905 | "node_modules/micromark-extension-gfm-footnote": { 1906 | "version": "2.1.0", 1907 | "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", 1908 | "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", 1909 | "dev": true, 1910 | "license": "MIT", 1911 | "dependencies": { 1912 | "devlop": "^1.0.0", 1913 | "micromark-core-commonmark": "^2.0.0", 1914 | "micromark-factory-space": "^2.0.0", 1915 | "micromark-util-character": "^2.0.0", 1916 | "micromark-util-normalize-identifier": "^2.0.0", 1917 | "micromark-util-sanitize-uri": "^2.0.0", 1918 | "micromark-util-symbol": "^2.0.0", 1919 | "micromark-util-types": "^2.0.0" 1920 | }, 1921 | "funding": { 1922 | "type": "opencollective", 1923 | "url": "https://opencollective.com/unified" 1924 | } 1925 | }, 1926 | "node_modules/micromark-extension-gfm-table": { 1927 | "version": "2.1.1", 1928 | "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", 1929 | "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", 1930 | "dev": true, 1931 | "license": "MIT", 1932 | "dependencies": { 1933 | "devlop": "^1.0.0", 1934 | "micromark-factory-space": "^2.0.0", 1935 | "micromark-util-character": "^2.0.0", 1936 | "micromark-util-symbol": "^2.0.0", 1937 | "micromark-util-types": "^2.0.0" 1938 | }, 1939 | "funding": { 1940 | "type": "opencollective", 1941 | "url": "https://opencollective.com/unified" 1942 | } 1943 | }, 1944 | "node_modules/micromark-extension-math": { 1945 | "version": "3.1.0", 1946 | "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", 1947 | "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", 1948 | "dev": true, 1949 | "license": "MIT", 1950 | "dependencies": { 1951 | "@types/katex": "^0.16.0", 1952 | "devlop": "^1.0.0", 1953 | "katex": "^0.16.0", 1954 | "micromark-factory-space": "^2.0.0", 1955 | "micromark-util-character": "^2.0.0", 1956 | "micromark-util-symbol": "^2.0.0", 1957 | "micromark-util-types": "^2.0.0" 1958 | }, 1959 | "funding": { 1960 | "type": "opencollective", 1961 | "url": "https://opencollective.com/unified" 1962 | } 1963 | }, 1964 | "node_modules/micromark-factory-destination": { 1965 | "version": "2.0.1", 1966 | "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", 1967 | "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", 1968 | "dev": true, 1969 | "funding": [ 1970 | { 1971 | "type": "GitHub Sponsors", 1972 | "url": "https://github.com/sponsors/unifiedjs" 1973 | }, 1974 | { 1975 | "type": "OpenCollective", 1976 | "url": "https://opencollective.com/unified" 1977 | } 1978 | ], 1979 | "license": "MIT", 1980 | "dependencies": { 1981 | "micromark-util-character": "^2.0.0", 1982 | "micromark-util-symbol": "^2.0.0", 1983 | "micromark-util-types": "^2.0.0" 1984 | } 1985 | }, 1986 | "node_modules/micromark-factory-label": { 1987 | "version": "2.0.1", 1988 | "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", 1989 | "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", 1990 | "dev": true, 1991 | "funding": [ 1992 | { 1993 | "type": "GitHub Sponsors", 1994 | "url": "https://github.com/sponsors/unifiedjs" 1995 | }, 1996 | { 1997 | "type": "OpenCollective", 1998 | "url": "https://opencollective.com/unified" 1999 | } 2000 | ], 2001 | "license": "MIT", 2002 | "dependencies": { 2003 | "devlop": "^1.0.0", 2004 | "micromark-util-character": "^2.0.0", 2005 | "micromark-util-symbol": "^2.0.0", 2006 | "micromark-util-types": "^2.0.0" 2007 | } 2008 | }, 2009 | "node_modules/micromark-factory-space": { 2010 | "version": "2.0.1", 2011 | "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", 2012 | "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", 2013 | "dev": true, 2014 | "funding": [ 2015 | { 2016 | "type": "GitHub Sponsors", 2017 | "url": "https://github.com/sponsors/unifiedjs" 2018 | }, 2019 | { 2020 | "type": "OpenCollective", 2021 | "url": "https://opencollective.com/unified" 2022 | } 2023 | ], 2024 | "license": "MIT", 2025 | "dependencies": { 2026 | "micromark-util-character": "^2.0.0", 2027 | "micromark-util-types": "^2.0.0" 2028 | } 2029 | }, 2030 | "node_modules/micromark-factory-title": { 2031 | "version": "2.0.1", 2032 | "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", 2033 | "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", 2034 | "dev": true, 2035 | "funding": [ 2036 | { 2037 | "type": "GitHub Sponsors", 2038 | "url": "https://github.com/sponsors/unifiedjs" 2039 | }, 2040 | { 2041 | "type": "OpenCollective", 2042 | "url": "https://opencollective.com/unified" 2043 | } 2044 | ], 2045 | "license": "MIT", 2046 | "dependencies": { 2047 | "micromark-factory-space": "^2.0.0", 2048 | "micromark-util-character": "^2.0.0", 2049 | "micromark-util-symbol": "^2.0.0", 2050 | "micromark-util-types": "^2.0.0" 2051 | } 2052 | }, 2053 | "node_modules/micromark-factory-whitespace": { 2054 | "version": "2.0.1", 2055 | "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", 2056 | "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", 2057 | "dev": true, 2058 | "funding": [ 2059 | { 2060 | "type": "GitHub Sponsors", 2061 | "url": "https://github.com/sponsors/unifiedjs" 2062 | }, 2063 | { 2064 | "type": "OpenCollective", 2065 | "url": "https://opencollective.com/unified" 2066 | } 2067 | ], 2068 | "license": "MIT", 2069 | "dependencies": { 2070 | "micromark-factory-space": "^2.0.0", 2071 | "micromark-util-character": "^2.0.0", 2072 | "micromark-util-symbol": "^2.0.0", 2073 | "micromark-util-types": "^2.0.0" 2074 | } 2075 | }, 2076 | "node_modules/micromark-util-character": { 2077 | "version": "2.1.1", 2078 | "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", 2079 | "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", 2080 | "dev": true, 2081 | "funding": [ 2082 | { 2083 | "type": "GitHub Sponsors", 2084 | "url": "https://github.com/sponsors/unifiedjs" 2085 | }, 2086 | { 2087 | "type": "OpenCollective", 2088 | "url": "https://opencollective.com/unified" 2089 | } 2090 | ], 2091 | "license": "MIT", 2092 | "dependencies": { 2093 | "micromark-util-symbol": "^2.0.0", 2094 | "micromark-util-types": "^2.0.0" 2095 | } 2096 | }, 2097 | "node_modules/micromark-util-chunked": { 2098 | "version": "2.0.1", 2099 | "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", 2100 | "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", 2101 | "dev": true, 2102 | "funding": [ 2103 | { 2104 | "type": "GitHub Sponsors", 2105 | "url": "https://github.com/sponsors/unifiedjs" 2106 | }, 2107 | { 2108 | "type": "OpenCollective", 2109 | "url": "https://opencollective.com/unified" 2110 | } 2111 | ], 2112 | "license": "MIT", 2113 | "dependencies": { 2114 | "micromark-util-symbol": "^2.0.0" 2115 | } 2116 | }, 2117 | "node_modules/micromark-util-classify-character": { 2118 | "version": "2.0.1", 2119 | "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", 2120 | "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", 2121 | "dev": true, 2122 | "funding": [ 2123 | { 2124 | "type": "GitHub Sponsors", 2125 | "url": "https://github.com/sponsors/unifiedjs" 2126 | }, 2127 | { 2128 | "type": "OpenCollective", 2129 | "url": "https://opencollective.com/unified" 2130 | } 2131 | ], 2132 | "license": "MIT", 2133 | "dependencies": { 2134 | "micromark-util-character": "^2.0.0", 2135 | "micromark-util-symbol": "^2.0.0", 2136 | "micromark-util-types": "^2.0.0" 2137 | } 2138 | }, 2139 | "node_modules/micromark-util-combine-extensions": { 2140 | "version": "2.0.1", 2141 | "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", 2142 | "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", 2143 | "dev": true, 2144 | "funding": [ 2145 | { 2146 | "type": "GitHub Sponsors", 2147 | "url": "https://github.com/sponsors/unifiedjs" 2148 | }, 2149 | { 2150 | "type": "OpenCollective", 2151 | "url": "https://opencollective.com/unified" 2152 | } 2153 | ], 2154 | "license": "MIT", 2155 | "dependencies": { 2156 | "micromark-util-chunked": "^2.0.0", 2157 | "micromark-util-types": "^2.0.0" 2158 | } 2159 | }, 2160 | "node_modules/micromark-util-decode-numeric-character-reference": { 2161 | "version": "2.0.2", 2162 | "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", 2163 | "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", 2164 | "dev": true, 2165 | "funding": [ 2166 | { 2167 | "type": "GitHub Sponsors", 2168 | "url": "https://github.com/sponsors/unifiedjs" 2169 | }, 2170 | { 2171 | "type": "OpenCollective", 2172 | "url": "https://opencollective.com/unified" 2173 | } 2174 | ], 2175 | "license": "MIT", 2176 | "dependencies": { 2177 | "micromark-util-symbol": "^2.0.0" 2178 | } 2179 | }, 2180 | "node_modules/micromark-util-encode": { 2181 | "version": "2.0.1", 2182 | "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", 2183 | "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", 2184 | "dev": true, 2185 | "funding": [ 2186 | { 2187 | "type": "GitHub Sponsors", 2188 | "url": "https://github.com/sponsors/unifiedjs" 2189 | }, 2190 | { 2191 | "type": "OpenCollective", 2192 | "url": "https://opencollective.com/unified" 2193 | } 2194 | ], 2195 | "license": "MIT" 2196 | }, 2197 | "node_modules/micromark-util-html-tag-name": { 2198 | "version": "2.0.1", 2199 | "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", 2200 | "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", 2201 | "dev": true, 2202 | "funding": [ 2203 | { 2204 | "type": "GitHub Sponsors", 2205 | "url": "https://github.com/sponsors/unifiedjs" 2206 | }, 2207 | { 2208 | "type": "OpenCollective", 2209 | "url": "https://opencollective.com/unified" 2210 | } 2211 | ], 2212 | "license": "MIT" 2213 | }, 2214 | "node_modules/micromark-util-normalize-identifier": { 2215 | "version": "2.0.1", 2216 | "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", 2217 | "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", 2218 | "dev": true, 2219 | "funding": [ 2220 | { 2221 | "type": "GitHub Sponsors", 2222 | "url": "https://github.com/sponsors/unifiedjs" 2223 | }, 2224 | { 2225 | "type": "OpenCollective", 2226 | "url": "https://opencollective.com/unified" 2227 | } 2228 | ], 2229 | "license": "MIT", 2230 | "dependencies": { 2231 | "micromark-util-symbol": "^2.0.0" 2232 | } 2233 | }, 2234 | "node_modules/micromark-util-resolve-all": { 2235 | "version": "2.0.1", 2236 | "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", 2237 | "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", 2238 | "dev": true, 2239 | "funding": [ 2240 | { 2241 | "type": "GitHub Sponsors", 2242 | "url": "https://github.com/sponsors/unifiedjs" 2243 | }, 2244 | { 2245 | "type": "OpenCollective", 2246 | "url": "https://opencollective.com/unified" 2247 | } 2248 | ], 2249 | "license": "MIT", 2250 | "dependencies": { 2251 | "micromark-util-types": "^2.0.0" 2252 | } 2253 | }, 2254 | "node_modules/micromark-util-sanitize-uri": { 2255 | "version": "2.0.1", 2256 | "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", 2257 | "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", 2258 | "dev": true, 2259 | "funding": [ 2260 | { 2261 | "type": "GitHub Sponsors", 2262 | "url": "https://github.com/sponsors/unifiedjs" 2263 | }, 2264 | { 2265 | "type": "OpenCollective", 2266 | "url": "https://opencollective.com/unified" 2267 | } 2268 | ], 2269 | "license": "MIT", 2270 | "dependencies": { 2271 | "micromark-util-character": "^2.0.0", 2272 | "micromark-util-encode": "^2.0.0", 2273 | "micromark-util-symbol": "^2.0.0" 2274 | } 2275 | }, 2276 | "node_modules/micromark-util-subtokenize": { 2277 | "version": "2.1.0", 2278 | "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", 2279 | "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", 2280 | "dev": true, 2281 | "funding": [ 2282 | { 2283 | "type": "GitHub Sponsors", 2284 | "url": "https://github.com/sponsors/unifiedjs" 2285 | }, 2286 | { 2287 | "type": "OpenCollective", 2288 | "url": "https://opencollective.com/unified" 2289 | } 2290 | ], 2291 | "license": "MIT", 2292 | "dependencies": { 2293 | "devlop": "^1.0.0", 2294 | "micromark-util-chunked": "^2.0.0", 2295 | "micromark-util-symbol": "^2.0.0", 2296 | "micromark-util-types": "^2.0.0" 2297 | } 2298 | }, 2299 | "node_modules/micromark-util-symbol": { 2300 | "version": "2.0.1", 2301 | "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", 2302 | "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", 2303 | "dev": true, 2304 | "funding": [ 2305 | { 2306 | "type": "GitHub Sponsors", 2307 | "url": "https://github.com/sponsors/unifiedjs" 2308 | }, 2309 | { 2310 | "type": "OpenCollective", 2311 | "url": "https://opencollective.com/unified" 2312 | } 2313 | ], 2314 | "license": "MIT" 2315 | }, 2316 | "node_modules/micromark-util-types": { 2317 | "version": "2.0.2", 2318 | "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", 2319 | "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", 2320 | "dev": true, 2321 | "funding": [ 2322 | { 2323 | "type": "GitHub Sponsors", 2324 | "url": "https://github.com/sponsors/unifiedjs" 2325 | }, 2326 | { 2327 | "type": "OpenCollective", 2328 | "url": "https://opencollective.com/unified" 2329 | } 2330 | ], 2331 | "license": "MIT" 2332 | }, 2333 | "node_modules/micromatch": { 2334 | "version": "4.0.8", 2335 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 2336 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 2337 | "dev": true, 2338 | "license": "MIT", 2339 | "dependencies": { 2340 | "braces": "^3.0.3", 2341 | "picomatch": "^2.3.1" 2342 | }, 2343 | "engines": { 2344 | "node": ">=8.6" 2345 | } 2346 | }, 2347 | "node_modules/micromatch/node_modules/picomatch": { 2348 | "version": "2.3.1", 2349 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 2350 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 2351 | "dev": true, 2352 | "license": "MIT", 2353 | "engines": { 2354 | "node": ">=8.6" 2355 | }, 2356 | "funding": { 2357 | "url": "https://github.com/sponsors/jonschlinkert" 2358 | } 2359 | }, 2360 | "node_modules/minimatch": { 2361 | "version": "10.0.1", 2362 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", 2363 | "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", 2364 | "dev": true, 2365 | "license": "ISC", 2366 | "dependencies": { 2367 | "brace-expansion": "^2.0.1" 2368 | }, 2369 | "engines": { 2370 | "node": "20 || >=22" 2371 | }, 2372 | "funding": { 2373 | "url": "https://github.com/sponsors/isaacs" 2374 | } 2375 | }, 2376 | "node_modules/minimist": { 2377 | "version": "1.2.8", 2378 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 2379 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 2380 | "dev": true, 2381 | "license": "MIT", 2382 | "funding": { 2383 | "url": "https://github.com/sponsors/ljharb" 2384 | } 2385 | }, 2386 | "node_modules/minipass": { 2387 | "version": "7.1.2", 2388 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 2389 | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 2390 | "dev": true, 2391 | "license": "ISC", 2392 | "engines": { 2393 | "node": ">=16 || 14 >=14.17" 2394 | } 2395 | }, 2396 | "node_modules/moment": { 2397 | "version": "2.29.4", 2398 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", 2399 | "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", 2400 | "dev": true, 2401 | "license": "MIT", 2402 | "engines": { 2403 | "node": "*" 2404 | } 2405 | }, 2406 | "node_modules/ms": { 2407 | "version": "2.1.3", 2408 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2409 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 2410 | "dev": true, 2411 | "license": "MIT" 2412 | }, 2413 | "node_modules/obsidian": { 2414 | "version": "1.8.7", 2415 | "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.8.7.tgz", 2416 | "integrity": "sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==", 2417 | "dev": true, 2418 | "license": "MIT", 2419 | "dependencies": { 2420 | "@types/codemirror": "5.60.8", 2421 | "moment": "2.29.4" 2422 | }, 2423 | "peerDependencies": { 2424 | "@codemirror/state": "^6.0.0", 2425 | "@codemirror/view": "^6.0.0" 2426 | } 2427 | }, 2428 | "node_modules/oxc-resolver": { 2429 | "version": "11.1.0", 2430 | "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.1.0.tgz", 2431 | "integrity": "sha512-/W/9O6m7lkDJMIXtXvNKXE6THIoNWwstsKpR/R8+yI9e7vC9wu92MDqLBxkgckZ2fTFmKEjozTxVibHBaRUgCA==", 2432 | "dev": true, 2433 | "license": "MIT", 2434 | "funding": { 2435 | "url": "https://github.com/sponsors/Boshen" 2436 | }, 2437 | "optionalDependencies": { 2438 | "@oxc-resolver/binding-darwin-arm64": "11.1.0", 2439 | "@oxc-resolver/binding-darwin-x64": "11.1.0", 2440 | "@oxc-resolver/binding-freebsd-x64": "11.1.0", 2441 | "@oxc-resolver/binding-linux-arm-gnueabihf": "11.1.0", 2442 | "@oxc-resolver/binding-linux-arm64-gnu": "11.1.0", 2443 | "@oxc-resolver/binding-linux-arm64-musl": "11.1.0", 2444 | "@oxc-resolver/binding-linux-riscv64-gnu": "11.1.0", 2445 | "@oxc-resolver/binding-linux-s390x-gnu": "11.1.0", 2446 | "@oxc-resolver/binding-linux-x64-gnu": "11.1.0", 2447 | "@oxc-resolver/binding-linux-x64-musl": "11.1.0", 2448 | "@oxc-resolver/binding-wasm32-wasi": "11.1.0", 2449 | "@oxc-resolver/binding-win32-arm64-msvc": "11.1.0", 2450 | "@oxc-resolver/binding-win32-x64-msvc": "11.1.0" 2451 | } 2452 | }, 2453 | "node_modules/package-json-from-dist": { 2454 | "version": "1.0.1", 2455 | "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", 2456 | "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", 2457 | "dev": true, 2458 | "license": "BlueOak-1.0.0" 2459 | }, 2460 | "node_modules/parse-entities": { 2461 | "version": "4.0.2", 2462 | "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", 2463 | "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", 2464 | "dev": true, 2465 | "license": "MIT", 2466 | "dependencies": { 2467 | "@types/unist": "^2.0.0", 2468 | "character-entities-legacy": "^3.0.0", 2469 | "character-reference-invalid": "^2.0.0", 2470 | "decode-named-character-reference": "^1.0.0", 2471 | "is-alphanumerical": "^2.0.0", 2472 | "is-decimal": "^2.0.0", 2473 | "is-hexadecimal": "^2.0.0" 2474 | }, 2475 | "funding": { 2476 | "type": "github", 2477 | "url": "https://github.com/sponsors/wooorm" 2478 | } 2479 | }, 2480 | "node_modules/path-key": { 2481 | "version": "3.1.1", 2482 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 2483 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 2484 | "dev": true, 2485 | "license": "MIT", 2486 | "engines": { 2487 | "node": ">=8" 2488 | } 2489 | }, 2490 | "node_modules/path-scurry": { 2491 | "version": "2.0.0", 2492 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", 2493 | "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", 2494 | "dev": true, 2495 | "license": "BlueOak-1.0.0", 2496 | "dependencies": { 2497 | "lru-cache": "^11.0.0", 2498 | "minipass": "^7.1.2" 2499 | }, 2500 | "engines": { 2501 | "node": "20 || >=22" 2502 | }, 2503 | "funding": { 2504 | "url": "https://github.com/sponsors/isaacs" 2505 | } 2506 | }, 2507 | "node_modules/picocolors": { 2508 | "version": "1.1.1", 2509 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 2510 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 2511 | "dev": true, 2512 | "license": "ISC" 2513 | }, 2514 | "node_modules/picomatch": { 2515 | "version": "4.0.2", 2516 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 2517 | "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 2518 | "dev": true, 2519 | "license": "MIT", 2520 | "engines": { 2521 | "node": ">=12" 2522 | }, 2523 | "funding": { 2524 | "url": "https://github.com/sponsors/jonschlinkert" 2525 | } 2526 | }, 2527 | "node_modules/punycode.js": { 2528 | "version": "2.3.1", 2529 | "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", 2530 | "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", 2531 | "dev": true, 2532 | "license": "MIT", 2533 | "engines": { 2534 | "node": ">=6" 2535 | } 2536 | }, 2537 | "node_modules/queue-microtask": { 2538 | "version": "1.2.3", 2539 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 2540 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 2541 | "dev": true, 2542 | "funding": [ 2543 | { 2544 | "type": "github", 2545 | "url": "https://github.com/sponsors/feross" 2546 | }, 2547 | { 2548 | "type": "patreon", 2549 | "url": "https://www.patreon.com/feross" 2550 | }, 2551 | { 2552 | "type": "consulting", 2553 | "url": "https://feross.org/support" 2554 | } 2555 | ], 2556 | "license": "MIT" 2557 | }, 2558 | "node_modules/reusify": { 2559 | "version": "1.1.0", 2560 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 2561 | "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 2562 | "dev": true, 2563 | "license": "MIT", 2564 | "engines": { 2565 | "iojs": ">=1.0.0", 2566 | "node": ">=0.10.0" 2567 | } 2568 | }, 2569 | "node_modules/run-con": { 2570 | "version": "1.3.2", 2571 | "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", 2572 | "integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==", 2573 | "dev": true, 2574 | "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", 2575 | "dependencies": { 2576 | "deep-extend": "^0.6.0", 2577 | "ini": "~4.1.0", 2578 | "minimist": "^1.2.8", 2579 | "strip-json-comments": "~3.1.1" 2580 | }, 2581 | "bin": { 2582 | "run-con": "cli.js" 2583 | } 2584 | }, 2585 | "node_modules/run-con/node_modules/strip-json-comments": { 2586 | "version": "3.1.1", 2587 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 2588 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 2589 | "dev": true, 2590 | "license": "MIT", 2591 | "engines": { 2592 | "node": ">=8" 2593 | }, 2594 | "funding": { 2595 | "url": "https://github.com/sponsors/sindresorhus" 2596 | } 2597 | }, 2598 | "node_modules/run-parallel": { 2599 | "version": "1.2.0", 2600 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 2601 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 2602 | "dev": true, 2603 | "funding": [ 2604 | { 2605 | "type": "github", 2606 | "url": "https://github.com/sponsors/feross" 2607 | }, 2608 | { 2609 | "type": "patreon", 2610 | "url": "https://www.patreon.com/feross" 2611 | }, 2612 | { 2613 | "type": "consulting", 2614 | "url": "https://feross.org/support" 2615 | } 2616 | ], 2617 | "license": "MIT", 2618 | "dependencies": { 2619 | "queue-microtask": "^1.2.2" 2620 | } 2621 | }, 2622 | "node_modules/shebang-command": { 2623 | "version": "2.0.0", 2624 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2625 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2626 | "dev": true, 2627 | "license": "MIT", 2628 | "dependencies": { 2629 | "shebang-regex": "^3.0.0" 2630 | }, 2631 | "engines": { 2632 | "node": ">=8" 2633 | } 2634 | }, 2635 | "node_modules/shebang-regex": { 2636 | "version": "3.0.0", 2637 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2638 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2639 | "dev": true, 2640 | "license": "MIT", 2641 | "engines": { 2642 | "node": ">=8" 2643 | } 2644 | }, 2645 | "node_modules/signal-exit": { 2646 | "version": "4.1.0", 2647 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 2648 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 2649 | "dev": true, 2650 | "license": "ISC", 2651 | "engines": { 2652 | "node": ">=14" 2653 | }, 2654 | "funding": { 2655 | "url": "https://github.com/sponsors/isaacs" 2656 | } 2657 | }, 2658 | "node_modules/smol-toml": { 2659 | "version": "1.3.4", 2660 | "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.4.tgz", 2661 | "integrity": "sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==", 2662 | "dev": true, 2663 | "license": "BSD-3-Clause", 2664 | "engines": { 2665 | "node": ">= 18" 2666 | }, 2667 | "funding": { 2668 | "url": "https://github.com/sponsors/cyyynthia" 2669 | } 2670 | }, 2671 | "node_modules/string-width": { 2672 | "version": "5.1.2", 2673 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 2674 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 2675 | "dev": true, 2676 | "license": "MIT", 2677 | "dependencies": { 2678 | "eastasianwidth": "^0.2.0", 2679 | "emoji-regex": "^9.2.2", 2680 | "strip-ansi": "^7.0.1" 2681 | }, 2682 | "engines": { 2683 | "node": ">=12" 2684 | }, 2685 | "funding": { 2686 | "url": "https://github.com/sponsors/sindresorhus" 2687 | } 2688 | }, 2689 | "node_modules/string-width-cjs": { 2690 | "name": "string-width", 2691 | "version": "4.2.3", 2692 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 2693 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 2694 | "dev": true, 2695 | "license": "MIT", 2696 | "dependencies": { 2697 | "emoji-regex": "^8.0.0", 2698 | "is-fullwidth-code-point": "^3.0.0", 2699 | "strip-ansi": "^6.0.1" 2700 | }, 2701 | "engines": { 2702 | "node": ">=8" 2703 | } 2704 | }, 2705 | "node_modules/string-width-cjs/node_modules/ansi-regex": { 2706 | "version": "5.0.1", 2707 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 2708 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 2709 | "dev": true, 2710 | "license": "MIT", 2711 | "engines": { 2712 | "node": ">=8" 2713 | } 2714 | }, 2715 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 2716 | "version": "8.0.0", 2717 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 2718 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 2719 | "dev": true, 2720 | "license": "MIT" 2721 | }, 2722 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 2723 | "version": "6.0.1", 2724 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2725 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2726 | "dev": true, 2727 | "license": "MIT", 2728 | "dependencies": { 2729 | "ansi-regex": "^5.0.1" 2730 | }, 2731 | "engines": { 2732 | "node": ">=8" 2733 | } 2734 | }, 2735 | "node_modules/strip-ansi": { 2736 | "version": "7.1.0", 2737 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 2738 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 2739 | "dev": true, 2740 | "license": "MIT", 2741 | "dependencies": { 2742 | "ansi-regex": "^6.0.1" 2743 | }, 2744 | "engines": { 2745 | "node": ">=12" 2746 | }, 2747 | "funding": { 2748 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 2749 | } 2750 | }, 2751 | "node_modules/strip-ansi-cjs": { 2752 | "name": "strip-ansi", 2753 | "version": "6.0.1", 2754 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2755 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2756 | "dev": true, 2757 | "license": "MIT", 2758 | "dependencies": { 2759 | "ansi-regex": "^5.0.1" 2760 | }, 2761 | "engines": { 2762 | "node": ">=8" 2763 | } 2764 | }, 2765 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 2766 | "version": "5.0.1", 2767 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 2768 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 2769 | "dev": true, 2770 | "license": "MIT", 2771 | "engines": { 2772 | "node": ">=8" 2773 | } 2774 | }, 2775 | "node_modules/strip-json-comments": { 2776 | "version": "5.0.2", 2777 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.2.tgz", 2778 | "integrity": "sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==", 2779 | "dev": true, 2780 | "license": "MIT", 2781 | "engines": { 2782 | "node": ">=14.16" 2783 | }, 2784 | "funding": { 2785 | "url": "https://github.com/sponsors/sindresorhus" 2786 | } 2787 | }, 2788 | "node_modules/style-mod": { 2789 | "version": "4.1.2", 2790 | "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", 2791 | "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", 2792 | "dev": true, 2793 | "license": "MIT" 2794 | }, 2795 | "node_modules/to-regex-range": { 2796 | "version": "5.0.1", 2797 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2798 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2799 | "dev": true, 2800 | "license": "MIT", 2801 | "dependencies": { 2802 | "is-number": "^7.0.0" 2803 | }, 2804 | "engines": { 2805 | "node": ">=8.0" 2806 | } 2807 | }, 2808 | "node_modules/tslib": { 2809 | "version": "2.6.2", 2810 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", 2811 | "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", 2812 | "dev": true, 2813 | "license": "0BSD" 2814 | }, 2815 | "node_modules/typescript": { 2816 | "version": "5.8.3", 2817 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 2818 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 2819 | "dev": true, 2820 | "license": "Apache-2.0", 2821 | "bin": { 2822 | "tsc": "bin/tsc", 2823 | "tsserver": "bin/tsserver" 2824 | }, 2825 | "engines": { 2826 | "node": ">=14.17" 2827 | } 2828 | }, 2829 | "node_modules/uc.micro": { 2830 | "version": "2.1.0", 2831 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", 2832 | "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", 2833 | "dev": true, 2834 | "license": "MIT" 2835 | }, 2836 | "node_modules/undici-types": { 2837 | "version": "6.21.0", 2838 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 2839 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 2840 | "dev": true, 2841 | "license": "MIT" 2842 | }, 2843 | "node_modules/w3c-keyname": { 2844 | "version": "2.2.8", 2845 | "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", 2846 | "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", 2847 | "dev": true, 2848 | "license": "MIT" 2849 | }, 2850 | "node_modules/walk-up-path": { 2851 | "version": "4.0.0", 2852 | "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", 2853 | "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", 2854 | "dev": true, 2855 | "license": "ISC", 2856 | "engines": { 2857 | "node": "20 || >=22" 2858 | } 2859 | }, 2860 | "node_modules/which": { 2861 | "version": "2.0.2", 2862 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2863 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2864 | "dev": true, 2865 | "license": "ISC", 2866 | "dependencies": { 2867 | "isexe": "^2.0.0" 2868 | }, 2869 | "bin": { 2870 | "node-which": "bin/node-which" 2871 | }, 2872 | "engines": { 2873 | "node": ">= 8" 2874 | } 2875 | }, 2876 | "node_modules/wrap-ansi": { 2877 | "version": "8.1.0", 2878 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 2879 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 2880 | "dev": true, 2881 | "license": "MIT", 2882 | "dependencies": { 2883 | "ansi-styles": "^6.1.0", 2884 | "string-width": "^5.0.1", 2885 | "strip-ansi": "^7.0.1" 2886 | }, 2887 | "engines": { 2888 | "node": ">=12" 2889 | }, 2890 | "funding": { 2891 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 2892 | } 2893 | }, 2894 | "node_modules/wrap-ansi-cjs": { 2895 | "name": "wrap-ansi", 2896 | "version": "7.0.0", 2897 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 2898 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 2899 | "dev": true, 2900 | "license": "MIT", 2901 | "dependencies": { 2902 | "ansi-styles": "^4.0.0", 2903 | "string-width": "^4.1.0", 2904 | "strip-ansi": "^6.0.0" 2905 | }, 2906 | "engines": { 2907 | "node": ">=10" 2908 | }, 2909 | "funding": { 2910 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 2911 | } 2912 | }, 2913 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 2914 | "version": "5.0.1", 2915 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 2916 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 2917 | "dev": true, 2918 | "license": "MIT", 2919 | "engines": { 2920 | "node": ">=8" 2921 | } 2922 | }, 2923 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 2924 | "version": "4.3.0", 2925 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 2926 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 2927 | "dev": true, 2928 | "license": "MIT", 2929 | "dependencies": { 2930 | "color-convert": "^2.0.1" 2931 | }, 2932 | "engines": { 2933 | "node": ">=8" 2934 | }, 2935 | "funding": { 2936 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2937 | } 2938 | }, 2939 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 2940 | "version": "8.0.0", 2941 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 2942 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 2943 | "dev": true, 2944 | "license": "MIT" 2945 | }, 2946 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 2947 | "version": "4.2.3", 2948 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 2949 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 2950 | "dev": true, 2951 | "license": "MIT", 2952 | "dependencies": { 2953 | "emoji-regex": "^8.0.0", 2954 | "is-fullwidth-code-point": "^3.0.0", 2955 | "strip-ansi": "^6.0.1" 2956 | }, 2957 | "engines": { 2958 | "node": ">=8" 2959 | } 2960 | }, 2961 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 2962 | "version": "6.0.1", 2963 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2964 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2965 | "dev": true, 2966 | "license": "MIT", 2967 | "dependencies": { 2968 | "ansi-regex": "^5.0.1" 2969 | }, 2970 | "engines": { 2971 | "node": ">=8" 2972 | } 2973 | }, 2974 | "node_modules/zod": { 2975 | "version": "3.25.51", 2976 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.51.tgz", 2977 | "integrity": "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==", 2978 | "dev": true, 2979 | "license": "MIT", 2980 | "funding": { 2981 | "url": "https://github.com/sponsors/colinhacks" 2982 | } 2983 | }, 2984 | "node_modules/zod-validation-error": { 2985 | "version": "3.4.1", 2986 | "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.1.tgz", 2987 | "integrity": "sha512-1KP64yqDPQ3rupxNv7oXhf7KdhHHgaqbKuspVoiN93TT0xrBjql+Svjkdjq/Qh/7GSMmgQs3AfvBT0heE35thw==", 2988 | "dev": true, 2989 | "license": "MIT", 2990 | "engines": { 2991 | "node": ">=18.0.0" 2992 | }, 2993 | "peerDependencies": { 2994 | "zod": "^3.24.4" 2995 | } 2996 | } 2997 | } 2998 | } 2999 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Proofreader", 3 | "description": "AI-based proofreading and stylistic improvements for your writing. Changes are inserted as suggestions directly in the editor, similar to suggested changes in word processing apps.", 4 | "author": "Christopher Grieser", 5 | "version": "1.2.3", 6 | "keywords": [], 7 | "license": "MIT", 8 | "main": "main.js", 9 | "devDependencies": { 10 | "@biomejs/biome": "latest", 11 | "@types/diff": "^7.0.2", 12 | "@types/node": "^22.5.5", 13 | "builtin-modules": "^3.2.0", 14 | "esbuild": "^0.25.1", 15 | "knip": "^5.30.2", 16 | "markdownlint-cli": "latest", 17 | "obsidian": "latest", 18 | "tslib": "2.6.2", 19 | "typescript": "^5.6.2", 20 | "@codemirror/view": "latest" 21 | }, 22 | "dependencies": { 23 | "diff": "^7.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/accept-reject-suggestions.ts: -------------------------------------------------------------------------------- 1 | import { Editor, EditorPosition, Notice } from "obsidian"; 2 | 3 | //────────────────────────────────────────────────────────────────────────────── 4 | 5 | export function rejectChanges(str: string): string { 6 | return str.replace(/~~([^=~]+)~~/g, "$1").replace(/==[^=~]+==/g, ""); 7 | } 8 | 9 | function acceptChanges(str: string): string { 10 | return str.replace(/==([^=~]+)==/g, "$1").replace(/~~[^=~]+~~/g, ""); 11 | } 12 | 13 | //────────────────────────────────────────────────────────────────────────────── 14 | 15 | function removeMarkup(text: string, mode: "accept" | "reject"): string { 16 | const noMarkup = mode === "accept" ? acceptChanges(text) : rejectChanges(text); 17 | const cleanedUp = noMarkup 18 | .replace(/ {2}(?!\n)/g, " ") // double spaces (not EoL due to 2-space-rule) 19 | .replace(/ ([,.:;—–!?])/g, "$1"); // spaces preceding punctuation 20 | return cleanedUp; 21 | } 22 | 23 | // Manually calculating the visibility of an offset is necessary, since 24 | // CodeMirror's viewport includes extra margin around the visible area. 25 | function positionVisibleOnScreen(editor: Editor, pos: EditorPosition): boolean { 26 | const offset = editor.posToOffset(pos); 27 | 28 | const coord = editor.cm.coordsAtPos(offset); 29 | if (!coord) return false; // no coord = outside viewport 30 | 31 | // FIX typo-casting as `unknown` and then actual type, since Obsidian's 32 | // typing is incomplete, see https://forum.obsidian.md/t/api-bug-editor-getscrollinfo-is-typed-incorrectly/98886 33 | const view = editor.getScrollInfo() as unknown as { clientHeight: number }; 34 | 35 | const visible = coord.top < view.clientHeight && coord.top > 0; 36 | return visible; 37 | } 38 | 39 | //────────────────────────────────────────────────────────────────────────────── 40 | 41 | export function acceptOrRejectInText(editor: Editor, mode: "accept" | "reject"): void { 42 | const selection = editor.getSelection(); 43 | const cursor = editor.getCursor(); 44 | const scope = selection ? "selection" : "paragraph"; 45 | const text = selection || editor.getLine(cursor.line); 46 | 47 | if (!text.match(/==|~~/)) { 48 | new Notice(`There are no highlights or strikethroughs in the ${scope}.`, 3000); 49 | return; 50 | } 51 | 52 | const updatedText = removeMarkup(text, mode); 53 | if (selection) editor.replaceSelection(updatedText); 54 | else editor.setLine(cursor.line, updatedText); 55 | 56 | // keep cursor location 57 | const charsLess = text.length - updatedText.length; 58 | cursor.ch = Math.max(cursor.ch - charsLess, 0); 59 | editor.setCursor(cursor); 60 | } 61 | 62 | export function acceptOrRejectNextSuggestion(editor: Editor, mode: "accept" | "reject"): void { 63 | const cursor = editor.getCursor(); 64 | const cursorOffset = editor.posToOffset(cursor) + 1; 65 | const text = editor.getValue(); 66 | 67 | // CASE 1: if cursor not visible, scroll to it instead 68 | if (!positionVisibleOnScreen(editor, cursor)) { 69 | new Notice("Cursor is not visible. Scrolled to the cursor instead."); 70 | editor.scrollIntoView({ from: cursor, to: cursor }, true); 71 | return; 72 | } 73 | 74 | // FIND NEXT SUGGESTION 75 | // since highlights and strikethroughs do not span lines, it is safe to 76 | // start searching at the beginning of the cursor line 77 | const startOfCursorlineOffset = editor.posToOffset({ line: cursor.line, ch: 0 }); 78 | let searchStart = startOfCursorlineOffset; 79 | 80 | let matchText = ""; 81 | let matchStart = 0; 82 | let matchEnd = 0; 83 | while (true) { 84 | // next match includes previous and next characters to catch leftover spaces 85 | const nextMatch = text.slice(searchStart).match(/ ?(==[^~=]*?==|~~[^~=]*~~).?/); 86 | if (!nextMatch) { 87 | new Notice("There are no highlights or strikethroughs until the end of the note.", 3000); 88 | return; 89 | } 90 | matchText = nextMatch[0]; 91 | matchStart = searchStart + (nextMatch.index as number); 92 | matchEnd = matchStart + matchText.length; 93 | const cursorOnMatch = cursorOffset >= matchStart && cursorOffset <= matchEnd; 94 | const cursorBeforeMatch = cursorOffset <= matchStart; 95 | if (cursorOnMatch || cursorBeforeMatch) break; 96 | 97 | // -1 to account for the next character being included in the pattern, 98 | // (strings with directly adjacent markup such as `==foobar==~~baz~~` 99 | // would otherwise be sliced to `~baz~~` on the next iteration) 100 | searchStart = matchEnd - 1; 101 | } 102 | const matchStartPos = editor.offsetToPos(matchStart); 103 | const matchEndPos = editor.offsetToPos(matchEnd); 104 | 105 | // CASE 2: if suggestion not visible, scroll to it instead 106 | if (!positionVisibleOnScreen(editor, matchEndPos)) { 107 | new Notice("Next suggestion not visible. Scrolled to next suggestion instead."); 108 | editor.scrollIntoView({ from: matchStartPos, to: matchEndPos }, true); 109 | editor.setCursor(matchStartPos); 110 | return; 111 | } 112 | 113 | // CASE 3: Cursor & suggestion visible -> update text 114 | const updatedText = removeMarkup(matchText, mode); 115 | editor.replaceRange(updatedText, matchStartPos, matchEndPos); 116 | editor.setCursor(matchStartPos); 117 | } 118 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "obsidian"; 2 | import { acceptOrRejectInText, acceptOrRejectNextSuggestion } from "./accept-reject-suggestions"; 3 | import { proofreadDocument, proofreadText } from "./proofread"; 4 | import { DEFAULT_SETTINGS, ProofreaderSettings, ProofreaderSettingsMenu } from "./settings"; 5 | 6 | // biome-ignore lint/style/noDefaultExport: required for Obsidian plugins to work 7 | export default class Proofreader extends Plugin { 8 | settings: ProofreaderSettings = DEFAULT_SETTINGS; 9 | 10 | override async onload(): Promise { 11 | // settings 12 | await this.loadSettings(); 13 | this.addSettingTab(new ProofreaderSettingsMenu(this)); 14 | 15 | // commands 16 | this.addCommand({ 17 | id: "proofread-selection-paragraph", 18 | name: "Proofread selection/paragraph", 19 | editorCallback: (editor): Promise => proofreadText(this, editor), 20 | icon: "bot-message-square", 21 | }); 22 | this.addCommand({ 23 | id: "proofread-full-document", 24 | name: "Proofread full document", 25 | editorCallback: (editor): Promise => proofreadDocument(this, editor), 26 | icon: "bot-message-square", 27 | }); 28 | this.addCommand({ 29 | id: "accept-suggestions-in-text", 30 | name: "Accept suggestions in selection/paragraph", 31 | editorCallback: (editor): void => acceptOrRejectInText(editor, "accept"), 32 | icon: "check-check", 33 | }); 34 | this.addCommand({ 35 | id: "reject-suggestions-in-text", 36 | name: "Reject suggestions in selection/paragraph", 37 | editorCallback: (editor): void => acceptOrRejectInText(editor, "reject"), 38 | icon: "x", 39 | }); 40 | this.addCommand({ 41 | id: "accept-next-suggestion", 42 | name: "Accept next suggestion (or go to suggestion if outside viewport)", 43 | editorCallback: (editor): void => acceptOrRejectNextSuggestion(editor, "accept"), 44 | icon: "check-check", 45 | }); 46 | this.addCommand({ 47 | id: "reject-next-suggestion", 48 | name: "Reject next suggestion (or go to suggestion if outside viewport)", 49 | editorCallback: (editor): void => acceptOrRejectNextSuggestion(editor, "reject"), 50 | icon: "x", 51 | }); 52 | 53 | console.info(this.manifest.name + " Plugin loaded."); 54 | } 55 | 56 | override onunload(): void { 57 | console.info(this.manifest.name + " Plugin unloaded."); 58 | } 59 | 60 | async saveSettings(): Promise { 61 | // Ensure default values are not written, so the user will not load 62 | // oudated defaults when the default settings are changed. 63 | const settings = structuredClone(this.settings); 64 | for (const key in settings) { 65 | if (!Object.hasOwn(settings, key)) continue; 66 | const name = key as keyof ProofreaderSettings; 67 | // @ts-expect-error intentional removal 68 | if (settings[name] === DEFAULT_SETTINGS[name]) settings[name] = undefined; 69 | } 70 | 71 | await this.saveData(settings); 72 | } 73 | 74 | async loadSettings(): Promise { 75 | const settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 76 | 77 | // DEPRECATION (2025-05-18) 78 | if (settings.openAiModel) { 79 | settings.model = settings.openAiModel; 80 | settings.openAiModel = undefined; 81 | this.saveData(settings); 82 | } 83 | 84 | this.settings = settings; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/obsidian-undocumented-api.d.ts: -------------------------------------------------------------------------------- 1 | import "obsidian"; 2 | import { EditorView } from "@codemirror/view"; 3 | 4 | declare module "obsidian" { 5 | interface Editor { 6 | cm: EditorView; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/proofread.ts: -------------------------------------------------------------------------------- 1 | import { Change, diffWords } from "diff"; 2 | import { Editor, Notice, getFrontMatterInfo } from "obsidian"; 3 | import { rejectChanges } from "./accept-reject-suggestions"; 4 | import Proofreader from "./main"; 5 | import { ModelName, ProviderAdapter } from "./providers/adapter"; 6 | import { MODEL_SPECS, PROVIDER_REQUEST_MAP } from "./providers/model-info"; 7 | import { ProofreaderSettings } from "./settings"; 8 | 9 | function getDiffMarkdown( 10 | settings: ProofreaderSettings, 11 | oldText: string, 12 | newText: string, 13 | isOverlength?: boolean, 14 | ): { textWithSuggestions: string; changeCount: number } { 15 | // ENSURE SAME AMOUNT OF SURROUNDING WHITESPACE 16 | // (A selection can have surrounding whitespace, but the AI response usually 17 | // removes those. This results in the text effectively being trimmed.) 18 | const leadingWhitespace = oldText.match(/^(\s*)/)?.[0] || ""; 19 | const trailingWhitespace = oldText.match(/(\s*)$/)?.[0] || ""; 20 | newText = newText.replace(/^(\s*)/, leadingWhitespace).replace(/(\s*)$/, trailingWhitespace); 21 | 22 | // GET DIFF 23 | // DOCS https://github.com/kpdecker/jsdiff#readme 24 | const diff = diffWords(oldText, newText); 25 | if (isOverlength) { 26 | // do not remove text after cutoff-length 27 | (diff.at(-1) as Change).removed = false; 28 | const cutOffCallout = 29 | "\n\n" + 30 | "> [!INFO] End of proofreading\n" + 31 | "> The input text was too long. Text after this point is unchanged." + 32 | "\n\n"; 33 | diff.splice(-2, 0, { added: false, removed: false, value: cutOffCallout }); 34 | } 35 | 36 | // CONVERT DIFF TO TEXT 37 | // with ==highlights== and ~~strikethrough~~ as suggestions 38 | let textWithChanges = diff 39 | .map((part) => { 40 | if (!part.added && !part.removed) return part.value; 41 | const withMarkup = part.added ? `==${part.value}==` : `~~${part.value}~~`; 42 | return withMarkup.replace(/^(==|~~)(\s)/, "$2$1"); // prevent leading spaces as they make markup invalid 43 | }) 44 | .join(""); 45 | 46 | // CLEANUP 47 | textWithChanges = textWithChanges 48 | .replace(/~~\[\^\w+\]~~/g, "$1") // preserve footnotes 49 | .replace(/~~(.+?)(.{1,2})~~==(\1)==/g, "$1~~$2~~") // only removal of 1-2 char, e.g. plural-s 50 | .replace(/~~(.+?)~~==(?:\1)(.{1,2})==/g, "$1==$2==") // only addition of 1-2 char 51 | .replace(/ {2}(?!$)/gm, " "); // rare double spaces created by diff (not EoL due to 2-space-rule) 52 | 53 | // PRESERVE SPECIAL CHARACTERS 54 | if (settings.preserveNonSmartPuncation) { 55 | textWithChanges = textWithChanges 56 | .replace(/~~"~~==[“”]==/g, '"') // preserve non-smart quotes 57 | .replace(/~~'~~==[‘’]==/g, "'") 58 | .replace(/(\d)~~-~~==–==(\d)/g, "$1-$2"); // preserve non-smart dashes in number ranges 59 | } 60 | 61 | // PRESERVE QUOTES 62 | if (settings.preserveBlockquotes) { 63 | textWithChanges = textWithChanges 64 | .replace(/^~~>~~/gm, ">") // if AI removes blockquote marker 65 | .replace(/^~~(>[^~=]*)~~$/gm, "$1") // if AI removes blockquote itself 66 | .replace(/^>.*/gm, (blockquote) => rejectChanges(blockquote)); 67 | } 68 | if (settings.preserveTextInsideQuotes) { 69 | textWithChanges = textWithChanges.replace(/"[^"]+"/g, (quote) => rejectChanges(quote)); 70 | } 71 | 72 | const changeCount = (textWithChanges.match(/==|~~/g)?.length || 0) / 2; 73 | return { textWithSuggestions: textWithChanges, changeCount: changeCount }; 74 | } 75 | 76 | async function validateAndGetChangesAndNotify( 77 | plugin: Proofreader, 78 | oldText: string, 79 | scope: string, 80 | ): Promise { 81 | const { app, settings } = plugin; 82 | 83 | // GUARD outdated model 84 | const model = MODEL_SPECS[settings.model as ModelName]; 85 | if (!model) { 86 | const errmsg = `⚠️ The model "${settings.model}" is outdated. Please select a more recent one in the settings.`; 87 | new Notice(errmsg, 10_000); 88 | return; 89 | } 90 | // GUARD valid start-text 91 | if (oldText.trim() === "") { 92 | new Notice(`${scope} is empty.`); 93 | return; 94 | } 95 | if (oldText.match(/==|~~/)) { 96 | const warnMsg = 97 | `${scope} already has highlights or strikethroughs.\n\n` + 98 | "Please accept/reject the changes before making another proofreading request."; 99 | new Notice(warnMsg, 6000); 100 | return; 101 | } 102 | 103 | // parameters 104 | const fileBefore = app.workspace.getActiveFile()?.path; 105 | const longInput = oldText.length > 1500; 106 | const veryLongInput = oldText.length > 15000; 107 | // Proofreading a document likely takes longer, we want to keep the finishing 108 | // message in case the user went afk. (In the Notice API, duration 0 means 109 | // keeping the notice until the user dismisses it.) 110 | const notifDuration = longInput ? 0 : 4_000; 111 | 112 | // notify on start 113 | let msgBeforeRequest = `🤖 ${scope} is being proofread…`; 114 | if (longInput) { 115 | msgBeforeRequest += "\n\nDue to the length of the text, this may take a moment."; 116 | if (veryLongInput) msgBeforeRequest += " (A minute or longer.)"; 117 | msgBeforeRequest += 118 | "\n\nDo not go to a different file or change the original text in the meantime."; 119 | } 120 | const notice = new Notice(msgBeforeRequest, 0); 121 | 122 | // perform request 123 | const requestFunc: ProviderAdapter = PROVIDER_REQUEST_MAP[model.provider]; 124 | const { newText, isOverlength } = (await requestFunc(settings, oldText)) || {}; 125 | notice.hide(); 126 | if (!newText) return; 127 | 128 | // check if active file changed 129 | const fileAfter = app.workspace.getActiveFile()?.path; 130 | if (fileBefore !== fileAfter) { 131 | const errmsg = "⚠️ The active file changed since the proofread has been triggered. Aborting."; 132 | new Notice(errmsg, notifDuration); 133 | return; 134 | } 135 | 136 | // check if diff is even needed 137 | const { textWithSuggestions, changeCount } = getDiffMarkdown( 138 | settings, 139 | oldText, 140 | newText, 141 | isOverlength, 142 | ); 143 | if (textWithSuggestions === oldText) { 144 | new Notice("✅ Text is good, nothing to change.", notifDuration); 145 | return; 146 | } 147 | 148 | // notify on changes 149 | if (isOverlength) { 150 | const msg = 151 | "Text is longer than the maximum output supported by the AI model.\n\n" + 152 | "Suggestions are thus only made until the cut-off point."; 153 | new Notice(msg, 10_000); 154 | } 155 | const pluralS = changeCount === 1 ? "" : "s"; 156 | new Notice(`🤖 ${changeCount} change${pluralS} made.`, notifDuration); 157 | 158 | return textWithSuggestions; 159 | } 160 | 161 | //────────────────────────────────────────────────────────────────────────────── 162 | 163 | export async function proofreadDocument(plugin: Proofreader, editor: Editor): Promise { 164 | const noteWithFrontmatter = editor.getValue(); 165 | const bodyStart = getFrontMatterInfo(noteWithFrontmatter).contentStart || 0; 166 | const bodyEnd = noteWithFrontmatter.length; 167 | const oldText = noteWithFrontmatter.slice(bodyStart); 168 | 169 | const changes = await validateAndGetChangesAndNotify(plugin, oldText, "Document"); 170 | if (!changes) return; 171 | 172 | const bodyStartPos = editor.offsetToPos(bodyStart); 173 | const bodyEndPos = editor.offsetToPos(bodyEnd); 174 | editor.replaceRange(changes, bodyStartPos, bodyEndPos); 175 | editor.setCursor(bodyStartPos); // to start of doc 176 | } 177 | 178 | export async function proofreadText(plugin: Proofreader, editor: Editor): Promise { 179 | const hasMultipleSelections = editor.listSelections().length > 1; 180 | if (hasMultipleSelections) { 181 | new Notice("Multiple selections are not supported."); 182 | return; 183 | } 184 | 185 | const cursor = editor.getCursor("from"); // `from` gives start if selection 186 | const selection = editor.getSelection(); 187 | const oldText = selection || editor.getLine(cursor.line); 188 | const scope = selection ? "Selection" : "Paragraph"; 189 | 190 | const changes = await validateAndGetChangesAndNotify(plugin, oldText, scope); 191 | if (!changes) return; 192 | 193 | if (selection) { 194 | editor.replaceSelection(changes); 195 | editor.setCursor(cursor); // to start of selection 196 | } else { 197 | editor.setLine(cursor.line, changes); 198 | editor.setCursor({ line: cursor.line, ch: 0 }); // to start of paragraph 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/providers/adapter.d.ts: -------------------------------------------------------------------------------- 1 | import { MODEL_SPECS } from "src/providers/model-info"; 2 | import { ProofreaderSettings } from "src/settings"; 3 | 4 | type ProviderResponse = { 5 | newText: string; // output text from LLM 6 | isOverlength: boolean; // whether output hit token limit, i.e., output is truncated 7 | }; 8 | 9 | export type ProviderAdapter = ( 10 | settings: ProofreaderSettings, 11 | oldText: string, 12 | ) => Promise; 13 | 14 | export type ModelName = keyof typeof MODEL_SPECS; 15 | export type ProviderName = (typeof MODEL_SPECS)[ModelName]["provider"]; 16 | -------------------------------------------------------------------------------- /src/providers/model-info.ts: -------------------------------------------------------------------------------- 1 | import { ProviderAdapter, ProviderName } from "./adapter"; 2 | import { openAiRequest } from "./openai"; 3 | 4 | export const PROVIDER_REQUEST_MAP: Record = { 5 | openai: openAiRequest, 6 | }; 7 | 8 | export const MODEL_SPECS = { 9 | "gpt-4.1-nano": { 10 | provider: "openai", 11 | displayText: "GPT 4.1 nano (recommended)", 12 | maxOutputTokens: 32_768, 13 | // `info` key is not actively used, just informational 14 | info: { 15 | costPerMillionTokens: { input: 0.1, output: 0.4 }, 16 | intelligence: 2, 17 | speed: 5, 18 | url: "https://platform.openai.com/docs/models/gpt-4.1-nano", 19 | }, 20 | }, 21 | "gpt-4.1-mini": { 22 | provider: "openai", 23 | displayText: "GPT 4.1 mini", 24 | maxOutputTokens: 32_768, 25 | info: { 26 | costPerMillionTokens: { input: 0.4, output: 1.6 }, 27 | intelligence: 3, 28 | speed: 4, 29 | url: "https://platform.openai.com/docs/models/gpt-4.1-mini", 30 | }, 31 | }, 32 | "gpt-4.1": { 33 | provider: "openai", 34 | displayText: "GPT 4.1 (for tasks beyond proofreading)", 35 | maxOutputTokens: 32_768, 36 | info: { 37 | costPerMillionTokens: { input: 2.0, output: 8.0 }, 38 | intelligence: 4, 39 | speed: 3, 40 | url: "https://platform.openai.com/docs/models/gpt-4.1", 41 | }, 42 | }, 43 | } as const; // `as const` needed for type inference 44 | -------------------------------------------------------------------------------- /src/providers/openai.ts: -------------------------------------------------------------------------------- 1 | import { Notice, RequestUrlResponse, requestUrl } from "obsidian"; 2 | import { logError } from "src/utils"; 3 | import { ProviderAdapter } from "./adapter"; 4 | import { MODEL_SPECS } from "./model-info"; 5 | 6 | export const openAiRequest: ProviderAdapter = async (settings, oldText) => { 7 | if (!settings.openAiApiKey) { 8 | new Notice("Please set your OpenAI API key in the plugin settings."); 9 | return; 10 | } 11 | 12 | let response: RequestUrlResponse; 13 | try { 14 | // DOCS https://platform.openai.com/docs/api-reference/chat 15 | response = await requestUrl({ 16 | url: "https://api.openai.com/v1/chat/completions", 17 | method: "POST", 18 | contentType: "application/json", 19 | // biome-ignore lint/style/useNamingConvention: not by me 20 | headers: { Authorization: "Bearer " + settings.openAiApiKey }, 21 | body: JSON.stringify({ 22 | model: settings.model, 23 | messages: [ 24 | { role: "developer", content: settings.staticPrompt }, 25 | { role: "user", content: oldText }, 26 | ], 27 | }), 28 | }); 29 | console.debug("[Proofreader plugin] OpenAI response", response); 30 | } catch (error) { 31 | if ((error as { status: number }).status === 401) { 32 | const msg = "OpenAI API key is not valid. Please verify the key in the plugin settings."; 33 | new Notice(msg, 6_000); 34 | return; 35 | } 36 | logError(error); 37 | return; 38 | } 39 | const newText = response.json?.choices?.[0].message.content; 40 | if (!newText) { 41 | logError(response); 42 | return; 43 | } 44 | console.debug("[Proofreader plugin] New text", newText); 45 | 46 | // determine overlength 47 | // https://platform.openai.com/docs/guides/conversation-state?api-mode=responses#managing-context-for-text-generation 48 | const outputTokensUsed = response.json?.usage?.completion_tokens || 0; 49 | const isOverlength = outputTokensUsed >= MODEL_SPECS[settings.model].maxOutputTokens; 50 | 51 | return { newText: newText, isOverlength: isOverlength }; 52 | }; 53 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { PluginSettingTab, Setting } from "obsidian"; 2 | import Proofreader from "./main"; 3 | import { ModelName } from "./providers/adapter"; 4 | import { MODEL_SPECS } from "./providers/model-info"; 5 | 6 | export const DEFAULT_SETTINGS = { 7 | openAiApiKey: "", 8 | model: "gpt-4.1-nano" as ModelName, 9 | staticPrompt: 10 | "Act as a professional editor. Please make suggestions how to improve clarity, readability, grammar, and language of the following text. Preserve the original meaning and any technical jargon. Suggest structural changes only if they significantly improve flow or understanding. Avoid unnecessary expansion or major reformatting (e.g., no unwarranted lists). Try to make as little changes as possible, refrain from doing any changes when the writing is already sufficiently clear and concise. Output only the revised text and nothing else. The text is:", 11 | preserveTextInsideQuotes: false, 12 | preserveBlockquotes: false, 13 | preserveNonSmartPuncation: false, 14 | }; 15 | 16 | export type ProofreaderSettings = typeof DEFAULT_SETTINGS; 17 | 18 | //────────────────────────────────────────────────────────────────────────────── 19 | 20 | // DOCS https://docs.obsidian.md/Plugins/User+interface/Settings 21 | export class ProofreaderSettingsMenu extends PluginSettingTab { 22 | plugin: Proofreader; 23 | 24 | constructor(plugin: Proofreader) { 25 | super(plugin.app, plugin); 26 | this.plugin = plugin; 27 | } 28 | 29 | display(): void { 30 | const { containerEl } = this; 31 | const settings = this.plugin.settings; 32 | 33 | containerEl.empty(); 34 | 35 | // API KEYS 36 | new Setting(containerEl).setName("OpenAI API key").addText((input) => { 37 | input.inputEl.type = "password"; // obfuscates the field 38 | input.inputEl.setCssProps({ width: "100%" }); 39 | input 40 | .setPlaceholder("sk-123456789…") 41 | .setValue(settings.openAiApiKey) 42 | .onChange(async (value) => { 43 | settings.openAiApiKey = value.trim(); 44 | await this.plugin.saveSettings(); 45 | }); 46 | }); 47 | 48 | new Setting(containerEl) 49 | .setName("Model") 50 | .setDesc( 51 | "The nano model is slightly quicker and cheaper. " + 52 | "The mini model is slightly higher quality, but also more expensive. ", 53 | ) 54 | .addDropdown((dropdown) => { 55 | for (const key in MODEL_SPECS) { 56 | if (!Object.hasOwn(MODEL_SPECS, key)) continue; 57 | const model = MODEL_SPECS[key as ModelName]; 58 | dropdown.addOption(key, model.displayText); 59 | } 60 | dropdown.setValue(settings.model).onChange(async (value) => { 61 | settings.model = value as ModelName; 62 | await this.plugin.saveSettings(); 63 | }); 64 | }); 65 | 66 | //──────────────────────────────────────────────────────────────────────── 67 | // CLEANUP OPTIONS 68 | new Setting(containerEl) 69 | .setName("Preserve text inside quotes") 70 | .setDesc( 71 | 'No changes will be made to text inside quotation marks (""). ' + 72 | "Note that this is not perfect, as the AI will sometimes suggest changes across quotes.", 73 | ) 74 | .addToggle((toggle) => 75 | toggle.setValue(settings.preserveTextInsideQuotes).onChange(async (value) => { 76 | settings.preserveTextInsideQuotes = value; 77 | await this.plugin.saveSettings(); 78 | }), 79 | ); 80 | new Setting(containerEl) 81 | .setName("Preserve text in blockquotes and callouts") 82 | .setDesc( 83 | "No changes will be made to lines beginning with `>`. " + 84 | "Note that this is not perfect, as the AI will sometimes suggest changes across paragraphs.", 85 | ) 86 | .addToggle((toggle) => 87 | toggle.setValue(settings.preserveBlockquotes).onChange(async (value) => { 88 | settings.preserveBlockquotes = value; 89 | await this.plugin.saveSettings(); 90 | }), 91 | ); 92 | new Setting(containerEl) 93 | .setName("Preserve non-smart punctuation") 94 | .setDesc( 95 | "Prevent the AI from changing non-smart punctuation to their smart counterparts, " + 96 | ' for instance changing `"` to `“` or `12-34` to `12–34`. ' + 97 | "This can be relevant when using tools like `pandoc`, which convert non-smart punctuation based on how they are configured.", 98 | ) 99 | .addToggle((toggle) => 100 | toggle.setValue(settings.preserveNonSmartPuncation).onChange(async (value) => { 101 | settings.preserveNonSmartPuncation = value; 102 | await this.plugin.saveSettings(); 103 | }), 104 | ); 105 | 106 | //──────────────────────────────────────────────────────────────────────── 107 | // ADVANCED 108 | new Setting(containerEl).setName("Advanced").setHeading(); 109 | 110 | new Setting(containerEl) 111 | .setName("System prompt") 112 | .setDesc( 113 | "The LLM must respond ONLY with the updated text for this plugin to work. " + 114 | "Most users do not need to change this setting. " + 115 | "Only change this if you know what you are doing.", 116 | ) 117 | .addTextArea((textarea) => { 118 | textarea.inputEl.setCssProps({ width: "25vw", height: "15em" }); 119 | textarea 120 | .setValue(settings.staticPrompt) 121 | .setPlaceholder("Make suggestions based on…") 122 | .onChange(async (value) => { 123 | if (value.trim() === "") return; 124 | settings.staticPrompt = value.trim(); 125 | await this.plugin.saveSettings(); 126 | }); 127 | }); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Notice, Platform } from "obsidian"; 2 | 3 | export function logError(obj: unknown): void { 4 | if (Platform.isMobileApp) { 5 | // No issue way of checking the logs on mobile, thus recommending to 6 | // retrieve error via running on desktop instead. 7 | new Notice("Error. For details, run the respective function on the desktop."); 8 | } else { 9 | const hotkey = Platform.isMacOS ? "cmd+opt+i" : "ctrl+shift+i"; 10 | new Notice(`Error. Check the console for more details (${hotkey}).`); 11 | console.error("[Proofreader plugin] error", obj); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* placeholder */ 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // DOCS https://www.typescriptlang.org/tsconfig 2 | //────────────────────────────────────────────────────────────────────────────── 3 | 4 | { 5 | "compilerOptions": { 6 | "baseUrl": ".", 7 | "moduleResolution": "node", 8 | "module": "ESNext", 9 | "target": "ES2022", 10 | "lib": ["DOM", "ES5", "ES6", "ES7", "es2023"], 11 | 12 | // SOURCE strictest tsconfig 2.0.0 https://github.com/tsconfig/bases/blob/main/bases/strictest.json 13 | "strict": true, 14 | "allowUnusedLabels": false, 15 | "allowUnreachableCode": false, 16 | "exactOptionalPropertyTypes": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "noImplicitOverride": true, 19 | "noImplicitReturns": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | 23 | // disabled 24 | "noUncheckedIndexedAccess": false, // *too* strict since it complains about every [0] 25 | "noPropertyAccessFromIndexSignature": false, // not useful https://stackoverflow.com/a/70748402/22114136 26 | 27 | // helpers 28 | "isolatedModules": true, 29 | "esModuleInterop": false, // setting to true causes issues with Obsidian's imported `moment` 30 | "importHelpers": true, 31 | "skipLibCheck": true, 32 | "forceConsistentCasingInFileNames": true 33 | }, 34 | "include": ["src/**/*.ts"], 35 | 36 | "$schema": "https://json.schemastore.org/tsconfig" 37 | } 38 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.1": "1.5.8", 3 | "0.5.0": "1.5.8", 4 | "0.5.1": "1.5.8", 5 | "0.5.2": "1.5.8", 6 | "0.9.0": "1.5.8", 7 | "0.10.0": "1.5.8", 8 | "0.10.1": "1.5.8", 9 | "0.10.2": "1.5.8", 10 | "0.11.0": "1.5.8", 11 | "0.11.1": "1.5.8", 12 | "0.12.0": "1.5.8", 13 | "0.12.1": "1.5.8", 14 | "0.13.0": "1.5.8", 15 | "0.14.0": "1.5.8", 16 | "0.15.0": "1.5.8", 17 | "0.15.1": "1.5.8", 18 | "0.15.2": "1.5.8", 19 | "0.15.3": "1.5.8", 20 | "0.15.4": "1.5.8", 21 | "0.15.5": "1.5.8", 22 | "0.15.6": "1.5.8", 23 | "0.15.7": "1.5.8", 24 | "1.0.0": "1.5.8", 25 | "1.0.1": "1.5.8", 26 | "1.0.2": "1.5.8", 27 | "1.1.0": "1.5.8", 28 | "1.1.1": "1.5.8", 29 | "1.1.2": "1.5.8", 30 | "1.1.3": "1.5.8", 31 | "1.2.0": "1.5.8", 32 | "1.2.1": "1.5.8", 33 | "1.2.2": "1.5.8", 34 | "1.2.3": "1.5.8" 35 | } 36 | --------------------------------------------------------------------------------