├── .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 | 
3 | 
4 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------