├── .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 ├── docs ├── .ignore ├── alt-file.canvas ├── alt-file.png ├── bookmark-cycler.canvas └── bookmark-cycler.png ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── commands │ ├── altfile.ts │ ├── bookmark-cycler.ts │ ├── cycle-files-in-folder.ts │ ├── cycle-tabs-across-splits.ts │ └── open-first-url-in-file.ts ├── main.ts ├── obsidian-undocumented-api.d.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: 0.13.19 34 | validations: 35 | required: true 36 | - type: checkboxes 37 | id: editor 38 | attributes: 39 | label: Which editor are you using? 40 | options: 41 | - label: New Editor 42 | - label: Legacy Editor 43 | - type: checkboxes 44 | id: checklist 45 | attributes: 46 | label: Checklist 47 | options: 48 | - label: I updated to the latest version of the plugin. 49 | required: true 50 | 51 | -------------------------------------------------------------------------------- /.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: textarea 7 | id: feature-requested 8 | attributes: 9 | label: Feature Requested 10 | description: A clear and concise description of the feature. 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 the request. 18 | - type: checkboxes 19 | id: checklist 20 | attributes: 21 | label: Checklist 22 | options: 23 | - label: The feature would be useful to more users than just me. 24 | required: true 25 | -------------------------------------------------------------------------------- /.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) 2023 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 | # 🪝 Grappling Hook 2 | ![Obsidian downloads](https://img.shields.io/badge/dynamic/json?logo=obsidian&color=%23483699&label=downloads&query=%24%5B%22grappling-hook%22%5D.downloads&url=https%3A%2F%2Fraw.githubusercontent.com%2Fobsidianmd%2Fobsidian-releases%2Fmaster%2Fcommunity-plugin-stats.json&style=plastic) 3 | ![Last release](https://img.shields.io/github/v/release/chrisgrieser/grappling-hook?label=Latest%20Release&style=plastic) 4 | 5 | Obsidian Plugin for blazingly fast file switching. For those who find the Quick 6 | Switcher still too slow. [Endorsed by Nick Milo.](https://youtu.be/mcrcRXp5d8A?t=462) 7 | 8 | 9 | 10 | - [Commands](#commands) 11 | * [Bookmark cycler](#bookmark-cycler) 12 | * [Alternate note](#alternate-note) 13 | * [Cycle tab/split](#cycle-tabsplit) 14 | * [Next/previous file in current folder](#nextprevious-file-in-current-folder) 15 | * [Open first URL in file](#open-first-url-in-file) 16 | - [Installation](#installation) 17 | - [About the developer](#about-the-developer) 18 | 19 | 20 | 21 | ## Commands 22 | 23 | ### Bookmark cycler 24 | Goes to your most recently modified bookmarked note. If you are already at a 25 | bookmarked note, goes to the next bookmarked note, in order of the last 26 | modification date. This allows you to quickly cycle between a core set of files 27 | that are important. The command works well for workflows where you work with a 28 | dynamic core set of main notes and many auxiliary notes. 29 | 30 | When you have text selected, the bookmark cycler switches to its alternative 31 | mode, and copies the selected text to the last modified bookmarked note, 32 | regardless the note you are. 33 | 34 | > [!NOTE] 35 | > Only bookmarked *files* are considered. Bookmarked *blocks* or *headers* are ignored. 36 | 37 | ![Illustration bookmark cycler](./docs/bookmark-cycler.png) 38 | *This command is inspired by the [Harpoon plugin for neovim](https://github.com/ThePrimeagen/harpoon).* 39 | 40 | ### Alternate note 41 | Go to the last file you were at. As opposed to the `Navigate Back` command, 42 | using the `Switch to Alternate Note` command moves you forward in history when 43 | you press it the second time. This allows you to rapidly switch between two 44 | files with only one hotkey. *This command is equivalent to vim's `:buffer #`.* 45 | 46 | The name of the alternate file is also displayed in the status bar. If the 47 | alternate file is already open in another tab, it switches to that tab. If not, 48 | the alternate file is opened in the current tab. 49 | 50 | ![Illustration alt-file](./docs/alt-file.png) 51 | 52 | ### Cycle tab/split 53 | Like the Obsidian built-in command `Go to next tab`, but includes tabs in other 54 | splits, meaning you can cycle through *all* open tabs with one hotkey. 55 | *This command similar to vim's `:w`.* 56 | 57 | ### Next/previous file in current folder 58 | Cycles through all files in the current folder, in alphabetical order. 59 | 60 | ### Open first URL in file 61 | Opens the first URL (external link) found in the file. This includes the 62 | frontmatter. Currently, works only in Source Mode and Live Preview. 63 | 64 | Note that this only works with URLs that are *actually* in the file, meaning URLs 65 | displayed via dataview or embedded notes are not supported. 66 | 67 | ## Installation 68 | ➡️ [Install in Obsidian](https://obsidian.md/plugins?id=grappling-hook) 69 | 70 | 71 | ## About the developer 72 | In my day job, I am a sociologist studying the social mechanisms underlying the 73 | digital economy. For my PhD project, I investigate the governance of the app 74 | economy and how software ecosystems manage the tension between innovation and 75 | compatibility. If you are interested in this subject, feel free to get in touch. 76 | 77 | - [Academic Website](https://chris-grieser.de/) 78 | - [Mastodon](https://pkm.social/@pseudometa) 79 | - [ResearchGate](https://www.researchgate.net/profile/Christopher-Grieser) 80 | - [LinkedIn](https://www.linkedin.com/in/christopher-grieser-ba693b17a/) 81 | 82 | 83 | Buy Me a Coffee at ko-fi.com 90 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/.ignore: -------------------------------------------------------------------------------- 1 | *.canvas 2 | -------------------------------------------------------------------------------- /docs/alt-file.canvas: -------------------------------------------------------------------------------- 1 | { 2 | "nodes":[ 3 | {"id":"4b6192584dcf595c","x":-560,"y":-160,"width":420,"height":360,"type":"group","label":"Grappling Hook's Alternate File"}, 4 | {"id":"c04590d9278f13c4","x":-1000,"y":-160,"width":400,"height":360,"type":"group","label":"Obsidian Navigation Commands"}, 5 | {"id":"913905d2b6cfb944","x":-420,"y":-140,"width":160,"height":65,"color":"1","type":"text","text":"#### current note"}, 6 | {"id":"c8e93bbc395e1ff1","x":-420,"y":115,"width":160,"height":65,"color":"3","type":"text","text":"#### previous note"}, 7 | {"id":"0825dc01fae0d44e","x":-420,"y":-12,"width":160,"height":65,"color":"3","type":"text","text":"#### previous note"}, 8 | {"id":"2a2115444a588e9f","x":-880,"y":-140,"width":160,"height":65,"color":"1","type":"text","text":"#### current note"}, 9 | {"id":"76162d5c3b416c4c","x":-880,"y":-12,"width":160,"height":65,"color":"3","type":"text","text":"#### previous note"}, 10 | {"id":"66ea98f8f0fd3125","x":-880,"y":115,"width":160,"height":65,"color":"3","type":"text","text":"#### previous note"} 11 | ], 12 | "edges":[ 13 | {"id":"48379d57d0a7772c","fromNode":"913905d2b6cfb944","fromSide":"left","toNode":"0825dc01fae0d44e","toSide":"left","color":"4","label":"alternate\nfile"}, 14 | {"id":"c3dc23c2c48a6963","fromNode":"0825dc01fae0d44e","fromSide":"right","toNode":"913905d2b6cfb944","toSide":"right","color":"4","label":"alternate\nfile"}, 15 | {"id":"fb955d216d6340f6","fromNode":"76162d5c3b416c4c","fromSide":"right","toNode":"2a2115444a588e9f","toSide":"right","color":"5","label":"forward"}, 16 | {"id":"1e5d7f976f3f2cde","fromNode":"66ea98f8f0fd3125","fromSide":"right","toNode":"76162d5c3b416c4c","toSide":"right","color":"5","label":"forward"}, 17 | {"id":"9ba209f7a75e8a3a","fromNode":"2a2115444a588e9f","fromSide":"left","toNode":"76162d5c3b416c4c","toSide":"left","color":"6","label":"back"}, 18 | {"id":"6b890d527019f4aa","fromNode":"76162d5c3b416c4c","fromSide":"left","toNode":"66ea98f8f0fd3125","toSide":"left","color":"6","label":"back"} 19 | ] 20 | } -------------------------------------------------------------------------------- /docs/alt-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisgrieser/grappling-hook/94e34c2fd6e86246442942b18338556216aa9feb/docs/alt-file.png -------------------------------------------------------------------------------- /docs/bookmark-cycler.canvas: -------------------------------------------------------------------------------- 1 | { 2 | "nodes":[ 3 | {"type":"group","id":"4b6192584dcf595c","x":-900,"y":-180,"width":940,"height":560,"label":"Bookmark Cycler"}, 4 | {"type":"group","id":"852be4a2a9c37015","x":-300,"y":-130,"width":300,"height":147,"label":"same hotkey"}, 5 | {"type":"text","text":"#### bookmarked note (modified today)","id":"0825dc01fae0d44e","x":-700,"y":-23,"width":270,"height":80,"color":"2"}, 6 | {"type":"text","text":"#### bookmarked note (mod. yesterday)","id":"50ca1ae33b7e8281","x":-700,"y":120,"width":270,"height":80,"color":"3"}, 7 | {"type":"text","text":"#### bookmarked note (mod. last week)","id":"923c37856e280677","x":-700,"y":280,"width":270,"height":80,"color":"3"}, 8 | {"type":"text","text":"#### current note","id":"913905d2b6cfb944","x":-700,"y":-160,"width":240,"height":50,"color":"1"}, 9 | {"type":"text","text":"#### with selection","id":"4c4f140cd72a2c96","x":-280,"y":-110,"width":260,"height":50,"color":"5"}, 10 | {"type":"text","text":"#### no selection","id":"d32abef198a75758","x":-280,"y":-48,"width":260,"height":50,"color":"4"} 11 | ], 12 | "edges":[ 13 | {"id":"67b07314825e87cf","fromNode":"923c37856e280677","fromSide":"right","toNode":"0825dc01fae0d44e","toSide":"right","color":"4","label":"navigate"}, 14 | {"id":"ea214818e6b54ac4","fromNode":"0825dc01fae0d44e","fromSide":"bottom","toNode":"50ca1ae33b7e8281","toSide":"top","color":"4","label":"navigate"}, 15 | {"id":"cabf8967be4471ad","fromNode":"913905d2b6cfb944","fromSide":"bottom","toNode":"0825dc01fae0d44e","toSide":"top","color":"4","label":"navigate"}, 16 | {"id":"f9185803444e2117","fromNode":"50ca1ae33b7e8281","fromSide":"bottom","toNode":"923c37856e280677","toSide":"top","color":"4","label":"navigate"}, 17 | {"id":"117b838704ab5ad1","fromNode":"913905d2b6cfb944","fromSide":"left","toNode":"0825dc01fae0d44e","toSide":"left","color":"5","label":"copy text"}, 18 | {"id":"39dad4fb2eeba3b7","fromNode":"50ca1ae33b7e8281","fromSide":"left","toNode":"0825dc01fae0d44e","toSide":"left","color":"5","label":"copy text"}, 19 | {"id":"ce547b3b3cfb6885","fromNode":"923c37856e280677","fromSide":"left","toNode":"0825dc01fae0d44e","toSide":"left","color":"5","label":"copy text"} 20 | ] 21 | } -------------------------------------------------------------------------------- /docs/bookmark-cycler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisgrieser/grappling-hook/94e34c2fd6e86246442942b18338556216aa9feb/docs/bookmark-cycler.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "grappling-hook", 3 | "name": "Grappling Hook", 4 | "description": "Obsidian Plugin for blazingly fast file switching. For those who find the Quick Switcher still too slow.", 5 | "version": "1.2.5", 6 | "minAppVersion": "1.5.8", 7 | "isDesktopOnly": false, 8 | "author": "pseudometa (Chris Grieser)", 9 | "authorUrl": "https://github.com/chrisgrieser/grappling-hook", 10 | "helpUrl": "https://github.com/chrisgrieser/grappling-hook#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": "Grappling Hook", 3 | "version": "1.2.5", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "Grappling Hook", 9 | "version": "1.2.5", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@biomejs/biome": "latest", 13 | "@types/node": "^22.5.5", 14 | "builtin-modules": "^3.2.0", 15 | "esbuild": "^0.25.1", 16 | "knip": "latest", 17 | "markdownlint-cli": "latest", 18 | "obsidian": "latest", 19 | "tslib": "2.7.0", 20 | "typescript": "^5.6.2" 21 | } 22 | }, 23 | "node_modules/@biomejs/biome": { 24 | "version": "1.9.3", 25 | "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.3.tgz", 26 | "integrity": "sha512-POjAPz0APAmX33WOQFGQrwLvlu7WLV4CFJMlB12b6ZSg+2q6fYu9kZwLCOA+x83zXfcPd1RpuWOKJW0GbBwLIQ==", 27 | "dev": true, 28 | "hasInstallScript": true, 29 | "license": "MIT OR Apache-2.0", 30 | "bin": { 31 | "biome": "bin/biome" 32 | }, 33 | "engines": { 34 | "node": ">=14.21.3" 35 | }, 36 | "funding": { 37 | "type": "opencollective", 38 | "url": "https://opencollective.com/biome" 39 | }, 40 | "optionalDependencies": { 41 | "@biomejs/cli-darwin-arm64": "1.9.3", 42 | "@biomejs/cli-darwin-x64": "1.9.3", 43 | "@biomejs/cli-linux-arm64": "1.9.3", 44 | "@biomejs/cli-linux-arm64-musl": "1.9.3", 45 | "@biomejs/cli-linux-x64": "1.9.3", 46 | "@biomejs/cli-linux-x64-musl": "1.9.3", 47 | "@biomejs/cli-win32-arm64": "1.9.3", 48 | "@biomejs/cli-win32-x64": "1.9.3" 49 | } 50 | }, 51 | "node_modules/@biomejs/cli-darwin-arm64": { 52 | "version": "1.9.3", 53 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.3.tgz", 54 | "integrity": "sha512-QZzD2XrjJDUyIZK+aR2i5DDxCJfdwiYbUKu9GzkCUJpL78uSelAHAPy7m0GuPMVtF/Uo+OKv97W3P9nuWZangQ==", 55 | "cpu": [ 56 | "arm64" 57 | ], 58 | "dev": true, 59 | "license": "MIT OR Apache-2.0", 60 | "optional": true, 61 | "os": [ 62 | "darwin" 63 | ], 64 | "engines": { 65 | "node": ">=14.21.3" 66 | } 67 | }, 68 | "node_modules/@biomejs/cli-darwin-x64": { 69 | "version": "1.9.3", 70 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.3.tgz", 71 | "integrity": "sha512-vSCoIBJE0BN3SWDFuAY/tRavpUtNoqiceJ5PrU3xDfsLcm/U6N93JSM0M9OAiC/X7mPPfejtr6Yc9vSgWlEgVw==", 72 | "cpu": [ 73 | "x64" 74 | ], 75 | "dev": true, 76 | "license": "MIT OR Apache-2.0", 77 | "optional": true, 78 | "os": [ 79 | "darwin" 80 | ], 81 | "engines": { 82 | "node": ">=14.21.3" 83 | } 84 | }, 85 | "node_modules/@biomejs/cli-linux-arm64": { 86 | "version": "1.9.3", 87 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.3.tgz", 88 | "integrity": "sha512-vJkAimD2+sVviNTbaWOGqEBy31cW0ZB52KtpVIbkuma7PlfII3tsLhFa+cwbRAcRBkobBBhqZ06hXoZAN8NODQ==", 89 | "cpu": [ 90 | "arm64" 91 | ], 92 | "dev": true, 93 | "license": "MIT OR Apache-2.0", 94 | "optional": true, 95 | "os": [ 96 | "linux" 97 | ], 98 | "engines": { 99 | "node": ">=14.21.3" 100 | } 101 | }, 102 | "node_modules/@biomejs/cli-linux-arm64-musl": { 103 | "version": "1.9.3", 104 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.3.tgz", 105 | "integrity": "sha512-VBzyhaqqqwP3bAkkBrhVq50i3Uj9+RWuj+pYmXrMDgjS5+SKYGE56BwNw4l8hR3SmYbLSbEo15GcV043CDSk+Q==", 106 | "cpu": [ 107 | "arm64" 108 | ], 109 | "dev": true, 110 | "license": "MIT OR Apache-2.0", 111 | "optional": true, 112 | "os": [ 113 | "linux" 114 | ], 115 | "engines": { 116 | "node": ">=14.21.3" 117 | } 118 | }, 119 | "node_modules/@biomejs/cli-linux-x64": { 120 | "version": "1.9.3", 121 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.3.tgz", 122 | "integrity": "sha512-x220V4c+romd26Mu1ptU+EudMXVS4xmzKxPVb9mgnfYlN4Yx9vD5NZraSx/onJnd3Gh/y8iPUdU5CDZJKg9COA==", 123 | "cpu": [ 124 | "x64" 125 | ], 126 | "dev": true, 127 | "license": "MIT OR Apache-2.0", 128 | "optional": true, 129 | "os": [ 130 | "linux" 131 | ], 132 | "engines": { 133 | "node": ">=14.21.3" 134 | } 135 | }, 136 | "node_modules/@biomejs/cli-linux-x64-musl": { 137 | "version": "1.9.3", 138 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.3.tgz", 139 | "integrity": "sha512-TJmnOG2+NOGM72mlczEsNki9UT+XAsMFAOo8J0me/N47EJ/vkLXxf481evfHLlxMejTY6IN8SdRSiPVLv6AHlA==", 140 | "cpu": [ 141 | "x64" 142 | ], 143 | "dev": true, 144 | "license": "MIT OR Apache-2.0", 145 | "optional": true, 146 | "os": [ 147 | "linux" 148 | ], 149 | "engines": { 150 | "node": ">=14.21.3" 151 | } 152 | }, 153 | "node_modules/@biomejs/cli-win32-arm64": { 154 | "version": "1.9.3", 155 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.3.tgz", 156 | "integrity": "sha512-lg/yZis2HdQGsycUvHWSzo9kOvnGgvtrYRgoCEwPBwwAL8/6crOp3+f47tPwI/LI1dZrhSji7PNsGKGHbwyAhw==", 157 | "cpu": [ 158 | "arm64" 159 | ], 160 | "dev": true, 161 | "license": "MIT OR Apache-2.0", 162 | "optional": true, 163 | "os": [ 164 | "win32" 165 | ], 166 | "engines": { 167 | "node": ">=14.21.3" 168 | } 169 | }, 170 | "node_modules/@biomejs/cli-win32-x64": { 171 | "version": "1.9.3", 172 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.3.tgz", 173 | "integrity": "sha512-cQMy2zanBkVLpmmxXdK6YePzmZx0s5Z7KEnwmrW54rcXK3myCNbQa09SwGZ8i/8sLw0H9F3X7K4rxVNGU8/D4Q==", 174 | "cpu": [ 175 | "x64" 176 | ], 177 | "dev": true, 178 | "license": "MIT OR Apache-2.0", 179 | "optional": true, 180 | "os": [ 181 | "win32" 182 | ], 183 | "engines": { 184 | "node": ">=14.21.3" 185 | } 186 | }, 187 | "node_modules/@codemirror/state": { 188 | "version": "6.4.1", 189 | "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", 190 | "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", 191 | "dev": true, 192 | "license": "MIT", 193 | "peer": true 194 | }, 195 | "node_modules/@codemirror/view": { 196 | "version": "6.34.1", 197 | "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.1.tgz", 198 | "integrity": "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ==", 199 | "dev": true, 200 | "license": "MIT", 201 | "peer": true, 202 | "dependencies": { 203 | "@codemirror/state": "^6.4.0", 204 | "style-mod": "^4.1.0", 205 | "w3c-keyname": "^2.2.4" 206 | } 207 | }, 208 | "node_modules/@esbuild/aix-ppc64": { 209 | "version": "0.25.1", 210 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", 211 | "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", 212 | "cpu": [ 213 | "ppc64" 214 | ], 215 | "dev": true, 216 | "license": "MIT", 217 | "optional": true, 218 | "os": [ 219 | "aix" 220 | ], 221 | "engines": { 222 | "node": ">=18" 223 | } 224 | }, 225 | "node_modules/@esbuild/android-arm": { 226 | "version": "0.25.1", 227 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", 228 | "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", 229 | "cpu": [ 230 | "arm" 231 | ], 232 | "dev": true, 233 | "license": "MIT", 234 | "optional": true, 235 | "os": [ 236 | "android" 237 | ], 238 | "engines": { 239 | "node": ">=18" 240 | } 241 | }, 242 | "node_modules/@esbuild/android-arm64": { 243 | "version": "0.25.1", 244 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", 245 | "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", 246 | "cpu": [ 247 | "arm64" 248 | ], 249 | "dev": true, 250 | "license": "MIT", 251 | "optional": true, 252 | "os": [ 253 | "android" 254 | ], 255 | "engines": { 256 | "node": ">=18" 257 | } 258 | }, 259 | "node_modules/@esbuild/android-x64": { 260 | "version": "0.25.1", 261 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", 262 | "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", 263 | "cpu": [ 264 | "x64" 265 | ], 266 | "dev": true, 267 | "license": "MIT", 268 | "optional": true, 269 | "os": [ 270 | "android" 271 | ], 272 | "engines": { 273 | "node": ">=18" 274 | } 275 | }, 276 | "node_modules/@esbuild/darwin-arm64": { 277 | "version": "0.25.1", 278 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", 279 | "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", 280 | "cpu": [ 281 | "arm64" 282 | ], 283 | "dev": true, 284 | "license": "MIT", 285 | "optional": true, 286 | "os": [ 287 | "darwin" 288 | ], 289 | "engines": { 290 | "node": ">=18" 291 | } 292 | }, 293 | "node_modules/@esbuild/darwin-x64": { 294 | "version": "0.25.1", 295 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", 296 | "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", 297 | "cpu": [ 298 | "x64" 299 | ], 300 | "dev": true, 301 | "license": "MIT", 302 | "optional": true, 303 | "os": [ 304 | "darwin" 305 | ], 306 | "engines": { 307 | "node": ">=18" 308 | } 309 | }, 310 | "node_modules/@esbuild/freebsd-arm64": { 311 | "version": "0.25.1", 312 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", 313 | "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", 314 | "cpu": [ 315 | "arm64" 316 | ], 317 | "dev": true, 318 | "license": "MIT", 319 | "optional": true, 320 | "os": [ 321 | "freebsd" 322 | ], 323 | "engines": { 324 | "node": ">=18" 325 | } 326 | }, 327 | "node_modules/@esbuild/freebsd-x64": { 328 | "version": "0.25.1", 329 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", 330 | "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", 331 | "cpu": [ 332 | "x64" 333 | ], 334 | "dev": true, 335 | "license": "MIT", 336 | "optional": true, 337 | "os": [ 338 | "freebsd" 339 | ], 340 | "engines": { 341 | "node": ">=18" 342 | } 343 | }, 344 | "node_modules/@esbuild/linux-arm": { 345 | "version": "0.25.1", 346 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", 347 | "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", 348 | "cpu": [ 349 | "arm" 350 | ], 351 | "dev": true, 352 | "license": "MIT", 353 | "optional": true, 354 | "os": [ 355 | "linux" 356 | ], 357 | "engines": { 358 | "node": ">=18" 359 | } 360 | }, 361 | "node_modules/@esbuild/linux-arm64": { 362 | "version": "0.25.1", 363 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", 364 | "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", 365 | "cpu": [ 366 | "arm64" 367 | ], 368 | "dev": true, 369 | "license": "MIT", 370 | "optional": true, 371 | "os": [ 372 | "linux" 373 | ], 374 | "engines": { 375 | "node": ">=18" 376 | } 377 | }, 378 | "node_modules/@esbuild/linux-ia32": { 379 | "version": "0.25.1", 380 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", 381 | "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", 382 | "cpu": [ 383 | "ia32" 384 | ], 385 | "dev": true, 386 | "license": "MIT", 387 | "optional": true, 388 | "os": [ 389 | "linux" 390 | ], 391 | "engines": { 392 | "node": ">=18" 393 | } 394 | }, 395 | "node_modules/@esbuild/linux-loong64": { 396 | "version": "0.25.1", 397 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", 398 | "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", 399 | "cpu": [ 400 | "loong64" 401 | ], 402 | "dev": true, 403 | "license": "MIT", 404 | "optional": true, 405 | "os": [ 406 | "linux" 407 | ], 408 | "engines": { 409 | "node": ">=18" 410 | } 411 | }, 412 | "node_modules/@esbuild/linux-mips64el": { 413 | "version": "0.25.1", 414 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", 415 | "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", 416 | "cpu": [ 417 | "mips64el" 418 | ], 419 | "dev": true, 420 | "license": "MIT", 421 | "optional": true, 422 | "os": [ 423 | "linux" 424 | ], 425 | "engines": { 426 | "node": ">=18" 427 | } 428 | }, 429 | "node_modules/@esbuild/linux-ppc64": { 430 | "version": "0.25.1", 431 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", 432 | "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", 433 | "cpu": [ 434 | "ppc64" 435 | ], 436 | "dev": true, 437 | "license": "MIT", 438 | "optional": true, 439 | "os": [ 440 | "linux" 441 | ], 442 | "engines": { 443 | "node": ">=18" 444 | } 445 | }, 446 | "node_modules/@esbuild/linux-riscv64": { 447 | "version": "0.25.1", 448 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", 449 | "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", 450 | "cpu": [ 451 | "riscv64" 452 | ], 453 | "dev": true, 454 | "license": "MIT", 455 | "optional": true, 456 | "os": [ 457 | "linux" 458 | ], 459 | "engines": { 460 | "node": ">=18" 461 | } 462 | }, 463 | "node_modules/@esbuild/linux-s390x": { 464 | "version": "0.25.1", 465 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", 466 | "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", 467 | "cpu": [ 468 | "s390x" 469 | ], 470 | "dev": true, 471 | "license": "MIT", 472 | "optional": true, 473 | "os": [ 474 | "linux" 475 | ], 476 | "engines": { 477 | "node": ">=18" 478 | } 479 | }, 480 | "node_modules/@esbuild/linux-x64": { 481 | "version": "0.25.1", 482 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", 483 | "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", 484 | "cpu": [ 485 | "x64" 486 | ], 487 | "dev": true, 488 | "license": "MIT", 489 | "optional": true, 490 | "os": [ 491 | "linux" 492 | ], 493 | "engines": { 494 | "node": ">=18" 495 | } 496 | }, 497 | "node_modules/@esbuild/netbsd-arm64": { 498 | "version": "0.25.1", 499 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", 500 | "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", 501 | "cpu": [ 502 | "arm64" 503 | ], 504 | "dev": true, 505 | "license": "MIT", 506 | "optional": true, 507 | "os": [ 508 | "netbsd" 509 | ], 510 | "engines": { 511 | "node": ">=18" 512 | } 513 | }, 514 | "node_modules/@esbuild/netbsd-x64": { 515 | "version": "0.25.1", 516 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", 517 | "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", 518 | "cpu": [ 519 | "x64" 520 | ], 521 | "dev": true, 522 | "license": "MIT", 523 | "optional": true, 524 | "os": [ 525 | "netbsd" 526 | ], 527 | "engines": { 528 | "node": ">=18" 529 | } 530 | }, 531 | "node_modules/@esbuild/openbsd-arm64": { 532 | "version": "0.25.1", 533 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", 534 | "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", 535 | "cpu": [ 536 | "arm64" 537 | ], 538 | "dev": true, 539 | "license": "MIT", 540 | "optional": true, 541 | "os": [ 542 | "openbsd" 543 | ], 544 | "engines": { 545 | "node": ">=18" 546 | } 547 | }, 548 | "node_modules/@esbuild/openbsd-x64": { 549 | "version": "0.25.1", 550 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", 551 | "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", 552 | "cpu": [ 553 | "x64" 554 | ], 555 | "dev": true, 556 | "license": "MIT", 557 | "optional": true, 558 | "os": [ 559 | "openbsd" 560 | ], 561 | "engines": { 562 | "node": ">=18" 563 | } 564 | }, 565 | "node_modules/@esbuild/sunos-x64": { 566 | "version": "0.25.1", 567 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", 568 | "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", 569 | "cpu": [ 570 | "x64" 571 | ], 572 | "dev": true, 573 | "license": "MIT", 574 | "optional": true, 575 | "os": [ 576 | "sunos" 577 | ], 578 | "engines": { 579 | "node": ">=18" 580 | } 581 | }, 582 | "node_modules/@esbuild/win32-arm64": { 583 | "version": "0.25.1", 584 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", 585 | "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", 586 | "cpu": [ 587 | "arm64" 588 | ], 589 | "dev": true, 590 | "license": "MIT", 591 | "optional": true, 592 | "os": [ 593 | "win32" 594 | ], 595 | "engines": { 596 | "node": ">=18" 597 | } 598 | }, 599 | "node_modules/@esbuild/win32-ia32": { 600 | "version": "0.25.1", 601 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", 602 | "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", 603 | "cpu": [ 604 | "ia32" 605 | ], 606 | "dev": true, 607 | "license": "MIT", 608 | "optional": true, 609 | "os": [ 610 | "win32" 611 | ], 612 | "engines": { 613 | "node": ">=18" 614 | } 615 | }, 616 | "node_modules/@esbuild/win32-x64": { 617 | "version": "0.25.1", 618 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", 619 | "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", 620 | "cpu": [ 621 | "x64" 622 | ], 623 | "dev": true, 624 | "license": "MIT", 625 | "optional": true, 626 | "os": [ 627 | "win32" 628 | ], 629 | "engines": { 630 | "node": ">=18" 631 | } 632 | }, 633 | "node_modules/@isaacs/cliui": { 634 | "version": "8.0.2", 635 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 636 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 637 | "dev": true, 638 | "license": "ISC", 639 | "dependencies": { 640 | "string-width": "^5.1.2", 641 | "string-width-cjs": "npm:string-width@^4.2.0", 642 | "strip-ansi": "^7.0.1", 643 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 644 | "wrap-ansi": "^8.1.0", 645 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 646 | }, 647 | "engines": { 648 | "node": ">=12" 649 | } 650 | }, 651 | "node_modules/@nodelib/fs.scandir": { 652 | "version": "2.1.5", 653 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 654 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 655 | "dev": true, 656 | "license": "MIT", 657 | "dependencies": { 658 | "@nodelib/fs.stat": "2.0.5", 659 | "run-parallel": "^1.1.9" 660 | }, 661 | "engines": { 662 | "node": ">= 8" 663 | } 664 | }, 665 | "node_modules/@nodelib/fs.stat": { 666 | "version": "2.0.5", 667 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 668 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 669 | "dev": true, 670 | "license": "MIT", 671 | "engines": { 672 | "node": ">= 8" 673 | } 674 | }, 675 | "node_modules/@nodelib/fs.walk": { 676 | "version": "1.2.8", 677 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 678 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 679 | "dev": true, 680 | "license": "MIT", 681 | "dependencies": { 682 | "@nodelib/fs.scandir": "2.1.5", 683 | "fastq": "^1.6.0" 684 | }, 685 | "engines": { 686 | "node": ">= 8" 687 | } 688 | }, 689 | "node_modules/@snyk/github-codeowners": { 690 | "version": "1.1.0", 691 | "resolved": "https://registry.npmjs.org/@snyk/github-codeowners/-/github-codeowners-1.1.0.tgz", 692 | "integrity": "sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==", 693 | "dev": true, 694 | "license": "MIT", 695 | "dependencies": { 696 | "commander": "^4.1.1", 697 | "ignore": "^5.1.8", 698 | "p-map": "^4.0.0" 699 | }, 700 | "bin": { 701 | "github-codeowners": "dist/cli.js" 702 | }, 703 | "engines": { 704 | "node": ">=8.10" 705 | } 706 | }, 707 | "node_modules/@types/codemirror": { 708 | "version": "5.60.8", 709 | "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", 710 | "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", 711 | "dev": true, 712 | "license": "MIT", 713 | "dependencies": { 714 | "@types/tern": "*" 715 | } 716 | }, 717 | "node_modules/@types/estree": { 718 | "version": "1.0.6", 719 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 720 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 721 | "dev": true, 722 | "license": "MIT" 723 | }, 724 | "node_modules/@types/node": { 725 | "version": "22.7.4", 726 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", 727 | "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", 728 | "dev": true, 729 | "license": "MIT", 730 | "dependencies": { 731 | "undici-types": "~6.19.2" 732 | } 733 | }, 734 | "node_modules/@types/tern": { 735 | "version": "0.23.9", 736 | "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", 737 | "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", 738 | "dev": true, 739 | "license": "MIT", 740 | "dependencies": { 741 | "@types/estree": "*" 742 | } 743 | }, 744 | "node_modules/aggregate-error": { 745 | "version": "3.1.0", 746 | "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", 747 | "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", 748 | "dev": true, 749 | "license": "MIT", 750 | "dependencies": { 751 | "clean-stack": "^2.0.0", 752 | "indent-string": "^4.0.0" 753 | }, 754 | "engines": { 755 | "node": ">=8" 756 | } 757 | }, 758 | "node_modules/ansi-regex": { 759 | "version": "5.0.1", 760 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 761 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 762 | "dev": true, 763 | "license": "MIT", 764 | "engines": { 765 | "node": ">=8" 766 | } 767 | }, 768 | "node_modules/ansi-styles": { 769 | "version": "6.2.1", 770 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 771 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 772 | "dev": true, 773 | "license": "MIT", 774 | "engines": { 775 | "node": ">=12" 776 | }, 777 | "funding": { 778 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 779 | } 780 | }, 781 | "node_modules/argparse": { 782 | "version": "2.0.1", 783 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 784 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 785 | "dev": true, 786 | "license": "Python-2.0" 787 | }, 788 | "node_modules/balanced-match": { 789 | "version": "1.0.2", 790 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 791 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 792 | "dev": true, 793 | "license": "MIT" 794 | }, 795 | "node_modules/brace-expansion": { 796 | "version": "2.0.1", 797 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 798 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 799 | "dev": true, 800 | "license": "MIT", 801 | "dependencies": { 802 | "balanced-match": "^1.0.0" 803 | } 804 | }, 805 | "node_modules/braces": { 806 | "version": "3.0.3", 807 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 808 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 809 | "dev": true, 810 | "license": "MIT", 811 | "dependencies": { 812 | "fill-range": "^7.1.1" 813 | }, 814 | "engines": { 815 | "node": ">=8" 816 | } 817 | }, 818 | "node_modules/builtin-modules": { 819 | "version": "3.3.0", 820 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", 821 | "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", 822 | "dev": true, 823 | "license": "MIT", 824 | "engines": { 825 | "node": ">=6" 826 | }, 827 | "funding": { 828 | "url": "https://github.com/sponsors/sindresorhus" 829 | } 830 | }, 831 | "node_modules/clean-stack": { 832 | "version": "2.2.0", 833 | "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", 834 | "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", 835 | "dev": true, 836 | "license": "MIT", 837 | "engines": { 838 | "node": ">=6" 839 | } 840 | }, 841 | "node_modules/clone": { 842 | "version": "1.0.4", 843 | "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", 844 | "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", 845 | "dev": true, 846 | "license": "MIT", 847 | "optional": true, 848 | "engines": { 849 | "node": ">=0.8" 850 | } 851 | }, 852 | "node_modules/color-convert": { 853 | "version": "2.0.1", 854 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 855 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 856 | "dev": true, 857 | "license": "MIT", 858 | "dependencies": { 859 | "color-name": "~1.1.4" 860 | }, 861 | "engines": { 862 | "node": ">=7.0.0" 863 | } 864 | }, 865 | "node_modules/color-name": { 866 | "version": "1.1.4", 867 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 868 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 869 | "dev": true, 870 | "license": "MIT" 871 | }, 872 | "node_modules/commander": { 873 | "version": "4.1.1", 874 | "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", 875 | "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", 876 | "dev": true, 877 | "license": "MIT", 878 | "engines": { 879 | "node": ">= 6" 880 | } 881 | }, 882 | "node_modules/cross-spawn": { 883 | "version": "7.0.6", 884 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 885 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 886 | "dev": true, 887 | "license": "MIT", 888 | "dependencies": { 889 | "path-key": "^3.1.0", 890 | "shebang-command": "^2.0.0", 891 | "which": "^2.0.1" 892 | }, 893 | "engines": { 894 | "node": ">= 8" 895 | } 896 | }, 897 | "node_modules/deep-extend": { 898 | "version": "0.6.0", 899 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 900 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 901 | "dev": true, 902 | "license": "MIT", 903 | "engines": { 904 | "node": ">=4.0.0" 905 | } 906 | }, 907 | "node_modules/defaults": { 908 | "version": "1.0.4", 909 | "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", 910 | "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", 911 | "dev": true, 912 | "license": "MIT", 913 | "optional": true, 914 | "dependencies": { 915 | "clone": "^1.0.2" 916 | }, 917 | "funding": { 918 | "url": "https://github.com/sponsors/sindresorhus" 919 | } 920 | }, 921 | "node_modules/eastasianwidth": { 922 | "version": "0.2.0", 923 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 924 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 925 | "dev": true, 926 | "license": "MIT" 927 | }, 928 | "node_modules/easy-table": { 929 | "version": "1.2.0", 930 | "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", 931 | "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==", 932 | "dev": true, 933 | "license": "MIT", 934 | "dependencies": { 935 | "ansi-regex": "^5.0.1" 936 | }, 937 | "optionalDependencies": { 938 | "wcwidth": "^1.0.1" 939 | } 940 | }, 941 | "node_modules/emoji-regex": { 942 | "version": "9.2.2", 943 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 944 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 945 | "dev": true, 946 | "license": "MIT" 947 | }, 948 | "node_modules/enhanced-resolve": { 949 | "version": "5.17.1", 950 | "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", 951 | "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", 952 | "dev": true, 953 | "license": "MIT", 954 | "dependencies": { 955 | "graceful-fs": "^4.2.4", 956 | "tapable": "^2.2.0" 957 | }, 958 | "engines": { 959 | "node": ">=10.13.0" 960 | } 961 | }, 962 | "node_modules/entities": { 963 | "version": "4.5.0", 964 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 965 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 966 | "dev": true, 967 | "license": "BSD-2-Clause", 968 | "engines": { 969 | "node": ">=0.12" 970 | }, 971 | "funding": { 972 | "url": "https://github.com/fb55/entities?sponsor=1" 973 | } 974 | }, 975 | "node_modules/esbuild": { 976 | "version": "0.25.1", 977 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", 978 | "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", 979 | "dev": true, 980 | "hasInstallScript": true, 981 | "license": "MIT", 982 | "bin": { 983 | "esbuild": "bin/esbuild" 984 | }, 985 | "engines": { 986 | "node": ">=18" 987 | }, 988 | "optionalDependencies": { 989 | "@esbuild/aix-ppc64": "0.25.1", 990 | "@esbuild/android-arm": "0.25.1", 991 | "@esbuild/android-arm64": "0.25.1", 992 | "@esbuild/android-x64": "0.25.1", 993 | "@esbuild/darwin-arm64": "0.25.1", 994 | "@esbuild/darwin-x64": "0.25.1", 995 | "@esbuild/freebsd-arm64": "0.25.1", 996 | "@esbuild/freebsd-x64": "0.25.1", 997 | "@esbuild/linux-arm": "0.25.1", 998 | "@esbuild/linux-arm64": "0.25.1", 999 | "@esbuild/linux-ia32": "0.25.1", 1000 | "@esbuild/linux-loong64": "0.25.1", 1001 | "@esbuild/linux-mips64el": "0.25.1", 1002 | "@esbuild/linux-ppc64": "0.25.1", 1003 | "@esbuild/linux-riscv64": "0.25.1", 1004 | "@esbuild/linux-s390x": "0.25.1", 1005 | "@esbuild/linux-x64": "0.25.1", 1006 | "@esbuild/netbsd-arm64": "0.25.1", 1007 | "@esbuild/netbsd-x64": "0.25.1", 1008 | "@esbuild/openbsd-arm64": "0.25.1", 1009 | "@esbuild/openbsd-x64": "0.25.1", 1010 | "@esbuild/sunos-x64": "0.25.1", 1011 | "@esbuild/win32-arm64": "0.25.1", 1012 | "@esbuild/win32-ia32": "0.25.1", 1013 | "@esbuild/win32-x64": "0.25.1" 1014 | } 1015 | }, 1016 | "node_modules/fast-glob": { 1017 | "version": "3.3.2", 1018 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", 1019 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", 1020 | "dev": true, 1021 | "license": "MIT", 1022 | "dependencies": { 1023 | "@nodelib/fs.stat": "^2.0.2", 1024 | "@nodelib/fs.walk": "^1.2.3", 1025 | "glob-parent": "^5.1.2", 1026 | "merge2": "^1.3.0", 1027 | "micromatch": "^4.0.4" 1028 | }, 1029 | "engines": { 1030 | "node": ">=8.6.0" 1031 | } 1032 | }, 1033 | "node_modules/fastq": { 1034 | "version": "1.17.1", 1035 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", 1036 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", 1037 | "dev": true, 1038 | "license": "ISC", 1039 | "dependencies": { 1040 | "reusify": "^1.0.4" 1041 | } 1042 | }, 1043 | "node_modules/fill-range": { 1044 | "version": "7.1.1", 1045 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 1046 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 1047 | "dev": true, 1048 | "license": "MIT", 1049 | "dependencies": { 1050 | "to-regex-range": "^5.0.1" 1051 | }, 1052 | "engines": { 1053 | "node": ">=8" 1054 | } 1055 | }, 1056 | "node_modules/foreground-child": { 1057 | "version": "3.3.0", 1058 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", 1059 | "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", 1060 | "dev": true, 1061 | "license": "ISC", 1062 | "dependencies": { 1063 | "cross-spawn": "^7.0.0", 1064 | "signal-exit": "^4.0.1" 1065 | }, 1066 | "engines": { 1067 | "node": ">=14" 1068 | }, 1069 | "funding": { 1070 | "url": "https://github.com/sponsors/isaacs" 1071 | } 1072 | }, 1073 | "node_modules/get-stdin": { 1074 | "version": "9.0.0", 1075 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", 1076 | "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", 1077 | "dev": true, 1078 | "license": "MIT", 1079 | "engines": { 1080 | "node": ">=12" 1081 | }, 1082 | "funding": { 1083 | "url": "https://github.com/sponsors/sindresorhus" 1084 | } 1085 | }, 1086 | "node_modules/glob": { 1087 | "version": "11.0.0", 1088 | "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", 1089 | "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", 1090 | "dev": true, 1091 | "license": "ISC", 1092 | "dependencies": { 1093 | "foreground-child": "^3.1.0", 1094 | "jackspeak": "^4.0.1", 1095 | "minimatch": "^10.0.0", 1096 | "minipass": "^7.1.2", 1097 | "package-json-from-dist": "^1.0.0", 1098 | "path-scurry": "^2.0.0" 1099 | }, 1100 | "bin": { 1101 | "glob": "dist/esm/bin.mjs" 1102 | }, 1103 | "engines": { 1104 | "node": "20 || >=22" 1105 | }, 1106 | "funding": { 1107 | "url": "https://github.com/sponsors/isaacs" 1108 | } 1109 | }, 1110 | "node_modules/glob-parent": { 1111 | "version": "5.1.2", 1112 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1113 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1114 | "dev": true, 1115 | "license": "ISC", 1116 | "dependencies": { 1117 | "is-glob": "^4.0.1" 1118 | }, 1119 | "engines": { 1120 | "node": ">= 6" 1121 | } 1122 | }, 1123 | "node_modules/graceful-fs": { 1124 | "version": "4.2.11", 1125 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 1126 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 1127 | "dev": true, 1128 | "license": "ISC" 1129 | }, 1130 | "node_modules/ignore": { 1131 | "version": "5.3.2", 1132 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 1133 | "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 1134 | "dev": true, 1135 | "license": "MIT", 1136 | "engines": { 1137 | "node": ">= 4" 1138 | } 1139 | }, 1140 | "node_modules/indent-string": { 1141 | "version": "4.0.0", 1142 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", 1143 | "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", 1144 | "dev": true, 1145 | "license": "MIT", 1146 | "engines": { 1147 | "node": ">=8" 1148 | } 1149 | }, 1150 | "node_modules/ini": { 1151 | "version": "4.1.3", 1152 | "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", 1153 | "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", 1154 | "dev": true, 1155 | "license": "ISC", 1156 | "engines": { 1157 | "node": "^14.17.0 || ^16.13.0 || >=18.0.0" 1158 | } 1159 | }, 1160 | "node_modules/is-extglob": { 1161 | "version": "2.1.1", 1162 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1163 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1164 | "dev": true, 1165 | "license": "MIT", 1166 | "engines": { 1167 | "node": ">=0.10.0" 1168 | } 1169 | }, 1170 | "node_modules/is-fullwidth-code-point": { 1171 | "version": "3.0.0", 1172 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1173 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1174 | "dev": true, 1175 | "license": "MIT", 1176 | "engines": { 1177 | "node": ">=8" 1178 | } 1179 | }, 1180 | "node_modules/is-glob": { 1181 | "version": "4.0.3", 1182 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1183 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1184 | "dev": true, 1185 | "license": "MIT", 1186 | "dependencies": { 1187 | "is-extglob": "^2.1.1" 1188 | }, 1189 | "engines": { 1190 | "node": ">=0.10.0" 1191 | } 1192 | }, 1193 | "node_modules/is-number": { 1194 | "version": "7.0.0", 1195 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1196 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1197 | "dev": true, 1198 | "license": "MIT", 1199 | "engines": { 1200 | "node": ">=0.12.0" 1201 | } 1202 | }, 1203 | "node_modules/isexe": { 1204 | "version": "2.0.0", 1205 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1206 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1207 | "dev": true, 1208 | "license": "ISC" 1209 | }, 1210 | "node_modules/jackspeak": { 1211 | "version": "4.0.2", 1212 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", 1213 | "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", 1214 | "dev": true, 1215 | "license": "BlueOak-1.0.0", 1216 | "dependencies": { 1217 | "@isaacs/cliui": "^8.0.2" 1218 | }, 1219 | "engines": { 1220 | "node": "20 || >=22" 1221 | }, 1222 | "funding": { 1223 | "url": "https://github.com/sponsors/isaacs" 1224 | } 1225 | }, 1226 | "node_modules/jiti": { 1227 | "version": "1.21.6", 1228 | "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", 1229 | "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", 1230 | "dev": true, 1231 | "license": "MIT", 1232 | "bin": { 1233 | "jiti": "bin/jiti.js" 1234 | } 1235 | }, 1236 | "node_modules/js-yaml": { 1237 | "version": "4.1.0", 1238 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1239 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1240 | "dev": true, 1241 | "license": "MIT", 1242 | "dependencies": { 1243 | "argparse": "^2.0.1" 1244 | }, 1245 | "bin": { 1246 | "js-yaml": "bin/js-yaml.js" 1247 | } 1248 | }, 1249 | "node_modules/jsonc-parser": { 1250 | "version": "3.3.1", 1251 | "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", 1252 | "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", 1253 | "dev": true, 1254 | "license": "MIT" 1255 | }, 1256 | "node_modules/jsonpointer": { 1257 | "version": "5.0.1", 1258 | "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", 1259 | "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", 1260 | "dev": true, 1261 | "license": "MIT", 1262 | "engines": { 1263 | "node": ">=0.10.0" 1264 | } 1265 | }, 1266 | "node_modules/knip": { 1267 | "version": "5.30.6", 1268 | "resolved": "https://registry.npmjs.org/knip/-/knip-5.30.6.tgz", 1269 | "integrity": "sha512-YkcnRVl0N99xZ7eaXE7KlH/4cPTCn6BGuk9KxINEdCMFN3yita2vGBizApy97ZOHgghy8tb589gQ3xvLMFIO4w==", 1270 | "dev": true, 1271 | "funding": [ 1272 | { 1273 | "type": "github", 1274 | "url": "https://github.com/sponsors/webpro" 1275 | }, 1276 | { 1277 | "type": "opencollective", 1278 | "url": "https://opencollective.com/knip" 1279 | }, 1280 | { 1281 | "type": "polar", 1282 | "url": "https://polar.sh/webpro-nl" 1283 | } 1284 | ], 1285 | "license": "ISC", 1286 | "dependencies": { 1287 | "@nodelib/fs.walk": "1.2.8", 1288 | "@snyk/github-codeowners": "1.1.0", 1289 | "easy-table": "1.2.0", 1290 | "enhanced-resolve": "^5.17.1", 1291 | "fast-glob": "^3.3.2", 1292 | "jiti": "^1.21.6", 1293 | "js-yaml": "^4.1.0", 1294 | "minimist": "^1.2.8", 1295 | "picocolors": "^1.0.0", 1296 | "picomatch": "^4.0.1", 1297 | "pretty-ms": "^9.0.0", 1298 | "smol-toml": "^1.3.0", 1299 | "strip-json-comments": "5.0.1", 1300 | "summary": "2.1.0", 1301 | "zod": "^3.22.4", 1302 | "zod-validation-error": "^3.0.3" 1303 | }, 1304 | "bin": { 1305 | "knip": "bin/knip.js", 1306 | "knip-bun": "bin/knip-bun.js" 1307 | }, 1308 | "engines": { 1309 | "node": ">=18.6.0" 1310 | }, 1311 | "peerDependencies": { 1312 | "@types/node": ">=18", 1313 | "typescript": ">=5.0.4" 1314 | } 1315 | }, 1316 | "node_modules/linkify-it": { 1317 | "version": "5.0.0", 1318 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", 1319 | "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", 1320 | "dev": true, 1321 | "license": "MIT", 1322 | "dependencies": { 1323 | "uc.micro": "^2.0.0" 1324 | } 1325 | }, 1326 | "node_modules/lru-cache": { 1327 | "version": "11.0.1", 1328 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", 1329 | "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", 1330 | "dev": true, 1331 | "license": "ISC", 1332 | "engines": { 1333 | "node": "20 || >=22" 1334 | } 1335 | }, 1336 | "node_modules/markdown-it": { 1337 | "version": "14.1.0", 1338 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", 1339 | "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", 1340 | "dev": true, 1341 | "license": "MIT", 1342 | "dependencies": { 1343 | "argparse": "^2.0.1", 1344 | "entities": "^4.4.0", 1345 | "linkify-it": "^5.0.0", 1346 | "mdurl": "^2.0.0", 1347 | "punycode.js": "^2.3.1", 1348 | "uc.micro": "^2.1.0" 1349 | }, 1350 | "bin": { 1351 | "markdown-it": "bin/markdown-it.mjs" 1352 | } 1353 | }, 1354 | "node_modules/markdownlint": { 1355 | "version": "0.35.0", 1356 | "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.35.0.tgz", 1357 | "integrity": "sha512-wgp8yesWjFBL7bycA3hxwHRdsZGJhjhyP1dSxKVKrza0EPFYtn+mHtkVy6dvP1kGSjovyG5B8yNP6Frj0UFUJg==", 1358 | "dev": true, 1359 | "license": "MIT", 1360 | "dependencies": { 1361 | "markdown-it": "14.1.0", 1362 | "markdownlint-micromark": "0.1.10" 1363 | }, 1364 | "engines": { 1365 | "node": ">=18" 1366 | }, 1367 | "funding": { 1368 | "url": "https://github.com/sponsors/DavidAnson" 1369 | } 1370 | }, 1371 | "node_modules/markdownlint-cli": { 1372 | "version": "0.42.0", 1373 | "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.42.0.tgz", 1374 | "integrity": "sha512-AjkzhhZa3TmEGi/CE2Wpmny69x1IrzqK2gPB0k8SmNMRgnSAJfyEO5FgZdWTHtJ6Nrdv5FWt5c4C5pkG6Dk30A==", 1375 | "dev": true, 1376 | "license": "MIT", 1377 | "dependencies": { 1378 | "commander": "~12.1.0", 1379 | "get-stdin": "~9.0.0", 1380 | "glob": "~11.0.0", 1381 | "ignore": "~6.0.2", 1382 | "js-yaml": "^4.1.0", 1383 | "jsonc-parser": "~3.3.1", 1384 | "jsonpointer": "5.0.1", 1385 | "markdownlint": "~0.35.0", 1386 | "minimatch": "~10.0.1", 1387 | "run-con": "~1.3.2", 1388 | "smol-toml": "~1.3.0" 1389 | }, 1390 | "bin": { 1391 | "markdownlint": "markdownlint.js" 1392 | }, 1393 | "engines": { 1394 | "node": ">=18" 1395 | } 1396 | }, 1397 | "node_modules/markdownlint-cli/node_modules/commander": { 1398 | "version": "12.1.0", 1399 | "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", 1400 | "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", 1401 | "dev": true, 1402 | "license": "MIT", 1403 | "engines": { 1404 | "node": ">=18" 1405 | } 1406 | }, 1407 | "node_modules/markdownlint-cli/node_modules/ignore": { 1408 | "version": "6.0.2", 1409 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", 1410 | "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", 1411 | "dev": true, 1412 | "license": "MIT", 1413 | "engines": { 1414 | "node": ">= 4" 1415 | } 1416 | }, 1417 | "node_modules/markdownlint-micromark": { 1418 | "version": "0.1.10", 1419 | "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.10.tgz", 1420 | "integrity": "sha512-no5ZfdqAdWGxftCLlySHSgddEjyW4kui4z7amQcGsSKfYC5v/ou+8mIQVyg9KQMeEZLNtz9OPDTj7nnTnoR4FQ==", 1421 | "dev": true, 1422 | "license": "MIT", 1423 | "engines": { 1424 | "node": ">=18" 1425 | }, 1426 | "funding": { 1427 | "url": "https://github.com/sponsors/DavidAnson" 1428 | } 1429 | }, 1430 | "node_modules/mdurl": { 1431 | "version": "2.0.0", 1432 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", 1433 | "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", 1434 | "dev": true, 1435 | "license": "MIT" 1436 | }, 1437 | "node_modules/merge2": { 1438 | "version": "1.4.1", 1439 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1440 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1441 | "dev": true, 1442 | "license": "MIT", 1443 | "engines": { 1444 | "node": ">= 8" 1445 | } 1446 | }, 1447 | "node_modules/micromatch": { 1448 | "version": "4.0.8", 1449 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 1450 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 1451 | "dev": true, 1452 | "license": "MIT", 1453 | "dependencies": { 1454 | "braces": "^3.0.3", 1455 | "picomatch": "^2.3.1" 1456 | }, 1457 | "engines": { 1458 | "node": ">=8.6" 1459 | } 1460 | }, 1461 | "node_modules/micromatch/node_modules/picomatch": { 1462 | "version": "2.3.1", 1463 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1464 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1465 | "dev": true, 1466 | "license": "MIT", 1467 | "engines": { 1468 | "node": ">=8.6" 1469 | }, 1470 | "funding": { 1471 | "url": "https://github.com/sponsors/jonschlinkert" 1472 | } 1473 | }, 1474 | "node_modules/minimatch": { 1475 | "version": "10.0.1", 1476 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", 1477 | "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", 1478 | "dev": true, 1479 | "license": "ISC", 1480 | "dependencies": { 1481 | "brace-expansion": "^2.0.1" 1482 | }, 1483 | "engines": { 1484 | "node": "20 || >=22" 1485 | }, 1486 | "funding": { 1487 | "url": "https://github.com/sponsors/isaacs" 1488 | } 1489 | }, 1490 | "node_modules/minimist": { 1491 | "version": "1.2.8", 1492 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1493 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1494 | "dev": true, 1495 | "license": "MIT", 1496 | "funding": { 1497 | "url": "https://github.com/sponsors/ljharb" 1498 | } 1499 | }, 1500 | "node_modules/minipass": { 1501 | "version": "7.1.2", 1502 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 1503 | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 1504 | "dev": true, 1505 | "license": "ISC", 1506 | "engines": { 1507 | "node": ">=16 || 14 >=14.17" 1508 | } 1509 | }, 1510 | "node_modules/moment": { 1511 | "version": "2.29.4", 1512 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", 1513 | "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", 1514 | "dev": true, 1515 | "license": "MIT", 1516 | "engines": { 1517 | "node": "*" 1518 | } 1519 | }, 1520 | "node_modules/obsidian": { 1521 | "version": "1.7.2", 1522 | "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.7.2.tgz", 1523 | "integrity": "sha512-k9hN9brdknJC+afKr5FQzDRuEFGDKbDjfCazJwpgibwCAoZNYHYV8p/s3mM8I6AsnKrPKNXf8xGuMZ4enWelZQ==", 1524 | "dev": true, 1525 | "license": "MIT", 1526 | "dependencies": { 1527 | "@types/codemirror": "5.60.8", 1528 | "moment": "2.29.4" 1529 | }, 1530 | "peerDependencies": { 1531 | "@codemirror/state": "^6.0.0", 1532 | "@codemirror/view": "^6.0.0" 1533 | } 1534 | }, 1535 | "node_modules/p-map": { 1536 | "version": "4.0.0", 1537 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", 1538 | "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", 1539 | "dev": true, 1540 | "license": "MIT", 1541 | "dependencies": { 1542 | "aggregate-error": "^3.0.0" 1543 | }, 1544 | "engines": { 1545 | "node": ">=10" 1546 | }, 1547 | "funding": { 1548 | "url": "https://github.com/sponsors/sindresorhus" 1549 | } 1550 | }, 1551 | "node_modules/package-json-from-dist": { 1552 | "version": "1.0.1", 1553 | "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", 1554 | "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", 1555 | "dev": true, 1556 | "license": "BlueOak-1.0.0" 1557 | }, 1558 | "node_modules/parse-ms": { 1559 | "version": "4.0.0", 1560 | "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", 1561 | "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", 1562 | "dev": true, 1563 | "license": "MIT", 1564 | "engines": { 1565 | "node": ">=18" 1566 | }, 1567 | "funding": { 1568 | "url": "https://github.com/sponsors/sindresorhus" 1569 | } 1570 | }, 1571 | "node_modules/path-key": { 1572 | "version": "3.1.1", 1573 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1574 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1575 | "dev": true, 1576 | "license": "MIT", 1577 | "engines": { 1578 | "node": ">=8" 1579 | } 1580 | }, 1581 | "node_modules/path-scurry": { 1582 | "version": "2.0.0", 1583 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", 1584 | "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", 1585 | "dev": true, 1586 | "license": "BlueOak-1.0.0", 1587 | "dependencies": { 1588 | "lru-cache": "^11.0.0", 1589 | "minipass": "^7.1.2" 1590 | }, 1591 | "engines": { 1592 | "node": "20 || >=22" 1593 | }, 1594 | "funding": { 1595 | "url": "https://github.com/sponsors/isaacs" 1596 | } 1597 | }, 1598 | "node_modules/picocolors": { 1599 | "version": "1.1.0", 1600 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", 1601 | "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", 1602 | "dev": true, 1603 | "license": "ISC" 1604 | }, 1605 | "node_modules/picomatch": { 1606 | "version": "4.0.2", 1607 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 1608 | "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 1609 | "dev": true, 1610 | "license": "MIT", 1611 | "engines": { 1612 | "node": ">=12" 1613 | }, 1614 | "funding": { 1615 | "url": "https://github.com/sponsors/jonschlinkert" 1616 | } 1617 | }, 1618 | "node_modules/pretty-ms": { 1619 | "version": "9.1.0", 1620 | "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.1.0.tgz", 1621 | "integrity": "sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==", 1622 | "dev": true, 1623 | "license": "MIT", 1624 | "dependencies": { 1625 | "parse-ms": "^4.0.0" 1626 | }, 1627 | "engines": { 1628 | "node": ">=18" 1629 | }, 1630 | "funding": { 1631 | "url": "https://github.com/sponsors/sindresorhus" 1632 | } 1633 | }, 1634 | "node_modules/punycode.js": { 1635 | "version": "2.3.1", 1636 | "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", 1637 | "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", 1638 | "dev": true, 1639 | "license": "MIT", 1640 | "engines": { 1641 | "node": ">=6" 1642 | } 1643 | }, 1644 | "node_modules/queue-microtask": { 1645 | "version": "1.2.3", 1646 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1647 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1648 | "dev": true, 1649 | "funding": [ 1650 | { 1651 | "type": "github", 1652 | "url": "https://github.com/sponsors/feross" 1653 | }, 1654 | { 1655 | "type": "patreon", 1656 | "url": "https://www.patreon.com/feross" 1657 | }, 1658 | { 1659 | "type": "consulting", 1660 | "url": "https://feross.org/support" 1661 | } 1662 | ], 1663 | "license": "MIT" 1664 | }, 1665 | "node_modules/reusify": { 1666 | "version": "1.0.4", 1667 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1668 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1669 | "dev": true, 1670 | "license": "MIT", 1671 | "engines": { 1672 | "iojs": ">=1.0.0", 1673 | "node": ">=0.10.0" 1674 | } 1675 | }, 1676 | "node_modules/run-con": { 1677 | "version": "1.3.2", 1678 | "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", 1679 | "integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==", 1680 | "dev": true, 1681 | "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", 1682 | "dependencies": { 1683 | "deep-extend": "^0.6.0", 1684 | "ini": "~4.1.0", 1685 | "minimist": "^1.2.8", 1686 | "strip-json-comments": "~3.1.1" 1687 | }, 1688 | "bin": { 1689 | "run-con": "cli.js" 1690 | } 1691 | }, 1692 | "node_modules/run-con/node_modules/strip-json-comments": { 1693 | "version": "3.1.1", 1694 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1695 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1696 | "dev": true, 1697 | "license": "MIT", 1698 | "engines": { 1699 | "node": ">=8" 1700 | }, 1701 | "funding": { 1702 | "url": "https://github.com/sponsors/sindresorhus" 1703 | } 1704 | }, 1705 | "node_modules/run-parallel": { 1706 | "version": "1.2.0", 1707 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1708 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1709 | "dev": true, 1710 | "funding": [ 1711 | { 1712 | "type": "github", 1713 | "url": "https://github.com/sponsors/feross" 1714 | }, 1715 | { 1716 | "type": "patreon", 1717 | "url": "https://www.patreon.com/feross" 1718 | }, 1719 | { 1720 | "type": "consulting", 1721 | "url": "https://feross.org/support" 1722 | } 1723 | ], 1724 | "license": "MIT", 1725 | "dependencies": { 1726 | "queue-microtask": "^1.2.2" 1727 | } 1728 | }, 1729 | "node_modules/shebang-command": { 1730 | "version": "2.0.0", 1731 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1732 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1733 | "dev": true, 1734 | "license": "MIT", 1735 | "dependencies": { 1736 | "shebang-regex": "^3.0.0" 1737 | }, 1738 | "engines": { 1739 | "node": ">=8" 1740 | } 1741 | }, 1742 | "node_modules/shebang-regex": { 1743 | "version": "3.0.0", 1744 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1745 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1746 | "dev": true, 1747 | "license": "MIT", 1748 | "engines": { 1749 | "node": ">=8" 1750 | } 1751 | }, 1752 | "node_modules/signal-exit": { 1753 | "version": "4.1.0", 1754 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 1755 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 1756 | "dev": true, 1757 | "license": "ISC", 1758 | "engines": { 1759 | "node": ">=14" 1760 | }, 1761 | "funding": { 1762 | "url": "https://github.com/sponsors/isaacs" 1763 | } 1764 | }, 1765 | "node_modules/smol-toml": { 1766 | "version": "1.3.1", 1767 | "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.1.tgz", 1768 | "integrity": "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==", 1769 | "dev": true, 1770 | "license": "BSD-3-Clause", 1771 | "engines": { 1772 | "node": ">= 18" 1773 | }, 1774 | "funding": { 1775 | "url": "https://github.com/sponsors/cyyynthia" 1776 | } 1777 | }, 1778 | "node_modules/string-width": { 1779 | "version": "5.1.2", 1780 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 1781 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 1782 | "dev": true, 1783 | "license": "MIT", 1784 | "dependencies": { 1785 | "eastasianwidth": "^0.2.0", 1786 | "emoji-regex": "^9.2.2", 1787 | "strip-ansi": "^7.0.1" 1788 | }, 1789 | "engines": { 1790 | "node": ">=12" 1791 | }, 1792 | "funding": { 1793 | "url": "https://github.com/sponsors/sindresorhus" 1794 | } 1795 | }, 1796 | "node_modules/string-width-cjs": { 1797 | "name": "string-width", 1798 | "version": "4.2.3", 1799 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1800 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1801 | "dev": true, 1802 | "license": "MIT", 1803 | "dependencies": { 1804 | "emoji-regex": "^8.0.0", 1805 | "is-fullwidth-code-point": "^3.0.0", 1806 | "strip-ansi": "^6.0.1" 1807 | }, 1808 | "engines": { 1809 | "node": ">=8" 1810 | } 1811 | }, 1812 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 1813 | "version": "8.0.0", 1814 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1815 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1816 | "dev": true, 1817 | "license": "MIT" 1818 | }, 1819 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 1820 | "version": "6.0.1", 1821 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1822 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1823 | "dev": true, 1824 | "license": "MIT", 1825 | "dependencies": { 1826 | "ansi-regex": "^5.0.1" 1827 | }, 1828 | "engines": { 1829 | "node": ">=8" 1830 | } 1831 | }, 1832 | "node_modules/strip-ansi": { 1833 | "version": "7.1.0", 1834 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 1835 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 1836 | "dev": true, 1837 | "license": "MIT", 1838 | "dependencies": { 1839 | "ansi-regex": "^6.0.1" 1840 | }, 1841 | "engines": { 1842 | "node": ">=12" 1843 | }, 1844 | "funding": { 1845 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 1846 | } 1847 | }, 1848 | "node_modules/strip-ansi-cjs": { 1849 | "name": "strip-ansi", 1850 | "version": "6.0.1", 1851 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1852 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1853 | "dev": true, 1854 | "license": "MIT", 1855 | "dependencies": { 1856 | "ansi-regex": "^5.0.1" 1857 | }, 1858 | "engines": { 1859 | "node": ">=8" 1860 | } 1861 | }, 1862 | "node_modules/strip-ansi/node_modules/ansi-regex": { 1863 | "version": "6.1.0", 1864 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", 1865 | "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", 1866 | "dev": true, 1867 | "license": "MIT", 1868 | "engines": { 1869 | "node": ">=12" 1870 | }, 1871 | "funding": { 1872 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 1873 | } 1874 | }, 1875 | "node_modules/strip-json-comments": { 1876 | "version": "5.0.1", 1877 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", 1878 | "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", 1879 | "dev": true, 1880 | "license": "MIT", 1881 | "engines": { 1882 | "node": ">=14.16" 1883 | }, 1884 | "funding": { 1885 | "url": "https://github.com/sponsors/sindresorhus" 1886 | } 1887 | }, 1888 | "node_modules/style-mod": { 1889 | "version": "4.1.2", 1890 | "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", 1891 | "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", 1892 | "dev": true, 1893 | "license": "MIT", 1894 | "peer": true 1895 | }, 1896 | "node_modules/summary": { 1897 | "version": "2.1.0", 1898 | "resolved": "https://registry.npmjs.org/summary/-/summary-2.1.0.tgz", 1899 | "integrity": "sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==", 1900 | "dev": true, 1901 | "license": "MIT" 1902 | }, 1903 | "node_modules/tapable": { 1904 | "version": "2.2.1", 1905 | "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", 1906 | "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", 1907 | "dev": true, 1908 | "license": "MIT", 1909 | "engines": { 1910 | "node": ">=6" 1911 | } 1912 | }, 1913 | "node_modules/to-regex-range": { 1914 | "version": "5.0.1", 1915 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1916 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1917 | "dev": true, 1918 | "license": "MIT", 1919 | "dependencies": { 1920 | "is-number": "^7.0.0" 1921 | }, 1922 | "engines": { 1923 | "node": ">=8.0" 1924 | } 1925 | }, 1926 | "node_modules/tslib": { 1927 | "version": "2.7.0", 1928 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", 1929 | "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", 1930 | "dev": true, 1931 | "license": "0BSD" 1932 | }, 1933 | "node_modules/typescript": { 1934 | "version": "5.6.2", 1935 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", 1936 | "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", 1937 | "dev": true, 1938 | "license": "Apache-2.0", 1939 | "bin": { 1940 | "tsc": "bin/tsc", 1941 | "tsserver": "bin/tsserver" 1942 | }, 1943 | "engines": { 1944 | "node": ">=14.17" 1945 | } 1946 | }, 1947 | "node_modules/uc.micro": { 1948 | "version": "2.1.0", 1949 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", 1950 | "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", 1951 | "dev": true, 1952 | "license": "MIT" 1953 | }, 1954 | "node_modules/undici-types": { 1955 | "version": "6.19.8", 1956 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 1957 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 1958 | "dev": true, 1959 | "license": "MIT" 1960 | }, 1961 | "node_modules/w3c-keyname": { 1962 | "version": "2.2.8", 1963 | "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", 1964 | "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", 1965 | "dev": true, 1966 | "license": "MIT", 1967 | "peer": true 1968 | }, 1969 | "node_modules/wcwidth": { 1970 | "version": "1.0.1", 1971 | "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", 1972 | "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", 1973 | "dev": true, 1974 | "license": "MIT", 1975 | "optional": true, 1976 | "dependencies": { 1977 | "defaults": "^1.0.3" 1978 | } 1979 | }, 1980 | "node_modules/which": { 1981 | "version": "2.0.2", 1982 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1983 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1984 | "dev": true, 1985 | "license": "ISC", 1986 | "dependencies": { 1987 | "isexe": "^2.0.0" 1988 | }, 1989 | "bin": { 1990 | "node-which": "bin/node-which" 1991 | }, 1992 | "engines": { 1993 | "node": ">= 8" 1994 | } 1995 | }, 1996 | "node_modules/wrap-ansi": { 1997 | "version": "8.1.0", 1998 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 1999 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 2000 | "dev": true, 2001 | "license": "MIT", 2002 | "dependencies": { 2003 | "ansi-styles": "^6.1.0", 2004 | "string-width": "^5.0.1", 2005 | "strip-ansi": "^7.0.1" 2006 | }, 2007 | "engines": { 2008 | "node": ">=12" 2009 | }, 2010 | "funding": { 2011 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 2012 | } 2013 | }, 2014 | "node_modules/wrap-ansi-cjs": { 2015 | "name": "wrap-ansi", 2016 | "version": "7.0.0", 2017 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 2018 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 2019 | "dev": true, 2020 | "license": "MIT", 2021 | "dependencies": { 2022 | "ansi-styles": "^4.0.0", 2023 | "string-width": "^4.1.0", 2024 | "strip-ansi": "^6.0.0" 2025 | }, 2026 | "engines": { 2027 | "node": ">=10" 2028 | }, 2029 | "funding": { 2030 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 2031 | } 2032 | }, 2033 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 2034 | "version": "4.3.0", 2035 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 2036 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 2037 | "dev": true, 2038 | "license": "MIT", 2039 | "dependencies": { 2040 | "color-convert": "^2.0.1" 2041 | }, 2042 | "engines": { 2043 | "node": ">=8" 2044 | }, 2045 | "funding": { 2046 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2047 | } 2048 | }, 2049 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 2050 | "version": "8.0.0", 2051 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 2052 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 2053 | "dev": true, 2054 | "license": "MIT" 2055 | }, 2056 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 2057 | "version": "4.2.3", 2058 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 2059 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 2060 | "dev": true, 2061 | "license": "MIT", 2062 | "dependencies": { 2063 | "emoji-regex": "^8.0.0", 2064 | "is-fullwidth-code-point": "^3.0.0", 2065 | "strip-ansi": "^6.0.1" 2066 | }, 2067 | "engines": { 2068 | "node": ">=8" 2069 | } 2070 | }, 2071 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 2072 | "version": "6.0.1", 2073 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2074 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2075 | "dev": true, 2076 | "license": "MIT", 2077 | "dependencies": { 2078 | "ansi-regex": "^5.0.1" 2079 | }, 2080 | "engines": { 2081 | "node": ">=8" 2082 | } 2083 | }, 2084 | "node_modules/zod": { 2085 | "version": "3.23.8", 2086 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", 2087 | "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", 2088 | "dev": true, 2089 | "license": "MIT", 2090 | "funding": { 2091 | "url": "https://github.com/sponsors/colinhacks" 2092 | } 2093 | }, 2094 | "node_modules/zod-validation-error": { 2095 | "version": "3.4.0", 2096 | "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz", 2097 | "integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==", 2098 | "dev": true, 2099 | "license": "MIT", 2100 | "engines": { 2101 | "node": ">=18.0.0" 2102 | }, 2103 | "peerDependencies": { 2104 | "zod": "^3.18.0" 2105 | } 2106 | } 2107 | } 2108 | } 2109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Grappling Hook", 3 | "version": "1.2.5", 4 | "description": "Obsidian Plugin for blazingly fast file switching. For people for whom using the Quick Switcher still takes too much time.", 5 | "main": "main.js", 6 | "keywords": [], 7 | "author": "Christopher Grieser", 8 | "license": "MIT", 9 | "devDependencies": { 10 | "@biomejs/biome": "latest", 11 | "@types/node": "^22.5.5", 12 | "builtin-modules": "^3.2.0", 13 | "esbuild": "^0.25.1", 14 | "knip": "latest", 15 | "markdownlint-cli": "latest", 16 | "obsidian": "latest", 17 | "tslib": "2.7.0", 18 | "typescript": "^5.6.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/altfile.ts: -------------------------------------------------------------------------------- 1 | import { FileView, Notice, TFile } from "obsidian"; 2 | import GrapplingHook from "src/main"; 3 | import { getRootLeaves } from "src/utils"; 4 | 5 | function getAlternateNote(plugin: GrapplingHook): TFile | null { 6 | const recentFiles = plugin.app.workspace.getLastOpenFiles(); 7 | const currentFile = plugin.app.workspace.getActiveViewOfType(FileView)?.file?.path; 8 | const openableExtensions = ["md", "png", "pdf", "jpeg", "jpg"]; 9 | 10 | for (const filePath of recentFiles) { 11 | const altTFile = plugin.app.vault.getFileByPath(filePath); 12 | const isOpenable = altTFile && openableExtensions.includes(altTFile.extension); 13 | if (filePath !== currentFile && isOpenable) return altTFile; 14 | } 15 | return null; 16 | } 17 | 18 | //────────────────────────────────────────────────────────────────────────────── 19 | 20 | export function updateStatusbar(plugin: GrapplingHook): void { 21 | const threshold = 30; 22 | const altTFile = getAlternateNote(plugin); 23 | let text = altTFile ? altTFile.basename : ""; 24 | if (text.length > threshold) text = text.slice(0, threshold) + "…"; 25 | plugin.statusbar.setText(text); 26 | } 27 | 28 | export function openAlternateNote(plugin: GrapplingHook): void { 29 | const altTFile = getAlternateNote(plugin); 30 | if (!altTFile) { 31 | new Notice("No valid recent note exists."); 32 | return; 33 | } 34 | 35 | const openTabs = getRootLeaves(plugin.app); 36 | if (openTabs.length === 0) { 37 | new Notice("No open tab."); 38 | return; 39 | } 40 | const altFileOpenInTab = openTabs.find((tab) => { 41 | return (tab.view as FileView).file?.path === altTFile.path; 42 | }); 43 | 44 | if (altFileOpenInTab) plugin.app.workspace.setActiveLeaf(altFileOpenInTab, { focus: true }); 45 | else plugin.app.workspace.getLeaf().openFile(altTFile); 46 | } 47 | -------------------------------------------------------------------------------- /src/commands/bookmark-cycler.ts: -------------------------------------------------------------------------------- 1 | import { App, MarkdownView, Notice } from "obsidian"; 2 | import GrapplingHook from "src/main"; 3 | import { BookmarkItem } from "src/obsidian-undocumented-api"; 4 | 5 | async function readBookmarksJson(app: App): Promise { 6 | let rawStr = ""; 7 | try { 8 | rawStr = await app.vault.adapter.read(app.vault.configDir + "/bookmarks.json"); 9 | } catch (_error) { 10 | // errors if file does not exist 11 | return []; 12 | } 13 | const bookmarkObjs: BookmarkItem[] = JSON.parse(rawStr).items; 14 | return bookmarkObjs; 15 | } 16 | 17 | async function getBookmarkedFilesSortedByMtime(app: App): Promise { 18 | // INFO fallback to bookmarks.json if bookmarks plugin is not enabled 19 | const bookmarkObjs = 20 | app.internalPlugins.plugins.bookmarks?.instance?.getBookmarks() || 21 | (await readBookmarksJson(app)); 22 | 23 | const bookmarkPaths = bookmarkObjs 24 | .reduce((acc: string[], bookmark) => { 25 | if (bookmark.type === "file" && bookmark.path) { 26 | const fileExists = app.vault.getFileByPath(bookmark.path); 27 | if (fileExists) acc.push(bookmark.path); 28 | } 29 | return acc; 30 | }, []) 31 | .sort((a, b) => { 32 | const aTfile = app.vault.getFileByPath(a); 33 | const bTfile = app.vault.getFileByPath(b); 34 | if (!aTfile || !bTfile) return 0; 35 | return bTfile.stat.mtime - aTfile.stat.mtime; 36 | }); 37 | return bookmarkPaths; 38 | } 39 | 40 | //────────────────────────────────────────────────────────────────────────────── 41 | 42 | export async function openLastModifiedBookmark(plugin: GrapplingHook): Promise { 43 | const lastBookmark = (await getBookmarkedFilesSortedByMtime(plugin.app))[0]; 44 | if (!lastBookmark) return; 45 | const file = plugin.app.vault.getFileByPath(lastBookmark); 46 | if (!file) return; 47 | await plugin.app.workspace.getLeaf().openFile(file); 48 | } 49 | 50 | // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: later 51 | export async function bookmarkCycler(plugin: GrapplingHook): Promise { 52 | const app = plugin.app; 53 | const view = app.workspace.getActiveViewOfType(MarkdownView); 54 | const editor = view?.editor; 55 | const mode = view?.getState().mode; 56 | 57 | // get BOOKMARKS 58 | const bookmarkPaths = await getBookmarkedFilesSortedByMtime(app); 59 | if (bookmarkPaths.length === 0) { 60 | new Notice("There are no bookmarked files."); 61 | return; 62 | } 63 | 64 | // get SELECTION 65 | let selection = ""; 66 | if (editor && mode === "source") { 67 | selection = editor.getSelection(); 68 | } else if (mode === "preview") { 69 | // in preview mode, get selection from active window (electron) 70 | // CAVEAT only retrieves plain text without markup though 71 | selection = activeWindow?.getSelection()?.toString() || ""; 72 | } 73 | 74 | // NO selection: cycle through bookmarks files 75 | // WITH selection: append to last modified bookmark 76 | if (selection) { 77 | const numberOfCursors = editor?.listSelections().length || 0; 78 | if (numberOfCursors > 1) { 79 | new Notice("Multiple Selections are not supported."); 80 | return; 81 | } 82 | const firstStarTFile = app.vault.getFileByPath(bookmarkPaths[0] as string); 83 | if (!firstStarTFile) { 84 | new Notice("There are no valid bookmarked files."); 85 | return; 86 | } 87 | await app.vault.append(firstStarTFile, selection + "\n"); 88 | new Notice(`Appended to "${firstStarTFile.basename}":\n\n"${selection}"`); 89 | } else { 90 | const currentFilePath = app.workspace.getActiveFile()?.path; 91 | 92 | // `findIndex()` returns -1 if current file is not bookmarked, which gives 93 | // simply `0` as next index, resulting in the first bookmarked file, which 94 | // is what we want 95 | const currentIndex = bookmarkPaths.findIndex((path: string) => path === currentFilePath); 96 | const nextIndex = (currentIndex + 1) % bookmarkPaths.length; 97 | const nextFilePath = bookmarkPaths[nextIndex] || ""; 98 | const nextFile = app.vault.getFileByPath(nextFilePath); 99 | if (!nextFile) { 100 | new Notice("There are no valid bookmarked files."); 101 | return; 102 | } 103 | if (nextFile.path === currentFilePath) { 104 | new Notice("Already at the sole starred file."); 105 | return; 106 | } 107 | await app.workspace.getLeaf().openFile(nextFile); 108 | } 109 | } 110 | 111 | export function sortBookmarksSidebar(plugin: GrapplingHook): void { 112 | const { app, settings } = plugin; 113 | const bookmarkPlugin = app.internalPlugins.plugins.bookmarks?.instance; 114 | if (!settings.keepBookmarksSidebarSorted || !bookmarkPlugin) return; 115 | 116 | bookmarkPlugin.items.sort((a, b) => { 117 | if (!a.path && !b.path) return 0; // no `.path` = non-file-bookmarks 118 | if (!a.path) return 1; // move non-files down 119 | if (!b.path) return -1; 120 | 121 | const aTfile = app.vault.getFileByPath(a.path); 122 | const bTfile = app.vault.getFileByPath(b.path); 123 | if (!aTfile && !bTfile) return 0; // no tfile = file does not exist anymore 124 | if (!aTfile) return 1; // move non-existing down 125 | if (!bTfile) return -1; 126 | return bTfile.stat.mtime - aTfile.stat.mtime; 127 | }); 128 | bookmarkPlugin._onItemsChanged(true); // trigger sidebar update 129 | } 130 | -------------------------------------------------------------------------------- /src/commands/cycle-files-in-folder.ts: -------------------------------------------------------------------------------- 1 | import { Notice, TFile } from "obsidian"; 2 | import GrapplingHook from "src/main"; 3 | 4 | export function cycleFilesInCurrentFolder(plugin: GrapplingHook, dir: "next" | "prev"): void { 5 | const currentFile = plugin.app.workspace.getActiveFile(); 6 | if (!currentFile) { 7 | new Notice("No file open."); 8 | return; 9 | } 10 | if (!currentFile.parent) { 11 | new Notice("File has no parent folder."); 12 | return; 13 | } 14 | 15 | const mdFileInFolder = currentFile.parent.children 16 | .filter((file) => file instanceof TFile && file.extension === "md") 17 | .sort((a, b) => (a.name < b.name ? -1 : 1)) as TFile[]; 18 | 19 | if (mdFileInFolder.length < 2) { 20 | new Notice("No other files in this folder to switch to."); 21 | return; 22 | } 23 | 24 | const currentIndex = mdFileInFolder.findIndex((file) => file.path === currentFile.path); 25 | const nextIndex = 26 | dir === "next" 27 | ? (currentIndex + 1) % mdFileInFolder.length 28 | : (currentIndex + mdFileInFolder.length - 1) % mdFileInFolder.length; 29 | const nextFile = mdFileInFolder[nextIndex] as TFile; 30 | 31 | plugin.app.workspace.getLeaf().openFile(nextFile); 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/cycle-tabs-across-splits.ts: -------------------------------------------------------------------------------- 1 | import { Notice, WorkspaceLeaf } from "obsidian"; 2 | import GrapplingHook from "src/main"; 3 | import { getRootLeaves } from "src/utils"; 4 | 5 | export function cycleTabsAcrossSplits(plugin: GrapplingHook): void { 6 | const activeLeaf = plugin.app.workspace.getLeaf(); 7 | if (!activeLeaf) return; 8 | 9 | const openTabs = getRootLeaves(plugin.app); 10 | if (openTabs.length < 2) { 11 | new Notice("No other tabs to switch to."); 12 | return; 13 | } 14 | const activeTabIndex = openTabs.findIndex((l) => l.id === activeLeaf.id); 15 | if (activeTabIndex === -1) { 16 | new Notice("No active tab found."); 17 | return; 18 | } 19 | const nextLeafIndex = (activeTabIndex + 1) % openTabs.length; 20 | const nextLeaf = openTabs[nextLeafIndex] as WorkspaceLeaf; 21 | plugin.app.workspace.setActiveLeaf(nextLeaf, { focus: true }); 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/open-first-url-in-file.ts: -------------------------------------------------------------------------------- 1 | import { Editor, Notice } from "obsidian"; 2 | 3 | const urlRegex = /https?:\/\/[^\s)"']+/; 4 | 5 | export function openFirstUrlInFile(editor: Editor): void { 6 | const [firstUrl] = editor.getValue().match(urlRegex) || []; 7 | if (!firstUrl) { 8 | new Notice("No URL found in current file."); 9 | return; 10 | } 11 | window.open(firstUrl); 12 | } 13 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Editor, Plugin } from "obsidian"; 2 | import { openAlternateNote, updateStatusbar } from "./commands/altfile"; 3 | import { 4 | bookmarkCycler, 5 | openLastModifiedBookmark, 6 | sortBookmarksSidebar, 7 | } from "./commands/bookmark-cycler"; 8 | import { cycleFilesInCurrentFolder } from "./commands/cycle-files-in-folder"; 9 | import { cycleTabsAcrossSplits } from "./commands/cycle-tabs-across-splits"; 10 | import { openFirstUrlInFile } from "./commands/open-first-url-in-file"; 11 | import { DEFAULT_SETTINGS, GrapplingHookSettingsMenu } from "./settings"; 12 | 13 | // biome-ignore lint/style/noDefaultExport: required for Obsidian plugins to work 14 | export default class GrapplingHook extends Plugin { 15 | statusbar = this.addStatusBarItem(); 16 | settings = DEFAULT_SETTINGS; // only fallback value, overwritten in `onload` 17 | cssclass = this.manifest.id; 18 | 19 | override async onload(): Promise { 20 | console.info(this.manifest.name + " Plugin loaded."); 21 | 22 | // settings & open last modified (if enabled) 23 | await this.loadSettings(); 24 | this.addSettingTab(new GrapplingHookSettingsMenu(this)); 25 | if (this.settings.openLastModifiedBookmarkOnStartup) { 26 | // `onLayoutReady` only triggers when Obsidian has finished loading 27 | this.app.workspace.onLayoutReady(() => openLastModifiedBookmark(this)); 28 | } 29 | 30 | // statusbar 31 | updateStatusbar(this); // initialize 32 | this.registerEvent(this.app.workspace.on("file-open", () => updateStatusbar(this))); 33 | 34 | // sort 35 | this.registerEvent(this.app.workspace.on("file-open", () => sortBookmarksSidebar(this))); 36 | 37 | // commands 38 | this.addCommand({ 39 | id: "alternate-note", 40 | name: "Switch to alternate note", 41 | callback: (): void => openAlternateNote(this), 42 | }); 43 | this.addCommand({ 44 | id: "cycle-starred-notes", 45 | name: "Cycle bookmarked notes / send selection to last bookmark", 46 | callback: async (): Promise => await bookmarkCycler(this), 47 | }); 48 | this.addCommand({ 49 | id: "cycle-tabs-across-splits", 50 | name: "Cycle tabs (across splits)", 51 | callback: (): void => cycleTabsAcrossSplits(this), 52 | }); 53 | this.addCommand({ 54 | id: "next-file-in-current-folder", 55 | name: "Next note in current folder", 56 | callback: (): void => cycleFilesInCurrentFolder(this, "next"), 57 | }); 58 | this.addCommand({ 59 | id: "previous-file-in-current-folder", 60 | name: "Previous note in current folder", 61 | callback: (): void => cycleFilesInCurrentFolder(this, "prev"), 62 | }); 63 | this.addCommand({ 64 | id: "open-first-url-in-file", 65 | name: "Open first url in file", 66 | editorCallback: (editor: Editor): void => openFirstUrlInFile(editor), 67 | }); 68 | } 69 | 70 | //─────────────────────────────────────────────────────────────────────────── 71 | 72 | override onunload(): void { 73 | console.info(this.manifest.name + " Plugin unloaded."); 74 | } 75 | 76 | async loadSettings(): Promise { 77 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 78 | } 79 | async saveSettings(): Promise { 80 | await this.saveData(this.settings); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/obsidian-undocumented-api.d.ts: -------------------------------------------------------------------------------- 1 | import "obsidian"; 2 | 3 | interface BookmarkItem { 4 | type: string; 5 | title: string; // filename 6 | path?: string; // no `.path` = non-file-bookmarks 7 | items: BookmarkItem[]; // if type "group", then can recursively contain itself 8 | } 9 | 10 | declare module "obsidian" { 11 | interface App { 12 | internalPlugins: { 13 | plugins: { 14 | bookmarks: { 15 | instance: { 16 | items: BookmarkItem[]; 17 | getBookmarks: () => BookmarkItem[]; 18 | _onItemsChanged(change: boolean): void; // update bookmarks sidebar 19 | }; 20 | }; 21 | }; 22 | }; 23 | } 24 | interface WorkspaceLeaf { 25 | id: string; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { PluginSettingTab, Setting } from "obsidian"; 2 | import GrapplingHook from "./main"; 3 | 4 | //────────────────────────────────────────────────────────────────────────────── 5 | 6 | export const DEFAULT_SETTINGS = { 7 | openLastModifiedBookmarkOnStartup: false, 8 | keepBookmarksSidebarSorted: false, 9 | }; 10 | 11 | //────────────────────────────────────────────────────────────────────────────── 12 | 13 | export class GrapplingHookSettingsMenu extends PluginSettingTab { 14 | plugin: GrapplingHook; 15 | 16 | constructor(plugin: GrapplingHook) { 17 | super(plugin.app, plugin); 18 | this.plugin = plugin; 19 | this.containerEl.addClass(plugin.cssclass); 20 | } 21 | 22 | display(): void { 23 | const { containerEl } = this; 24 | containerEl.empty(); 25 | const settings = this.plugin.settings; 26 | 27 | new Setting(containerEl) 28 | .setName("Startup: open last modified bookmark") 29 | .setDesc( 30 | "By default, Obsidian opens the most recent file on startup. " + 31 | "Enable this to open the last modified bookmark instead.", 32 | ) 33 | .addToggle((toggle) => 34 | toggle.setValue(settings.openLastModifiedBookmarkOnStartup).onChange(async (value) => { 35 | settings.openLastModifiedBookmarkOnStartup = value; 36 | await this.plugin.saveSettings(); 37 | }), 38 | ); 39 | 40 | new Setting(containerEl) 41 | .setName("Auto-sort items in bookmark sidebar by last modified time") 42 | .setDesc( 43 | "[🧪 Experimental] " + 44 | "This will make the order of bookmarks in the sidebar reflect the order the bookmark-cycling command will move through files. " + 45 | "(Note that this feature only works for bookmarks that are not in a folder.)", 46 | ) 47 | .addToggle((toggle) => 48 | toggle.setValue(settings.keepBookmarksSidebarSorted).onChange(async (value) => { 49 | settings.keepBookmarksSidebarSorted = value; 50 | await this.plugin.saveSettings(); 51 | }), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { App, WorkspaceLeaf } from "obsidian"; 2 | 3 | export function getRootLeaves(app: App): WorkspaceLeaf[] { 4 | const rootLeaves: WorkspaceLeaf[] = []; 5 | app.workspace.iterateRootLeaves((leaf) => { 6 | rootLeaves.push(leaf); 7 | }); 8 | return rootLeaves; 9 | } 10 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* alt file indicator */ 2 | .status-bar-item.plugin-grappling-hook:not(:empty)::before { 3 | content: "↪"; 4 | margin-right: 4px; 5 | } 6 | 7 | /* do not leave some gap for themes adding padding to statusbar items */ 8 | .status-bar-item.plugin-grappling-hook:empty { 9 | display: none; 10 | } 11 | -------------------------------------------------------------------------------- /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.0": "1.0", 3 | "0.1.1": "1.0", 4 | "0.1.1.1": "1.0", 5 | "0.1.1.2": "1.0", 6 | "0.1.1.3": "1.0", 7 | "0.1.1.4": "1.0", 8 | "0.1.1.5": "1.0", 9 | "0.2.0": "1.0", 10 | "0.2.1": "1.0", 11 | "0.2.2": "1.0", 12 | "0.2.3": "1.0", 13 | "0.2.4": "1.0", 14 | "0.2.5": "1.0", 15 | "0.3.0": "1.0", 16 | "0.3.1": "1.0", 17 | "0.3.2": "1.0", 18 | "0.4.0": "1.0", 19 | "0.4.1": "1.0", 20 | "0.5.0": "1.2.6", 21 | "0.5.1": "1.2.6", 22 | "0.5.2": "1.2.6", 23 | "0.5.3": "1.2.6", 24 | "0.5.4": "1.2.6", 25 | "0.6.0": "1.2.6", 26 | "0.7.0": "1.2.6", 27 | "0.7.1": "1.2.6", 28 | "0.7.2": "1.2.6", 29 | "0.7.3": "1.5.8", 30 | "0.8.0": "1.5.8", 31 | "0.8.1": "1.5.8", 32 | "0.8.2": "1.5.8", 33 | "0.9.0": "1.5.8", 34 | "1.0.0": "1.5.8", 35 | "1.1.0": "1.5.8", 36 | "1.1.1": "1.5.8", 37 | "1.1.2": "1.5.8", 38 | "1.1.3": "1.5.8", 39 | "1.2.0": "1.5.8", 40 | "1.2.1": "1.5.8", 41 | "1.2.2": "1.5.8", 42 | "1.2.3": "1.5.8", 43 | "1.2.4": "1.5.8", 44 | "1.2.5": "1.5.8" 45 | } 46 | --------------------------------------------------------------------------------