diff --git a/.github/workflows/bundle-desktop.yml b/.github/workflows/bundle-desktop.yml index 38798cc7..c4856158 100644 --- a/.github/workflows/bundle-desktop.yml +++ b/.github/workflows/bundle-desktop.yml @@ -21,6 +21,11 @@ on: required: false default: true type: boolean + ref: + description: 'Git ref to checkout (branch, tag, or SHA). Defaults to main branch if not specified.' + required: false + type: string + default: '' secrets: CERTIFICATE_OSX_APPLICATION: description: 'Certificate for macOS application signing' @@ -45,6 +50,30 @@ jobs: runs-on: macos-latest name: Bundle Desktop App on macOS steps: + # Debug information about the workflow and inputs + - name: Debug workflow info + env: + WORKFLOW_NAME: ${{ github.workflow }} + WORKFLOW_REF: ${{ github.ref }} + EVENT_NAME: ${{ github.event_name }} + REPOSITORY: ${{ github.repository }} + INPUT_REF: ${{ inputs.ref }} + INPUT_VERSION: ${{ inputs.version }} + INPUT_SIGNING: ${{ inputs.signing }} + INPUT_QUICK_TEST: ${{ inputs.quick_test }} + run: | + echo "=== Workflow Information ===" + echo "Workflow: ${WORKFLOW_NAME}" + echo "Ref: ${WORKFLOW_REF}" + echo "Event: ${EVENT_NAME}" + echo "Repo: ${REPOSITORY}" + echo "" + echo "=== Input Parameters ===" + echo "Build ref: ${INPUT_REF:-}" + echo "Version: ${INPUT_VERSION:-not set}" + echo "Signing: ${INPUT_SIGNING:-false}" + echo "Quick test: ${INPUT_QUICK_TEST:-true}" + # Check initial disk space - name: Check initial disk space run: df -h @@ -52,43 +81,63 @@ jobs: # Validate Signing Secrets if signing is enabled - name: Validate Signing Secrets if: ${{ inputs.signing }} + env: + HAS_CERT: ${{ secrets.CERTIFICATE_OSX_APPLICATION != '' }} + HAS_CERT_PASS: ${{ secrets.CERTIFICATE_PASSWORD != '' }} + HAS_APPLE_ID: ${{ secrets.APPLE_ID != '' }} + HAS_APPLE_PASS: ${{ secrets.APPLE_ID_PASSWORD != '' }} + HAS_TEAM_ID: ${{ secrets.APPLE_TEAM_ID != '' }} run: | - if [[ -z "${{ secrets.CERTIFICATE_OSX_APPLICATION }}" ]]; then - echo "Error: CERTIFICATE_OSX_APPLICATION secret is required for signing." - exit 1 - fi - if [[ -z "${{ secrets.CERTIFICATE_PASSWORD }}" ]]; then - echo "Error: CERTIFICATE_PASSWORD secret is required for signing." - exit 1 - fi - if [[ -z "${{ secrets.APPLE_ID }}" ]]; then - echo "Error: APPLE_ID secret is required for signing." - exit 1 - fi - if [[ -z "${{ secrets.APPLE_ID_PASSWORD }}" ]]; then - echo "Error: APPLE_ID_PASSWORD secret is required for signing." - exit 1 - fi - if [[ -z "${{ secrets.APPLE_TEAM_ID }}" ]]; then - echo "Error: APPLE_TEAM_ID secret is required for signing." + missing=() + [[ "${HAS_CERT}" != "true" ]] && missing+=("CERTIFICATE_OSX_APPLICATION") + [[ "${HAS_CERT_PASS}" != "true" ]] && missing+=("CERTIFICATE_PASSWORD") + [[ "${HAS_APPLE_ID}" != "true" ]] && missing+=("APPLE_ID") + [[ "${HAS_APPLE_PASS}" != "true" ]] && missing+=("APPLE_ID_PASSWORD") + [[ "${HAS_TEAM_ID}" != "true" ]] && missing+=("APPLE_TEAM_ID") + + if (( ${#missing[@]} > 0 )); then + echo "Error: Missing required signing secrets:" + printf '%s\n' "${missing[@]}" exit 1 fi + echo "All required signing secrets are present." - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + # Only pass ref if it's explicitly set, otherwise let checkout action use its default behavior + ref: ${{ inputs.ref != '' && inputs.ref || '' }} + fetch-depth: 0 + + - name: Debug git status + run: | + echo "=== Git Status ===" + git status + echo "" + echo "=== Current Commit ===" + git rev-parse HEAD + git rev-parse --abbrev-ref HEAD + echo "" + echo "=== Recent Commits ===" + git log --oneline -n 5 + echo "" + echo "=== Remote Branches ===" + git branch -r # Update versions before build - name: Update versions if: ${{ inputs.version != '' }} + env: + VERSION: ${{ inputs.version }} run: | # Update version in Cargo.toml - sed -i.bak 's/^version = ".*"/version = "'${{ inputs.version }}'"/' Cargo.toml + sed -i.bak "s/^version = \".*\"/version = \"${VERSION}\"/" Cargo.toml rm -f Cargo.toml.bak # Update version in package.json cd ui/desktop - npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version + npm version "${VERSION}" --no-git-tag-version --allow-same-version # Pre-build cleanup to ensure enough disk space - name: Pre-build cleanup @@ -105,6 +154,11 @@ jobs: # Check disk space after cleanup df -h + - name: Install protobuf + run: | + brew install protobuf + echo "PROTOC=$(which protoc)" >> $GITHUB_ENV + - name: Setup Rust uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # pin@stable with: @@ -194,6 +248,10 @@ jobs: - name: Make Signed App if: ${{ inputs.signing }} + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} run: | attempt=0 max_attempts=2 @@ -208,10 +266,6 @@ jobs: exit 1 fi working-directory: ui/desktop - env: - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Final cleanup before artifact upload run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50eebbfa..37699a1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,8 @@ on: push: - paths-ignore: - - "documentation/**" branches: - main pull_request: - paths-ignore: - - "documentation/**" branches: - main workflow_dispatch: @@ -61,7 +57,7 @@ jobs: - name: Install Dependencies run: | sudo apt update -y - sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev + sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev protobuf-compiler - name: Setup Rust uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # pin@stable diff --git a/.github/workflows/pr-comment-bundle.yml b/.github/workflows/pr-comment-bundle.yml index 3e772e16..d3db1f2b 100644 --- a/.github/workflows/pr-comment-bundle.yml +++ b/.github/workflows/pr-comment-bundle.yml @@ -28,9 +28,26 @@ jobs: runs-on: ubuntu-latest outputs: continue: ${{ steps.command.outputs.continue || github.event_name == 'workflow_dispatch' }} - # Cannot use github.event.pull_request.number since the trigger is 'issue_comment' pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }} + pr_sha: ${{ steps.get_pr_info.outputs.sha }} steps: + - name: Debug workflow trigger + env: + WORKFLOW_NAME: ${{ github.workflow }} + WORKFLOW_REF: ${{ github.ref }} + EVENT_NAME: ${{ github.event_name }} + EVENT_ACTION: ${{ github.event.action }} + ACTOR: ${{ github.actor }} + REPOSITORY: ${{ github.repository }} + run: | + echo "=== Workflow Trigger Info ===" + echo "Workflow: ${WORKFLOW_NAME}" + echo "Ref: ${WORKFLOW_REF}" + echo "Event: ${EVENT_NAME}" + echo "Action: ${EVENT_ACTION}" + echo "Actor: ${ACTOR}" + echo "Repository: ${REPOSITORY}" + - if: ${{ github.event_name == 'issue_comment' }} uses: github/command@319d5236cc34ed2cb72a47c058a363db0b628ebe # pin@v1.3.0 id: command @@ -40,13 +57,56 @@ jobs: reaction: "eyes" allowed_contexts: pull_request + # Get the PR's SHA + - name: Get PR info + id: get_pr_info + if: ${{ steps.command.outputs.continue == 'true' || github.event_name == 'workflow_dispatch' }} + uses: actions/github-script@v7 + with: + script: | + let prNumber; + + if (context.eventName === 'workflow_dispatch') { + prNumber = context.payload.inputs.pr_number; + } else { + prNumber = context.payload.issue.number; + } + + if (!prNumber) { + throw new Error('No PR number found'); + } + + console.log('Using PR number:', prNumber); + + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: parseInt(prNumber, 10) + }); + + console.log('PR Details:', { + number: pr.number, + head: { + ref: pr.head.ref, + sha: pr.head.sha, + label: pr.head.label + }, + base: { + ref: pr.base.ref, + sha: pr.base.sha, + label: pr.base.label + } + }); + + core.setOutput('sha', pr.head.sha); + bundle-desktop: - # Only run this if ".bundle" command is detected. needs: [trigger-on-command] if: ${{ needs.trigger-on-command.outputs.continue == 'true' }} uses: ./.github/workflows/bundle-desktop.yml with: signing: true + ref: ${{ needs.trigger-on-command.outputs.pr_sha }} secrets: CERTIFICATE_OSX_APPLICATION: ${{ secrets.CERTIFICATE_OSX_APPLICATION }} CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }} diff --git a/.gitignore b/.gitignore index bb1fcc21..3d5ef353 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +__pycache__ +*.pyc +*.jar run_cli.sh tokenizer_files/ .DS_Store diff --git a/.husky/pre-commit b/.husky/pre-commit index b7dc5230..f2796dca 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -4,4 +4,10 @@ if git diff --cached --name-only | grep -q "^ui/desktop/"; then . "$(dirname -- "$0")/_/husky.sh" cd ui/desktop && npx lint-staged -fi \ No newline at end of file +fi + +# Only auto-format ui-v2 TS code if relevant files are modified +if git diff --cached --name-only | grep -q "^ui-v2/"; then + . "$(dirname -- "$0")/_/husky.sh" + cd ui-v2 && npx lint-staged +fi diff --git a/Cargo.lock b/Cargo.lock index 7a1ffa7b..dc646e5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "const-random", "getrandom 0.2.15", "once_cell", "version_check", @@ -183,6 +184,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arg_enum_proc_macro" version = "0.3.4" @@ -212,6 +219,269 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "arrow" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05048a8932648b63f21c37d88b552ccc8a65afb6dfe9fc9f30ce79174c2e7a85" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-csv", + "arrow-data", + "arrow-ipc", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", +] + +[[package]] +name = "arrow-arith" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8a57966e43bfe9a3277984a14c24ec617ad874e4c0e1d2a1b083a39cfbf22c" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "num", +] + +[[package]] +name = "arrow-array" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f4a9468c882dc66862cef4e1fd8423d47e67972377d85d80e022786427768c" +dependencies = [ + "ahash", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "chrono-tz", + "half", + "hashbrown 0.14.5", + "num", +] + +[[package]] +name = "arrow-buffer" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c975484888fc95ec4a632cdc98be39c085b1bb518531b0c80c5d462063e5daa1" +dependencies = [ + "bytes", + "half", + "num", +] + +[[package]] +name = "arrow-cast" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da26719e76b81d8bc3faad1d4dbdc1bcc10d14704e63dc17fc9f3e7e1e567c8e" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "atoi", + "base64 0.22.1", + "chrono", + "comfy-table", + "half", + "lexical-core", + "num", + "ryu", +] + +[[package]] +name = "arrow-csv" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13c36dc5ddf8c128df19bab27898eea64bf9da2b555ec1cd17a8ff57fba9ec2" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "csv", + "csv-core", + "lazy_static", + "lexical-core", + "regex", +] + +[[package]] +name = "arrow-data" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd9d6f18c65ef7a2573ab498c374d8ae364b4a4edf67105357491c031f716ca5" +dependencies = [ + "arrow-buffer", + "arrow-schema", + "half", + "num", +] + +[[package]] +name = "arrow-ipc" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e786e1cdd952205d9a8afc69397b317cfbb6e0095e445c69cda7e8da5c1eeb0f" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "flatbuffers", + "lz4_flex", + "zstd", +] + +[[package]] +name = "arrow-json" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb22284c5a2a01d73cebfd88a33511a3234ab45d66086b2ca2d1228c3498e445" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "indexmap 2.7.1", + "lexical-core", + "num", + "serde", + "serde_json", +] + +[[package]] +name = "arrow-ord" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42745f86b1ab99ef96d1c0bcf49180848a64fe2c7a7a0d945bc64fa2b21ba9bc" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "half", + "num", +] + +[[package]] +name = "arrow-row" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd09a518c602a55bd406bcc291a967b284cfa7a63edfbf8b897ea4748aad23c" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", +] + +[[package]] +name = "arrow-schema" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e972cd1ff4a4ccd22f86d3e53e835c2ed92e0eea6a3e8eadb72b4f1ac802cf8" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "arrow-select" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600bae05d43483d216fb3494f8c32fdbefd8aa4e1de237e790dbb3d9f44690a3" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num", +] + +[[package]] +name = "arrow-string" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc1985b67cb45f6606a248ac2b4a288849f196bab8c657ea5589f47cdd55e6" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num", + "regex", + "regex-syntax 0.8.5", +] + +[[package]] +name = "askama" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash 2.1.1", + "serde", + "serde_derive", + "syn 2.0.99", +] + +[[package]] +name = "askama_parser" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf315ce6524c857bb129ff794935cf6d42c82a6cff60526fe2a63593de4d0d4f" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -222,6 +492,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-compat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bab94bde396a3f7b4962e396fdad640e241ed797d4d8d77fc8c237d14c58fc0" +dependencies = [ + "futures-core", + "futures-io", + "once_cell", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-compression" version = "0.4.20" @@ -238,6 +521,55 @@ dependencies = [ "zstd-safe", ] +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-priority-channel" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acde96f444d31031f760c5c43dc786b97d3e1cb2ee49dd06898383fe9a999758" +dependencies = [ + "event-listener 4.0.3", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -271,6 +603,21 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "async_cell" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "834eee9ce518130a3b4d5af09ecc43e9d6b57ee76613f227a1ddd6b77c7a62bc" + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -336,7 +683,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand", + "fastrand 2.3.0", "hex", "http 0.2.12", "ring 0.17.12", @@ -374,7 +721,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand", + "fastrand 2.3.0", "http 0.2.12", "http-body 0.4.6", "once_cell", @@ -401,7 +748,30 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand", + "fastrand 2.3.0", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-dynamodb" +version = "1.67.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250a727b598ad84f28a41165e6d7a1fcbfb13b5da88723f42d04e9122948f4a5" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.61.1", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand 2.3.0", "http 0.2.12", "once_cell", "regex-lite", @@ -591,7 +961,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "bytes", - "fastrand", + "fastrand 2.3.0", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", @@ -675,15 +1045,15 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.9" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" dependencies = [ - "async-trait", - "axum-core 0.4.5", + "axum-core", "axum-macros", "base64 0.22.1", "bytes", + "form_urlencoded", "futures-util", "http 1.2.0", "http-body 1.0.1", @@ -691,7 +1061,7 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "itoa", - "matchit 0.7.3", + "matchit", "memchr", "mime", "percent-encoding", @@ -711,54 +1081,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" -dependencies = [ - "axum-core 0.5.0", - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "itoa", - "matchit 0.8.4", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 1.0.2", - "tower 0.5.2", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.2", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "axum-core" version = "0.5.0" @@ -785,8 +1107,8 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b" dependencies = [ - "axum 0.8.1", - "axum-core 0.5.0", + "axum", + "axum-core", "bytes", "futures-util", "http 1.2.0", @@ -802,9 +1124,9 @@ dependencies = [ [[package]] name = "axum-macros" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", @@ -854,6 +1176,15 @@ dependencies = [ "vsimd", ] +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + [[package]] name = "bat" version = "0.24.0" @@ -959,12 +1290,33 @@ dependencies = [ "serde", ] +[[package]] +name = "bitpacking" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c1d3e2bfd8d06048a179f7b17afc3188effa10385e7b00dc65af6aae732ea92" +dependencies = [ + "crunchy", +] + [[package]] name = "bitstream-io" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.6.1" @@ -1059,9 +1411,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" @@ -1097,6 +1449,51 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d2c12f985c78475a6b8d629afd0c360260ef34cfef52efccdcfd31972f81c2e" +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "cast" version = "0.3.0" @@ -1142,6 +1539,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "census" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" + [[package]] name = "cesu8" version = "1.1.0" @@ -1192,9 +1595,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1202,7 +1605,29 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-targets 0.52.6", +] + +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", ] [[package]] @@ -1376,22 +1801,22 @@ dependencies = [ ] [[package]] -name = "config" -version = "0.13.4" +name = "comfy-table" +version = "7.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" dependencies = [ - "async-trait", - "json5", - "lazy_static", - "nom", - "pathdiff", - "ron 0.7.1", - "rust-ini 0.18.0", - "serde", - "serde_json", - "toml 0.5.11", - "yaml-rust", + "unicode-segmentation", + "unicode-width 0.2.0", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", ] [[package]] @@ -1603,6 +2028,15 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "croner" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38fd53511eaf0b00a185613875fee58b208dfce016577d0ad4bb548e1c4fb3ee" +dependencies = [ + "chrono", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1631,6 +2065,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1653,6 +2096,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +dependencies = [ + "memchr", +] + [[package]] name = "ctor" version = "0.2.9" @@ -1698,12 +2162,362 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +[[package]] +name = "datafusion" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4fd4a99fc70d40ef7e52b243b4a399c3f8d353a40d5ecb200deee05e49c61bb" +dependencies = [ + "ahash", + "arrow", + "arrow-array", + "arrow-ipc", + "arrow-schema", + "async-trait", + "bytes", + "chrono", + "dashmap 6.1.0", + "datafusion-catalog", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-functions-nested", + "datafusion-optimizer", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-optimizer", + "datafusion-physical-plan", + "datafusion-sql", + "futures", + "glob", + "half", + "hashbrown 0.14.5", + "indexmap 2.7.1", + "itertools 0.12.1", + "log", + "num_cpus", + "object_store", + "parking_lot", + "paste", + "pin-project-lite", + "rand 0.8.5", + "sqlparser", + "tempfile", + "tokio", + "url", + "uuid", +] + +[[package]] +name = "datafusion-catalog" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b3cfbd84c6003594ae1972314e3df303a27ce8ce755fcea3240c90f4c0529" +dependencies = [ + "arrow-schema", + "async-trait", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-plan", +] + +[[package]] +name = "datafusion-common" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fdbc877e3e40dcf88cc8f283d9f5c8851f0a3aa07fee657b1b75ac1ad49b9c" +dependencies = [ + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-schema", + "chrono", + "half", + "hashbrown 0.14.5", + "instant", + "libc", + "num_cpus", + "object_store", + "sqlparser", +] + +[[package]] +name = "datafusion-common-runtime" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7496d1f664179f6ce3a5cbef6566056ccaf3ea4aa72cc455f80e62c1dd86b1" +dependencies = [ + "tokio", +] + +[[package]] +name = "datafusion-execution" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e70968c815b611116951e3dd876aef04bf217da31b72eec01ee6a959336a1" +dependencies = [ + "arrow", + "chrono", + "dashmap 6.1.0", + "datafusion-common", + "datafusion-expr", + "futures", + "hashbrown 0.14.5", + "log", + "object_store", + "parking_lot", + "rand 0.8.5", + "tempfile", + "url", +] + +[[package]] +name = "datafusion-expr" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c1841c409d9518c17971d15c9bae62e629eb937e6fb6c68cd32e9186f8b30d2" +dependencies = [ + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "chrono", + "datafusion-common", + "paste", + "serde_json", + "sqlparser", + "strum", + "strum_macros", +] + +[[package]] +name = "datafusion-functions" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e481cf34d2a444bd8fa09b65945f0ce83dc92df8665b761505b3d9f351bebb" +dependencies = [ + "arrow", + "arrow-buffer", + "base64 0.22.1", + "chrono", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "hashbrown 0.14.5", + "hex", + "itertools 0.12.1", + "log", + "rand 0.8.5", + "regex", + "unicode-segmentation", + "uuid", +] + +[[package]] +name = "datafusion-functions-aggregate" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4ece19f73c02727e5e8654d79cd5652de371352c1df3c4ac3e419ecd6943fb" +dependencies = [ + "ahash", + "arrow", + "arrow-schema", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "log", + "paste", + "sqlparser", +] + +[[package]] +name = "datafusion-functions-nested" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1474552cc824e8c9c88177d454db5781d4b66757d4aca75719306b8343a5e8d" +dependencies = [ + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions", + "datafusion-functions-aggregate", + "itertools 0.12.1", + "log", + "paste", + "rand 0.8.5", +] + +[[package]] +name = "datafusion-optimizer" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791ff56f55608bc542d1ea7a68a64bdc86a9413f5a381d06a39fd49c2a3ab906" +dependencies = [ + "arrow", + "async-trait", + "chrono", + "datafusion-common", + "datafusion-expr", + "datafusion-physical-expr", + "hashbrown 0.14.5", + "indexmap 2.7.1", + "itertools 0.12.1", + "log", + "paste", + "regex-syntax 0.8.5", +] + +[[package]] +name = "datafusion-physical-expr" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a223962b3041304a3e20ed07a21d5de3d88d7e4e71ca192135db6d24e3365a4" +dependencies = [ + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "arrow-string", + "base64 0.22.1", + "chrono", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr-common", + "half", + "hashbrown 0.14.5", + "hex", + "indexmap 2.7.1", + "itertools 0.12.1", + "log", + "paste", + "petgraph", + "regex", +] + +[[package]] +name = "datafusion-physical-expr-common" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5e7d8532a1601cd916881db87a70b0a599900d23f3db2897d389032da53bc6" +dependencies = [ + "ahash", + "arrow", + "datafusion-common", + "datafusion-expr", + "hashbrown 0.14.5", + "rand 0.8.5", +] + +[[package]] +name = "datafusion-physical-optimizer" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb9c78f308e050f5004671039786a925c3fee83b90004e9fcfd328d7febdcc0" +dependencies = [ + "datafusion-common", + "datafusion-execution", + "datafusion-physical-expr", + "datafusion-physical-plan", +] + +[[package]] +name = "datafusion-physical-plan" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d1116949432eb2d30f6362707e2846d942e491052a206f2ddcb42d08aea1ffe" +dependencies = [ + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "async-trait", + "chrono", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "futures", + "half", + "hashbrown 0.14.5", + "indexmap 2.7.1", + "itertools 0.12.1", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "rand 0.8.5", + "tokio", +] + +[[package]] +name = "datafusion-sql" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45d0180711165fe94015d7c4123eb3e1cf5fb60b1506453200b8d1ce666bef0" +dependencies = [ + "arrow", + "arrow-array", + "arrow-schema", + "datafusion-common", + "datafusion-expr", + "log", + "regex", + "sqlparser", + "strum", +] + [[package]] name = "dbus" version = "0.9.7" @@ -1726,7 +2540,7 @@ dependencies = [ "num", "once_cell", "openssl", - "rand", + "rand 0.8.5", ] [[package]] @@ -1747,6 +2561,26 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +[[package]] +name = "deepsize" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb987ec36f6bf7bfbea3f928b75590b736fc42af8e54d97592481351b2b96c" +dependencies = [ + "deepsize_derive", +] + +[[package]] +name = "deepsize_derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990101d41f3bc8c1a45641024377ee284ecc338e5ecf3ea0f0e236d897c72796" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1914,12 +2748,24 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dyn-clone" version = "1.0.19" @@ -1969,6 +2815,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + [[package]] name = "error-code" version = "3.3.1" @@ -1995,6 +2850,23 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "eventsource-client" version = "0.12.2" @@ -2007,7 +2879,7 @@ dependencies = [ "hyper-timeout", "log", "pin-project", - "rand", + "rand 0.8.5", "tokio", ] @@ -2037,6 +2909,21 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "fastdivide" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -2050,7 +2937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", - "rustix", + "rustix 0.38.44", "windows-sys 0.52.0", ] @@ -2063,6 +2950,34 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flatbuffers" +version = "24.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + [[package]] name = "flate2" version = "1.1.0" @@ -2079,6 +2994,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -2136,6 +3057,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "fs2" version = "0.4.3" @@ -2146,6 +3076,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "fs4" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" +dependencies = [ + "rustix 0.38.44", + "windows-sys 0.52.0", +] + +[[package]] +name = "fsst" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac423dce38c8aafc3d348d9f9c207ac030385ba2edda08bcff43c74a29ce3eac" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -2194,6 +3149,21 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -2338,6 +3308,17 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "google-apis-common" version = "7.0.0" @@ -2423,15 +3404,16 @@ dependencies = [ [[package]] name = "goose" -version = "1.0.20" +version = "1.0.24" dependencies = [ "anyhow", + "arrow", "async-stream", "async-trait", "aws-config", "aws-sdk-bedrockruntime", "aws-smithy-types", - "axum 0.7.9", + "axum", "base64 0.21.7", "blake3", "chrono", @@ -2441,10 +3423,12 @@ dependencies = [ "etcetera", "fs2", "futures", + "futures-util", "include_dir", - "indoc", - "jsonwebtoken 9.3.1", + "indoc 2.0.6", + "jsonwebtoken", "keyring", + "lancedb", "lazy_static", "mcp-client", "mcp-core", @@ -2453,7 +3437,7 @@ dependencies = [ "nanoid", "once_cell", "paste", - "rand", + "rand 0.8.5", "regex", "reqwest 0.12.12", "serde", @@ -2467,6 +3451,7 @@ dependencies = [ "thiserror 1.0.69", "tokenizers", "tokio", + "tokio-cron-scheduler", "tracing", "tracing-subscriber", "url", @@ -2501,12 +3486,13 @@ dependencies = [ [[package]] name = "goose-bench" -version = "1.0.20" +version = "1.0.24" dependencies = [ "anyhow", "async-trait", "chrono", "ctor", + "dotenvy", "goose", "include_dir", "mcp-core", @@ -2524,7 +3510,7 @@ dependencies = [ [[package]] name = "goose-cli" -version = "1.0.20" +version = "1.0.24" dependencies = [ "anyhow", "async-trait", @@ -2542,8 +3528,10 @@ dependencies = [ "mcp-client", "mcp-core", "mcp-server", + "minijinja", + "nix 0.30.1", "once_cell", - "rand", + "rand 0.8.5", "regex", "reqwest 0.12.12", "rustyline", @@ -2551,6 +3539,7 @@ dependencies = [ "serde_json", "serde_yaml", "shlex", + "tar", "temp-env", "tempfile", "test-case", @@ -2563,7 +3552,7 @@ dependencies = [ [[package]] name = "goose-ffi" -version = "1.0.20" +version = "1.0.24" dependencies = [ "cbindgen", "futures", @@ -2575,9 +3564,38 @@ dependencies = [ "tokio", ] +[[package]] +name = "goose-llm" +version = "1.0.24" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.21.7", + "chrono", + "criterion", + "ctor", + "dotenv", + "include_dir", + "indoc 1.0.9", + "lazy_static", + "minijinja", + "once_cell", + "regex", + "reqwest 0.12.12", + "serde", + "serde_json", + "smallvec", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tracing", + "uniffi", + "url", +] + [[package]] name = "goose-mcp" -version = "1.0.20" +version = "1.0.24" dependencies = [ "anyhow", "async-trait", @@ -2585,14 +3603,16 @@ dependencies = [ "chrono", "docx-rs", "etcetera", + "google-apis-common", "google-docs1", "google-drive3", "google-sheets4", "http-body-util", + "hyper 1.6.0", "ignore", "image 0.24.9", "include_dir", - "indoc", + "indoc 2.0.6", "keyring", "kill_tree", "lazy_static", @@ -2605,6 +3625,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", + "serde_with", "serial_test", "shellexpand", "sysinfo 0.32.1", @@ -2617,18 +3638,20 @@ dependencies = [ "umya-spreadsheet", "url", "urlencoding", + "utoipa", "webbrowser", "xcap", ] [[package]] name = "goose-server" -version = "1.0.20" +version = "1.0.24" dependencies = [ "anyhow", "async-trait", - "axum 0.7.9", + "axum", "axum-extra", + "base64 0.21.7", "bytes", "chrono", "clap 4.5.31", @@ -2648,6 +3671,7 @@ dependencies = [ "serde_yaml", "thiserror 1.0.69", "tokio", + "tokio-cron-scheduler", "tokio-stream", "tower 0.5.2", "tower-http", @@ -2717,6 +3741,7 @@ checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", + "num-traits", ] [[package]] @@ -2743,6 +3768,11 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "hashlink" @@ -2849,6 +3879,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "htmlescape" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" + [[package]] name = "http" version = "0.2.12" @@ -2917,6 +3953,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + [[package]] name = "hyper" version = "0.14.32" @@ -2934,7 +3976,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -3022,12 +4064,21 @@ dependencies = [ "http-body 1.0.1", "hyper 1.6.0", "pin-project-lite", - "socket2", + "socket2 0.5.8", "tokio", "tower-service", "tracing", ] +[[package]] +name = "hyperloglogplus" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "621debdf94dcac33e50475fdd76d34d5ea9c0362a834b9db08c3024696c1fbe3" +dependencies = [ + "serde", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -3323,6 +4374,12 @@ dependencies = [ "web-time", ] +[[package]] +name = "indoc" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" + [[package]] name = "indoc" version = "2.0.6" @@ -3339,6 +4396,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "interpolate_name" version = "0.2.4" @@ -3350,6 +4419,17 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -3532,6 +4612,440 @@ dependencies = [ "windows 0.52.0", ] +[[package]] +name = "lance" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cd1ece8f8ca17955c805e846b43acc61922bf9729977807037a3b1e26584e4" +dependencies = [ + "arrow", + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "async-recursion", + "async-trait", + "async_cell", + "aws-credential-types", + "aws-sdk-dynamodb", + "byteorder", + "bytes", + "chrono", + "dashmap 5.5.3", + "datafusion", + "datafusion-functions", + "datafusion-physical-expr", + "deepsize", + "futures", + "half", + "itertools 0.12.1", + "lance-arrow", + "lance-core", + "lance-datafusion", + "lance-encoding", + "lance-file", + "lance-index", + "lance-io", + "lance-linalg", + "lance-table", + "lazy_static", + "log", + "moka", + "object_store", + "permutation", + "pin-project", + "prost", + "prost-build", + "rand 0.8.5", + "roaring", + "serde", + "serde_json", + "snafu", + "tantivy", + "tempfile", + "tokio", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "lance-arrow" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385c62668af77d75da2242c6e86b9c0868d92f13961643ba3d8dc7eb05deb5da" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "arrow-select", + "getrandom 0.2.15", + "half", + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "lance-core" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ca182e47d2926aa59526e2573dd1e634b72111b275410cb42dca30aef9f315" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-schema", + "async-trait", + "byteorder", + "bytes", + "chrono", + "datafusion-common", + "datafusion-sql", + "deepsize", + "futures", + "lance-arrow", + "lazy_static", + "libc", + "log", + "mock_instant", + "moka", + "num_cpus", + "object_store", + "pin-project", + "prost", + "rand 0.8.5", + "roaring", + "serde_json", + "snafu", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "lance-datafusion" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d90f3f250a6761d5f7e2a2d6838402defdad18367a122b260a2fadb1dea30464" +dependencies = [ + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "arrow-select", + "async-trait", + "datafusion", + "datafusion-common", + "datafusion-functions", + "datafusion-physical-expr", + "futures", + "lance-arrow", + "lance-core", + "lazy_static", + "log", + "prost", + "snafu", + "tokio", +] + +[[package]] +name = "lance-encoding" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d187963282728ba0c756ae7dfdeef3e47a4ac6db56385860c665f07b6e59ae" +dependencies = [ + "arrayref", + "arrow", + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "arrow-select", + "bytemuck", + "byteorder", + "bytes", + "fsst", + "futures", + "hex", + "hyperloglogplus", + "itertools 0.12.1", + "lance-arrow", + "lance-core", + "lazy_static", + "log", + "num-traits", + "paste", + "prost", + "prost-build", + "prost-types", + "rand 0.8.5", + "seq-macro", + "snafu", + "tokio", + "tracing", + "zstd", +] + +[[package]] +name = "lance-file" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bdc8b00b0664e3944648f43b61785e5317ecc25b0015a80e2deba6f28a76e4a" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "async-recursion", + "async-trait", + "byteorder", + "bytes", + "datafusion-common", + "deepsize", + "futures", + "lance-arrow", + "lance-core", + "lance-encoding", + "lance-io", + "log", + "num-traits", + "object_store", + "prost", + "prost-build", + "prost-types", + "roaring", + "snafu", + "tempfile", + "tokio", + "tracing", +] + +[[package]] +name = "lance-index" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7051aa7a28c3dc0708cddd6f4448fa86968d183bc2be14a5acd7495fb8d8fd9" +dependencies = [ + "arrow", + "arrow-array", + "arrow-ord", + "arrow-schema", + "arrow-select", + "async-recursion", + "async-trait", + "bitvec", + "bytes", + "crossbeam-queue", + "datafusion", + "datafusion-common", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-sql", + "deepsize", + "futures", + "half", + "itertools 0.12.1", + "lance-arrow", + "lance-core", + "lance-datafusion", + "lance-encoding", + "lance-file", + "lance-io", + "lance-linalg", + "lance-table", + "lazy_static", + "log", + "moka", + "num-traits", + "object_store", + "prost", + "prost-build", + "rand 0.8.5", + "rayon", + "roaring", + "serde", + "serde_json", + "snafu", + "tantivy", + "tempfile", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "lance-io" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d612eebbb3d9feca9c2b860f176f920142c859b3e175248a20b0250b898b149" +dependencies = [ + "arrow", + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "arrow-select", + "async-priority-channel", + "async-recursion", + "async-trait", + "aws-config", + "aws-credential-types", + "byteorder", + "bytes", + "chrono", + "deepsize", + "futures", + "lance-arrow", + "lance-core", + "lazy_static", + "log", + "object_store", + "path_abs", + "pin-project", + "prost", + "prost-build", + "rand 0.8.5", + "shellexpand", + "snafu", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "lance-linalg" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d642bfbece852f40e0aa01daa445940f80ede9c2914bd25cd720dac266ce41a9" +dependencies = [ + "arrow-array", + "arrow-ord", + "arrow-schema", + "bitvec", + "cc", + "deepsize", + "futures", + "half", + "lance-arrow", + "lance-core", + "lazy_static", + "log", + "num-traits", + "rand 0.8.5", + "rayon", + "tokio", + "tracing", +] + +[[package]] +name = "lance-table" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd8c8aaf1b74110fbd1a24cb30f54280cb7a0dc2cdd87513a7679385ca97f952" +dependencies = [ + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ipc", + "arrow-schema", + "async-trait", + "aws-credential-types", + "aws-sdk-dynamodb", + "byteorder", + "bytes", + "chrono", + "deepsize", + "futures", + "lance-arrow", + "lance-core", + "lance-file", + "lance-io", + "lazy_static", + "log", + "object_store", + "prost", + "prost-build", + "prost-types", + "rand 0.8.5", + "rangemap", + "roaring", + "serde", + "serde_json", + "snafu", + "tokio", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "lance-testing" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc37f345c06661a29cf9d89ad4c52f84ba2f037ff94403c1f4b486edd7713103" +dependencies = [ + "arrow-array", + "arrow-schema", + "lance-arrow", + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "lancedb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c041835505f86cb5cc5cc6238a6bb981f7addda58afb40eabd76c2f91e47b9b0" +dependencies = [ + "arrow", + "arrow-array", + "arrow-cast", + "arrow-data", + "arrow-ipc", + "arrow-ord", + "arrow-schema", + "async-trait", + "bytes", + "chrono", + "datafusion-common", + "datafusion-physical-plan", + "futures", + "half", + "lance", + "lance-datafusion", + "lance-encoding", + "lance-index", + "lance-linalg", + "lance-table", + "lance-testing", + "lazy_static", + "log", + "moka", + "num-traits", + "object_store", + "pin-project", + "regex", + "serde", + "serde_json", + "serde_with", + "snafu", + "tokio", + "url", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -3551,10 +5065,80 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] -name = "libc" -version = "0.2.170" +name = "levenshtein_automata" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" + +[[package]] +name = "lexical-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" +dependencies = [ + "lexical-util", + "lexical-write-integer", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libdbus-sys" @@ -3598,6 +5182,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.3" @@ -3606,6 +5196,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.0", "libc", + "redox_syscall", ] [[package]] @@ -3626,12 +5217,24 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "litemap" version = "0.7.5" @@ -3699,6 +5302,33 @@ dependencies = [ "weezl", ] +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.2", +] + +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "macro_rules_attribute" version = "0.2.0" @@ -3733,12 +5363,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "matchit" version = "0.8.4" @@ -3764,7 +5388,8 @@ dependencies = [ "eventsource-client", "futures", "mcp-core", - "rand", + "nix 0.30.1", + "rand 0.8.5", "reqwest 0.11.27", "serde", "serde_json", @@ -3842,12 +5467,31 @@ dependencies = [ "digest", ] +[[package]] +name = "measure_time" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbefd235b0aadd181626f281e1d684e116972988c14c264e42069d5e8a5775cc" +dependencies = [ + "instant", + "log", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" @@ -3900,6 +5544,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mock_instant" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9366861eb2a2c436c20b12c8dbec5f798cea6b47ad99216be0282942e2c81ea0" +dependencies = [ + "once_cell", +] + [[package]] name = "mockall" version = "0.13.1" @@ -3926,6 +5579,31 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "moka" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa6e72583bf6830c956235bff0d5afec8cf2952f579ebad18ae7821a917d950f" +dependencies = [ + "async-io", + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "futures-util", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "scheduled-thread-pool", + "skeptic", + "smallvec", + "tagptr", + "thiserror 1.0.69", + "triomphe", + "uuid", +] + [[package]] name = "monostate" version = "0.1.14" @@ -3948,22 +5626,16 @@ dependencies = [ ] [[package]] -name = "multer" -version = "2.1.0" +name = "multimap" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 0.2.12", - "httparse", - "log", - "memchr", - "mime", - "spin 0.9.8", - "version_check", -] +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "murmurhash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" [[package]] name = "nanoid" @@ -3971,7 +5643,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" dependencies = [ - "rand", + "rand 0.8.5", ] [[package]] @@ -4018,6 +5690,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -4161,6 +5845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -4198,7 +5883,7 @@ dependencies = [ "chrono", "getrandom 0.2.15", "http 1.2.0", - "rand", + "rand 0.8.5", "reqwest 0.12.12", "serde", "serde_json", @@ -4226,6 +5911,37 @@ dependencies = [ "memchr", ] +[[package]] +name = "object_store" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6da452820c715ce78221e8202ccc599b4a52f3e1eb3eedb487b680c81a8e3f3" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "chrono", + "futures", + "humantime", + "hyper 1.6.0", + "itertools 0.13.0", + "md-5", + "parking_lot", + "percent-encoding", + "quick-xml 0.36.2", + "rand 0.8.5", + "reqwest 0.12.12", + "ring", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "snafu", + "tokio", + "tracing", + "url", + "walkdir", +] + [[package]] name = "once_cell" version = "1.20.3" @@ -4233,12 +5949,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] -name = "onig" -version = "6.4.0" +name = "oneshot" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" + +[[package]] +name = "onig" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.0", "libc", "once_cell", "onig_sys", @@ -4246,9 +5968,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", @@ -4358,6 +6080,21 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "ownedbytes" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a059efb063b8f425b948e042e6b9bd85edfe60e913630ed727b23e2dfcc558" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -4381,6 +6118,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.15" @@ -4393,7 +6139,10 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05ef02f6342ac01d8a93b65f96db53fe68a92a15f41144f97fb00a9e669633c3" dependencies = [ + "serde", + "serde_derive", "std_prelude", + "stfu8", ] [[package]] @@ -4427,6 +6176,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "permutation" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df202b0b0f5b8e389955afd5f27b007b00fb948162953f1db9c70d2c7e3157d7" + [[package]] name = "pest" version = "2.7.15" @@ -4472,6 +6227,54 @@ dependencies = [ "sha2", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.7.1", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -4510,6 +6313,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plist" version = "1.7.0" @@ -4564,6 +6373,22 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "portable-atomic" version = "1.11.0" @@ -4673,6 +6498,59 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.99", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + [[package]] name = "psl-types" version = "2.0.11" @@ -4689,6 +6567,17 @@ dependencies = [ "psl-types", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +dependencies = [ + "bitflags 2.9.0", + "memchr", + "unicase", +] + [[package]] name = "qoi" version = "0.4.1" @@ -4698,6 +6587,22 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "2.0.1" @@ -4722,6 +6627,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quick-xml" version = "0.37.2" @@ -4744,7 +6659,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls 0.23.23", - "socket2", + "socket2 0.5.8", "thiserror 2.0.12", "tokio", "tracing", @@ -4758,8 +6673,8 @@ checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", "getrandom 0.2.15", - "rand", - "ring 0.17.12", + "rand 0.8.5", + "ring", "rustc-hash 2.1.1", "rustls 0.23.23", "rustls-pki-types", @@ -4779,7 +6694,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.8", "tracing", "windows-sys 0.59.0", ] @@ -4793,6 +6708,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "radix_trie" version = "0.2.1" @@ -4810,8 +6731,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -4821,7 +6752,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -4833,6 +6774,25 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "rangemap" version = "1.5.1" @@ -4865,8 +6825,8 @@ dependencies = [ "once_cell", "paste", "profiling", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "simd_helpers", "system-deps", "thiserror 1.0.69", @@ -4889,6 +6849,15 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -5113,24 +7082,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - -[[package]] -name = "ring" -version = "0.17.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9b823fa29b721a59671b41d6b06e66b29e0628e207e8b1c3ceeda701ec928d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", @@ -5141,14 +7095,13 @@ dependencies = [ ] [[package]] -name = "ron" -version = "0.7.1" +name = "roaring" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +checksum = "41589aba99537475bf697f2118357cad1c31590c5a1b9f6d9fc4ad6d07503661" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", - "serde", + "bytemuck", + "byteorder", ] [[package]] @@ -5180,7 +7133,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" dependencies = [ "cfg-if", - "ordered-multimap 0.7.3", + "ordered-multimap", +] + +[[package]] +name = "rust-stemmers" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" +dependencies = [ + "serde", + "serde_derive", ] [[package]] @@ -5210,6 +7173,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.44" @@ -5219,7 +7196,20 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -5382,6 +7372,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "schemars" version = "0.8.22" @@ -5418,6 +7417,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "sct" version = "0.7.1" @@ -5481,6 +7500,15 @@ name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] name = "serde" @@ -5718,6 +7746,42 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata 0.14.2", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" +dependencies = [ + "serde", +] + [[package]] name = "slab" version = "0.4.9" @@ -5732,6 +7796,9 @@ name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +dependencies = [ + "serde", +] [[package]] name = "smawk" @@ -5739,6 +7806,38 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.8" @@ -5773,18 +7872,51 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "sqlparser" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a404d0e14905361b918cb8afdb73605e25c1d5029312bd9785142dcb3aa49e" +dependencies = [ + "log", + "sqlparser_derive", +] + +[[package]] +name = "sqlparser_derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "std_prelude" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe" +[[package]] +name = "stfu8" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51f1e89f093f99e7432c491c382b88a6860a5adbe6bf02574bf0a08efff1978" + [[package]] name = "strsim" version = "0.10.0" @@ -5797,6 +7929,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.99", +] + [[package]] name = "subtle" version = "2.6.1" @@ -5935,6 +8089,170 @@ dependencies = [ "version-compare", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tantivy" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8d0582f186c0a6d55655d24543f15e43607299425c5ad8352c242b914b31856" +dependencies = [ + "aho-corasick", + "arc-swap", + "base64 0.22.1", + "bitpacking", + "byteorder", + "census", + "crc32fast", + "crossbeam-channel", + "downcast-rs", + "fastdivide", + "fnv", + "fs4", + "htmlescape", + "itertools 0.12.1", + "levenshtein_automata", + "log", + "lru", + "lz4_flex", + "measure_time", + "memmap2", + "num_cpus", + "once_cell", + "oneshot", + "rayon", + "regex", + "rust-stemmers", + "rustc-hash 1.1.0", + "serde", + "serde_json", + "sketches-ddsketch", + "smallvec", + "tantivy-bitpacker", + "tantivy-columnar", + "tantivy-common", + "tantivy-fst", + "tantivy-query-grammar", + "tantivy-stacker", + "tantivy-tokenizer-api", + "tempfile", + "thiserror 1.0.69", + "time", + "uuid", + "winapi", +] + +[[package]] +name = "tantivy-bitpacker" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284899c2325d6832203ac6ff5891b297fc5239c3dc754c5bc1977855b23c10df" +dependencies = [ + "bitpacking", +] + +[[package]] +name = "tantivy-columnar" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12722224ffbe346c7fec3275c699e508fd0d4710e629e933d5736ec524a1f44e" +dependencies = [ + "downcast-rs", + "fastdivide", + "itertools 0.12.1", + "serde", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-sstable", + "tantivy-stacker", +] + +[[package]] +name = "tantivy-common" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8019e3cabcfd20a1380b491e13ff42f57bb38bf97c3d5fa5c07e50816e0621f4" +dependencies = [ + "async-trait", + "byteorder", + "ownedbytes", + "serde", + "time", +] + +[[package]] +name = "tantivy-fst" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" +dependencies = [ + "byteorder", + "regex-syntax 0.8.5", + "utf8-ranges", +] + +[[package]] +name = "tantivy-query-grammar" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847434d4af57b32e309f4ab1b4f1707a6c566656264caa427ff4285c4d9d0b82" +dependencies = [ + "nom", +] + +[[package]] +name = "tantivy-sstable" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c69578242e8e9fc989119f522ba5b49a38ac20f576fc778035b96cc94f41f98e" +dependencies = [ + "tantivy-bitpacker", + "tantivy-common", + "tantivy-fst", + "zstd", +] + +[[package]] +name = "tantivy-stacker" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56d6ff5591fc332739b3ce7035b57995a3ce29a93ffd6012660e0949c956ea8" +dependencies = [ + "murmurhash32", + "rand_distr", + "tantivy-common", +] + +[[package]] +name = "tantivy-tokenizer-api" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0dcade25819a89cfe6f17d932c9cedff11989936bf6dd4f336d50392053b04" +dependencies = [ + "serde", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -5958,10 +8276,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.3.0", "getrandom 0.3.1", "once_cell", - "rustix", + "rustix 0.38.44", "windows-sys 0.59.0", ] @@ -5980,7 +8298,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ - "rustix", + "rustix 0.38.44", "windows-sys 0.59.0", ] @@ -6202,7 +8520,7 @@ dependencies = [ "monostate", "onig", "paste", - "rand", + "rand 0.8.5", "rayon", "rayon-cond", "regex", @@ -6229,11 +8547,26 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.8", "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-cron-scheduler" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71ce8f810abc9fabebccc30302a952f9e89c6cf246fafaf170fef164063141" +dependencies = [ + "chrono", + "croner", + "num-derive", + "num-traits", + "tokio", + "tracing", + "uuid", +] + [[package]] name = "tokio-io-timeout" version = "1.2.0" @@ -6288,26 +8621,14 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.21.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", "tokio", - "tungstenite 0.21.0", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.24.0", + "tungstenite", ] [[package]] @@ -6514,6 +8835,12 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" + [[package]] name = "try-lock" version = "0.2.5" @@ -6522,39 +8849,29 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.21.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ - "byteorder", "bytes", "data-encoding", "http 1.2.0", "httparse", "log", - "rand", + "rand 0.9.1", "sha1", - "thiserror 1.0.69", - "url", + "thiserror 2.0.12", "utf-8", ] [[package]] -name = "tungstenite" -version = "0.24.0" +name = "twox-hash" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.2.0", - "httparse", - "log", - "rand", - "sha1", - "thiserror 1.0.69", - "utf-8", + "cfg-if", + "static_assertions", ] [[package]] @@ -6649,6 +8966,128 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "uniffi" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd1d240101ba3b9d7532ae86d9cb64d9a7ff63e13a2b7b9e94a32a601d8233" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata 0.19.2", + "clap 4.5.31", + "uniffi_bindgen", + "uniffi_core", + "uniffi_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0525f06d749ea80d8049dc0bb038bb87941e3d909eefa76b6f0a5589b59ac5" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata 0.19.2", + "fs-err", + "glob", + "goblin", + "heck 0.5.0", + "indexmap 2.7.1", + "once_cell", + "serde", + "tempfile", + "textwrap", + "toml 0.5.11", + "uniffi_internal_macros", + "uniffi_meta", + "uniffi_pipeline", + "uniffi_udl", +] + +[[package]] +name = "uniffi_core" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fa8eb4d825b4ed095cb13483cba6927c3002b9eb603cef9b7688758cc3772e" +dependencies = [ + "anyhow", + "async-compat", + "bytes", + "once_cell", + "static_assertions", +] + +[[package]] +name = "uniffi_internal_macros" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b547d69d699e52f2129fde4b57ae0d00b5216e59ed5b56097c95c86ba06095" +dependencies = [ + "anyhow", + "indexmap 2.7.1", + "proc-macro2", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "uniffi_macros" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f1de72edc8cb9201c7d650e3678840d143e4499004571aac49e6cb1b17da43" +dependencies = [ + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.99", + "toml 0.5.11", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acc9204632f6a555b2cba7c8852c5523bc1aa5f3eff605c64af5054ea28b72e" +dependencies = [ + "anyhow", + "siphasher 0.3.11", + "uniffi_internal_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi_pipeline" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b5336a9a925b358183837d31541d12590b7fcec373256d3770de02dff24c69" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.7.1", + "tempfile", + "uniffi_internal_macros", +] + +[[package]] +name = "uniffi_udl" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f95e73373d85f04736bc51997d3e6855721144ec4384cae9ca8513c80615e129" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "weedle2", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -6697,6 +9136,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" +[[package]] +name = "utf8-ranges" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -6785,6 +9230,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.5.0" @@ -6978,6 +9429,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + [[package]] name = "weezl" version = "0.1.8" @@ -6993,7 +9453,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.44", ] [[package]] @@ -7144,12 +9604,6 @@ dependencies = [ "syn 2.0.99", ] -[[package]] -name = "windows-link" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" - [[package]] name = "windows-registry" version = "0.2.0" @@ -7467,6 +9921,25 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xattr" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" +dependencies = [ + "libc", + "rustix 1.0.7", +] + [[package]] name = "xcap" version = "0.0.14" diff --git a/Cargo.toml b/Cargo.toml index fc084063..f44b43e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] edition = "2021" -version = "1.0.20" +version = "1.0.24" authors = ["Block "] license = "Apache-2.0" repository = "https://github.com/block/goose" diff --git a/Justfile b/Justfile index b5b95114..0049be73 100644 --- a/Justfile +++ b/Justfile @@ -79,6 +79,11 @@ run-ui: @echo "Running UI..." cd ui/desktop && npm install && npm run start-gui +run-ui-only: + @echo "Running UI..." + cd ui/desktop && npm install && npm run start-gui + + # Run UI with alpha changes run-ui-alpha: @just release-binary @@ -319,3 +324,24 @@ win-total-dbg *allparam: win-total-rls *allparam: just win-bld-rls{{allparam}} just win-run-rls + +### Build and run the Kotlin example with +### auto-generated bindings for goose-llm +kotlin-example: + # Build Rust dylib and generate Kotlin bindings + cargo build -p goose-llm + cargo run --features=uniffi/cli --bin uniffi-bindgen generate \ + --library ./target/debug/libgoose_llm.dylib --language kotlin --out-dir bindings/kotlin + + # Compile and run the Kotlin example + cd bindings/kotlin/ && kotlinc \ + example/Usage.kt \ + uniffi/goose_llm/goose_llm.kt \ + -classpath "libs/kotlin-stdlib-1.9.0.jar:libs/kotlinx-coroutines-core-jvm-1.7.3.jar:libs/jna-5.13.0.jar" \ + -include-runtime \ + -d example.jar + + cd bindings/kotlin/ && java \ + -Djna.library.path=$HOME/Development/goose/target/debug \ + -classpath "example.jar:libs/kotlin-stdlib-1.9.0.jar:libs/kotlinx-coroutines-core-jvm-1.7.3.jar:libs/jna-5.13.0.jar" \ + UsageKt \ No newline at end of file diff --git a/bindings/kotlin/example/Usage.kt b/bindings/kotlin/example/Usage.kt new file mode 100644 index 00000000..7c3fc4a3 --- /dev/null +++ b/bindings/kotlin/example/Usage.kt @@ -0,0 +1,200 @@ +import kotlinx.coroutines.runBlocking +import uniffi.goose_llm.* + +fun main() = runBlocking { + val now = System.currentTimeMillis() / 1000 + val msgs = listOf( + // 1) User sends a plain-text prompt + Message( + role = Role.USER, + created = now, + content = listOf( + MessageContent.Text( + TextContent("What is 7 x 6?") + ) + ) + ), + + // 2) Assistant makes a tool request (ToolReq) to calculate 7×6 + Message( + role = Role.ASSISTANT, + created = now + 2, + content = listOf( + MessageContent.ToolReq( + ToolRequest( + id = "calc1", + toolCall = """ + { + "status": "success", + "value": { + "name": "calculator_extension__toolname", + "arguments": { + "operation": "multiply", + "numbers": [7, 6] + }, + "needsApproval": false + } + } + """.trimIndent() + ) + ) + ) + ), + + // 3) User (on behalf of the tool) responds with the tool result (ToolResp) + Message( + role = Role.USER, + created = now + 3, + content = listOf( + MessageContent.ToolResp( + ToolResponse( + id = "calc1", + toolResult = """ + { + "status": "success", + "value": [ + {"type": "text", "text": "42"} + ] + } + """.trimIndent() + ) + ) + ) + ), + ) + + printMessages(msgs) + println("---\n") + + // Setup provider + val providerName = "databricks" + val host = System.getenv("DATABRICKS_HOST") ?: error("DATABRICKS_HOST not set") + val token = System.getenv("DATABRICKS_TOKEN") ?: error("DATABRICKS_TOKEN not set") + val providerConfig = """{"host": "$host", "token": "$token"}""" + + println("Provider Name: $providerName") + println("Provider Config: $providerConfig") + + + val sessionName = generateSessionName(providerName, providerConfig, msgs) + println("\nSession Name: $sessionName") + + val tooltip = generateTooltip(providerName, providerConfig, msgs) + println("\nTooltip: $tooltip") + + // Completion + val modelName = "goose-gpt-4-1" + val modelConfig = ModelConfig( + modelName, + 100000u, // UInt + 0.1f, // Float + 200 // Int + ) + + val calculatorTool = createToolConfig( + name = "calculator", + description = "Perform basic arithmetic operations", + inputSchema = """ + { + "type": "object", + "required": ["operation", "numbers"], + "properties": { + "operation": { + "type": "string", + "enum": ["add", "subtract", "multiply", "divide"], + "description": "The arithmetic operation to perform" + }, + "numbers": { + "type": "array", + "items": { "type": "number" }, + "description": "List of numbers to operate on in order" + } + } + } + """.trimIndent(), + approvalMode = ToolApprovalMode.AUTO + ) + + val calculator_extension = ExtensionConfig( + name = "calculator_extension", + instructions = "This extension provides a calculator tool.", + tools = listOf(calculatorTool) + ) + + val extensions = listOf(calculator_extension) + val systemPreamble = "You are a helpful assistant." + + + val req = createCompletionRequest( + providerName, + providerConfig, + modelConfig, + systemPreamble, + msgs, + extensions + ) + + val response = completion(req) + println("\nCompletion Response:\n${response.message}") + println() + + // ---- UI Extraction (custom schema) ---- + runUiExtraction(providerName, providerConfig) +} + + +suspend fun runUiExtraction(providerName: String, providerConfig: String) { + val systemPrompt = "You are a UI generator AI. Convert the user input into a JSON-driven UI." + val messages = listOf( + Message( + role = Role.USER, + created = System.currentTimeMillis() / 1000, + content = listOf( + MessageContent.Text( + TextContent("Make a User Profile Form") + ) + ) + ) + ) + val schema = """{ + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["div","button","header","section","field","form"] + }, + "label": { "type": "string" }, + "children": { + "type": "array", + "items": { "${'$'}ref": "#" } + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "value": { "type": "string" } + }, + "required": ["name","value"], + "additionalProperties": false + } + } + }, + "required": ["type","label","children","attributes"], + "additionalProperties": false + }""".trimIndent(); + + try { + val response = generateStructuredOutputs( + providerName = providerName, + providerConfig = providerConfig, + systemPrompt = systemPrompt, + messages = messages, + schema = schema + ) + println("\nUI Extraction Output:\n${response}") + } catch (e: ProviderException) { + println("\nUI Extraction failed:\n${e.message}") + } +} diff --git a/bindings/kotlin/uniffi/goose_llm/goose_llm.kt b/bindings/kotlin/uniffi/goose_llm/goose_llm.kt new file mode 100644 index 00000000..b5b16337 --- /dev/null +++ b/bindings/kotlin/uniffi/goose_llm/goose_llm.kt @@ -0,0 +1,3081 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package uniffi.goose_llm + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the details of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Callback +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.ptr.* +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.suspendCancellableCoroutine +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.CharBuffer +import java.nio.charset.CodingErrorAction +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicLong +import kotlin.coroutines.resume + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +/** + * @suppress + */ +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values. + // When dealing with these fields, make sure to call `toULong()`. + @JvmField var capacity: Long = 0 + + @JvmField var len: Long = 0 + + @JvmField var data: Pointer? = null + + class ByValue : + RustBuffer(), + Structure.ByValue + + class ByReference : + RustBuffer(), + Structure.ByReference + + internal fun setValue(other: RustBuffer) { + capacity = other.capacity + len = other.len + data = other.data + } + + companion object { + internal fun alloc(size: ULong = 0UL) = + uniffiRustCall { status -> + // Note: need to convert the size to a `Long` value to make this work with JVM. + UniffiLib.INSTANCE.ffi_goose_llm_rustbuffer_alloc(size.toLong(), status) + }.also { + if (it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=$size)") + } + } + + internal fun create( + capacity: ULong, + len: ULong, + data: Pointer?, + ): RustBuffer.ByValue { + var buf = RustBuffer.ByValue() + buf.capacity = capacity.toLong() + buf.len = len.toLong() + buf.data = data + return buf + } + + internal fun free(buf: RustBuffer.ByValue) = + uniffiRustCall { status -> + UniffiLib.INSTANCE.ffi_goose_llm_rustbuffer_free(buf, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +/** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + * + * @suppress + */ +class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setLong(0, value.capacity) + pointer.setLong(8, value.len) + pointer.setPointer(16, value.data) + } + + /** + * Get a `RustBuffer.ByValue` from this reference. + */ + fun getValue(): RustBuffer.ByValue { + val pointer = getPointer() + val value = RustBuffer.ByValue() + value.writeField("capacity", pointer.getLong(0)) + value.writeField("len", pointer.getLong(8)) + value.writeField("data", pointer.getLong(16)) + + return value + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +internal open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + + @JvmField var data: Pointer? = null + + class ByValue : + ForeignBytes(), + Structure.ByValue +} + +/** + * The FfiConverter interface handles converter types to and from the FFI + * + * All implementing objects should be public to support external types. When a + * type is external we need to import it's FfiConverter. + * + * @suppress + */ +public interface FfiConverter { + // Convert an FFI type to a Kotlin type + fun lift(value: FfiType): KotlinType + + // Convert an Kotlin type to an FFI type + fun lower(value: KotlinType): FfiType + + // Read a Kotlin type from a `ByteBuffer` + fun read(buf: ByteBuffer): KotlinType + + // Calculate bytes to allocate when creating a `RustBuffer` + // + // This must return at least as many bytes as the write() function will + // write. It can return more bytes than needed, for example when writing + // Strings we can't know the exact bytes needed until we the UTF-8 + // encoding, so we pessimistically allocate the largest size possible (3 + // bytes per codepoint). Allocating extra bytes is not really a big deal + // because the `RustBuffer` is short-lived. + fun allocationSize(value: KotlinType): ULong + + // Write a Kotlin type to a `ByteBuffer` + fun write( + value: KotlinType, + buf: ByteBuffer, + ) + + // Lower a value into a `RustBuffer` + // + // This method lowers a value into a `RustBuffer` rather than the normal + // FfiType. It's used by the callback interface code. Callback interface + // returns are always serialized into a `RustBuffer` regardless of their + // normal FFI type. + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { + val rbuf = RustBuffer.alloc(allocationSize(value)) + try { + val bbuf = + rbuf.data!!.getByteBuffer(0, rbuf.capacity).also { + it.order(ByteOrder.BIG_ENDIAN) + } + write(value, bbuf) + rbuf.writeField("len", bbuf.position().toLong()) + return rbuf + } catch (e: Throwable) { + RustBuffer.free(rbuf) + throw e + } + } + + // Lift a value from a `RustBuffer`. + // + // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. + // It's currently only used by the `FfiConverterRustBuffer` class below. + fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { + val byteBuf = rbuf.asByteBuffer()!! + try { + val item = read(byteBuf) + if (byteBuf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } + } +} + +/** + * FfiConverter that uses `RustBuffer` as the FfiType + * + * @suppress + */ +public interface FfiConverterRustBuffer : FfiConverter { + override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) + + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. + +internal const val UNIFFI_CALL_SUCCESS = 0.toByte() +internal const val UNIFFI_CALL_ERROR = 1.toByte() +internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() + +@Structure.FieldOrder("code", "error_buf") +internal open class UniffiRustCallStatus : Structure() { + @JvmField var code: Byte = 0 + + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + class ByValue : + UniffiRustCallStatus(), + Structure.ByValue + + fun isSuccess(): Boolean = code == UNIFFI_CALL_SUCCESS + + fun isError(): Boolean = code == UNIFFI_CALL_ERROR + + fun isPanic(): Boolean = code == UNIFFI_CALL_UNEXPECTED_ERROR + + companion object { + fun create( + code: Byte, + errorBuf: RustBuffer.ByValue, + ): UniffiRustCallStatus.ByValue { + val callStatus = UniffiRustCallStatus.ByValue() + callStatus.code = code + callStatus.error_buf = errorBuf + return callStatus + } + } +} + +class InternalException( + message: String, +) : kotlin.Exception(message) + +/** + * Each top-level error class has a companion object that can lift the error from the call status's rust buffer + * + * @suppress + */ +interface UniffiRustCallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun uniffiRustCallWithError( + errorHandler: UniffiRustCallStatusErrorHandler, + callback: (UniffiRustCallStatus) -> U, +): U { + var status = UniffiRustCallStatus() + val return_value = callback(status) + uniffiCheckCallStatus(errorHandler, status) + return return_value +} + +// Check UniffiRustCallStatus and throw an error if the call wasn't successful +private fun uniffiCheckCallStatus( + errorHandler: UniffiRustCallStatusErrorHandler, + status: UniffiRustCallStatus, +) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(FfiConverterString.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +/** + * UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR + * + * @suppress + */ +object UniffiNullRustCallStatusErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U = + uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback) + +internal inline fun uniffiTraitInterfaceCall( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, +) { + try { + writeReturn(makeCall()) + } catch (e: kotlin.Exception) { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } +} + +internal inline fun uniffiTraitInterfaceCallWithError( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, + lowerError: (E) -> RustBuffer.ByValue, +) { + try { + writeReturn(makeCall()) + } catch (e: kotlin.Exception) { + if (e is E) { + callStatus.code = UNIFFI_CALL_ERROR + callStatus.error_buf = lowerError(e) + } else { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } + } +} + +// Map handles to objects +// +// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. +internal class UniffiHandleMap { + private val map = ConcurrentHashMap() + private val counter = + java.util.concurrent.atomic + .AtomicLong(0) + + val size: Int + get() = map.size + + // Insert a new object into the handle map and get a handle for it + fun insert(obj: T): Long { + val handle = counter.getAndAdd(1) + map.put(handle, obj) + return handle + } + + // Get an object from the handle map + fun get(handle: Long): T = map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") + + // Remove an entry from the handlemap and get the Kotlin object back + fun remove(handle: Long): T = map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") +} + +// Contains loading, initialization code, +// and the FFI Function declarations in a com.sun.jna.Library. +@Synchronized +private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "goose_llm" +} + +private inline fun loadIndirect(componentName: String): Lib = + Native.load(findLibraryName(componentName), Lib::class.java) + +// Define FFI callback types +internal interface UniffiRustFutureContinuationCallback : com.sun.jna.Callback { + fun callback( + `data`: Long, + `pollResult`: Byte, + ) +} + +internal interface UniffiForeignFutureFree : com.sun.jna.Callback { + fun callback(`handle`: Long) +} + +internal interface UniffiCallbackInterfaceFree : com.sun.jna.Callback { + fun callback(`handle`: Long) +} + +@Structure.FieldOrder("handle", "free") +internal open class UniffiForeignFuture( + @JvmField internal var `handle`: Long = 0.toLong(), + @JvmField internal var `free`: UniffiForeignFutureFree? = null, +) : Structure() { + class UniffiByValue( + `handle`: Long = 0.toLong(), + `free`: UniffiForeignFutureFree? = null, + ) : UniffiForeignFuture(`handle`, `free`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFuture) { + `handle` = other.`handle` + `free` = other.`free` + } +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU8(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU8 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU8.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI8(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI8 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI8.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU16(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU16 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU16.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI16(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI16 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI16.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU32(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU32 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU32.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI32(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI32 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI32.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU64(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU64 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU64.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI64(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI64 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI64.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF32( + @JvmField internal var `returnValue`: Float = 0.0f, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Float = 0.0f, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructF32(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteF32 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructF32.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF64( + @JvmField internal var `returnValue`: Double = 0.0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Double = 0.0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructF64(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteF64 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructF64.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructPointer( + @JvmField internal var `returnValue`: Pointer = Pointer.NULL, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Pointer = Pointer.NULL, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructPointer(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructPointer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompletePointer : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructPointer.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructRustBuffer( + @JvmField internal var `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructRustBuffer(`returnValue`, `callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructRustBuffer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteRustBuffer : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructRustBuffer.UniffiByValue, + ) +} + +@Structure.FieldOrder("callStatus") +internal open class UniffiForeignFutureStructVoid( + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructVoid(`callStatus`), + Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructVoid) { + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructVoid.UniffiByValue, + ) +} + +// For large crates we prevent `MethodTooLargeException` (see #2340) +// N.B. the name of the extension is very misleading, since it is +// rather `InterfaceTooLargeException`, caused by too many methods +// in the interface for large crates. +// +// By splitting the otherwise huge interface into two parts +// * UniffiLib +// * IntegrityCheckingUniffiLib (this) +// we allow for ~2x as many methods in the UniffiLib interface. +// +// The `ffi_uniffi_contract_version` method and all checksum methods are put +// into `IntegrityCheckingUniffiLib` and these methods are called only once, +// when the library is loaded. +internal interface IntegrityCheckingUniffiLib : Library { + // Integrity check functions only + fun uniffi_goose_llm_checksum_func_completion(): Short + + fun uniffi_goose_llm_checksum_func_create_completion_request(): Short + + fun uniffi_goose_llm_checksum_func_create_tool_config(): Short + + fun uniffi_goose_llm_checksum_func_generate_session_name(): Short + + fun uniffi_goose_llm_checksum_func_generate_structured_outputs(): Short + + fun uniffi_goose_llm_checksum_func_generate_tooltip(): Short + + fun uniffi_goose_llm_checksum_func_print_messages(): Short + + fun ffi_goose_llm_uniffi_contract_version(): Int +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. +internal interface UniffiLib : Library { + companion object { + internal val INSTANCE: UniffiLib by lazy { + val componentName = "goose_llm" + // For large crates we prevent `MethodTooLargeException` (see #2340) + // N.B. the name of the extension is very misleading, since it is + // rather `InterfaceTooLargeException`, caused by too many methods + // in the interface for large crates. + // + // By splitting the otherwise huge interface into two parts + // * UniffiLib (this) + // * IntegrityCheckingUniffiLib + // And all checksum methods are put into `IntegrityCheckingUniffiLib` + // we allow for ~2x as many methods in the UniffiLib interface. + // + // Thus we first load the library with `loadIndirect` as `IntegrityCheckingUniffiLib` + // so that we can (optionally!) call `uniffiCheckApiChecksums`... + loadIndirect(componentName) + .also { lib: IntegrityCheckingUniffiLib -> + uniffiCheckContractApiVersion(lib) + uniffiCheckApiChecksums(lib) + } + // ... and then we load the library as `UniffiLib` + // N.B. we cannot use `loadIndirect` once and then try to cast it to `UniffiLib` + // => results in `java.lang.ClassCastException: com.sun.proxy.$Proxy cannot be cast to ...` + // error. So we must call `loadIndirect` twice. For crates large enough + // to trigger this issue, the performance impact is negligible, running on + // a macOS M1 machine the `loadIndirect` call takes ~50ms. + val lib = loadIndirect(componentName) + // No need to check the contract version and checksums, since + // we already did that with `IntegrityCheckingUniffiLib` above. + // Loading of library with integrity check done. + lib + } + } + + // FFI functions + fun uniffi_goose_llm_fn_func_completion(`req`: RustBuffer.ByValue): Long + + fun uniffi_goose_llm_fn_func_create_completion_request( + `providerName`: RustBuffer.ByValue, + `providerConfig`: RustBuffer.ByValue, + `modelConfig`: RustBuffer.ByValue, + `systemPreamble`: RustBuffer.ByValue, + `messages`: RustBuffer.ByValue, + `extensions`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_goose_llm_fn_func_create_tool_config( + `name`: RustBuffer.ByValue, + `description`: RustBuffer.ByValue, + `inputSchema`: RustBuffer.ByValue, + `approvalMode`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_goose_llm_fn_func_generate_session_name( + `providerName`: RustBuffer.ByValue, + `providerConfig`: RustBuffer.ByValue, + `messages`: RustBuffer.ByValue, + ): Long + + fun uniffi_goose_llm_fn_func_generate_structured_outputs( + `providerName`: RustBuffer.ByValue, + `providerConfig`: RustBuffer.ByValue, + `systemPrompt`: RustBuffer.ByValue, + `messages`: RustBuffer.ByValue, + `schema`: RustBuffer.ByValue, + ): Long + + fun uniffi_goose_llm_fn_func_generate_tooltip( + `providerName`: RustBuffer.ByValue, + `providerConfig`: RustBuffer.ByValue, + `messages`: RustBuffer.ByValue, + ): Long + + fun uniffi_goose_llm_fn_func_print_messages( + `messages`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun ffi_goose_llm_rustbuffer_alloc( + `size`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_goose_llm_rustbuffer_from_bytes( + `bytes`: ForeignBytes.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_goose_llm_rustbuffer_free( + `buf`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun ffi_goose_llm_rustbuffer_reserve( + `buf`: RustBuffer.ByValue, + `additional`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_goose_llm_rust_future_poll_u8( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_u8(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_u8(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_u8( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun ffi_goose_llm_rust_future_poll_i8( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_i8(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_i8(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_i8( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun ffi_goose_llm_rust_future_poll_u16( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_u16(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_u16(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_u16( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Short + + fun ffi_goose_llm_rust_future_poll_i16( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_i16(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_i16(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_i16( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Short + + fun ffi_goose_llm_rust_future_poll_u32( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_u32(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_u32(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_u32( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun ffi_goose_llm_rust_future_poll_i32( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_i32(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_i32(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_i32( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun ffi_goose_llm_rust_future_poll_u64( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_u64(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_u64(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_u64( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Long + + fun ffi_goose_llm_rust_future_poll_i64( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_i64(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_i64(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_i64( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Long + + fun ffi_goose_llm_rust_future_poll_f32( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_f32(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_f32(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_f32( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Float + + fun ffi_goose_llm_rust_future_poll_f64( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_f64(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_f64(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_f64( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Double + + fun ffi_goose_llm_rust_future_poll_pointer( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_pointer(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_pointer(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_pointer( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Pointer + + fun ffi_goose_llm_rust_future_poll_rust_buffer( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_rust_buffer(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_rust_buffer(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_rust_buffer( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_goose_llm_rust_future_poll_void( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_goose_llm_rust_future_cancel_void(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_free_void(`handle`: Long): Unit + + fun ffi_goose_llm_rust_future_complete_void( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Unit +} + +private fun uniffiCheckContractApiVersion(lib: IntegrityCheckingUniffiLib) { + // Get the bindings contract version from our ComponentInterface + val bindings_contract_version = 29 + // Get the scaffolding contract version by calling the into the dylib + val scaffolding_contract_version = lib.ffi_goose_llm_uniffi_contract_version() + if (bindings_contract_version != scaffolding_contract_version) { + throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") + } +} + +@Suppress("UNUSED_PARAMETER") +private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { + if (lib.uniffi_goose_llm_checksum_func_completion() != 47457.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_goose_llm_checksum_func_create_completion_request() != 39068.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_goose_llm_checksum_func_create_tool_config() != 49910.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_goose_llm_checksum_func_generate_session_name() != 64087.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_goose_llm_checksum_func_generate_structured_outputs() != 43426.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_goose_llm_checksum_func_generate_tooltip() != 41121.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_goose_llm_checksum_func_print_messages() != 30278.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +/** + * @suppress + */ +public fun uniffiEnsureInitialized() { + UniffiLib.INSTANCE +} + +// Async support +// Async return type handlers + +internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte() +internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte() + +internal val uniffiContinuationHandleMap = UniffiHandleMap>() + +// FFI type for Rust future continuations +internal object uniffiRustFutureContinuationCallbackImpl : UniffiRustFutureContinuationCallback { + override fun callback( + data: Long, + pollResult: Byte, + ) { + uniffiContinuationHandleMap.remove(data).resume(pollResult) + } +} + +internal suspend fun uniffiRustCallAsync( + rustFuture: Long, + pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit, + completeFunc: (Long, UniffiRustCallStatus) -> F, + freeFunc: (Long) -> Unit, + liftFunc: (F) -> T, + errorHandler: UniffiRustCallStatusErrorHandler, +): T { + try { + do { + val pollResult = + suspendCancellableCoroutine { continuation -> + pollFunc( + rustFuture, + uniffiRustFutureContinuationCallbackImpl, + uniffiContinuationHandleMap.insert(continuation), + ) + } + } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY) + + return liftFunc( + uniffiRustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }), + ) + } finally { + freeFunc(rustFuture) + } +} + +// Public interface members begin here. + +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + + companion object { + fun destroy(vararg args: Any?) { + for (arg in args) { + when (arg) { + is Disposable -> arg.destroy() + is ArrayList<*> -> { + for (idx in arg.indices) { + val element = arg[idx] + if (element is Disposable) { + element.destroy() + } + } + } + is Map<*, *> -> { + for (element in arg.values) { + if (element is Disposable) { + element.destroy() + } + } + } + is Iterable<*> -> { + for (element in arg) { + if (element is Disposable) { + element.destroy() + } + } + } + } + } + } + } +} + +/** + * @suppress + */ +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +/** + * Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. + * + * @suppress + * */ +object NoPointer + +/** + * @suppress + */ +public object FfiConverterUInt : FfiConverter { + override fun lift(value: Int): UInt = value.toUInt() + + override fun read(buf: ByteBuffer): UInt = lift(buf.getInt()) + + override fun lower(value: UInt): Int = value.toInt() + + override fun allocationSize(value: UInt) = 4UL + + override fun write( + value: UInt, + buf: ByteBuffer, + ) { + buf.putInt(value.toInt()) + } +} + +/** + * @suppress + */ +public object FfiConverterInt : FfiConverter { + override fun lift(value: Int): Int = value + + override fun read(buf: ByteBuffer): Int = buf.getInt() + + override fun lower(value: Int): Int = value + + override fun allocationSize(value: Int) = 4UL + + override fun write( + value: Int, + buf: ByteBuffer, + ) { + buf.putInt(value) + } +} + +/** + * @suppress + */ +public object FfiConverterLong : FfiConverter { + override fun lift(value: Long): Long = value + + override fun read(buf: ByteBuffer): Long = buf.getLong() + + override fun lower(value: Long): Long = value + + override fun allocationSize(value: Long) = 8UL + + override fun write( + value: Long, + buf: ByteBuffer, + ) { + buf.putLong(value) + } +} + +/** + * @suppress + */ +public object FfiConverterFloat : FfiConverter { + override fun lift(value: Float): Float = value + + override fun read(buf: ByteBuffer): Float = buf.getFloat() + + override fun lower(value: Float): Float = value + + override fun allocationSize(value: Float) = 4UL + + override fun write( + value: Float, + buf: ByteBuffer, + ) { + buf.putFloat(value) + } +} + +/** + * @suppress + */ +public object FfiConverterDouble : FfiConverter { + override fun lift(value: Double): Double = value + + override fun read(buf: ByteBuffer): Double = buf.getDouble() + + override fun lower(value: Double): Double = value + + override fun allocationSize(value: Double) = 8UL + + override fun write( + value: Double, + buf: ByteBuffer, + ) { + buf.putDouble(value) + } +} + +/** + * @suppress + */ +public object FfiConverterString : FfiConverter { + // Note: we don't inherit from FfiConverterRustBuffer, because we use a + // special encoding when lowering/lifting. We can use `RustBuffer.len` to + // store our length and avoid writing it out to the buffer. + override fun lift(value: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(value.len.toInt()) + value.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(value) + } + } + + override fun read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } + + fun toUtf8(value: String): ByteBuffer { + // Make sure we don't have invalid UTF-16, check for lone surrogates. + return Charsets.UTF_8.newEncoder().run { + onMalformedInput(CodingErrorAction.REPORT) + encode(CharBuffer.wrap(value)) + } + } + + override fun lower(value: String): RustBuffer.ByValue { + val byteBuf = toUtf8(value) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteBuf.limit().toULong()) + rbuf.asByteBuffer()!!.put(byteBuf) + return rbuf + } + + // We aren't sure exactly how many bytes our string will be once it's UTF-8 + // encoded. Allocate 3 bytes per UTF-16 code unit which will always be + // enough. + override fun allocationSize(value: String): ULong { + val sizeForLength = 4UL + val sizeForString = value.length.toULong() * 3UL + return sizeForLength + sizeForString + } + + override fun write( + value: String, + buf: ByteBuffer, + ) { + val byteBuf = toUtf8(value) + buf.putInt(byteBuf.limit()) + buf.put(byteBuf) + } +} + +data class CompletionResponse( + var `message`: Message, + var `model`: kotlin.String, + var `usage`: Usage, + var `runtimeMetrics`: RuntimeMetrics, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeCompletionResponse : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CompletionResponse = + CompletionResponse( + FfiConverterTypeMessage.read(buf), + FfiConverterString.read(buf), + FfiConverterTypeUsage.read(buf), + FfiConverterTypeRuntimeMetrics.read(buf), + ) + + override fun allocationSize(value: CompletionResponse) = + ( + FfiConverterTypeMessage.allocationSize(value.`message`) + + FfiConverterString.allocationSize(value.`model`) + + FfiConverterTypeUsage.allocationSize(value.`usage`) + + FfiConverterTypeRuntimeMetrics.allocationSize(value.`runtimeMetrics`) + ) + + override fun write( + value: CompletionResponse, + buf: ByteBuffer, + ) { + FfiConverterTypeMessage.write(value.`message`, buf) + FfiConverterString.write(value.`model`, buf) + FfiConverterTypeUsage.write(value.`usage`, buf) + FfiConverterTypeRuntimeMetrics.write(value.`runtimeMetrics`, buf) + } +} + +data class ExtensionConfig( + var `name`: kotlin.String, + var `instructions`: kotlin.String?, + var `tools`: List, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeExtensionConfig : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ExtensionConfig = + ExtensionConfig( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterSequenceTypeToolConfig.read(buf), + ) + + override fun allocationSize(value: ExtensionConfig) = + ( + FfiConverterString.allocationSize(value.`name`) + + FfiConverterOptionalString.allocationSize(value.`instructions`) + + FfiConverterSequenceTypeToolConfig.allocationSize(value.`tools`) + ) + + override fun write( + value: ExtensionConfig, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`name`, buf) + FfiConverterOptionalString.write(value.`instructions`, buf) + FfiConverterSequenceTypeToolConfig.write(value.`tools`, buf) + } +} + +data class ImageContent( + var `data`: kotlin.String, + var `mimeType`: kotlin.String, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeImageContent : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ImageContent = + ImageContent( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + + override fun allocationSize(value: ImageContent) = + ( + FfiConverterString.allocationSize(value.`data`) + + FfiConverterString.allocationSize(value.`mimeType`) + ) + + override fun write( + value: ImageContent, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`data`, buf) + FfiConverterString.write(value.`mimeType`, buf) + } +} + +/** + * A message to or from an LLM + */ +data class Message( + var `role`: Role, + var `created`: kotlin.Long, + var `content`: Contents, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeMessage : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Message = + Message( + FfiConverterTypeRole.read(buf), + FfiConverterLong.read(buf), + FfiConverterTypeContents.read(buf), + ) + + override fun allocationSize(value: Message) = + ( + FfiConverterTypeRole.allocationSize(value.`role`) + + FfiConverterLong.allocationSize(value.`created`) + + FfiConverterTypeContents.allocationSize(value.`content`) + ) + + override fun write( + value: Message, + buf: ByteBuffer, + ) { + FfiConverterTypeRole.write(value.`role`, buf) + FfiConverterLong.write(value.`created`, buf) + FfiConverterTypeContents.write(value.`content`, buf) + } +} + +/** + * Configuration for model-specific settings and limits + */ +data class ModelConfig( + /** + * The name of the model to use + */ + var `modelName`: kotlin.String, + /** + * Optional explicit context limit that overrides any defaults + */ + var `contextLimit`: kotlin.UInt?, + /** + * Optional temperature setting (0.0 - 1.0) + */ + var `temperature`: kotlin.Float?, + /** + * Optional maximum tokens to generate + */ + var `maxTokens`: kotlin.Int?, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeModelConfig : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ModelConfig = + ModelConfig( + FfiConverterString.read(buf), + FfiConverterOptionalUInt.read(buf), + FfiConverterOptionalFloat.read(buf), + FfiConverterOptionalInt.read(buf), + ) + + override fun allocationSize(value: ModelConfig) = + ( + FfiConverterString.allocationSize(value.`modelName`) + + FfiConverterOptionalUInt.allocationSize(value.`contextLimit`) + + FfiConverterOptionalFloat.allocationSize(value.`temperature`) + + FfiConverterOptionalInt.allocationSize(value.`maxTokens`) + ) + + override fun write( + value: ModelConfig, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`modelName`, buf) + FfiConverterOptionalUInt.write(value.`contextLimit`, buf) + FfiConverterOptionalFloat.write(value.`temperature`, buf) + FfiConverterOptionalInt.write(value.`maxTokens`, buf) + } +} + +data class ProviderCompleteResponse( + var `message`: Message, + var `model`: kotlin.String, + var `usage`: Usage, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeProviderCompleteResponse : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ProviderCompleteResponse = + ProviderCompleteResponse( + FfiConverterTypeMessage.read(buf), + FfiConverterString.read(buf), + FfiConverterTypeUsage.read(buf), + ) + + override fun allocationSize(value: ProviderCompleteResponse) = + ( + FfiConverterTypeMessage.allocationSize(value.`message`) + + FfiConverterString.allocationSize(value.`model`) + + FfiConverterTypeUsage.allocationSize(value.`usage`) + ) + + override fun write( + value: ProviderCompleteResponse, + buf: ByteBuffer, + ) { + FfiConverterTypeMessage.write(value.`message`, buf) + FfiConverterString.write(value.`model`, buf) + FfiConverterTypeUsage.write(value.`usage`, buf) + } +} + +/** + * Response from a structured‐extraction call + */ +data class ProviderExtractResponse( + /** + * The extracted JSON object + */ + var `data`: Value, + /** + * Which model produced it + */ + var `model`: kotlin.String, + /** + * Token usage stats + */ + var `usage`: Usage, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeProviderExtractResponse : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ProviderExtractResponse = + ProviderExtractResponse( + FfiConverterTypeValue.read(buf), + FfiConverterString.read(buf), + FfiConverterTypeUsage.read(buf), + ) + + override fun allocationSize(value: ProviderExtractResponse) = + ( + FfiConverterTypeValue.allocationSize(value.`data`) + + FfiConverterString.allocationSize(value.`model`) + + FfiConverterTypeUsage.allocationSize(value.`usage`) + ) + + override fun write( + value: ProviderExtractResponse, + buf: ByteBuffer, + ) { + FfiConverterTypeValue.write(value.`data`, buf) + FfiConverterString.write(value.`model`, buf) + FfiConverterTypeUsage.write(value.`usage`, buf) + } +} + +data class RedactedThinkingContent( + var `data`: kotlin.String, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeRedactedThinkingContent : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): RedactedThinkingContent = + RedactedThinkingContent( + FfiConverterString.read(buf), + ) + + override fun allocationSize(value: RedactedThinkingContent) = + ( + FfiConverterString.allocationSize(value.`data`) + ) + + override fun write( + value: RedactedThinkingContent, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`data`, buf) + } +} + +data class RuntimeMetrics( + var `totalTimeSec`: kotlin.Float, + var `totalTimeSecProvider`: kotlin.Float, + var `tokensPerSecond`: kotlin.Double?, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeRuntimeMetrics : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): RuntimeMetrics = + RuntimeMetrics( + FfiConverterFloat.read(buf), + FfiConverterFloat.read(buf), + FfiConverterOptionalDouble.read(buf), + ) + + override fun allocationSize(value: RuntimeMetrics) = + ( + FfiConverterFloat.allocationSize(value.`totalTimeSec`) + + FfiConverterFloat.allocationSize(value.`totalTimeSecProvider`) + + FfiConverterOptionalDouble.allocationSize(value.`tokensPerSecond`) + ) + + override fun write( + value: RuntimeMetrics, + buf: ByteBuffer, + ) { + FfiConverterFloat.write(value.`totalTimeSec`, buf) + FfiConverterFloat.write(value.`totalTimeSecProvider`, buf) + FfiConverterOptionalDouble.write(value.`tokensPerSecond`, buf) + } +} + +data class TextContent( + var `text`: kotlin.String, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeTextContent : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TextContent = + TextContent( + FfiConverterString.read(buf), + ) + + override fun allocationSize(value: TextContent) = + ( + FfiConverterString.allocationSize(value.`text`) + ) + + override fun write( + value: TextContent, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`text`, buf) + } +} + +data class ThinkingContent( + var `thinking`: kotlin.String, + var `signature`: kotlin.String, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeThinkingContent : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ThinkingContent = + ThinkingContent( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + + override fun allocationSize(value: ThinkingContent) = + ( + FfiConverterString.allocationSize(value.`thinking`) + + FfiConverterString.allocationSize(value.`signature`) + ) + + override fun write( + value: ThinkingContent, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`thinking`, buf) + FfiConverterString.write(value.`signature`, buf) + } +} + +data class ToolConfig( + var `name`: kotlin.String, + var `description`: kotlin.String, + var `inputSchema`: Value, + var `approvalMode`: ToolApprovalMode, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeToolConfig : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ToolConfig = + ToolConfig( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterTypeValue.read(buf), + FfiConverterTypeToolApprovalMode.read(buf), + ) + + override fun allocationSize(value: ToolConfig) = + ( + FfiConverterString.allocationSize(value.`name`) + + FfiConverterString.allocationSize(value.`description`) + + FfiConverterTypeValue.allocationSize(value.`inputSchema`) + + FfiConverterTypeToolApprovalMode.allocationSize(value.`approvalMode`) + ) + + override fun write( + value: ToolConfig, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`name`, buf) + FfiConverterString.write(value.`description`, buf) + FfiConverterTypeValue.write(value.`inputSchema`, buf) + FfiConverterTypeToolApprovalMode.write(value.`approvalMode`, buf) + } +} + +data class ToolRequest( + var `id`: kotlin.String, + var `toolCall`: ToolRequestToolCall, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeToolRequest : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ToolRequest = + ToolRequest( + FfiConverterString.read(buf), + FfiConverterTypeToolRequestToolCall.read(buf), + ) + + override fun allocationSize(value: ToolRequest) = + ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterTypeToolRequestToolCall.allocationSize(value.`toolCall`) + ) + + override fun write( + value: ToolRequest, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`id`, buf) + FfiConverterTypeToolRequestToolCall.write(value.`toolCall`, buf) + } +} + +data class ToolResponse( + var `id`: kotlin.String, + var `toolResult`: ToolResponseToolResult, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeToolResponse : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ToolResponse = + ToolResponse( + FfiConverterString.read(buf), + FfiConverterTypeToolResponseToolResult.read(buf), + ) + + override fun allocationSize(value: ToolResponse) = + ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterTypeToolResponseToolResult.allocationSize(value.`toolResult`) + ) + + override fun write( + value: ToolResponse, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`id`, buf) + FfiConverterTypeToolResponseToolResult.write(value.`toolResult`, buf) + } +} + +data class Usage( + var `inputTokens`: kotlin.Int?, + var `outputTokens`: kotlin.Int?, + var `totalTokens`: kotlin.Int?, +) { + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeUsage : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Usage = + Usage( + FfiConverterOptionalInt.read(buf), + FfiConverterOptionalInt.read(buf), + FfiConverterOptionalInt.read(buf), + ) + + override fun allocationSize(value: Usage) = + ( + FfiConverterOptionalInt.allocationSize(value.`inputTokens`) + + FfiConverterOptionalInt.allocationSize(value.`outputTokens`) + + FfiConverterOptionalInt.allocationSize(value.`totalTokens`) + ) + + override fun write( + value: Usage, + buf: ByteBuffer, + ) { + FfiConverterOptionalInt.write(value.`inputTokens`, buf) + FfiConverterOptionalInt.write(value.`outputTokens`, buf) + FfiConverterOptionalInt.write(value.`totalTokens`, buf) + } +} + +sealed class CompletionException( + message: String, +) : kotlin.Exception(message) { + class UnknownProvider( + message: String, + ) : CompletionException(message) + + class Provider( + message: String, + ) : CompletionException(message) + + class Template( + message: String, + ) : CompletionException(message) + + class Json( + message: String, + ) : CompletionException(message) + + class ToolNotFound( + message: String, + ) : CompletionException(message) + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): CompletionException = FfiConverterTypeCompletionError.lift(error_buf) + } +} + +/** + * @suppress + */ +public object FfiConverterTypeCompletionError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CompletionException = + when (buf.getInt()) { + 1 -> CompletionException.UnknownProvider(FfiConverterString.read(buf)) + 2 -> CompletionException.Provider(FfiConverterString.read(buf)) + 3 -> CompletionException.Template(FfiConverterString.read(buf)) + 4 -> CompletionException.Json(FfiConverterString.read(buf)) + 5 -> CompletionException.ToolNotFound(FfiConverterString.read(buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + + override fun allocationSize(value: CompletionException): ULong = 4UL + + override fun write( + value: CompletionException, + buf: ByteBuffer, + ) { + when (value) { + is CompletionException.UnknownProvider -> { + buf.putInt(1) + Unit + } + is CompletionException.Provider -> { + buf.putInt(2) + Unit + } + is CompletionException.Template -> { + buf.putInt(3) + Unit + } + is CompletionException.Json -> { + buf.putInt(4) + Unit + } + is CompletionException.ToolNotFound -> { + buf.putInt(5) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +sealed class Content { + data class Text( + val v1: TextContent, + ) : Content() { + companion object + } + + data class Image( + val v1: ImageContent, + ) : Content() { + companion object + } + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeContent : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Content = + when (buf.getInt()) { + 1 -> + Content.Text( + FfiConverterTypeTextContent.read(buf), + ) + 2 -> + Content.Image( + FfiConverterTypeImageContent.read(buf), + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + + override fun allocationSize(value: Content) = + when (value) { + is Content.Text -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeTextContent.allocationSize(value.v1) + ) + } + is Content.Image -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeImageContent.allocationSize(value.v1) + ) + } + } + + override fun write( + value: Content, + buf: ByteBuffer, + ) { + when (value) { + is Content.Text -> { + buf.putInt(1) + FfiConverterTypeTextContent.write(value.v1, buf) + Unit + } + is Content.Image -> { + buf.putInt(2) + FfiConverterTypeImageContent.write(value.v1, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +/** + * Content passed inside a message, which can be both simple content and tool content + */ +sealed class MessageContent { + data class Text( + val v1: TextContent, + ) : MessageContent() { + companion object + } + + data class Image( + val v1: ImageContent, + ) : MessageContent() { + companion object + } + + data class ToolReq( + val v1: ToolRequest, + ) : MessageContent() { + companion object + } + + data class ToolResp( + val v1: ToolResponse, + ) : MessageContent() { + companion object + } + + data class Thinking( + val v1: ThinkingContent, + ) : MessageContent() { + companion object + } + + data class RedactedThinking( + val v1: RedactedThinkingContent, + ) : MessageContent() { + companion object + } + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeMessageContent : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): MessageContent = + when (buf.getInt()) { + 1 -> + MessageContent.Text( + FfiConverterTypeTextContent.read(buf), + ) + 2 -> + MessageContent.Image( + FfiConverterTypeImageContent.read(buf), + ) + 3 -> + MessageContent.ToolReq( + FfiConverterTypeToolRequest.read(buf), + ) + 4 -> + MessageContent.ToolResp( + FfiConverterTypeToolResponse.read(buf), + ) + 5 -> + MessageContent.Thinking( + FfiConverterTypeThinkingContent.read(buf), + ) + 6 -> + MessageContent.RedactedThinking( + FfiConverterTypeRedactedThinkingContent.read(buf), + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + + override fun allocationSize(value: MessageContent) = + when (value) { + is MessageContent.Text -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeTextContent.allocationSize(value.v1) + ) + } + is MessageContent.Image -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeImageContent.allocationSize(value.v1) + ) + } + is MessageContent.ToolReq -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeToolRequest.allocationSize(value.v1) + ) + } + is MessageContent.ToolResp -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeToolResponse.allocationSize(value.v1) + ) + } + is MessageContent.Thinking -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeThinkingContent.allocationSize(value.v1) + ) + } + is MessageContent.RedactedThinking -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterTypeRedactedThinkingContent.allocationSize(value.v1) + ) + } + } + + override fun write( + value: MessageContent, + buf: ByteBuffer, + ) { + when (value) { + is MessageContent.Text -> { + buf.putInt(1) + FfiConverterTypeTextContent.write(value.v1, buf) + Unit + } + is MessageContent.Image -> { + buf.putInt(2) + FfiConverterTypeImageContent.write(value.v1, buf) + Unit + } + is MessageContent.ToolReq -> { + buf.putInt(3) + FfiConverterTypeToolRequest.write(value.v1, buf) + Unit + } + is MessageContent.ToolResp -> { + buf.putInt(4) + FfiConverterTypeToolResponse.write(value.v1, buf) + Unit + } + is MessageContent.Thinking -> { + buf.putInt(5) + FfiConverterTypeThinkingContent.write(value.v1, buf) + Unit + } + is MessageContent.RedactedThinking -> { + buf.putInt(6) + FfiConverterTypeRedactedThinkingContent.write(value.v1, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +sealed class ProviderException : kotlin.Exception() { + class Authentication( + val v1: kotlin.String, + ) : ProviderException() { + override val message + get() = "v1=${ v1 }" + } + + class ContextLengthExceeded( + val v1: kotlin.String, + ) : ProviderException() { + override val message + get() = "v1=${ v1 }" + } + + class RateLimitExceeded( + val v1: kotlin.String, + ) : ProviderException() { + override val message + get() = "v1=${ v1 }" + } + + class ServerException( + val v1: kotlin.String, + ) : ProviderException() { + override val message + get() = "v1=${ v1 }" + } + + class RequestFailed( + val v1: kotlin.String, + ) : ProviderException() { + override val message + get() = "v1=${ v1 }" + } + + class ExecutionException( + val v1: kotlin.String, + ) : ProviderException() { + override val message + get() = "v1=${ v1 }" + } + + class UsageException( + val v1: kotlin.String, + ) : ProviderException() { + override val message + get() = "v1=${ v1 }" + } + + class ResponseParseException( + val v1: kotlin.String, + ) : ProviderException() { + override val message + get() = "v1=${ v1 }" + } + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): ProviderException = FfiConverterTypeProviderError.lift(error_buf) + } +} + +/** + * @suppress + */ +public object FfiConverterTypeProviderError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ProviderException = + when (buf.getInt()) { + 1 -> + ProviderException.Authentication( + FfiConverterString.read(buf), + ) + 2 -> + ProviderException.ContextLengthExceeded( + FfiConverterString.read(buf), + ) + 3 -> + ProviderException.RateLimitExceeded( + FfiConverterString.read(buf), + ) + 4 -> + ProviderException.ServerException( + FfiConverterString.read(buf), + ) + 5 -> + ProviderException.RequestFailed( + FfiConverterString.read(buf), + ) + 6 -> + ProviderException.ExecutionException( + FfiConverterString.read(buf), + ) + 7 -> + ProviderException.UsageException( + FfiConverterString.read(buf), + ) + 8 -> + ProviderException.ResponseParseException( + FfiConverterString.read(buf), + ) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + + override fun allocationSize(value: ProviderException): ULong = + when (value) { + is ProviderException.Authentication -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ProviderException.ContextLengthExceeded -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ProviderException.RateLimitExceeded -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ProviderException.ServerException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ProviderException.RequestFailed -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ProviderException.ExecutionException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ProviderException.UsageException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ProviderException.ResponseParseException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + } + + override fun write( + value: ProviderException, + buf: ByteBuffer, + ) { + when (value) { + is ProviderException.Authentication -> { + buf.putInt(1) + FfiConverterString.write(value.v1, buf) + Unit + } + is ProviderException.ContextLengthExceeded -> { + buf.putInt(2) + FfiConverterString.write(value.v1, buf) + Unit + } + is ProviderException.RateLimitExceeded -> { + buf.putInt(3) + FfiConverterString.write(value.v1, buf) + Unit + } + is ProviderException.ServerException -> { + buf.putInt(4) + FfiConverterString.write(value.v1, buf) + Unit + } + is ProviderException.RequestFailed -> { + buf.putInt(5) + FfiConverterString.write(value.v1, buf) + Unit + } + is ProviderException.ExecutionException -> { + buf.putInt(6) + FfiConverterString.write(value.v1, buf) + Unit + } + is ProviderException.UsageException -> { + buf.putInt(7) + FfiConverterString.write(value.v1, buf) + Unit + } + is ProviderException.ResponseParseException -> { + buf.putInt(8) + FfiConverterString.write(value.v1, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +enum class Role { + USER, + ASSISTANT, + ; + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeRole : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = + try { + Role.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: Role) = 4UL + + override fun write( + value: Role, + buf: ByteBuffer, + ) { + buf.putInt(value.ordinal + 1) + } +} + +enum class ToolApprovalMode { + AUTO, + MANUAL, + SMART, + ; + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeToolApprovalMode : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = + try { + ToolApprovalMode.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: ToolApprovalMode) = 4UL + + override fun write( + value: ToolApprovalMode, + buf: ByteBuffer, + ) { + buf.putInt(value.ordinal + 1) + } +} + +sealed class ToolException : kotlin.Exception() { + class InvalidParameters( + val v1: kotlin.String, + ) : ToolException() { + override val message + get() = "v1=${ v1 }" + } + + class ExecutionException( + val v1: kotlin.String, + ) : ToolException() { + override val message + get() = "v1=${ v1 }" + } + + class SchemaException( + val v1: kotlin.String, + ) : ToolException() { + override val message + get() = "v1=${ v1 }" + } + + class NotFound( + val v1: kotlin.String, + ) : ToolException() { + override val message + get() = "v1=${ v1 }" + } + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): ToolException = FfiConverterTypeToolError.lift(error_buf) + } +} + +/** + * @suppress + */ +public object FfiConverterTypeToolError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ToolException = + when (buf.getInt()) { + 1 -> + ToolException.InvalidParameters( + FfiConverterString.read(buf), + ) + 2 -> + ToolException.ExecutionException( + FfiConverterString.read(buf), + ) + 3 -> + ToolException.SchemaException( + FfiConverterString.read(buf), + ) + 4 -> + ToolException.NotFound( + FfiConverterString.read(buf), + ) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + + override fun allocationSize(value: ToolException): ULong = + when (value) { + is ToolException.InvalidParameters -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ToolException.ExecutionException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ToolException.SchemaException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + is ToolException.NotFound -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.v1) + ) + } + + override fun write( + value: ToolException, + buf: ByteBuffer, + ) { + when (value) { + is ToolException.InvalidParameters -> { + buf.putInt(1) + FfiConverterString.write(value.v1, buf) + Unit + } + is ToolException.ExecutionException -> { + buf.putInt(2) + FfiConverterString.write(value.v1, buf) + Unit + } + is ToolException.SchemaException -> { + buf.putInt(3) + FfiConverterString.write(value.v1, buf) + Unit + } + is ToolException.NotFound -> { + buf.putInt(4) + FfiConverterString.write(value.v1, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +/** + * @suppress + */ +public object FfiConverterOptionalUInt : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.UInt? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterUInt.read(buf) + } + + override fun allocationSize(value: kotlin.UInt?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterUInt.allocationSize(value) + } + } + + override fun write( + value: kotlin.UInt?, + buf: ByteBuffer, + ) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterUInt.write(value, buf) + } + } +} + +/** + * @suppress + */ +public object FfiConverterOptionalInt : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.Int? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterInt.read(buf) + } + + override fun allocationSize(value: kotlin.Int?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterInt.allocationSize(value) + } + } + + override fun write( + value: kotlin.Int?, + buf: ByteBuffer, + ) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterInt.write(value, buf) + } + } +} + +/** + * @suppress + */ +public object FfiConverterOptionalFloat : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.Float? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterFloat.read(buf) + } + + override fun allocationSize(value: kotlin.Float?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterFloat.allocationSize(value) + } + } + + override fun write( + value: kotlin.Float?, + buf: ByteBuffer, + ) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterFloat.write(value, buf) + } + } +} + +/** + * @suppress + */ +public object FfiConverterOptionalDouble : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.Double? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterDouble.read(buf) + } + + override fun allocationSize(value: kotlin.Double?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterDouble.allocationSize(value) + } + } + + override fun write( + value: kotlin.Double?, + buf: ByteBuffer, + ) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterDouble.write(value, buf) + } + } +} + +/** + * @suppress + */ +public object FfiConverterOptionalString : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.String? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterString.read(buf) + } + + override fun allocationSize(value: kotlin.String?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterString.allocationSize(value) + } + } + + override fun write( + value: kotlin.String?, + buf: ByteBuffer, + ) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterString.write(value, buf) + } + } +} + +/** + * @suppress + */ +public object FfiConverterSequenceTypeExtensionConfig : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeExtensionConfig.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeExtensionConfig.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeExtensionConfig.write(it, buf) + } + } +} + +/** + * @suppress + */ +public object FfiConverterSequenceTypeMessage : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeMessage.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeMessage.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeMessage.write(it, buf) + } + } +} + +/** + * @suppress + */ +public object FfiConverterSequenceTypeToolConfig : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeToolConfig.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeToolConfig.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeToolConfig.write(it, buf) + } + } +} + +/** + * @suppress + */ +public object FfiConverterSequenceTypeMessageContent : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeMessageContent.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeMessageContent.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeMessageContent.write(it, buf) + } + } +} + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias CompletionRequest = kotlin.String +public typealias FfiConverterTypeCompletionRequest = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias Contents = List +public typealias FfiConverterTypeContents = FfiConverterSequenceTypeMessageContent + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias ToolRequestToolCall = kotlin.String +public typealias FfiConverterTypeToolRequestToolCall = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias ToolResponseToolResult = kotlin.String +public typealias FfiConverterTypeToolResponseToolResult = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias Value = kotlin.String +public typealias FfiConverterTypeValue = FfiConverterString + +/** + * Public API for the Goose LLM completion function + */ +@Throws(CompletionException::class) +@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") +suspend fun `completion`(`req`: CompletionRequest): CompletionResponse = + uniffiRustCallAsync( + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_completion(FfiConverterTypeCompletionRequest.lower(`req`)), + { future, callback, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_free_rust_buffer(future) }, + // lift function + { FfiConverterTypeCompletionResponse.lift(it) }, + // Error FFI converter + CompletionException.ErrorHandler, + ) + +fun `createCompletionRequest`( + `providerName`: kotlin.String, + `providerConfig`: Value, + `modelConfig`: ModelConfig, + `systemPreamble`: kotlin.String, + `messages`: List, + `extensions`: List, +): CompletionRequest = + FfiConverterTypeCompletionRequest.lift( + uniffiRustCall { _status -> + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_create_completion_request( + FfiConverterString.lower(`providerName`), + FfiConverterTypeValue.lower(`providerConfig`), + FfiConverterTypeModelConfig.lower(`modelConfig`), + FfiConverterString.lower(`systemPreamble`), + FfiConverterSequenceTypeMessage.lower(`messages`), + FfiConverterSequenceTypeExtensionConfig.lower(`extensions`), + _status, + ) + }, + ) + +fun `createToolConfig`( + `name`: kotlin.String, + `description`: kotlin.String, + `inputSchema`: Value, + `approvalMode`: ToolApprovalMode, +): ToolConfig = + FfiConverterTypeToolConfig.lift( + uniffiRustCall { _status -> + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_create_tool_config( + FfiConverterString.lower(`name`), + FfiConverterString.lower(`description`), + FfiConverterTypeValue.lower(`inputSchema`), + FfiConverterTypeToolApprovalMode.lower(`approvalMode`), + _status, + ) + }, + ) + +/** + * Generates a short (≤4 words) session name + */ +@Throws(ProviderException::class) +@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") +suspend fun `generateSessionName`( + `providerName`: kotlin.String, + `providerConfig`: Value, + `messages`: List, +): kotlin.String = + uniffiRustCallAsync( + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_generate_session_name( + FfiConverterString.lower(`providerName`), + FfiConverterTypeValue.lower(`providerConfig`), + FfiConverterSequenceTypeMessage.lower(`messages`), + ), + { future, callback, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_free_rust_buffer(future) }, + // lift function + { FfiConverterString.lift(it) }, + // Error FFI converter + ProviderException.ErrorHandler, + ) + +/** + * Generates a structured output based on the provided schema, + * system prompt and user messages. + */ +@Throws(ProviderException::class) +@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") +suspend fun `generateStructuredOutputs`( + `providerName`: kotlin.String, + `providerConfig`: Value, + `systemPrompt`: kotlin.String, + `messages`: List, + `schema`: Value, +): ProviderExtractResponse = + uniffiRustCallAsync( + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_generate_structured_outputs( + FfiConverterString.lower(`providerName`), + FfiConverterTypeValue.lower(`providerConfig`), + FfiConverterString.lower(`systemPrompt`), + FfiConverterSequenceTypeMessage.lower(`messages`), + FfiConverterTypeValue.lower(`schema`), + ), + { future, callback, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_free_rust_buffer(future) }, + // lift function + { FfiConverterTypeProviderExtractResponse.lift(it) }, + // Error FFI converter + ProviderException.ErrorHandler, + ) + +/** + * Generates a tooltip summarizing the last two messages in the session, + * including any tool calls or results. + */ +@Throws(ProviderException::class) +@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") +suspend fun `generateTooltip`( + `providerName`: kotlin.String, + `providerConfig`: Value, + `messages`: List, +): kotlin.String = + uniffiRustCallAsync( + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_generate_tooltip( + FfiConverterString.lower(`providerName`), + FfiConverterTypeValue.lower(`providerConfig`), + FfiConverterSequenceTypeMessage.lower(`messages`), + ), + { future, callback, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_free_rust_buffer(future) }, + // lift function + { FfiConverterString.lift(it) }, + // Error FFI converter + ProviderException.ErrorHandler, + ) + +fun `printMessages`(`messages`: List) = + uniffiRustCall { _status -> + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_print_messages( + FfiConverterSequenceTypeMessage.lower(`messages`), + _status, + ) + } diff --git a/bindings/python/goose_llm.py b/bindings/python/goose_llm.py new file mode 100644 index 00000000..6fe87ab7 --- /dev/null +++ b/bindings/python/goose_llm.py @@ -0,0 +1,3001 @@ + + +# This file was autogenerated by some hot garbage in the `uniffi` crate. +# Trust me, you don't want to mess with it! + +# Common helper code. +# +# Ideally this would live in a separate .py file where it can be unittested etc +# in isolation, and perhaps even published as a re-useable package. +# +# However, it's important that the details of how this helper code works (e.g. the +# way that different builtin types are passed across the FFI) exactly match what's +# expected by the rust code on the other side of the interface. In practice right +# now that means coming from the exact some version of `uniffi` that was used to +# compile the rust component. The easiest way to ensure this is to bundle the Python +# helpers directly inline like we're doing here. + +from __future__ import annotations +import os +import sys +import ctypes +import enum +import struct +import contextlib +import datetime +import threading +import itertools +import traceback +import typing +import asyncio +import platform + +# Used for default argument values +_DEFAULT = object() # type: typing.Any + + +class _UniffiRustBuffer(ctypes.Structure): + _fields_ = [ + ("capacity", ctypes.c_uint64), + ("len", ctypes.c_uint64), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + @staticmethod + def default(): + return _UniffiRustBuffer(0, 0, None) + + @staticmethod + def alloc(size): + return _uniffi_rust_call(_UniffiLib.ffi_goose_llm_rustbuffer_alloc, size) + + @staticmethod + def reserve(rbuf, additional): + return _uniffi_rust_call(_UniffiLib.ffi_goose_llm_rustbuffer_reserve, rbuf, additional) + + def free(self): + return _uniffi_rust_call(_UniffiLib.ffi_goose_llm_rustbuffer_free, self) + + def __str__(self): + return "_UniffiRustBuffer(capacity={}, len={}, data={})".format( + self.capacity, + self.len, + self.data[0:self.len] + ) + + @contextlib.contextmanager + def alloc_with_builder(*args): + """Context-manger to allocate a buffer using a _UniffiRustBufferBuilder. + + The allocated buffer will be automatically freed if an error occurs, ensuring that + we don't accidentally leak it. + """ + builder = _UniffiRustBufferBuilder() + try: + yield builder + except: + builder.discard() + raise + + @contextlib.contextmanager + def consume_with_stream(self): + """Context-manager to consume a buffer using a _UniffiRustBufferStream. + + The _UniffiRustBuffer will be freed once the context-manager exits, ensuring that we don't + leak it even if an error occurs. + """ + try: + s = _UniffiRustBufferStream.from_rust_buffer(self) + yield s + if s.remaining() != 0: + raise RuntimeError("junk data left in buffer at end of consume_with_stream") + finally: + self.free() + + @contextlib.contextmanager + def read_with_stream(self): + """Context-manager to read a buffer using a _UniffiRustBufferStream. + + This is like consume_with_stream, but doesn't free the buffer afterwards. + It should only be used with borrowed `_UniffiRustBuffer` data. + """ + s = _UniffiRustBufferStream.from_rust_buffer(self) + yield s + if s.remaining() != 0: + raise RuntimeError("junk data left in buffer at end of read_with_stream") + +class _UniffiForeignBytes(ctypes.Structure): + _fields_ = [ + ("len", ctypes.c_int32), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + def __str__(self): + return "_UniffiForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len]) + + +class _UniffiRustBufferStream: + """ + Helper for structured reading of bytes from a _UniffiRustBuffer + """ + + def __init__(self, data, len): + self.data = data + self.len = len + self.offset = 0 + + @classmethod + def from_rust_buffer(cls, buf): + return cls(buf.data, buf.len) + + def remaining(self): + return self.len - self.offset + + def _unpack_from(self, size, format): + if self.offset + size > self.len: + raise InternalError("read past end of rust buffer") + value = struct.unpack(format, self.data[self.offset:self.offset+size])[0] + self.offset += size + return value + + def read(self, size): + if self.offset + size > self.len: + raise InternalError("read past end of rust buffer") + data = self.data[self.offset:self.offset+size] + self.offset += size + return data + + def read_i8(self): + return self._unpack_from(1, ">b") + + def read_u8(self): + return self._unpack_from(1, ">B") + + def read_i16(self): + return self._unpack_from(2, ">h") + + def read_u16(self): + return self._unpack_from(2, ">H") + + def read_i32(self): + return self._unpack_from(4, ">i") + + def read_u32(self): + return self._unpack_from(4, ">I") + + def read_i64(self): + return self._unpack_from(8, ">q") + + def read_u64(self): + return self._unpack_from(8, ">Q") + + def read_float(self): + v = self._unpack_from(4, ">f") + return v + + def read_double(self): + return self._unpack_from(8, ">d") + +class _UniffiRustBufferBuilder: + """ + Helper for structured writing of bytes into a _UniffiRustBuffer. + """ + + def __init__(self): + self.rbuf = _UniffiRustBuffer.alloc(16) + self.rbuf.len = 0 + + def finalize(self): + rbuf = self.rbuf + self.rbuf = None + return rbuf + + def discard(self): + if self.rbuf is not None: + rbuf = self.finalize() + rbuf.free() + + @contextlib.contextmanager + def _reserve(self, num_bytes): + if self.rbuf.len + num_bytes > self.rbuf.capacity: + self.rbuf = _UniffiRustBuffer.reserve(self.rbuf, num_bytes) + yield None + self.rbuf.len += num_bytes + + def _pack_into(self, size, format, value): + with self._reserve(size): + # XXX TODO: I feel like I should be able to use `struct.pack_into` here but can't figure it out. + for i, byte in enumerate(struct.pack(format, value)): + self.rbuf.data[self.rbuf.len + i] = byte + + def write(self, value): + with self._reserve(len(value)): + for i, byte in enumerate(value): + self.rbuf.data[self.rbuf.len + i] = byte + + def write_i8(self, v): + self._pack_into(1, ">b", v) + + def write_u8(self, v): + self._pack_into(1, ">B", v) + + def write_i16(self, v): + self._pack_into(2, ">h", v) + + def write_u16(self, v): + self._pack_into(2, ">H", v) + + def write_i32(self, v): + self._pack_into(4, ">i", v) + + def write_u32(self, v): + self._pack_into(4, ">I", v) + + def write_i64(self, v): + self._pack_into(8, ">q", v) + + def write_u64(self, v): + self._pack_into(8, ">Q", v) + + def write_float(self, v): + self._pack_into(4, ">f", v) + + def write_double(self, v): + self._pack_into(8, ">d", v) + + def write_c_size_t(self, v): + self._pack_into(ctypes.sizeof(ctypes.c_size_t) , "@N", v) +# A handful of classes and functions to support the generated data structures. +# This would be a good candidate for isolating in its own ffi-support lib. + +class InternalError(Exception): + pass + +class _UniffiRustCallStatus(ctypes.Structure): + """ + Error runtime. + """ + _fields_ = [ + ("code", ctypes.c_int8), + ("error_buf", _UniffiRustBuffer), + ] + + # These match the values from the uniffi::rustcalls module + CALL_SUCCESS = 0 + CALL_ERROR = 1 + CALL_UNEXPECTED_ERROR = 2 + + @staticmethod + def default(): + return _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer.default()) + + def __str__(self): + if self.code == _UniffiRustCallStatus.CALL_SUCCESS: + return "_UniffiRustCallStatus(CALL_SUCCESS)" + elif self.code == _UniffiRustCallStatus.CALL_ERROR: + return "_UniffiRustCallStatus(CALL_ERROR)" + elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)" + else: + return "_UniffiRustCallStatus()" + +def _uniffi_rust_call(fn, *args): + # Call a rust function + return _uniffi_rust_call_with_error(None, fn, *args) + +def _uniffi_rust_call_with_error(error_ffi_converter, fn, *args): + # Call a rust function and handle any errors + # + # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code. + # error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result. + call_status = _UniffiRustCallStatus.default() + + args_with_error = args + (ctypes.byref(call_status),) + result = fn(*args_with_error) + _uniffi_check_call_status(error_ffi_converter, call_status) + return result + +def _uniffi_check_call_status(error_ffi_converter, call_status): + if call_status.code == _UniffiRustCallStatus.CALL_SUCCESS: + pass + elif call_status.code == _UniffiRustCallStatus.CALL_ERROR: + if error_ffi_converter is None: + call_status.error_buf.free() + raise InternalError("_uniffi_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None") + else: + raise error_ffi_converter.lift(call_status.error_buf) + elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + # When the rust code sees a panic, it tries to construct a _UniffiRustBuffer + # with the message. But if that code panics, then it just sends back + # an empty buffer. + if call_status.error_buf.len > 0: + msg = _UniffiConverterString.lift(call_status.error_buf) + else: + msg = "Unknown rust panic" + raise InternalError(msg) + else: + raise InternalError("Invalid _UniffiRustCallStatus code: {}".format( + call_status.code)) + +def _uniffi_trait_interface_call(call_status, make_call, write_return_value): + try: + return write_return_value(make_call()) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = _UniffiConverterString.lower(repr(e)) + +def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error): + try: + try: + return write_return_value(make_call()) + except error_type as e: + call_status.code = _UniffiRustCallStatus.CALL_ERROR + call_status.error_buf = lower_error(e) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = _UniffiConverterString.lower(repr(e)) +class _UniffiHandleMap: + """ + A map where inserting, getting and removing data is synchronized with a lock. + """ + + def __init__(self): + # type Handle = int + self._map = {} # type: Dict[Handle, Any] + self._lock = threading.Lock() + self._counter = itertools.count() + + def insert(self, obj): + with self._lock: + handle = next(self._counter) + self._map[handle] = obj + return handle + + def get(self, handle): + try: + with self._lock: + return self._map[handle] + except KeyError: + raise InternalError("_UniffiHandleMap.get: Invalid handle") + + def remove(self, handle): + try: + with self._lock: + return self._map.pop(handle) + except KeyError: + raise InternalError("_UniffiHandleMap.remove: Invalid handle") + + def __len__(self): + return len(self._map) +# Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI. +class _UniffiConverterPrimitive: + @classmethod + def lift(cls, value): + return value + + @classmethod + def lower(cls, value): + return value + +class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): + @classmethod + def check_lower(cls, value): + try: + value = value.__index__() + except Exception: + raise TypeError("'{}' object cannot be interpreted as an integer".format(type(value).__name__)) + if not isinstance(value, int): + raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__)) + if not cls.VALUE_MIN <= value < cls.VALUE_MAX: + raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX)) + +class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive): + @classmethod + def check_lower(cls, value): + try: + value = value.__float__() + except Exception: + raise TypeError("must be real number, not {}".format(type(value).__name__)) + if not isinstance(value, float): + raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__)) + +# Helper class for wrapper types that will always go through a _UniffiRustBuffer. +# Classes should inherit from this and implement the `read` and `write` static methods. +class _UniffiConverterRustBuffer: + @classmethod + def lift(cls, rbuf): + with rbuf.consume_with_stream() as stream: + return cls.read(stream) + + @classmethod + def lower(cls, value): + with _UniffiRustBuffer.alloc_with_builder() as builder: + cls.write(value, builder) + return builder.finalize() + +# Contains loading, initialization code, and the FFI Function declarations. +# Define some ctypes FFI types that we use in the library + +""" +Function pointer for a Rust task, which a callback function that takes a opaque pointer +""" +_UNIFFI_RUST_TASK = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int8) + +def _uniffi_future_callback_t(return_type): + """ + Factory function to create callback function types for async functions + """ + return ctypes.CFUNCTYPE(None, ctypes.c_uint64, return_type, _UniffiRustCallStatus) + +def _uniffi_load_indirect(): + """ + This is how we find and load the dynamic library provided by the component. + For now we just look it up by name. + """ + if sys.platform == "darwin": + libname = "lib{}.dylib" + elif sys.platform.startswith("win"): + # As of python3.8, ctypes does not seem to search $PATH when loading DLLs. + # We could use `os.add_dll_directory` to configure the search path, but + # it doesn't feel right to mess with application-wide settings. Let's + # assume that the `.dll` is next to the `.py` file and load by full path. + libname = os.path.join( + os.path.dirname(__file__), + "{}.dll", + ) + else: + # Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos + libname = "lib{}.so" + + libname = libname.format("goose_llm") + path = os.path.join(os.path.dirname(__file__), libname) + lib = ctypes.cdll.LoadLibrary(path) + return lib + +def _uniffi_check_contract_api_version(lib): + # Get the bindings contract version from our ComponentInterface + bindings_contract_version = 29 + # Get the scaffolding contract version by calling the into the dylib + scaffolding_contract_version = lib.ffi_goose_llm_uniffi_contract_version() + if bindings_contract_version != scaffolding_contract_version: + raise InternalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + +def _uniffi_check_api_checksums(lib): + if lib.uniffi_goose_llm_checksum_func_completion() != 47457: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_create_completion_request() != 51008: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_create_tool_config() != 22809: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_generate_session_name() != 9810: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_generate_tooltip() != 15466: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_print_messages() != 30278: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + +# A ctypes library to expose the extern-C FFI definitions. +# This is an implementation detail which will be called internally by the public API. + +_UniffiLib = _uniffi_load_indirect() +_UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK = ctypes.CFUNCTYPE(None,ctypes.c_uint64,ctypes.c_int8, +) +_UNIFFI_FOREIGN_FUTURE_FREE = ctypes.CFUNCTYPE(None,ctypes.c_uint64, +) +_UNIFFI_CALLBACK_INTERFACE_FREE = ctypes.CFUNCTYPE(None,ctypes.c_uint64, +) +class _UniffiForeignFuture(ctypes.Structure): + _fields_ = [ + ("handle", ctypes.c_uint64), + ("free", _UNIFFI_FOREIGN_FUTURE_FREE), + ] +class _UniffiForeignFutureStructU8(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint8), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_U8 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructU8, +) +class _UniffiForeignFutureStructI8(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int8), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_I8 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructI8, +) +class _UniffiForeignFutureStructU16(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint16), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_U16 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructU16, +) +class _UniffiForeignFutureStructI16(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int16), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_I16 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructI16, +) +class _UniffiForeignFutureStructU32(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint32), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_U32 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructU32, +) +class _UniffiForeignFutureStructI32(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int32), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_I32 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructI32, +) +class _UniffiForeignFutureStructU64(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint64), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_U64 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructU64, +) +class _UniffiForeignFutureStructI64(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int64), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_I64 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructI64, +) +class _UniffiForeignFutureStructF32(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_float), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_F32 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructF32, +) +class _UniffiForeignFutureStructF64(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_double), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_F64 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructF64, +) +class _UniffiForeignFutureStructPointer(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_void_p), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_POINTER = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructPointer, +) +class _UniffiForeignFutureStructRustBuffer(ctypes.Structure): + _fields_ = [ + ("return_value", _UniffiRustBuffer), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructRustBuffer, +) +class _UniffiForeignFutureStructVoid(ctypes.Structure): + _fields_ = [ + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_VOID = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructVoid, +) +_UniffiLib.uniffi_goose_llm_fn_func_completion.argtypes = ( + _UniffiRustBuffer, +) +_UniffiLib.uniffi_goose_llm_fn_func_completion.restype = ctypes.c_uint64 +_UniffiLib.uniffi_goose_llm_fn_func_create_completion_request.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_goose_llm_fn_func_create_completion_request.restype = _UniffiRustBuffer +_UniffiLib.uniffi_goose_llm_fn_func_create_tool_config.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_goose_llm_fn_func_create_tool_config.restype = _UniffiRustBuffer +_UniffiLib.uniffi_goose_llm_fn_func_generate_session_name.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_goose_llm_fn_func_generate_session_name.restype = ctypes.c_uint64 +_UniffiLib.uniffi_goose_llm_fn_func_generate_tooltip.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_goose_llm_fn_func_generate_tooltip.restype = ctypes.c_uint64 +_UniffiLib.uniffi_goose_llm_fn_func_print_messages.argtypes = ( + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_goose_llm_fn_func_print_messages.restype = None +_UniffiLib.ffi_goose_llm_rustbuffer_alloc.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rustbuffer_alloc.restype = _UniffiRustBuffer +_UniffiLib.ffi_goose_llm_rustbuffer_from_bytes.argtypes = ( + _UniffiForeignBytes, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rustbuffer_from_bytes.restype = _UniffiRustBuffer +_UniffiLib.ffi_goose_llm_rustbuffer_free.argtypes = ( + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rustbuffer_free.restype = None +_UniffiLib.ffi_goose_llm_rustbuffer_reserve.argtypes = ( + _UniffiRustBuffer, + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rustbuffer_reserve.restype = _UniffiRustBuffer +_UniffiLib.ffi_goose_llm_rust_future_poll_u8.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_u8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_u8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_u8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_u8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_u8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_u8.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_u8.restype = ctypes.c_uint8 +_UniffiLib.ffi_goose_llm_rust_future_poll_i8.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_i8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_i8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_i8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_i8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_i8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_i8.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_i8.restype = ctypes.c_int8 +_UniffiLib.ffi_goose_llm_rust_future_poll_u16.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_u16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_u16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_u16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_u16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_u16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_u16.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_u16.restype = ctypes.c_uint16 +_UniffiLib.ffi_goose_llm_rust_future_poll_i16.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_i16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_i16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_i16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_i16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_i16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_i16.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_i16.restype = ctypes.c_int16 +_UniffiLib.ffi_goose_llm_rust_future_poll_u32.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_u32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_u32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_u32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_u32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_u32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_u32.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_u32.restype = ctypes.c_uint32 +_UniffiLib.ffi_goose_llm_rust_future_poll_i32.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_i32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_i32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_i32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_i32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_i32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_i32.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_i32.restype = ctypes.c_int32 +_UniffiLib.ffi_goose_llm_rust_future_poll_u64.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_u64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_u64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_u64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_u64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_u64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_u64.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_u64.restype = ctypes.c_uint64 +_UniffiLib.ffi_goose_llm_rust_future_poll_i64.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_i64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_i64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_i64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_i64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_i64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_i64.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_i64.restype = ctypes.c_int64 +_UniffiLib.ffi_goose_llm_rust_future_poll_f32.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_f32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_f32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_f32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_f32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_f32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_f32.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_f32.restype = ctypes.c_float +_UniffiLib.ffi_goose_llm_rust_future_poll_f64.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_f64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_f64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_f64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_f64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_f64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_f64.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_f64.restype = ctypes.c_double +_UniffiLib.ffi_goose_llm_rust_future_poll_pointer.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_pointer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_pointer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_pointer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_pointer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_pointer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_pointer.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_pointer.restype = ctypes.c_void_p +_UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_rust_buffer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_rust_buffer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer.restype = _UniffiRustBuffer +_UniffiLib.ffi_goose_llm_rust_future_poll_void.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_void.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_void.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_void.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_void.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_void.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_void.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_void.restype = None +_UniffiLib.uniffi_goose_llm_checksum_func_completion.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_completion.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_create_completion_request.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_create_completion_request.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_create_tool_config.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_create_tool_config.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_generate_session_name.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_generate_session_name.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_generate_tooltip.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_generate_tooltip.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_print_messages.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_print_messages.restype = ctypes.c_uint16 +_UniffiLib.ffi_goose_llm_uniffi_contract_version.argtypes = ( +) +_UniffiLib.ffi_goose_llm_uniffi_contract_version.restype = ctypes.c_uint32 + +_uniffi_check_contract_api_version(_UniffiLib) +# _uniffi_check_api_checksums(_UniffiLib) + +# Public interface members begin here. + + +class _UniffiConverterUInt32(_UniffiConverterPrimitiveInt): + CLASS_NAME = "u32" + VALUE_MIN = 0 + VALUE_MAX = 2**32 + + @staticmethod + def read(buf): + return buf.read_u32() + + @staticmethod + def write(value, buf): + buf.write_u32(value) + +class _UniffiConverterInt32(_UniffiConverterPrimitiveInt): + CLASS_NAME = "i32" + VALUE_MIN = -2**31 + VALUE_MAX = 2**31 + + @staticmethod + def read(buf): + return buf.read_i32() + + @staticmethod + def write(value, buf): + buf.write_i32(value) + +class _UniffiConverterInt64(_UniffiConverterPrimitiveInt): + CLASS_NAME = "i64" + VALUE_MIN = -2**63 + VALUE_MAX = 2**63 + + @staticmethod + def read(buf): + return buf.read_i64() + + @staticmethod + def write(value, buf): + buf.write_i64(value) + +class _UniffiConverterFloat(_UniffiConverterPrimitiveFloat): + @staticmethod + def read(buf): + return buf.read_float() + + @staticmethod + def write(value, buf): + buf.write_float(value) + +class _UniffiConverterDouble(_UniffiConverterPrimitiveFloat): + @staticmethod + def read(buf): + return buf.read_double() + + @staticmethod + def write(value, buf): + buf.write_double(value) + +class _UniffiConverterString: + @staticmethod + def check_lower(value): + if not isinstance(value, str): + raise TypeError("argument must be str, not {}".format(type(value).__name__)) + return value + + @staticmethod + def read(buf): + size = buf.read_i32() + if size < 0: + raise InternalError("Unexpected negative string length") + utf8_bytes = buf.read(size) + return utf8_bytes.decode("utf-8") + + @staticmethod + def write(value, buf): + utf8_bytes = value.encode("utf-8") + buf.write_i32(len(utf8_bytes)) + buf.write(utf8_bytes) + + @staticmethod + def lift(buf): + with buf.consume_with_stream() as stream: + return stream.read(stream.remaining()).decode("utf-8") + + @staticmethod + def lower(value): + with _UniffiRustBuffer.alloc_with_builder() as builder: + builder.write(value.encode("utf-8")) + return builder.finalize() + + +class CompletionResponse: + message: "Message" + model: "str" + usage: "Usage" + runtime_metrics: "RuntimeMetrics" + def __init__(self, *, message: "Message", model: "str", usage: "Usage", runtime_metrics: "RuntimeMetrics"): + self.message = message + self.model = model + self.usage = usage + self.runtime_metrics = runtime_metrics + + def __str__(self): + return "CompletionResponse(message={}, model={}, usage={}, runtime_metrics={})".format(self.message, self.model, self.usage, self.runtime_metrics) + + def __eq__(self, other): + if self.message != other.message: + return False + if self.model != other.model: + return False + if self.usage != other.usage: + return False + if self.runtime_metrics != other.runtime_metrics: + return False + return True + +class _UniffiConverterTypeCompletionResponse(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return CompletionResponse( + message=_UniffiConverterTypeMessage.read(buf), + model=_UniffiConverterString.read(buf), + usage=_UniffiConverterTypeUsage.read(buf), + runtime_metrics=_UniffiConverterTypeRuntimeMetrics.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterTypeMessage.check_lower(value.message) + _UniffiConverterString.check_lower(value.model) + _UniffiConverterTypeUsage.check_lower(value.usage) + _UniffiConverterTypeRuntimeMetrics.check_lower(value.runtime_metrics) + + @staticmethod + def write(value, buf): + _UniffiConverterTypeMessage.write(value.message, buf) + _UniffiConverterString.write(value.model, buf) + _UniffiConverterTypeUsage.write(value.usage, buf) + _UniffiConverterTypeRuntimeMetrics.write(value.runtime_metrics, buf) + + +class ExtensionConfig: + name: "str" + instructions: "typing.Optional[str]" + tools: "typing.List[ToolConfig]" + def __init__(self, *, name: "str", instructions: "typing.Optional[str]", tools: "typing.List[ToolConfig]"): + self.name = name + self.instructions = instructions + self.tools = tools + + def __str__(self): + return "ExtensionConfig(name={}, instructions={}, tools={})".format(self.name, self.instructions, self.tools) + + def __eq__(self, other): + if self.name != other.name: + return False + if self.instructions != other.instructions: + return False + if self.tools != other.tools: + return False + return True + +class _UniffiConverterTypeExtensionConfig(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ExtensionConfig( + name=_UniffiConverterString.read(buf), + instructions=_UniffiConverterOptionalString.read(buf), + tools=_UniffiConverterSequenceTypeToolConfig.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.name) + _UniffiConverterOptionalString.check_lower(value.instructions) + _UniffiConverterSequenceTypeToolConfig.check_lower(value.tools) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.name, buf) + _UniffiConverterOptionalString.write(value.instructions, buf) + _UniffiConverterSequenceTypeToolConfig.write(value.tools, buf) + + +class ImageContent: + data: "str" + mime_type: "str" + def __init__(self, *, data: "str", mime_type: "str"): + self.data = data + self.mime_type = mime_type + + def __str__(self): + return "ImageContent(data={}, mime_type={})".format(self.data, self.mime_type) + + def __eq__(self, other): + if self.data != other.data: + return False + if self.mime_type != other.mime_type: + return False + return True + +class _UniffiConverterTypeImageContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ImageContent( + data=_UniffiConverterString.read(buf), + mime_type=_UniffiConverterString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.data) + _UniffiConverterString.check_lower(value.mime_type) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.data, buf) + _UniffiConverterString.write(value.mime_type, buf) + + +class Message: + """ + A message to or from an LLM + """ + + role: "Role" + created: "int" + content: "Contents" + def __init__(self, *, role: "Role", created: "int", content: "Contents"): + self.role = role + self.created = created + self.content = content + + def __str__(self): + return "Message(role={}, created={}, content={})".format(self.role, self.created, self.content) + + def __eq__(self, other): + if self.role != other.role: + return False + if self.created != other.created: + return False + if self.content != other.content: + return False + return True + +class _UniffiConverterTypeMessage(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return Message( + role=_UniffiConverterTypeRole.read(buf), + created=_UniffiConverterInt64.read(buf), + content=_UniffiConverterTypeContents.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterTypeRole.check_lower(value.role) + _UniffiConverterInt64.check_lower(value.created) + _UniffiConverterTypeContents.check_lower(value.content) + + @staticmethod + def write(value, buf): + _UniffiConverterTypeRole.write(value.role, buf) + _UniffiConverterInt64.write(value.created, buf) + _UniffiConverterTypeContents.write(value.content, buf) + + +class ModelConfig: + """ + Configuration for model-specific settings and limits + """ + + model_name: "str" + """ + The name of the model to use + """ + + context_limit: "typing.Optional[int]" + """ + Optional explicit context limit that overrides any defaults + """ + + temperature: "typing.Optional[float]" + """ + Optional temperature setting (0.0 - 1.0) + """ + + max_tokens: "typing.Optional[int]" + """ + Optional maximum tokens to generate + """ + + def __init__(self, *, model_name: "str", context_limit: "typing.Optional[int]", temperature: "typing.Optional[float]", max_tokens: "typing.Optional[int]"): + self.model_name = model_name + self.context_limit = context_limit + self.temperature = temperature + self.max_tokens = max_tokens + + def __str__(self): + return "ModelConfig(model_name={}, context_limit={}, temperature={}, max_tokens={})".format(self.model_name, self.context_limit, self.temperature, self.max_tokens) + + def __eq__(self, other): + if self.model_name != other.model_name: + return False + if self.context_limit != other.context_limit: + return False + if self.temperature != other.temperature: + return False + if self.max_tokens != other.max_tokens: + return False + return True + +class _UniffiConverterTypeModelConfig(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ModelConfig( + model_name=_UniffiConverterString.read(buf), + context_limit=_UniffiConverterOptionalUInt32.read(buf), + temperature=_UniffiConverterOptionalFloat.read(buf), + max_tokens=_UniffiConverterOptionalInt32.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.model_name) + _UniffiConverterOptionalUInt32.check_lower(value.context_limit) + _UniffiConverterOptionalFloat.check_lower(value.temperature) + _UniffiConverterOptionalInt32.check_lower(value.max_tokens) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.model_name, buf) + _UniffiConverterOptionalUInt32.write(value.context_limit, buf) + _UniffiConverterOptionalFloat.write(value.temperature, buf) + _UniffiConverterOptionalInt32.write(value.max_tokens, buf) + + +class ProviderCompleteResponse: + message: "Message" + model: "str" + usage: "Usage" + def __init__(self, *, message: "Message", model: "str", usage: "Usage"): + self.message = message + self.model = model + self.usage = usage + + def __str__(self): + return "ProviderCompleteResponse(message={}, model={}, usage={})".format(self.message, self.model, self.usage) + + def __eq__(self, other): + if self.message != other.message: + return False + if self.model != other.model: + return False + if self.usage != other.usage: + return False + return True + +class _UniffiConverterTypeProviderCompleteResponse(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ProviderCompleteResponse( + message=_UniffiConverterTypeMessage.read(buf), + model=_UniffiConverterString.read(buf), + usage=_UniffiConverterTypeUsage.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterTypeMessage.check_lower(value.message) + _UniffiConverterString.check_lower(value.model) + _UniffiConverterTypeUsage.check_lower(value.usage) + + @staticmethod + def write(value, buf): + _UniffiConverterTypeMessage.write(value.message, buf) + _UniffiConverterString.write(value.model, buf) + _UniffiConverterTypeUsage.write(value.usage, buf) + + +class RedactedThinkingContent: + data: "str" + def __init__(self, *, data: "str"): + self.data = data + + def __str__(self): + return "RedactedThinkingContent(data={})".format(self.data) + + def __eq__(self, other): + if self.data != other.data: + return False + return True + +class _UniffiConverterTypeRedactedThinkingContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return RedactedThinkingContent( + data=_UniffiConverterString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.data) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.data, buf) + + +class RuntimeMetrics: + total_time_sec: "float" + total_time_sec_provider: "float" + tokens_per_second: "typing.Optional[float]" + def __init__(self, *, total_time_sec: "float", total_time_sec_provider: "float", tokens_per_second: "typing.Optional[float]"): + self.total_time_sec = total_time_sec + self.total_time_sec_provider = total_time_sec_provider + self.tokens_per_second = tokens_per_second + + def __str__(self): + return "RuntimeMetrics(total_time_sec={}, total_time_sec_provider={}, tokens_per_second={})".format(self.total_time_sec, self.total_time_sec_provider, self.tokens_per_second) + + def __eq__(self, other): + if self.total_time_sec != other.total_time_sec: + return False + if self.total_time_sec_provider != other.total_time_sec_provider: + return False + if self.tokens_per_second != other.tokens_per_second: + return False + return True + +class _UniffiConverterTypeRuntimeMetrics(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return RuntimeMetrics( + total_time_sec=_UniffiConverterFloat.read(buf), + total_time_sec_provider=_UniffiConverterFloat.read(buf), + tokens_per_second=_UniffiConverterOptionalDouble.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterFloat.check_lower(value.total_time_sec) + _UniffiConverterFloat.check_lower(value.total_time_sec_provider) + _UniffiConverterOptionalDouble.check_lower(value.tokens_per_second) + + @staticmethod + def write(value, buf): + _UniffiConverterFloat.write(value.total_time_sec, buf) + _UniffiConverterFloat.write(value.total_time_sec_provider, buf) + _UniffiConverterOptionalDouble.write(value.tokens_per_second, buf) + + +class TextContent: + text: "str" + def __init__(self, *, text: "str"): + self.text = text + + def __str__(self): + return "TextContent(text={})".format(self.text) + + def __eq__(self, other): + if self.text != other.text: + return False + return True + +class _UniffiConverterTypeTextContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return TextContent( + text=_UniffiConverterString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.text) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.text, buf) + + +class ThinkingContent: + thinking: "str" + signature: "str" + def __init__(self, *, thinking: "str", signature: "str"): + self.thinking = thinking + self.signature = signature + + def __str__(self): + return "ThinkingContent(thinking={}, signature={})".format(self.thinking, self.signature) + + def __eq__(self, other): + if self.thinking != other.thinking: + return False + if self.signature != other.signature: + return False + return True + +class _UniffiConverterTypeThinkingContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ThinkingContent( + thinking=_UniffiConverterString.read(buf), + signature=_UniffiConverterString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.thinking) + _UniffiConverterString.check_lower(value.signature) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.thinking, buf) + _UniffiConverterString.write(value.signature, buf) + + +class ToolRequest: + id: "str" + tool_call: "ToolRequestToolCall" + def __init__(self, *, id: "str", tool_call: "ToolRequestToolCall"): + self.id = id + self.tool_call = tool_call + + def __str__(self): + return "ToolRequest(id={}, tool_call={})".format(self.id, self.tool_call) + + def __eq__(self, other): + if self.id != other.id: + return False + if self.tool_call != other.tool_call: + return False + return True + +class _UniffiConverterTypeToolRequest(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ToolRequest( + id=_UniffiConverterString.read(buf), + tool_call=_UniffiConverterTypeToolRequestToolCall.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.id) + _UniffiConverterTypeToolRequestToolCall.check_lower(value.tool_call) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.id, buf) + _UniffiConverterTypeToolRequestToolCall.write(value.tool_call, buf) + + +class ToolResponse: + id: "str" + tool_result: "ToolResponseToolResult" + def __init__(self, *, id: "str", tool_result: "ToolResponseToolResult"): + self.id = id + self.tool_result = tool_result + + def __str__(self): + return "ToolResponse(id={}, tool_result={})".format(self.id, self.tool_result) + + def __eq__(self, other): + if self.id != other.id: + return False + if self.tool_result != other.tool_result: + return False + return True + +class _UniffiConverterTypeToolResponse(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ToolResponse( + id=_UniffiConverterString.read(buf), + tool_result=_UniffiConverterTypeToolResponseToolResult.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.id) + _UniffiConverterTypeToolResponseToolResult.check_lower(value.tool_result) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.id, buf) + _UniffiConverterTypeToolResponseToolResult.write(value.tool_result, buf) + + +class Usage: + input_tokens: "typing.Optional[int]" + output_tokens: "typing.Optional[int]" + total_tokens: "typing.Optional[int]" + def __init__(self, *, input_tokens: "typing.Optional[int]", output_tokens: "typing.Optional[int]", total_tokens: "typing.Optional[int]"): + self.input_tokens = input_tokens + self.output_tokens = output_tokens + self.total_tokens = total_tokens + + def __str__(self): + return "Usage(input_tokens={}, output_tokens={}, total_tokens={})".format(self.input_tokens, self.output_tokens, self.total_tokens) + + def __eq__(self, other): + if self.input_tokens != other.input_tokens: + return False + if self.output_tokens != other.output_tokens: + return False + if self.total_tokens != other.total_tokens: + return False + return True + +class _UniffiConverterTypeUsage(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return Usage( + input_tokens=_UniffiConverterOptionalInt32.read(buf), + output_tokens=_UniffiConverterOptionalInt32.read(buf), + total_tokens=_UniffiConverterOptionalInt32.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterOptionalInt32.check_lower(value.input_tokens) + _UniffiConverterOptionalInt32.check_lower(value.output_tokens) + _UniffiConverterOptionalInt32.check_lower(value.total_tokens) + + @staticmethod + def write(value, buf): + _UniffiConverterOptionalInt32.write(value.input_tokens, buf) + _UniffiConverterOptionalInt32.write(value.output_tokens, buf) + _UniffiConverterOptionalInt32.write(value.total_tokens, buf) + + +# CompletionError +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class CompletionError(Exception): + pass + +_UniffiTempCompletionError = CompletionError + +class CompletionError: # type: ignore + class UnknownProvider(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.UnknownProvider({})".format(repr(str(self))) + _UniffiTempCompletionError.UnknownProvider = UnknownProvider # type: ignore + class Provider(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.Provider({})".format(repr(str(self))) + _UniffiTempCompletionError.Provider = Provider # type: ignore + class Template(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.Template({})".format(repr(str(self))) + _UniffiTempCompletionError.Template = Template # type: ignore + class Json(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.Json({})".format(repr(str(self))) + _UniffiTempCompletionError.Json = Json # type: ignore + class ToolNotFound(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.ToolNotFound({})".format(repr(str(self))) + _UniffiTempCompletionError.ToolNotFound = ToolNotFound # type: ignore + +CompletionError = _UniffiTempCompletionError # type: ignore +del _UniffiTempCompletionError + + +class _UniffiConverterTypeCompletionError(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return CompletionError.UnknownProvider( + _UniffiConverterString.read(buf), + ) + if variant == 2: + return CompletionError.Provider( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return CompletionError.Template( + _UniffiConverterString.read(buf), + ) + if variant == 4: + return CompletionError.Json( + _UniffiConverterString.read(buf), + ) + if variant == 5: + return CompletionError.ToolNotFound( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if isinstance(value, CompletionError.UnknownProvider): + return + if isinstance(value, CompletionError.Provider): + return + if isinstance(value, CompletionError.Template): + return + if isinstance(value, CompletionError.Json): + return + if isinstance(value, CompletionError.ToolNotFound): + return + + @staticmethod + def write(value, buf): + if isinstance(value, CompletionError.UnknownProvider): + buf.write_i32(1) + if isinstance(value, CompletionError.Provider): + buf.write_i32(2) + if isinstance(value, CompletionError.Template): + buf.write_i32(3) + if isinstance(value, CompletionError.Json): + buf.write_i32(4) + if isinstance(value, CompletionError.ToolNotFound): + buf.write_i32(5) + + + + + +class Content: + def __init__(self): + raise RuntimeError("Content cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class TEXT: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"Content.TEXT{self._values!r}" + + def __eq__(self, other): + if not other.is_TEXT(): + return False + return self._values == other._values + class IMAGE: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"Content.IMAGE{self._values!r}" + + def __eq__(self, other): + if not other.is_IMAGE(): + return False + return self._values == other._values + + + # For each variant, we have `is_NAME` and `is_name` methods for easily checking + # whether an instance is that variant. + def is_TEXT(self) -> bool: + return isinstance(self, Content.TEXT) + def is_text(self) -> bool: + return isinstance(self, Content.TEXT) + def is_IMAGE(self) -> bool: + return isinstance(self, Content.IMAGE) + def is_image(self) -> bool: + return isinstance(self, Content.IMAGE) + + +# Now, a little trick - we make each nested variant class be a subclass of the main +# enum class, so that method calls and instance checks etc will work intuitively. +# We might be able to do this a little more neatly with a metaclass, but this'll do. +Content.TEXT = type("Content.TEXT", (Content.TEXT, Content,), {}) # type: ignore +Content.IMAGE = type("Content.IMAGE", (Content.IMAGE, Content,), {}) # type: ignore + + + + +class _UniffiConverterTypeContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return Content.TEXT( + _UniffiConverterTypeTextContent.read(buf), + ) + if variant == 2: + return Content.IMAGE( + _UniffiConverterTypeImageContent.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value.is_TEXT(): + _UniffiConverterTypeTextContent.check_lower(value._values[0]) + return + if value.is_IMAGE(): + _UniffiConverterTypeImageContent.check_lower(value._values[0]) + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value.is_TEXT(): + buf.write_i32(1) + _UniffiConverterTypeTextContent.write(value._values[0], buf) + if value.is_IMAGE(): + buf.write_i32(2) + _UniffiConverterTypeImageContent.write(value._values[0], buf) + + + + + + + +class MessageContent: + """ + Content passed inside a message, which can be both simple content and tool content + """ + + def __init__(self): + raise RuntimeError("MessageContent cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class TEXT: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.TEXT{self._values!r}" + + def __eq__(self, other): + if not other.is_TEXT(): + return False + return self._values == other._values + class IMAGE: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.IMAGE{self._values!r}" + + def __eq__(self, other): + if not other.is_IMAGE(): + return False + return self._values == other._values + class TOOL_REQ: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.TOOL_REQ{self._values!r}" + + def __eq__(self, other): + if not other.is_TOOL_REQ(): + return False + return self._values == other._values + class TOOL_RESP: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.TOOL_RESP{self._values!r}" + + def __eq__(self, other): + if not other.is_TOOL_RESP(): + return False + return self._values == other._values + class THINKING: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.THINKING{self._values!r}" + + def __eq__(self, other): + if not other.is_THINKING(): + return False + return self._values == other._values + class REDACTED_THINKING: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.REDACTED_THINKING{self._values!r}" + + def __eq__(self, other): + if not other.is_REDACTED_THINKING(): + return False + return self._values == other._values + + + # For each variant, we have `is_NAME` and `is_name` methods for easily checking + # whether an instance is that variant. + def is_TEXT(self) -> bool: + return isinstance(self, MessageContent.TEXT) + def is_text(self) -> bool: + return isinstance(self, MessageContent.TEXT) + def is_IMAGE(self) -> bool: + return isinstance(self, MessageContent.IMAGE) + def is_image(self) -> bool: + return isinstance(self, MessageContent.IMAGE) + def is_TOOL_REQ(self) -> bool: + return isinstance(self, MessageContent.TOOL_REQ) + def is_tool_req(self) -> bool: + return isinstance(self, MessageContent.TOOL_REQ) + def is_TOOL_RESP(self) -> bool: + return isinstance(self, MessageContent.TOOL_RESP) + def is_tool_resp(self) -> bool: + return isinstance(self, MessageContent.TOOL_RESP) + def is_THINKING(self) -> bool: + return isinstance(self, MessageContent.THINKING) + def is_thinking(self) -> bool: + return isinstance(self, MessageContent.THINKING) + def is_REDACTED_THINKING(self) -> bool: + return isinstance(self, MessageContent.REDACTED_THINKING) + def is_redacted_thinking(self) -> bool: + return isinstance(self, MessageContent.REDACTED_THINKING) + + +# Now, a little trick - we make each nested variant class be a subclass of the main +# enum class, so that method calls and instance checks etc will work intuitively. +# We might be able to do this a little more neatly with a metaclass, but this'll do. +MessageContent.TEXT = type("MessageContent.TEXT", (MessageContent.TEXT, MessageContent,), {}) # type: ignore +MessageContent.IMAGE = type("MessageContent.IMAGE", (MessageContent.IMAGE, MessageContent,), {}) # type: ignore +MessageContent.TOOL_REQ = type("MessageContent.TOOL_REQ", (MessageContent.TOOL_REQ, MessageContent,), {}) # type: ignore +MessageContent.TOOL_RESP = type("MessageContent.TOOL_RESP", (MessageContent.TOOL_RESP, MessageContent,), {}) # type: ignore +MessageContent.THINKING = type("MessageContent.THINKING", (MessageContent.THINKING, MessageContent,), {}) # type: ignore +MessageContent.REDACTED_THINKING = type("MessageContent.REDACTED_THINKING", (MessageContent.REDACTED_THINKING, MessageContent,), {}) # type: ignore + + + + +class _UniffiConverterTypeMessageContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return MessageContent.TEXT( + _UniffiConverterTypeTextContent.read(buf), + ) + if variant == 2: + return MessageContent.IMAGE( + _UniffiConverterTypeImageContent.read(buf), + ) + if variant == 3: + return MessageContent.TOOL_REQ( + _UniffiConverterTypeToolRequest.read(buf), + ) + if variant == 4: + return MessageContent.TOOL_RESP( + _UniffiConverterTypeToolResponse.read(buf), + ) + if variant == 5: + return MessageContent.THINKING( + _UniffiConverterTypeThinkingContent.read(buf), + ) + if variant == 6: + return MessageContent.REDACTED_THINKING( + _UniffiConverterTypeRedactedThinkingContent.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value.is_TEXT(): + _UniffiConverterTypeTextContent.check_lower(value._values[0]) + return + if value.is_IMAGE(): + _UniffiConverterTypeImageContent.check_lower(value._values[0]) + return + if value.is_TOOL_REQ(): + _UniffiConverterTypeToolRequest.check_lower(value._values[0]) + return + if value.is_TOOL_RESP(): + _UniffiConverterTypeToolResponse.check_lower(value._values[0]) + return + if value.is_THINKING(): + _UniffiConverterTypeThinkingContent.check_lower(value._values[0]) + return + if value.is_REDACTED_THINKING(): + _UniffiConverterTypeRedactedThinkingContent.check_lower(value._values[0]) + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value.is_TEXT(): + buf.write_i32(1) + _UniffiConverterTypeTextContent.write(value._values[0], buf) + if value.is_IMAGE(): + buf.write_i32(2) + _UniffiConverterTypeImageContent.write(value._values[0], buf) + if value.is_TOOL_REQ(): + buf.write_i32(3) + _UniffiConverterTypeToolRequest.write(value._values[0], buf) + if value.is_TOOL_RESP(): + buf.write_i32(4) + _UniffiConverterTypeToolResponse.write(value._values[0], buf) + if value.is_THINKING(): + buf.write_i32(5) + _UniffiConverterTypeThinkingContent.write(value._values[0], buf) + if value.is_REDACTED_THINKING(): + buf.write_i32(6) + _UniffiConverterTypeRedactedThinkingContent.write(value._values[0], buf) + + + + +# ProviderError +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class ProviderError(Exception): + pass + +_UniffiTempProviderError = ProviderError + +class ProviderError: # type: ignore + class Authentication(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.Authentication({})".format(str(self)) + _UniffiTempProviderError.Authentication = Authentication # type: ignore + class ContextLengthExceeded(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.ContextLengthExceeded({})".format(str(self)) + _UniffiTempProviderError.ContextLengthExceeded = ContextLengthExceeded # type: ignore + class RateLimitExceeded(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.RateLimitExceeded({})".format(str(self)) + _UniffiTempProviderError.RateLimitExceeded = RateLimitExceeded # type: ignore + class ServerError(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.ServerError({})".format(str(self)) + _UniffiTempProviderError.ServerError = ServerError # type: ignore + class RequestFailed(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.RequestFailed({})".format(str(self)) + _UniffiTempProviderError.RequestFailed = RequestFailed # type: ignore + class ExecutionError(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.ExecutionError({})".format(str(self)) + _UniffiTempProviderError.ExecutionError = ExecutionError # type: ignore + class UsageError(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.UsageError({})".format(str(self)) + _UniffiTempProviderError.UsageError = UsageError # type: ignore + class ResponseParseError(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.ResponseParseError({})".format(str(self)) + _UniffiTempProviderError.ResponseParseError = ResponseParseError # type: ignore + +ProviderError = _UniffiTempProviderError # type: ignore +del _UniffiTempProviderError + + +class _UniffiConverterTypeProviderError(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return ProviderError.Authentication( + _UniffiConverterString.read(buf), + ) + if variant == 2: + return ProviderError.ContextLengthExceeded( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return ProviderError.RateLimitExceeded( + _UniffiConverterString.read(buf), + ) + if variant == 4: + return ProviderError.ServerError( + _UniffiConverterString.read(buf), + ) + if variant == 5: + return ProviderError.RequestFailed( + _UniffiConverterString.read(buf), + ) + if variant == 6: + return ProviderError.ExecutionError( + _UniffiConverterString.read(buf), + ) + if variant == 7: + return ProviderError.UsageError( + _UniffiConverterString.read(buf), + ) + if variant == 8: + return ProviderError.ResponseParseError( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if isinstance(value, ProviderError.Authentication): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.ContextLengthExceeded): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.RateLimitExceeded): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.ServerError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.RequestFailed): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.ExecutionError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.UsageError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.ResponseParseError): + _UniffiConverterString.check_lower(value._values[0]) + return + + @staticmethod + def write(value, buf): + if isinstance(value, ProviderError.Authentication): + buf.write_i32(1) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.ContextLengthExceeded): + buf.write_i32(2) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.RateLimitExceeded): + buf.write_i32(3) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.ServerError): + buf.write_i32(4) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.RequestFailed): + buf.write_i32(5) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.ExecutionError): + buf.write_i32(6) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.UsageError): + buf.write_i32(7) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.ResponseParseError): + buf.write_i32(8) + _UniffiConverterString.write(value._values[0], buf) + + + + + +class Role(enum.Enum): + USER = 0 + + ASSISTANT = 1 + + + +class _UniffiConverterTypeRole(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return Role.USER + if variant == 2: + return Role.ASSISTANT + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value == Role.USER: + return + if value == Role.ASSISTANT: + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value == Role.USER: + buf.write_i32(1) + if value == Role.ASSISTANT: + buf.write_i32(2) + + + + + + + +class ToolApprovalMode(enum.Enum): + AUTO = 0 + + MANUAL = 1 + + SMART = 2 + + + +class _UniffiConverterTypeToolApprovalMode(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return ToolApprovalMode.AUTO + if variant == 2: + return ToolApprovalMode.MANUAL + if variant == 3: + return ToolApprovalMode.SMART + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value == ToolApprovalMode.AUTO: + return + if value == ToolApprovalMode.MANUAL: + return + if value == ToolApprovalMode.SMART: + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value == ToolApprovalMode.AUTO: + buf.write_i32(1) + if value == ToolApprovalMode.MANUAL: + buf.write_i32(2) + if value == ToolApprovalMode.SMART: + buf.write_i32(3) + + + + +# ToolError +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class ToolError(Exception): + pass + +_UniffiTempToolError = ToolError + +class ToolError: # type: ignore + class InvalidParameters(_UniffiTempToolError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ToolError.InvalidParameters({})".format(str(self)) + _UniffiTempToolError.InvalidParameters = InvalidParameters # type: ignore + class ExecutionError(_UniffiTempToolError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ToolError.ExecutionError({})".format(str(self)) + _UniffiTempToolError.ExecutionError = ExecutionError # type: ignore + class SchemaError(_UniffiTempToolError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ToolError.SchemaError({})".format(str(self)) + _UniffiTempToolError.SchemaError = SchemaError # type: ignore + class NotFound(_UniffiTempToolError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ToolError.NotFound({})".format(str(self)) + _UniffiTempToolError.NotFound = NotFound # type: ignore + +ToolError = _UniffiTempToolError # type: ignore +del _UniffiTempToolError + + +class _UniffiConverterTypeToolError(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return ToolError.InvalidParameters( + _UniffiConverterString.read(buf), + ) + if variant == 2: + return ToolError.ExecutionError( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return ToolError.SchemaError( + _UniffiConverterString.read(buf), + ) + if variant == 4: + return ToolError.NotFound( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if isinstance(value, ToolError.InvalidParameters): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ToolError.ExecutionError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ToolError.SchemaError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ToolError.NotFound): + _UniffiConverterString.check_lower(value._values[0]) + return + + @staticmethod + def write(value, buf): + if isinstance(value, ToolError.InvalidParameters): + buf.write_i32(1) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ToolError.ExecutionError): + buf.write_i32(2) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ToolError.SchemaError): + buf.write_i32(3) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ToolError.NotFound): + buf.write_i32(4) + _UniffiConverterString.write(value._values[0], buf) + + + +class _UniffiConverterOptionalUInt32(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterUInt32.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterUInt32.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterUInt32.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterOptionalInt32(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterInt32.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterInt32.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterInt32.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterOptionalFloat(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterFloat.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterFloat.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterFloat.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterOptionalDouble(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterDouble.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterDouble.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterDouble.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterOptionalString(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterString.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterString.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterString.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterSequenceTypeExtensionConfig(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeExtensionConfig.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeExtensionConfig.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeExtensionConfig.read(buf) for i in range(count) + ] + + + +class _UniffiConverterSequenceTypeMessage(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeMessage.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeMessage.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeMessage.read(buf) for i in range(count) + ] + + + +class _UniffiConverterSequenceTypeMessageContent(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeMessageContent.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeMessageContent.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeMessageContent.read(buf) for i in range(count) + ] + + + +class _UniffiConverterSequenceTypeToolConfig(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeToolConfig.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeToolConfig.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeToolConfig.read(buf) for i in range(count) + ] + + +class _UniffiConverterTypeCompletionRequest: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + + +class _UniffiConverterTypeContents: + @staticmethod + def write(value, buf): + _UniffiConverterSequenceTypeMessageContent.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterSequenceTypeMessageContent.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterSequenceTypeMessageContent.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterSequenceTypeMessageContent.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterSequenceTypeMessageContent.lower(value) + + +class _UniffiConverterTypeJsonValueFfi: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + + +class _UniffiConverterTypeToolConfig: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + + +class _UniffiConverterTypeToolRequestToolCall: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + + +class _UniffiConverterTypeToolResponseToolResult: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + +# objects. +CompletionRequest = str +Contents = typing.List[MessageContent] +JsonValueFfi = str +ToolConfig = str +ToolRequestToolCall = str +ToolResponseToolResult = str + +# Async support# RustFuturePoll values +_UNIFFI_RUST_FUTURE_POLL_READY = 0 +_UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1 + +# Stores futures for _uniffi_continuation_callback +_UniffiContinuationHandleMap = _UniffiHandleMap() + +_UNIFFI_GLOBAL_EVENT_LOOP = None + +""" +Set the event loop to use for async functions + +This is needed if some async functions run outside of the eventloop, for example: + - A non-eventloop thread is spawned, maybe from `EventLoop.run_in_executor` or maybe from the + Rust code spawning its own thread. + - The Rust code calls an async callback method from a sync callback function, using something + like `pollster` to block on the async call. + +In this case, we need an event loop to run the Python async function, but there's no eventloop set +for the thread. Use `uniffi_set_event_loop` to force an eventloop to be used in this case. +""" +def uniffi_set_event_loop(eventloop: asyncio.BaseEventLoop): + global _UNIFFI_GLOBAL_EVENT_LOOP + _UNIFFI_GLOBAL_EVENT_LOOP = eventloop + +def _uniffi_get_event_loop(): + if _UNIFFI_GLOBAL_EVENT_LOOP is not None: + return _UNIFFI_GLOBAL_EVENT_LOOP + else: + return asyncio.get_running_loop() + +# Continuation callback for async functions +# lift the return value or error and resolve the future, causing the async function to resume. +@_UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK +def _uniffi_continuation_callback(future_ptr, poll_code): + (eventloop, future) = _UniffiContinuationHandleMap.remove(future_ptr) + eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code) + +def _uniffi_set_future_result(future, poll_code): + if not future.cancelled(): + future.set_result(poll_code) + +async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, lift_func, error_ffi_converter): + try: + eventloop = _uniffi_get_event_loop() + + # Loop and poll until we see a _UNIFFI_RUST_FUTURE_POLL_READY value + while True: + future = eventloop.create_future() + ffi_poll( + rust_future, + _uniffi_continuation_callback, + _UniffiContinuationHandleMap.insert((eventloop, future)), + ) + poll_code = await future + if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY: + break + + return lift_func( + _uniffi_rust_call_with_error(error_ffi_converter, ffi_complete, rust_future) + ) + finally: + ffi_free(rust_future) +async def completion(req: "CompletionRequest") -> "CompletionResponse": + + """ + Public API for the Goose LLM completion function + """ + + _UniffiConverterTypeCompletionRequest.check_lower(req) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_goose_llm_fn_func_completion( + _UniffiConverterTypeCompletionRequest.lower(req)), + _UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer, + # lift function + _UniffiConverterTypeCompletionResponse.lift, + + # Error FFI converter +_UniffiConverterTypeCompletionError, + + ) + +def create_completion_request(provider_name: "str",provider_config: "JsonValueFfi",model_config: "ModelConfig",system_preamble: "str",messages: "typing.List[Message]",extensions: "typing.List[ExtensionConfig]") -> "CompletionRequest": + _UniffiConverterString.check_lower(provider_name) + + _UniffiConverterTypeJsonValueFfi.check_lower(provider_config) + + _UniffiConverterTypeModelConfig.check_lower(model_config) + + _UniffiConverterString.check_lower(system_preamble) + + _UniffiConverterSequenceTypeMessage.check_lower(messages) + + _UniffiConverterSequenceTypeExtensionConfig.check_lower(extensions) + + return _UniffiConverterTypeCompletionRequest.lift(_uniffi_rust_call(_UniffiLib.uniffi_goose_llm_fn_func_create_completion_request, + _UniffiConverterString.lower(provider_name), + _UniffiConverterTypeJsonValueFfi.lower(provider_config), + _UniffiConverterTypeModelConfig.lower(model_config), + _UniffiConverterString.lower(system_preamble), + _UniffiConverterSequenceTypeMessage.lower(messages), + _UniffiConverterSequenceTypeExtensionConfig.lower(extensions))) + + +def create_tool_config(name: "str",description: "str",input_schema: "JsonValueFfi",approval_mode: "ToolApprovalMode") -> "ToolConfig": + _UniffiConverterString.check_lower(name) + + _UniffiConverterString.check_lower(description) + + _UniffiConverterTypeJsonValueFfi.check_lower(input_schema) + + _UniffiConverterTypeToolApprovalMode.check_lower(approval_mode) + + return _UniffiConverterTypeToolConfig.lift(_uniffi_rust_call(_UniffiLib.uniffi_goose_llm_fn_func_create_tool_config, + _UniffiConverterString.lower(name), + _UniffiConverterString.lower(description), + _UniffiConverterTypeJsonValueFfi.lower(input_schema), + _UniffiConverterTypeToolApprovalMode.lower(approval_mode))) + +async def generate_session_name(provider_name: "str",provider_config: "JsonValueFfi",messages: "typing.List[Message]") -> "str": + + """ + Generates a short (≤4 words) session name + """ + + _UniffiConverterString.check_lower(provider_name) + + _UniffiConverterTypeJsonValueFfi.check_lower(provider_config) + + _UniffiConverterSequenceTypeMessage.check_lower(messages) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_goose_llm_fn_func_generate_session_name( + _UniffiConverterString.lower(provider_name), + _UniffiConverterTypeJsonValueFfi.lower(provider_config), + _UniffiConverterSequenceTypeMessage.lower(messages)), + _UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer, + # lift function + _UniffiConverterString.lift, + + # Error FFI converter +_UniffiConverterTypeProviderError, + + ) +async def generate_tooltip(provider_name: "str",provider_config: "JsonValueFfi",messages: "typing.List[Message]") -> "str": + + """ + Generates a tooltip summarizing the last two messages in the session, + including any tool calls or results. + """ + + _UniffiConverterString.check_lower(provider_name) + + _UniffiConverterTypeJsonValueFfi.check_lower(provider_config) + + _UniffiConverterSequenceTypeMessage.check_lower(messages) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_goose_llm_fn_func_generate_tooltip( + _UniffiConverterString.lower(provider_name), + _UniffiConverterTypeJsonValueFfi.lower(provider_config), + _UniffiConverterSequenceTypeMessage.lower(messages)), + _UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer, + # lift function + _UniffiConverterString.lift, + + # Error FFI converter +_UniffiConverterTypeProviderError, + + ) + +def print_messages(messages: "typing.List[Message]") -> None: + _UniffiConverterSequenceTypeMessage.check_lower(messages) + + _uniffi_rust_call(_UniffiLib.uniffi_goose_llm_fn_func_print_messages, + _UniffiConverterSequenceTypeMessage.lower(messages)) + + +__all__ = [ + "InternalError", + "CompletionError", + "Content", + "MessageContent", + "ProviderError", + "Role", + "ToolApprovalMode", + "ToolError", + "CompletionResponse", + "ExtensionConfig", + "ImageContent", + "Message", + "ModelConfig", + "ProviderCompleteResponse", + "RedactedThinkingContent", + "RuntimeMetrics", + "TextContent", + "ThinkingContent", + "ToolRequest", + "ToolResponse", + "Usage", + "completion", + "create_completion_request", + "create_tool_config", + "generate_session_name", + "generate_tooltip", + "print_messages", +] + diff --git a/bindings/python/usage.py b/bindings/python/usage.py new file mode 100644 index 00000000..bdfb3918 --- /dev/null +++ b/bindings/python/usage.py @@ -0,0 +1,133 @@ +import asyncio +import os +import time +from goose_llm import ( + Message, MessageContent, TextContent, ToolRequest, ToolResponse, + Role, ModelConfig, ToolApprovalMode, + create_tool_config, ExtensionConfig, + generate_session_name, generate_tooltip, + create_completion_request, completion +) + +async def main(): + now = int(time.time()) + + # 1) User sends a plain-text prompt + messages = [ + Message( + role=Role.USER, + created=now, + content=[MessageContent.TEXT(TextContent(text="What is 7 x 6?"))] + ), + + # 2) Assistant makes a tool request + Message( + role=Role.ASSISTANT, + created=now + 2, + content=[MessageContent.TOOL_REQ(ToolRequest( + id="calc1", + tool_call=""" + { + "status": "success", + "value": { + "name": "calculator_extension__toolname", + "arguments": { + "operation": "multiply", + "numbers": [7, 6] + }, + "needsApproval": false + } + } + """ + ))] + ), + + # 3) User sends tool result + Message( + role=Role.USER, + created=now + 3, + content=[MessageContent.TOOL_RESP(ToolResponse( + id="calc1", + tool_result=""" + { + "status": "success", + "value": [ + {"type": "text", "text": "42"} + ] + } + """ + ))] + ) + ] + + provider_name = "databricks" + provider_config = f'''{{ + "host": "{os.environ.get("DATABRICKS_HOST")}", + "token": "{os.environ.get("DATABRICKS_TOKEN")}" + }}''' + + print(f"Provider Name: {provider_name}") + print(f"Provider Config: {provider_config}") + + session_name = await generate_session_name(provider_name, provider_config, messages) + print(f"\nSession Name: {session_name}") + + tooltip = await generate_tooltip(provider_name, provider_config, messages) + print(f"\nTooltip: {tooltip}") + + model_config = ModelConfig( + model_name="goose-gpt-4-1", + max_tokens=500, + temperature=0.1, + context_limit=4096, + ) + + calculator_tool = create_tool_config( + name="calculator", + description="Perform basic arithmetic operations", + input_schema=""" + { + "type": "object", + "required": ["operation", "numbers"], + "properties": { + "operation": { + "type": "string", + "enum": ["add", "subtract", "multiply", "divide"], + "description": "The arithmetic operation to perform" + }, + "numbers": { + "type": "array", + "items": { "type": "number" }, + "description": "List of numbers to operate on in order" + } + } + } + """, + approval_mode=ToolApprovalMode.AUTO + ) + + calculator_extension = ExtensionConfig( + name="calculator_extension", + instructions="This extension provides a calculator tool.", + tools=[calculator_tool] + ) + + system_preamble = "You are a helpful assistant." + extensions = [calculator_extension] + + req = create_completion_request( + provider_name, + provider_config, + model_config, + system_preamble, + messages, + extensions + ) + + resp = await completion(req) + print(f"\nCompletion Response:\n{resp.message}") + print(f"Msg content: {resp.message.content[0][0]}") + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/crates/goose-bench/Cargo.toml b/crates/goose-bench/Cargo.toml index 1616bd4f..6c98c99b 100644 --- a/crates/goose-bench/Cargo.toml +++ b/crates/goose-bench/Cargo.toml @@ -25,6 +25,7 @@ include_dir = "0.7.4" once_cell = "1.19" regex = "1.11.1" toml = "0.8.20" +dotenvy = "0.15.7" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["wincred"] } diff --git a/crates/goose-bench/README.md b/crates/goose-bench/README.md new file mode 100644 index 00000000..927cecf7 --- /dev/null +++ b/crates/goose-bench/README.md @@ -0,0 +1,273 @@ +# Goose Benchmarking Framework + +The `goose-bench` crate provides a framework for benchmarking and evaluating LLM models with the Goose framework. This tool helps quantify model performance across various tasks and generate structured reports. + +## Features + +- Run benchmark suites across multiple LLM models +- Execute evaluations in parallel when supported +- Generate structured JSON and CSV reports +- Process evaluation results with custom scripts +- Calculate aggregate metrics across evaluations +- Support for tool-shim evaluation +- Generate leaderboards and comparative metrics + +## Prerequisites + +- **Python Environment**: The `generate-leaderboard` command executes Python scripts and requires a valid Python environment with necessary dependencies (pandas, etc.) +- **OpenAI API Key**: For evaluations using LLM-as-judge (like `blog_summary` and `restaurant_research`), you must have an `OPENAI_API_KEY` environment variable set, as the judge uses the OpenAI GPT-4o model + +## Benchmark Workflow + +Running benchmarks is a two-step process: + +### Step 1: Run Benchmarks + +First, run the benchmark evaluations with your configuration: + +```bash +goose bench run --config /path/to/your-config.json +``` + +This will execute all evaluations for all models specified in your configuration and create a benchmark directory with results. + +### Step 2: Generate Leaderboard + +After the benchmarks complete, generate the leaderboard and aggregated metrics: + +```bash +goose bench generate-leaderboard --benchmark-dir /path/to/benchmark-output-directory +``` + +The benchmark directory path will be shown in the output of the previous command, typically in the format `benchmark-YYYY-MM-DD-HH:MM:SS`. + +**Note**: This command requires a valid Python environment as it executes Python scripts for data aggregation and leaderboard generation. + +## Configuration + +Benchmark configuration is provided through a JSON file. Here's a sample configuration file (leaderboard-config.json) that you can use as a template: + +```json +{ + "models": [ + { + "provider": "databricks", + "name": "gpt-4-1-mini", + "parallel_safe": true, + "tool_shim": { + "use_tool_shim": false, + "tool_shim_model": null + } + }, + { + "provider": "databricks", + "name": "claude-3-5-sonnet", + "parallel_safe": true, + "tool_shim": null + }, + { + "provider": "databricks", + "name": "gpt-4o", + "parallel_safe": true, + "tool_shim": null + } + ], + "evals": [ + { + "selector": "core:developer", + "post_process_cmd": null, + "parallel_safe": true + }, + { + "selector": "core:developer_search_replace", + "post_process_cmd": null, + "parallel_safe": true + }, + { + "selector": "vibes:blog_summary", + "post_process_cmd": "/Users/ahau/Development/goose-1.0/goose/scripts/bench-postprocess-scripts/llm-judges/run_vibes_judge.sh", + "parallel_safe": true + }, + { + "selector": "vibes:restaurant_research", + "post_process_cmd": "/Users/ahau/Development/goose-1.0/goose/scripts/bench-postprocess-scripts/llm-judges/run_vibes_judge.sh", + "parallel_safe": true + } + ], + "include_dirs": [], + "repeat": 3, + "run_id": null, + "output_dir": "/path/to/output/directory", + "eval_result_filename": "eval-results.json", + "run_summary_filename": "run-results-summary.json", + "env_file": "/path/to/.goosebench.env" +} +``` + +## Configuration Options + +### Models + +- `provider`: The LLM provider (e.g., "databricks", "openai") +- `name`: The model name +- `parallel_safe`: Whether the model can be run in parallel +- `tool_shim`: Configuration for tool-shim support + - `use_tool_shim`: Whether to use tool-shim + - `tool_shim_model`: Optional custom model for tool-shim + +### Evaluations + +- `selector`: The evaluation selector in format `suite:evaluation` +- `post_process_cmd`: Optional path to a post-processing script +- `parallel_safe`: Whether the evaluation can be run in parallel + +### Global Configuration + +- `include_dirs`: Additional directories to include in the benchmark environment +- `repeat`: Number of times to repeat evaluations (for statistical significance) +- `run_id`: Optional identifier for the run (defaults to timestamp) +- `output_dir`: Directory to store benchmark results (must be absolute path) +- `eval_result_filename`: Filename for individual evaluation results +- `run_summary_filename`: Filename for run summary +- `env_file`: Optional path to environment variables file + +## Environment Variables + +You can provide environment variables through the `env_file` configuration option. This is useful for provider API keys and other sensitive information. Example `.goosebench.env` file: + +```bash +OPENAI_API_KEY=your_openai_api_key_here +DATABRICKS_TOKEN=your_databricks_token_here +# Add other environment variables as needed +``` + +**Important**: For evaluations that use LLM-as-judge (like `blog_summary` and `restaurant_research`), you must set `OPENAI_API_KEY` as the judging system uses OpenAI's GPT-4o model. + +## Post-Processing + +You can specify post-processing commands for evaluations, which will be executed after each evaluation completes. The command receives the path to the evaluation results file as its first argument. + +For example, the `run_vibes_judge.sh` script processes outputs from the `blog_summary` and `restaurant_research` evaluations, using LLM-based judging to assign scores. + +## Output Structure + +Results are organized in a directory structure that follows this pattern: + +``` +{benchmark_dir}/ +├── config.cfg # Configuration used for the benchmark +├── {provider}-{model}/ +│ ├── eval-results/ +│ │ └── aggregate_metrics.csv # Aggregated metrics for this model +│ └── run-{run_id}/ +│ ├── {suite}/ +│ │ └── {evaluation}/ +│ │ ├── eval-results.json # Individual evaluation results +│ │ ├── {eval_name}.jsonl # Session logs +│ │ └── work_dir.json # Info about evaluation working dir +│ └── run-results-summary.json # Summary of all evaluations in this run +├── leaderboard.csv # Final leaderboard comparing all models +└── all_metrics.csv # Union of all metrics across all models +``` + +### Output Files Explained + +#### Per-Model Files + +- **`eval-results/aggregate_metrics.csv`**: Contains aggregated metrics for each evaluation, averaged across all runs. Includes metrics like `score_mean`, `total_tokens_mean`, `prompt_execution_time_seconds_mean`, etc. + +#### Global Output Files + +- **`leaderboard.csv`**: Final leaderboard ranking all models by their average performance across evaluations. Contains columns like: + - `provider`, `model_name`: Model identification + - `avg_score_mean`: Average score across all evaluations + - `avg_prompt_execution_time_seconds_mean`: Average execution time + - `avg_total_tool_calls_mean`: Average number of tool calls + - `avg_total_tokens_mean`: Average token usage + +- **`all_metrics.csv`**: Comprehensive dataset containing detailed metrics for every model-evaluation combination. This is a union of all individual model metrics, useful for detailed analysis and custom reporting. + +Each model gets its own directory, containing run results and aggregated CSV files for analysis. The `generate-leaderboard` command processes all individual evaluation results and creates the comparative metrics files. + +## Error Handling and Troubleshooting + +**Important**: The current version of goose-bench does not have robust error handling for common issues that can occur during evaluation runs, such as: + +- Rate limiting from inference providers +- Network timeouts or connection errors +- Provider API errors that cause early session termination +- Resource exhaustion or memory issues + +### Checking for Failed Evaluations + +After running benchmarks, you should inspect the generated metrics files to identify any evaluations that may have failed or terminated early: + +1. **Check the `aggregate_metrics.csv` files** in each model's `eval-results/` directory for: + - Missing evaluations (fewer rows than expected) + - Unusually low scores or metrics + - Zero or near-zero execution times + - Missing or NaN values + +2. **Look for `server_error_mean` column** in the aggregate metrics - values greater than 0 indicate server errors occurred during evaluation + +3. **Review session logs** (`.jsonl` files) in individual evaluation directories for error messages like: + - "Server error" + - "Rate limit exceeded" + - "TEMPORARILY_UNAVAILABLE" + - Unexpected session terminations + +### Re-running Failed Evaluations + +If you identify failed evaluations, you may need to: + +1. **Adjust rate limiting**: Add delays between requests or reduce parallel execution +2. **Update environment variables**: Ensure API keys and tokens are valid +3. **Re-run specific model/evaluation combinations**: Create a new config with only the failed combinations +4. **Check provider status**: Verify the inference provider is operational + +Example of creating a config to re-run failed evaluations: + +```json +{ + "models": [ + { + "provider": "databricks", + "name": "claude-3-5-sonnet", + "parallel_safe": false + } + ], + "evals": [ + { + "selector": "vibes:blog_summary", + "post_process_cmd": "/path/to/scripts/bench-postprocess-scripts/llm-judges/run_vibes_judge.sh", + "parallel_safe": false + } + ], + "repeat": 1, + "output_dir": "/path/to/retry-benchmark" +} +``` + +We recommend monitoring evaluation progress and checking for errors regularly, especially when running large benchmark suites across multiple models. + +## Available Commands + +### List Evaluations +```bash +goose bench selectors --config /path/to/config.json +``` + +### Generate Initial Config +```bash +goose bench init-config --name my-benchmark-config.json +``` + +### Run Benchmarks +```bash +goose bench run --config /path/to/config.json +``` + +### Generate Leaderboard +```bash +goose bench generate-leaderboard --benchmark-dir /path/to/benchmark-output +``` diff --git a/crates/goose-bench/src/bench_config.rs b/crates/goose-bench/src/bench_config.rs index d3289605..fa582a76 100644 --- a/crates/goose-bench/src/bench_config.rs +++ b/crates/goose-bench/src/bench_config.rs @@ -30,6 +30,7 @@ pub struct BenchRunConfig { pub include_dirs: Vec, pub repeat: Option, pub run_id: Option, + pub output_dir: Option, pub eval_result_filename: String, pub run_summary_filename: String, pub env_file: Option, @@ -63,6 +64,7 @@ impl Default for BenchRunConfig { include_dirs: vec![], repeat: Some(2), run_id: None, + output_dir: None, eval_result_filename: "eval-results.json".to_string(), run_summary_filename: "run-results-summary.json".to_string(), env_file: None, diff --git a/crates/goose-bench/src/bench_work_dir.rs b/crates/goose-bench/src/bench_work_dir.rs index f832033e..9729af49 100644 --- a/crates/goose-bench/src/bench_work_dir.rs +++ b/crates/goose-bench/src/bench_work_dir.rs @@ -1,9 +1,9 @@ +use anyhow::Context; use chrono::Local; use include_dir::{include_dir, Dir}; use serde::{Deserialize, Serialize}; use std::fs; use std::io; -use std::io::ErrorKind; use std::path::Path; use std::path::PathBuf; use std::process::Command; @@ -53,15 +53,35 @@ impl BenchmarkWorkDir { } } - pub fn init_experiment() { + pub fn init_experiment(output_dir: PathBuf) -> anyhow::Result<()> { + if !output_dir.is_absolute() { + anyhow::bail!( + "Internal Error: init_experiment received a non-absolute path: {}", + output_dir.display() + ); + } + // create experiment folder let current_time = Local::now().format("%H:%M:%S").to_string(); let current_date = Local::now().format("%Y-%m-%d").to_string(); - let exp_name = format!("{}-{}", ¤t_date, current_time); - let base_path = PathBuf::from(format!("./benchmark-{}", exp_name)); - fs::create_dir_all(&base_path).unwrap(); - std::env::set_current_dir(&base_path).unwrap(); + let exp_folder_name = format!("benchmark-{}-{}", ¤t_date, ¤t_time); + let base_path = output_dir.join(exp_folder_name); + + fs::create_dir_all(&base_path).with_context(|| { + format!( + "Failed to create benchmark directory: {}", + base_path.display() + ) + })?; + std::env::set_current_dir(&base_path).with_context(|| { + format!( + "Failed to change working directory to: {}", + base_path.display() + ) + })?; + Ok(()) } + pub fn canonical_dirs(include_dirs: Vec) -> Vec { include_dirs .iter() @@ -186,7 +206,7 @@ impl BenchmarkWorkDir { Ok(()) } else { let error_message = String::from_utf8_lossy(&output.stderr).to_string(); - Err(io::Error::new(ErrorKind::Other, error_message)) + Err(io::Error::other(error_message)) } } diff --git a/crates/goose-bench/src/eval_suites/evaluation.rs b/crates/goose-bench/src/eval_suites/evaluation.rs index a0e4f880..cb129198 100644 --- a/crates/goose-bench/src/eval_suites/evaluation.rs +++ b/crates/goose-bench/src/eval_suites/evaluation.rs @@ -3,17 +3,29 @@ use crate::bench_work_dir::BenchmarkWorkDir; use anyhow::Result; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use std::fmt; pub type Model = (String, String); pub type Extension = String; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub enum EvalMetricValue { Integer(i64), Float(f64), String(String), Boolean(bool), } + +impl fmt::Display for EvalMetricValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EvalMetricValue::Integer(i) => write!(f, "{}", i), + EvalMetricValue::Float(fl) => write!(f, "{:.2}", fl), + EvalMetricValue::String(s) => write!(f, "{}", s), + EvalMetricValue::Boolean(b) => write!(f, "{}", b), + } + } +} #[derive(Debug, Serialize)] pub struct EvalMetric { pub name: String, diff --git a/crates/goose-bench/src/reporting.rs b/crates/goose-bench/src/reporting.rs index 0c587862..719cd80c 100644 --- a/crates/goose-bench/src/reporting.rs +++ b/crates/goose-bench/src/reporting.rs @@ -98,17 +98,6 @@ impl BenchmarkResults { } } -impl fmt::Display for EvalMetricValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - EvalMetricValue::Integer(i) => write!(f, "{}", i), - EvalMetricValue::Float(fl) => write!(f, "{:.2}", fl), - EvalMetricValue::String(s) => write!(f, "{}", s), - EvalMetricValue::Boolean(b) => write!(f, "{}", b), - } - } -} - impl fmt::Display for BenchmarkResults { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "Benchmark Results")?; diff --git a/crates/goose-bench/src/runners/bench_runner.rs b/crates/goose-bench/src/runners/bench_runner.rs index b826afb4..a48620e6 100644 --- a/crates/goose-bench/src/runners/bench_runner.rs +++ b/crates/goose-bench/src/runners/bench_runner.rs @@ -3,6 +3,7 @@ use crate::bench_work_dir::BenchmarkWorkDir; use crate::eval_suites::EvaluationSuite; use crate::runners::model_runner::ModelRunner; use crate::utilities::{await_process_exits, parallel_bench_cmd}; +use anyhow::Context; use std::path::PathBuf; #[derive(Clone)] @@ -11,9 +12,27 @@ pub struct BenchRunner { } impl BenchRunner { - pub fn new(config: PathBuf) -> anyhow::Result { - let config = BenchRunConfig::from(config)?; - BenchmarkWorkDir::init_experiment(); + pub fn new(config_path: PathBuf) -> anyhow::Result { + let config = BenchRunConfig::from(config_path.clone())?; + + let resolved_output_dir = match &config.output_dir { + Some(path) => { + if !path.is_absolute() { + anyhow::bail!( + "Config Error in '{}': 'output_dir' must be an absolute path, but found relative path: {}", + config_path.display(), + path.display() + ); + } + path.clone() + } + None => std::env::current_dir().context( + "Failed to get current working directory to use as default output directory", + )?, + }; + + BenchmarkWorkDir::init_experiment(resolved_output_dir)?; + config.save("config.cfg".to_string()); Ok(BenchRunner { config }) } diff --git a/crates/goose-bench/src/runners/eval_runner.rs b/crates/goose-bench/src/runners/eval_runner.rs index 09c4259a..ccac8f8d 100644 --- a/crates/goose-bench/src/runners/eval_runner.rs +++ b/crates/goose-bench/src/runners/eval_runner.rs @@ -4,12 +4,14 @@ use crate::bench_work_dir::BenchmarkWorkDir; use crate::eval_suites::{EvaluationSuite, ExtensionRequirements}; use crate::reporting::EvaluationResult; use crate::utilities::await_process_exits; +use anyhow::{bail, Context, Result}; use std::env; use std::fs; use std::future::Future; use std::path::PathBuf; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; +use tracing; #[derive(Clone)] pub struct EvalRunner { @@ -17,13 +19,17 @@ pub struct EvalRunner { } impl EvalRunner { - pub fn from(config: String) -> anyhow::Result { - let config = BenchRunConfig::from_string(config)?; + pub fn from(config: String) -> Result { + let config = BenchRunConfig::from_string(config) + .context("Failed to parse evaluation configuration")?; Ok(EvalRunner { config }) } - fn create_work_dir(&self, config: &BenchRunConfig) -> anyhow::Result { - let goose_model = config.models.first().unwrap(); + fn create_work_dir(&self, config: &BenchRunConfig) -> Result { + let goose_model = config + .models + .first() + .context("No model specified in configuration")?; let model_name = goose_model.name.clone(); let provider_name = goose_model.provider.clone(); @@ -48,13 +54,21 @@ impl EvalRunner { let work_dir = BenchmarkWorkDir::new(work_dir_name, include_dir); Ok(work_dir) } - pub async fn run(&mut self, agent_generator: F) -> anyhow::Result<()> + + pub async fn run(&mut self, agent_generator: F) -> Result<()> where F: Fn(ExtensionRequirements, String) -> Fut, Fut: Future + Send, { - let mut work_dir = self.create_work_dir(&self.config)?; - let bench_eval = self.config.evals.first().unwrap(); + let mut work_dir = self + .create_work_dir(&self.config) + .context("Failed to create evaluation work directory")?; + + let bench_eval = self + .config + .evals + .first() + .context("No evaluations specified in configuration")?; let run_id = &self .config @@ -65,41 +79,89 @@ impl EvalRunner { // create entire dir subtree for eval and cd into dir for running eval work_dir.set_eval(&bench_eval.selector, run_id); + tracing::info!("Set evaluation directory for {}", bench_eval.selector); if let Some(eval) = EvaluationSuite::from(&bench_eval.selector) { - let now_stamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos(); + let now_stamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .context("Failed to get current timestamp")? + .as_nanos(); + let session_id = format!("{}-{}", bench_eval.selector.clone(), now_stamp); let mut agent = agent_generator(eval.required_extensions(), session_id).await; + tracing::info!("Agent created for {}", eval.name()); let mut result = EvaluationResult::new(eval.name().to_string()); - if let Ok(metrics) = eval.run(&mut agent, &mut work_dir).await { - for (name, metric) in metrics { - result.add_metric(name, metric); + match eval.run(&mut agent, &mut work_dir).await { + Ok(metrics) => { + tracing::info!("Evaluation run successful with {} metrics", metrics.len()); + for (name, metric) in metrics { + result.add_metric(name, metric); + } } - - // Add any errors that occurred - for error in agent.get_errors().await { - result.add_error(error); + Err(e) => { + tracing::error!("Evaluation run failed: {}", e); } } - let eval_results = serde_json::to_string_pretty(&result)?; + // Add any errors that occurred + let errors = agent.get_errors().await; + tracing::info!("Agent reported {} errors", errors.len()); + for error in errors { + result.add_error(error); + } + + // Write results to file + let eval_results = serde_json::to_string_pretty(&result) + .context("Failed to serialize evaluation results to JSON")?; + + let eval_results_file = env::current_dir() + .context("Failed to get current directory")? + .join(&self.config.eval_result_filename); + + fs::write(&eval_results_file, &eval_results).with_context(|| { + format!( + "Failed to write evaluation results to {}", + eval_results_file.display() + ) + })?; + + tracing::info!( + "Wrote evaluation results to {}", + eval_results_file.display() + ); - let eval_results_file = env::current_dir()?.join(&self.config.eval_result_filename); - fs::write(&eval_results_file, &eval_results)?; self.config.save("config.cfg".to_string()); work_dir.save(); // handle running post-process cmd if configured if let Some(cmd) = &bench_eval.post_process_cmd { - let handle = Command::new(cmd).arg(&eval_results_file).spawn()?; + tracing::info!("Running post-process command: {:?}", cmd); + + let handle = Command::new(cmd) + .arg(&eval_results_file) + .spawn() + .with_context(|| { + format!("Failed to execute post-process command: {:?}", cmd) + })?; + await_process_exits(&mut [handle], Vec::new()); } // copy session file into eval-dir - let here = env::current_dir()?.canonicalize()?; - BenchmarkWorkDir::deep_copy(agent.session_file().as_path(), here.as_path(), false)?; + let here = env::current_dir() + .context("Failed to get current directory")? + .canonicalize() + .context("Failed to canonicalize current directory path")?; + + BenchmarkWorkDir::deep_copy(agent.session_file().as_path(), here.as_path(), false) + .context("Failed to copy session file to evaluation directory")?; + + tracing::info!("Evaluation completed successfully"); + } else { + tracing::error!("No evaluation found for selector: {}", bench_eval.selector); + bail!("No evaluation found for selector: {}", bench_eval.selector); } Ok(()) diff --git a/crates/goose-bench/src/runners/metric_aggregator.rs b/crates/goose-bench/src/runners/metric_aggregator.rs new file mode 100644 index 00000000..6c41ac00 --- /dev/null +++ b/crates/goose-bench/src/runners/metric_aggregator.rs @@ -0,0 +1,81 @@ +use anyhow::{bail, ensure, Context, Result}; +use std::path::PathBuf; +use tracing; + +pub struct MetricAggregator; + +impl MetricAggregator { + /// Generate leaderboard and aggregated metrics CSV files from benchmark directory + pub fn generate_csv_from_benchmark_dir(benchmark_dir: &PathBuf) -> Result<()> { + use std::process::Command; + + // Step 1: Run prepare_aggregate_metrics.py to create aggregate_metrics.csv files + let prepare_script_path = std::env::current_dir() + .context("Failed to get current working directory")? + .join("scripts") + .join("bench-postprocess-scripts") + .join("prepare_aggregate_metrics.py"); + + ensure!( + prepare_script_path.exists(), + "Prepare script not found: {}", + prepare_script_path.display() + ); + + tracing::info!( + "Preparing aggregate metrics from benchmark directory: {}", + benchmark_dir.display() + ); + + let output = Command::new(&prepare_script_path) + .arg("--benchmark-dir") + .arg(benchmark_dir) + .output() + .context("Failed to execute prepare_aggregate_metrics.py script")?; + + if !output.status.success() { + let error_message = String::from_utf8_lossy(&output.stderr); + bail!("Failed to prepare aggregate metrics: {}", error_message); + } + + let success_message = String::from_utf8_lossy(&output.stdout); + tracing::info!("{}", success_message); + + // Step 2: Run generate_leaderboard.py to create the final leaderboard + let leaderboard_script_path = std::env::current_dir() + .context("Failed to get current working directory")? + .join("scripts") + .join("bench-postprocess-scripts") + .join("generate_leaderboard.py"); + + ensure!( + leaderboard_script_path.exists(), + "Leaderboard script not found: {}", + leaderboard_script_path.display() + ); + + tracing::info!( + "Generating leaderboard from benchmark directory: {}", + benchmark_dir.display() + ); + + let output = Command::new(&leaderboard_script_path) + .arg("--benchmark-dir") + .arg(benchmark_dir) + .arg("--leaderboard-output") + .arg("leaderboard.csv") + .arg("--union-output") + .arg("all_metrics.csv") + .output() + .context("Failed to execute generate_leaderboard.py script")?; + + if !output.status.success() { + let error_message = String::from_utf8_lossy(&output.stderr); + bail!("Failed to generate leaderboard: {}", error_message); + } + + let success_message = String::from_utf8_lossy(&output.stdout); + tracing::info!("{}", success_message); + Ok(()) + } +} diff --git a/crates/goose-bench/src/runners/mod.rs b/crates/goose-bench/src/runners/mod.rs index b78eb0c8..70fb4845 100644 --- a/crates/goose-bench/src/runners/mod.rs +++ b/crates/goose-bench/src/runners/mod.rs @@ -1,3 +1,4 @@ pub mod bench_runner; pub mod eval_runner; +pub mod metric_aggregator; pub mod model_runner; diff --git a/crates/goose-bench/src/runners/model_runner.rs b/crates/goose-bench/src/runners/model_runner.rs index 2937bd50..3310cf14 100644 --- a/crates/goose-bench/src/runners/model_runner.rs +++ b/crates/goose-bench/src/runners/model_runner.rs @@ -3,12 +3,14 @@ use crate::eval_suites::EvaluationSuite; use crate::reporting::{BenchmarkResults, SuiteResult}; use crate::runners::eval_runner::EvalRunner; use crate::utilities::{await_process_exits, parallel_bench_cmd}; +use anyhow::{Context, Result}; +use dotenvy::from_path_iter; use std::collections::HashMap; use std::fs::read_to_string; -use std::io::{self, BufRead}; use std::path::PathBuf; use std::process::Child; use std::thread; +use tracing; #[derive(Clone)] pub struct ModelRunner { @@ -16,23 +18,27 @@ pub struct ModelRunner { } impl ModelRunner { - pub fn from(config: String) -> anyhow::Result { - let config = BenchRunConfig::from_string(config)?; + pub fn from(config: String) -> Result { + let config = + BenchRunConfig::from_string(config).context("Failed to parse configuration")?; Ok(ModelRunner { config }) } - pub fn run(&self) -> anyhow::Result<()> { - let model = self.config.models.first().unwrap(); + pub fn run(&self) -> Result<()> { + let model = self + .config + .models + .first() + .context("No model specified in config")?; let suites = self.collect_evals_for_run(); let mut handles = vec![]; for i in 0..self.config.repeat.unwrap_or(1) { - let mut self_copy = self.clone(); + let self_copy = self.clone(); let model_clone = model.clone(); let suites_clone = suites.clone(); - // create thread to handle launching parallel processes to run model's evals in parallel - let handle = thread::spawn(move || { + let handle = thread::spawn(move || -> Result<()> { self_copy.run_benchmark(&model_clone, suites_clone, i.to_string()) }); handles.push(handle); @@ -41,55 +47,32 @@ impl ModelRunner { let mut all_runs_results: Vec = Vec::new(); for i in 0..self.config.repeat.unwrap_or(1) { - let run_results = - self.collect_run_results(model.clone(), suites.clone(), i.to_string())?; - all_runs_results.push(run_results); + match self.collect_run_results(model.clone(), suites.clone(), i.to_string()) { + Ok(run_results) => all_runs_results.push(run_results), + Err(e) => { + tracing::error!("Failed to collect results for run {}: {}", i, e) + } + } } - // write summary file Ok(()) } - fn load_env_file(&self, path: &PathBuf) -> anyhow::Result> { - let file = std::fs::File::open(path)?; - let reader = io::BufReader::new(file); - let mut env_vars = Vec::new(); - - for line in reader.lines() { - let line = line?; - // Skip empty lines and comments - if line.trim().is_empty() || line.trim_start().starts_with('#') { - continue; - } - - // Split on first '=' only - if let Some((key, value)) = line.split_once('=') { - let key = key.trim().to_string(); - // Remove quotes if present - let value = value - .trim() - .trim_matches('"') - .trim_matches('\'') - .to_string(); - env_vars.push((key, value)); - } - } - - Ok(env_vars) - } - fn run_benchmark( - &mut self, + &self, model: &BenchModel, suites: HashMap>, run_id: String, - ) -> anyhow::Result<()> { + ) -> Result<()> { let mut results_handles = HashMap::>::new(); // Load environment variables from file if specified let mut envs = self.toolshim_envs(); if let Some(env_file) = &self.config.env_file { - let env_vars = self.load_env_file(env_file)?; + let env_vars = ModelRunner::load_env_file(env_file).context(format!( + "Failed to load environment file: {}", + env_file.display() + ))?; envs.extend(env_vars); } envs.push(("GOOSE_MODEL".to_string(), model.clone().name)); @@ -116,9 +99,13 @@ impl ModelRunner { // Run parallel-safe evaluations in parallel if !parallel_evals.is_empty() { for eval_selector in ¶llel_evals { - self.config.run_id = Some(run_id.clone()); - self.config.evals = vec![(*eval_selector).clone()]; - let cfg = self.config.to_string()?; + let mut config_copy = self.config.clone(); + config_copy.run_id = Some(run_id.clone()); + config_copy.evals = vec![(*eval_selector).clone()]; + let cfg = config_copy + .to_string() + .context("Failed to serialize configuration")?; + let handle = parallel_bench_cmd("exec-eval".to_string(), cfg, envs.clone()); results_handles.get_mut(suite).unwrap().push(handle); } @@ -126,9 +113,13 @@ impl ModelRunner { // Run non-parallel-safe evaluations sequentially for eval_selector in &sequential_evals { - self.config.run_id = Some(run_id.clone()); - self.config.evals = vec![(*eval_selector).clone()]; - let cfg = self.config.to_string()?; + let mut config_copy = self.config.clone(); + config_copy.run_id = Some(run_id.clone()); + config_copy.evals = vec![(*eval_selector).clone()]; + let cfg = config_copy + .to_string() + .context("Failed to serialize configuration")?; + let handle = parallel_bench_cmd("exec-eval".to_string(), cfg, envs.clone()); // Wait for this process to complete before starting the next one @@ -150,7 +141,7 @@ impl ModelRunner { model: BenchModel, suites: HashMap>, run_id: String, - ) -> anyhow::Result { + ) -> Result { let mut results = BenchmarkResults::new(model.provider.clone()); let mut summary_path: Option = None; @@ -161,7 +152,17 @@ impl ModelRunner { let mut eval_path = EvalRunner::path_for_eval(&model, eval_selector, run_id.clone()); eval_path.push(self.config.eval_result_filename.clone()); - let eval_result = serde_json::from_str(&read_to_string(&eval_path)?)?; + + let content = read_to_string(&eval_path).with_context(|| { + format!( + "Failed to read evaluation results from {}", + eval_path.display() + ) + })?; + + let eval_result = serde_json::from_str(&content) + .context("Failed to parse evaluation results JSON")?; + suite_result.add_evaluation(eval_result); // use current eval to determine where the summary should be written @@ -180,12 +181,21 @@ impl ModelRunner { results.add_suite(suite_result); } - let mut run_summary = PathBuf::new(); - run_summary.push(summary_path.clone().unwrap()); - run_summary.push(&self.config.run_summary_filename); + if let Some(path) = summary_path { + let mut run_summary = PathBuf::new(); + run_summary.push(path); + run_summary.push(&self.config.run_summary_filename); - let output_str = serde_json::to_string_pretty(&results)?; - std::fs::write(run_summary, &output_str)?; + let output_str = serde_json::to_string_pretty(&results) + .context("Failed to serialize benchmark results to JSON")?; + + std::fs::write(&run_summary, &output_str).with_context(|| { + format!( + "Failed to write results summary to {}", + run_summary.display() + ) + })?; + } Ok(results) } @@ -210,20 +220,29 @@ impl ModelRunner { fn toolshim_envs(&self) -> Vec<(String, String)> { // read tool-shim preference from config, set respective env vars accordingly - let model = self.config.models.first().unwrap(); - let mut shim_envs: Vec<(String, String)> = Vec::new(); - if let Some(shim_opt) = &model.tool_shim { - if shim_opt.use_tool_shim { - shim_envs.push(("GOOSE_TOOLSHIM".to_string(), "true".to_string())); - if let Some(shim_model) = &shim_opt.tool_shim_model { - shim_envs.push(( - "GOOSE_TOOLSHIM_OLLAMA_MODEL".to_string(), - shim_model.clone(), - )); + if let Some(model) = self.config.models.first() { + if let Some(shim_opt) = &model.tool_shim { + if shim_opt.use_tool_shim { + shim_envs.push(("GOOSE_TOOLSHIM".to_string(), "true".to_string())); + if let Some(shim_model) = &shim_opt.tool_shim_model { + shim_envs.push(( + "GOOSE_TOOLSHIM_OLLAMA_MODEL".to_string(), + shim_model.clone(), + )); + } } } } shim_envs } + + fn load_env_file(path: &PathBuf) -> Result> { + let iter = + from_path_iter(path).context("Failed to read environment variables from file")?; + let env_vars = iter + .map(|item| item.context("Failed to parse environment variable")) + .collect::>()?; + Ok(env_vars) + } } diff --git a/crates/goose-bench/src/utilities.rs b/crates/goose-bench/src/utilities.rs index 0eb04412..930a2be7 100644 --- a/crates/goose-bench/src/utilities.rs +++ b/crates/goose-bench/src/utilities.rs @@ -1,15 +1,14 @@ +use anyhow::Result; use std::env; use std::process::{Child, Command}; use std::thread::JoinHandle; +use tracing; -pub fn await_process_exits( - child_processes: &mut [Child], - handles: Vec>>, -) { +pub fn await_process_exits(child_processes: &mut [Child], handles: Vec>>) { for child in child_processes.iter_mut() { match child.wait() { - Ok(status) => println!("Child exited with status: {}", status), - Err(e) => println!("Error waiting for child: {}", e), + Ok(status) => tracing::info!("Child exited with status: {}", status), + Err(e) => tracing::error!("Error waiting for child: {}", e), } } @@ -18,7 +17,7 @@ pub fn await_process_exits( Ok(_res) => (), Err(e) => { // Handle thread panic - println!("Thread panicked: {:?}", e); + tracing::error!("Thread panicked: {:?}", e); } } } diff --git a/crates/goose-cli/Cargo.toml b/crates/goose-cli/Cargo.toml index 698d0d15..dfe73e48 100644 --- a/crates/goose-cli/Cargo.toml +++ b/crates/goose-cli/Cargo.toml @@ -52,6 +52,9 @@ shlex = "1.3.0" async-trait = "0.1.86" base64 = "0.22.1" regex = "1.11.1" +minijinja = "2.8.0" +nix = { version = "0.30.1", features = ["process", "signal"] } +tar = "0.4" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["wincred"] } diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index e400dfbe..4e6deb47 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -7,15 +7,22 @@ use crate::commands::bench::agent_generator; use crate::commands::configure::handle_configure; use crate::commands::info::handle_info; use crate::commands::mcp::run_server; +use crate::commands::project::{handle_project_default, handle_projects_interactive}; use crate::commands::recipe::{handle_deeplink, handle_validate}; +// Import the new handlers from commands::schedule +use crate::commands::schedule::{ + handle_schedule_add, handle_schedule_list, handle_schedule_remove, handle_schedule_run_now, + handle_schedule_sessions, +}; use crate::commands::session::{handle_session_list, handle_session_remove}; use crate::logging::setup_logging; -use crate::recipe::load_recipe; +use crate::recipes::recipe::{explain_recipe_with_parameters, load_recipe_as_template}; use crate::session; use crate::session::{build_session, SessionBuilderConfig}; use goose_bench::bench_config::BenchRunConfig; use goose_bench::runners::bench_runner::BenchRunner; use goose_bench::runners::eval_runner::EvalRunner; +use goose_bench::runners::metric_aggregator::MetricAggregator; use goose_bench::runners::model_runner::ModelRunner; use std::io::Read; use std::path::PathBuf; @@ -59,6 +66,13 @@ fn extract_identifier(identifier: Identifier) -> session::Identifier { } } +fn parse_key_val(s: &str) -> Result<(String, String), String> { + match s.split_once('=') { + Some((key, value)) => Ok((key.to_string(), value.to_string())), + None => Err(format!("invalid KEY=VALUE: {}", s)), + } +} + #[derive(Subcommand)] enum SessionCommand { #[command(about = "List all available sessions")] @@ -81,17 +95,52 @@ enum SessionCommand { )] ascending: bool, }, - #[command(about = "Remove sessions")] + #[command(about = "Remove sessions. Runs interactively if no ID or regex is provided.")] Remove { - #[arg(short, long, help = "session id to be removed", default_value = "")] + #[arg(short, long, help = "Session ID to be removed (optional)")] + id: Option, + #[arg(short, long, help = "Regex for removing matched sessions (optional)")] + regex: Option, + }, +} + +#[derive(Subcommand, Debug)] +enum SchedulerCommand { + #[command(about = "Add a new scheduled job")] + Add { + #[arg(long, help = "Unique ID for the job")] id: String, + #[arg(long, help = "Cron string for the schedule (e.g., '0 0 * * * *')")] + cron: String, #[arg( - short, long, - help = "regex for removing matched session", - default_value = "" + help = "Recipe source (path to file, or base64 encoded recipe string)" )] - regex: String, + recipe_source: String, + }, + #[command(about = "List all scheduled jobs")] + List {}, + #[command(about = "Remove a scheduled job by ID")] + Remove { + #[arg(long, help = "ID of the job to remove")] // Changed from positional to named --id + id: String, + }, + /// List sessions created by a specific schedule + #[command(about = "List sessions created by a specific schedule")] + Sessions { + /// ID of the schedule + #[arg(long, help = "ID of the schedule")] // Explicitly make it --id + id: String, + /// Maximum number of sessions to return + #[arg(long, help = "Maximum number of sessions to return")] + limit: Option, + }, + /// Run a scheduled job immediately + #[command(about = "Run a scheduled job immediately")] + RunNow { + /// ID of the schedule to run + #[arg(long, help = "ID of the schedule to run")] // Explicitly make it --id + id: String, }, } @@ -134,24 +183,39 @@ pub enum BenchCommand { #[arg(short, long, help = "A serialized config file for the eval only.")] config: String, }, + + #[command( + name = "generate-leaderboard", + about = "Generate a leaderboard CSV from benchmark results" + )] + GenerateLeaderboard { + #[arg( + short, + long, + help = "Path to the benchmark directory containing model evaluation results" + )] + benchmark_dir: PathBuf, + }, } #[derive(Subcommand)] enum RecipeCommand { /// Validate a recipe file - #[command(about = "Validate a recipe file")] + #[command(about = "Validate a recipe")] Validate { - /// Path to the recipe file to validate - #[arg(help = "Path to the recipe file to validate")] - file: String, + /// Recipe name to get recipe file to validate + #[arg(help = "recipe name to get recipe file or full path to the recipe file to validate")] + recipe_name: String, }, /// Generate a deeplink for a recipe file - #[command(about = "Generate a deeplink for a recipe file")] + #[command(about = "Generate a deeplink for a recipe")] Deeplink { - /// Path to the recipe file - #[arg(help = "Path to the recipe file")] - file: String, + /// Recipe name to get recipe file to generate deeplink + #[arg( + help = "recipe name to get recipe file or full path to the recipe file to generate deeplink" + )] + recipe_name: String, }, } @@ -194,6 +258,14 @@ enum Command { )] resume: bool, + /// Show message history when resuming + #[arg( + long, + help = "Show previous messages when resuming a session", + requires = "resume" + )] + history: bool, + /// Enable debug output mode #[arg( long, @@ -202,6 +274,15 @@ enum Command { )] debug: bool, + /// Maximum number of consecutive identical tool calls allowed + #[arg( + long = "max-tool-repetitions", + value_name = "NUMBER", + help = "Maximum number of consecutive identical tool calls allowed", + long_help = "Set a limit on how many times the same tool can be called consecutively with identical parameters. Helps prevent infinite loops." + )] + max_tool_repetitions: Option, + /// Add stdio extensions with environment variables and commands #[arg( long = "with-extension", @@ -233,6 +314,14 @@ enum Command { builtins: Vec, }, + /// Open the last project directory + #[command(about = "Open the last project directory", visible_alias = "p")] + Project {}, + + /// List recent project directories + #[command(about = "List recent project directories", visible_alias = "ps")] + Projects, + /// Execute commands from an instruction file #[command(about = "Execute commands from an instruction file or stdin")] Run { @@ -259,18 +348,28 @@ enum Command { )] input_text: Option, - /// Path to recipe.yaml file + /// Recipe name or full path to the recipe file #[arg( short = None, long = "recipe", - value_name = "FILE", - help = "Path to recipe.yaml file", - long_help = "Path to a recipe.yaml file that defines a custom agent configuration", + value_name = "RECIPE_NAME or FULL_PATH_TO_RECIPE_FILE", + help = "Recipe name to get recipe file or the full path of the recipe file (use --explain to see recipe details)", + long_help = "Recipe name to get recipe file or the full path of the recipe file that defines a custom agent configuration. Use --explain to see the recipe's title, description, and parameters.", conflicts_with = "instructions", conflicts_with = "input_text" )] recipe: Option, + #[arg( + long, + value_name = "KEY=VALUE", + help = "Dynamic parameters (e.g., --params username=alice --params channel_name=goose-channel)", + long_help = "Key-value parameters to pass to the recipe file. Can be specified multiple times.", + action = clap::ArgAction::Append, + value_parser = parse_key_val, + )] + params: Vec<(String, String)>, + /// Continue in interactive mode after processing input #[arg( short = 's', @@ -279,6 +378,31 @@ enum Command { )] interactive: bool, + /// Run without storing a session file + #[arg( + long = "no-session", + help = "Run without storing a session file", + long_help = "Execute commands without creating or using a session file. Useful for automated runs.", + conflicts_with_all = ["resume", "name", "path"] + )] + no_session: bool, + + /// Show the recipe title, description, and parameters + #[arg( + long = "explain", + help = "Show the recipe title, description, and parameters" + )] + explain: bool, + + /// Maximum number of consecutive identical tool calls allowed + #[arg( + long = "max-tool-repetitions", + value_name = "NUMBER", + help = "Maximum number of consecutive identical tool calls allowed", + long_help = "Set a limit on how many times the same tool can be called consecutively with identical parameters. Helps prevent infinite loops." + )] + max_tool_repetitions: Option, + /// Identifier for this run session #[command(flatten)] identifier: Option, @@ -339,6 +463,13 @@ enum Command { command: RecipeCommand, }, + /// Manage scheduled jobs + #[command(about = "Manage scheduled jobs", visible_alias = "sched")] + Schedule { + #[command(subcommand)] + command: SchedulerCommand, + }, + /// Update the Goose CLI version #[command(about = "Update the goose CLI version")] Update { @@ -378,6 +509,11 @@ struct InputConfig { pub async fn cli() -> Result<()> { let cli = Cli::parse(); + // Track the current directory in projects.json + if let Err(e) = crate::project_tracker::update_project_tracker(None, None) { + eprintln!("Warning: Failed to update project tracker: {}", e); + } + match cli.command { Some(Command::Configure {}) => { let _ = handle_configure().await; @@ -394,7 +530,9 @@ pub async fn cli() -> Result<()> { command, identifier, resume, + history, debug, + max_tool_repetitions, extensions, remote_extensions, builtins, @@ -414,26 +552,44 @@ pub async fn cli() -> Result<()> { } None => { // Run session command by default - let mut session = build_session(SessionBuilderConfig { + let mut session: crate::Session = build_session(SessionBuilderConfig { identifier: identifier.map(extract_identifier), resume, + no_session: false, extensions, remote_extensions, builtins, extensions_override: None, additional_system_prompt: None, debug, + max_tool_repetitions, }) .await; setup_logging( session.session_file().file_stem().and_then(|s| s.to_str()), None, )?; + + // Render previous messages if resuming a session and history flag is set + if resume && history { + session.render_message_history(); + } + let _ = session.interactive(None).await; Ok(()) } }; } + Some(Command::Project {}) => { + // Default behavior: offer to resume the last project + handle_project_default()?; + return Ok(()); + } + Some(Command::Projects) => { + // Interactive project selection + handle_projects_interactive()?; + return Ok(()); + } Some(Command::Run { instructions, input_text, @@ -441,13 +597,17 @@ pub async fn cli() -> Result<()> { interactive, identifier, resume, + no_session, debug, + max_tool_repetitions, extensions, remote_extensions, builtins, + params, + explain, }) => { - let input_config = match (instructions, input_text, recipe) { - (Some(file), _, _) if file == "-" => { + let input_config = match (instructions, input_text, recipe, explain) { + (Some(file), _, _, _) if file == "-" => { let mut input = String::new(); std::io::stdin() .read_to_string(&mut input) @@ -459,7 +619,7 @@ pub async fn cli() -> Result<()> { additional_system_prompt: None, } } - (Some(file), _, _) => { + (Some(file), _, _, _) => { let contents = std::fs::read_to_string(&file).unwrap_or_else(|err| { eprintln!( "Instruction file not found — did you mean to use goose run --text?\n{}", @@ -473,23 +633,28 @@ pub async fn cli() -> Result<()> { additional_system_prompt: None, } } - (_, Some(text), _) => InputConfig { + (_, Some(text), _, _) => InputConfig { contents: Some(text), extensions_override: None, additional_system_prompt: None, }, - (_, _, Some(file)) => { - let recipe = load_recipe(&file, true).unwrap_or_else(|err| { - eprintln!("{}: {}", console::style("Error").red().bold(), err); - std::process::exit(1); - }); + (_, _, Some(recipe_name), explain) => { + if explain { + explain_recipe_with_parameters(&recipe_name, params)?; + return Ok(()); + } + let recipe = + load_recipe_as_template(&recipe_name, params).unwrap_or_else(|err| { + eprintln!("{}: {}", console::style("Error").red().bold(), err); + std::process::exit(1); + }); InputConfig { contents: recipe.prompt, extensions_override: recipe.extensions, - additional_system_prompt: Some(recipe.instructions), + additional_system_prompt: recipe.instructions, } } - (None, None, None) => { + (None, None, None, _) => { eprintln!("Error: Must provide either --instructions (-i), --text (-t), or --recipe. Use -i - for stdin."); std::process::exit(1); } @@ -498,12 +663,14 @@ pub async fn cli() -> Result<()> { let mut session = build_session(SessionBuilderConfig { identifier: identifier.map(extract_identifier), resume, + no_session, extensions, remote_extensions, builtins, extensions_override: input_config.extensions_override, additional_system_prompt: input_config.additional_system_prompt, debug, + max_tool_repetitions, }) .await; @@ -523,6 +690,32 @@ pub async fn cli() -> Result<()> { return Ok(()); } + Some(Command::Schedule { command }) => { + match command { + SchedulerCommand::Add { + id, + cron, + recipe_source, + } => { + handle_schedule_add(id, cron, recipe_source).await?; + } + SchedulerCommand::List {} => { + handle_schedule_list().await?; + } + SchedulerCommand::Remove { id } => { + handle_schedule_remove(id).await?; + } + SchedulerCommand::Sessions { id, limit } => { + // New arm + handle_schedule_sessions(id, limit).await?; + } + SchedulerCommand::RunNow { id } => { + // New arm + handle_schedule_run_now(id).await?; + } + } + return Ok(()); + } Some(Command::Update { canary, reconfigure, @@ -533,22 +726,31 @@ pub async fn cli() -> Result<()> { Some(Command::Bench { cmd }) => { match cmd { BenchCommand::Selectors { config } => BenchRunner::list_selectors(config)?, - BenchCommand::InitConfig { name } => BenchRunConfig::default().save(name), + BenchCommand::InitConfig { name } => { + let mut config = BenchRunConfig::default(); + let cwd = + std::env::current_dir().expect("Failed to get current working directory"); + config.output_dir = Some(cwd); + config.save(name); + } BenchCommand::Run { config } => BenchRunner::new(config)?.run()?, BenchCommand::EvalModel { config } => ModelRunner::from(config)?.run()?, BenchCommand::ExecEval { config } => { EvalRunner::from(config)?.run(agent_generator).await? } + BenchCommand::GenerateLeaderboard { benchmark_dir } => { + MetricAggregator::generate_csv_from_benchmark_dir(&benchmark_dir)? + } } return Ok(()); } Some(Command::Recipe { command }) => { match command { - RecipeCommand::Validate { file } => { - handle_validate(file)?; + RecipeCommand::Validate { recipe_name } => { + handle_validate(&recipe_name)?; } - RecipeCommand::Deeplink { file } => { - handle_deeplink(file)?; + RecipeCommand::Deeplink { recipe_name } => { + handle_deeplink(&recipe_name)?; } } return Ok(()); @@ -559,7 +761,19 @@ pub async fn cli() -> Result<()> { Ok(()) } else { // Run session command by default - let mut session = build_session(SessionBuilderConfig::default()).await; + let mut session = build_session(SessionBuilderConfig { + identifier: None, + resume: false, + no_session: false, + extensions: Vec::new(), + remote_extensions: Vec::new(), + builtins: Vec::new(), + extensions_override: None, + additional_system_prompt: None, + debug: false, + max_tool_repetitions: None, + }) + .await; setup_logging( session.session_file().file_stem().and_then(|s| s.to_str()), None, diff --git a/crates/goose-cli/src/commands/bench.rs b/crates/goose-cli/src/commands/bench.rs index 8055ac9b..ee9514e2 100644 --- a/crates/goose-cli/src/commands/bench.rs +++ b/crates/goose-cli/src/commands/bench.rs @@ -34,12 +34,14 @@ pub async fn agent_generator( let base_session = build_session(SessionBuilderConfig { identifier, resume: false, + no_session: false, extensions: requirements.external, remote_extensions: requirements.remote, builtins: requirements.builtin, extensions_override: None, additional_system_prompt: None, debug: false, + max_tool_repetitions: None, }) .await; diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index ec6ca71d..7e49c50c 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -21,6 +21,8 @@ use serde_json::{json, Value}; use std::collections::HashMap; use std::error::Error; +use crate::recipes::github_recipe::GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY; + // useful for light themes where there is no dicernible colour contrast between // cursor-selected and cursor-unselected items. const MULTISELECT_VISIBILITY_HINT: &str = "<"; @@ -193,7 +195,7 @@ pub async fn handle_configure() -> Result<(), Box> { .item( "settings", "Goose Settings", - "Set the Goose Mode, Tool Output, Tool Permissions, Experiment and more", + "Set the Goose Mode, Tool Output, Tool Permissions, Experiment, Goose recipe github repo and more", ) .interact()?; @@ -325,11 +327,40 @@ pub async fn configure_provider_dialog() -> Result> { } } - // Select model, defaulting to the provider's recommended model UNLESS there is an env override - let default_model = std::env::var("GOOSE_MODEL").unwrap_or(provider_meta.default_model.clone()); - let model: String = cliclack::input("Enter a model from that provider:") - .default_input(&default_model) - .interact()?; + // Attempt to fetch supported models for this provider + let spin = spinner(); + spin.start("Attempting to fetch supported models..."); + let models_res = { + let temp_model_config = goose::model::ModelConfig::new(provider_meta.default_model.clone()); + let temp_provider = create(provider_name, temp_model_config)?; + temp_provider.fetch_supported_models_async().await + }; + spin.stop(style("Model fetch complete").green()); + + // Select a model: on fetch error show styled error and abort; if Some(models), show list; if None, free-text input + let model: String = match models_res { + Err(e) => { + // Provider hook error + cliclack::outro(style(e.to_string()).on_red().white())?; + return Ok(false); + } + Ok(Some(models)) => cliclack::select("Select a model:") + .items( + &models + .iter() + .map(|m| (m, m.as_str(), "")) + .collect::>(), + ) + .interact()? + .to_string(), + Ok(None) => { + let default_model = + std::env::var("GOOSE_MODEL").unwrap_or(provider_meta.default_model.clone()); + cliclack::input("Enter a model from that provider:") + .default_input(&default_model) + .interact()? + } + }; // Test the configuration let spin = spinner(); @@ -793,6 +824,11 @@ pub fn remove_extension_dialog() -> Result<(), Box> { pub async fn configure_settings_dialog() -> Result<(), Box> { let setting_type = cliclack::select("What setting would you like to configure?") .item("goose_mode", "Goose Mode", "Configure Goose mode") + .item( + "goose_router_strategy", + "Router Tool Selection Strategy", + "Configure the strategy for selecting tools to use", + ) .item( "tool_permission", "Tool Permission", @@ -808,12 +844,20 @@ pub async fn configure_settings_dialog() -> Result<(), Box> { "Toggle Experiment", "Enable or disable an experiment feature", ) + .item( + "recipe", + "Goose recipe github repo", + "Goose will pull recipes from this repo if not found locally.", + ) .interact()?; match setting_type { "goose_mode" => { configure_goose_mode_dialog()?; } + "goose_router_strategy" => { + configure_goose_router_strategy_dialog()?; + } "tool_permission" => { configure_tool_permissions_dialog().await.and(Ok(()))?; } @@ -823,6 +867,9 @@ pub async fn configure_settings_dialog() -> Result<(), Box> { "experiment" => { toggle_experiments_dialog()?; } + "recipe" => { + configure_recipe_dialog()?; + } _ => unreachable!(), }; @@ -882,6 +929,49 @@ pub fn configure_goose_mode_dialog() -> Result<(), Box> { Ok(()) } +pub fn configure_goose_router_strategy_dialog() -> Result<(), Box> { + let config = Config::global(); + + // Check if GOOSE_ROUTER_STRATEGY is set as an environment variable + if std::env::var("GOOSE_ROUTER_TOOL_SELECTION_STRATEGY").is_ok() { + let _ = cliclack::log::info("Notice: GOOSE_ROUTER_TOOL_SELECTION_STRATEGY environment variable is set. Configuration will override this."); + } + + let strategy = cliclack::select("Which router strategy would you like to use?") + .item( + "vector", + "Vector Strategy", + "Use vector-based similarity to select tools", + ) + .item( + "default", + "Default Strategy", + "Use the default tool selection strategy", + ) + .interact()?; + + match strategy { + "vector" => { + config.set_param( + "GOOSE_ROUTER_TOOL_SELECTION_STRATEGY", + Value::String("vector".to_string()), + )?; + cliclack::outro( + "Set to Vector Strategy - using vector-based similarity for tool selection", + )?; + } + "default" => { + config.set_param( + "GOOSE_ROUTER_TOOL_SELECTION_STRATEGY", + Value::String("default".to_string()), + )?; + cliclack::outro("Set to Default Strategy - using default tool selection")?; + } + _ => unreachable!(), + }; + Ok(()) +} + pub fn configure_tool_output_dialog() -> Result<(), Box> { let config = Config::global(); // Check if GOOSE_CLI_MIN_PRIORITY is set as an environment variable @@ -1104,3 +1194,26 @@ pub async fn configure_tool_permissions_dialog() -> Result<(), Box> { Ok(()) } + +fn configure_recipe_dialog() -> Result<(), Box> { + let key_name = GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY; + let config = Config::global(); + let default_recipe_repo = std::env::var(key_name) + .ok() + .or_else(|| config.get_param(key_name).unwrap_or(None)); + let mut recipe_repo_input = cliclack::input( + "Enter your Goose Recipe Github repo (owner/repo): eg: my_org/goose-recipes", + ) + .required(false); + if let Some(recipe_repo) = default_recipe_repo { + recipe_repo_input = recipe_repo_input.default_input(&recipe_repo); + } + let input_value: String = recipe_repo_input.interact()?; + // if input is blank, it clears the recipe github repo settings in the config file + if input_value.clone().trim().is_empty() { + config.delete(key_name)?; + } else { + config.set_param(key_name, Value::String(input_value))?; + } + Ok(()) +} diff --git a/crates/goose-cli/src/commands/mcp.rs b/crates/goose-cli/src/commands/mcp.rs index 791fe5ad..8fbd399f 100644 --- a/crates/goose-cli/src/commands/mcp.rs +++ b/crates/goose-cli/src/commands/mcp.rs @@ -7,6 +7,16 @@ use mcp_server::router::RouterService; use mcp_server::{BoundedService, ByteTransport, Server}; use tokio::io::{stdin, stdout}; +use std::sync::Arc; +use tokio::sync::Notify; + +#[cfg(unix)] +use nix::sys::signal::{kill, Signal}; +#[cfg(unix)] +use nix::unistd::getpgrp; +#[cfg(unix)] +use nix::unistd::Pid; + pub async fn run_server(name: &str) -> Result<()> { // Initialize logging crate::logging::setup_logging(Some(&format!("mcp-{name}")), None)?; @@ -26,10 +36,38 @@ pub async fn run_server(name: &str) -> Result<()> { _ => None, }; + // Create shutdown notification channel + let shutdown = Arc::new(Notify::new()); + let shutdown_clone = shutdown.clone(); + + // Spawn shutdown signal handler + tokio::spawn(async move { + crate::signal::shutdown_signal().await; + shutdown_clone.notify_one(); + }); + // Create and run the server let server = Server::new(router.unwrap_or_else(|| panic!("Unknown server requested {}", name))); let transport = ByteTransport::new(stdin(), stdout()); tracing::info!("Server initialized and ready to handle requests"); - Ok(server.run(transport).await?) + + tokio::select! { + result = server.run(transport) => { + Ok(result?) + } + _ = shutdown.notified() => { + // On Unix systems, kill the entire process group + #[cfg(unix)] + { + fn terminate_process_group() { + let pgid = getpgrp(); + kill(Pid::from_raw(-pgid.as_raw()), Signal::SIGTERM) + .expect("Failed to send SIGTERM to process group"); + } + terminate_process_group(); + } + Ok(()) + } + } } diff --git a/crates/goose-cli/src/commands/mod.rs b/crates/goose-cli/src/commands/mod.rs index 30840daa..bda22fbd 100644 --- a/crates/goose-cli/src/commands/mod.rs +++ b/crates/goose-cli/src/commands/mod.rs @@ -2,6 +2,8 @@ pub mod bench; pub mod configure; pub mod info; pub mod mcp; +pub mod project; pub mod recipe; +pub mod schedule; pub mod session; pub mod update; diff --git a/crates/goose-cli/src/commands/project.rs b/crates/goose-cli/src/commands/project.rs new file mode 100644 index 00000000..07ed8c2e --- /dev/null +++ b/crates/goose-cli/src/commands/project.rs @@ -0,0 +1,307 @@ +use anyhow::Result; +use chrono::DateTime; +use cliclack::{self, intro, outro}; +use std::path::Path; + +use crate::project_tracker::ProjectTracker; + +/// Format a DateTime for display +fn format_date(date: DateTime) -> String { + // Format: "2025-05-08 18:15:30" + date.format("%Y-%m-%d %H:%M:%S").to_string() +} + +/// Handle the default project command +/// +/// Offers options to resume the most recently accessed project +pub fn handle_project_default() -> Result<()> { + let tracker = ProjectTracker::load()?; + let mut projects = tracker.list_projects(); + + if projects.is_empty() { + // If no projects exist, just start a new one in the current directory + println!("No previous projects found. Starting a new session in the current directory."); + let mut command = std::process::Command::new("goose"); + command.arg("session"); + let status = command.status()?; + + if !status.success() { + println!("Failed to run Goose. Exit code: {:?}", status.code()); + } + return Ok(()); + } + + // Sort projects by last_accessed (newest first) + projects.sort_by(|a, b| b.last_accessed.cmp(&a.last_accessed)); + + // Get the most recent project + let project = &projects[0]; + let project_dir = &project.path; + + // Check if the directory exists + if !Path::new(project_dir).exists() { + println!( + "Most recent project directory '{}' no longer exists.", + project_dir + ); + return Ok(()); + } + + // Format the path for display + let path = Path::new(project_dir); + let components: Vec<_> = path.components().collect(); + let len = components.len(); + let short_path = if len <= 2 { + project_dir.clone() + } else { + let mut path_str = String::new(); + path_str.push_str("..."); + for component in components.iter().skip(len - 2) { + path_str.push('/'); + path_str.push_str(component.as_os_str().to_string_lossy().as_ref()); + } + path_str + }; + + // Ask the user what they want to do + let _ = intro("Goose Project Manager"); + + let current_dir = std::env::current_dir()?; + let current_dir_display = current_dir.display(); + + let choice = cliclack::select("Choose an option:") + .item( + "resume", + format!("Resume project with session: {}", short_path), + "Continue with the previous session", + ) + .item( + "fresh", + format!("Resume project with fresh session: {}", short_path), + "Change to the project directory but start a new session", + ) + .item( + "new", + format!( + "Start new project in current directory: {}", + current_dir_display + ), + "Stay in the current directory and start a new session", + ) + .interact()?; + + match choice { + "resume" => { + let _ = outro(format!("Changing to directory: {}", project_dir)); + + // Get the session ID if available + let session_id = project.last_session_id.clone(); + + // Change to the project directory + std::env::set_current_dir(project_dir)?; + + // Build the command to run Goose + let mut command = std::process::Command::new("goose"); + command.arg("session"); + + if let Some(id) = session_id { + command.arg("--name").arg(&id).arg("--resume"); + println!("Resuming session: {}", id); + } + + // Execute the command + let status = command.status()?; + + if !status.success() { + println!("Failed to run Goose. Exit code: {:?}", status.code()); + } + } + "fresh" => { + let _ = outro(format!( + "Changing to directory: {} with a fresh session", + project_dir + )); + + // Change to the project directory + std::env::set_current_dir(project_dir)?; + + // Build the command to run Goose with a fresh session + let mut command = std::process::Command::new("goose"); + command.arg("session"); + + // Execute the command + let status = command.status()?; + + if !status.success() { + println!("Failed to run Goose. Exit code: {:?}", status.code()); + } + } + "new" => { + let _ = outro("Starting a new session in the current directory"); + + // Build the command to run Goose + let mut command = std::process::Command::new("goose"); + command.arg("session"); + + // Execute the command + let status = command.status()?; + + if !status.success() { + println!("Failed to run Goose. Exit code: {:?}", status.code()); + } + } + _ => { + let _ = outro("Operation canceled"); + } + } + + Ok(()) +} + +/// Handle the interactive projects command +/// +/// Shows a list of projects and lets the user select one to resume +pub fn handle_projects_interactive() -> Result<()> { + let tracker = ProjectTracker::load()?; + let mut projects = tracker.list_projects(); + + if projects.is_empty() { + println!("No projects found."); + return Ok(()); + } + + // Sort projects by last_accessed (newest first) + projects.sort_by(|a, b| b.last_accessed.cmp(&a.last_accessed)); + + // Format project paths for display + let project_choices: Vec<(String, String)> = projects + .iter() + .enumerate() + .map(|(i, project)| { + let path = Path::new(&project.path); + let components: Vec<_> = path.components().collect(); + let len = components.len(); + let short_path = if len <= 2 { + project.path.clone() + } else { + let mut path_str = String::new(); + path_str.push_str("..."); + for component in components.iter().skip(len - 2) { + path_str.push('/'); + path_str.push_str(component.as_os_str().to_string_lossy().as_ref()); + } + path_str + }; + + // Include last instruction if available (truncated) + let instruction_preview = + project + .last_instruction + .as_ref() + .map_or(String::new(), |instr| { + let truncated = if instr.len() > 40 { + format!("{}...", &instr[0..37]) + } else { + instr.clone() + }; + format!(" [{}]", truncated) + }); + + let formatted_date = format_date(project.last_accessed); + ( + format!("{}", i + 1), // Value to return + format!("{} ({}){}", short_path, formatted_date, instruction_preview), // Display text with instruction + ) + }) + .collect(); + + // Let the user select a project + let _ = intro("Goose Project Manager"); + let mut select = cliclack::select("Select a project:"); + + // Add each project as an option + for (value, display) in &project_choices { + select = select.item(value, display, ""); + } + + // Add a cancel option + let cancel_value = String::from("cancel"); + select = select.item(&cancel_value, "Cancel", "Don't resume any project"); + + let selected = select.interact()?; + + if selected == "cancel" { + let _ = outro("Project selection canceled."); + return Ok(()); + } + + // Parse the selected index + let index = selected.parse::().unwrap_or(0); + if index == 0 || index > projects.len() { + let _ = outro("Invalid selection."); + return Ok(()); + } + + // Get the selected project + let project = &projects[index - 1]; + let project_dir = &project.path; + + // Check if the directory exists + if !Path::new(project_dir).exists() { + let _ = outro(format!( + "Project directory '{}' no longer exists.", + project_dir + )); + return Ok(()); + } + + // Ask if the user wants to resume the session or start a new one + let session_id = project.last_session_id.clone(); + let has_previous_session = session_id.is_some(); + + // Change to the project directory first + std::env::set_current_dir(project_dir)?; + let _ = outro(format!("Changed to directory: {}", project_dir)); + + // Only ask about resuming if there's a previous session + let resume_session = if has_previous_session { + let session_choice = cliclack::select("What would you like to do?") + .item( + "resume", + "Resume previous session", + "Continue with the previous session", + ) + .item( + "new", + "Start new session", + "Start a fresh session in this project directory", + ) + .interact()?; + + session_choice == "resume" + } else { + false + }; + + // Build the command to run Goose + let mut command = std::process::Command::new("goose"); + command.arg("session"); + + if resume_session { + if let Some(id) = session_id { + command.arg("--name").arg(&id).arg("--resume"); + println!("Resuming session: {}", id); + } + } else { + println!("Starting new session"); + } + + // Execute the command + let status = command.status()?; + + if !status.success() { + println!("Failed to run Goose. Exit code: {:?}", status.code()); + } + + Ok(()) +} diff --git a/crates/goose-cli/src/commands/recipe.rs b/crates/goose-cli/src/commands/recipe.rs index 388133ef..9abc036b 100644 --- a/crates/goose-cli/src/commands/recipe.rs +++ b/crates/goose-cli/src/commands/recipe.rs @@ -1,9 +1,8 @@ use anyhow::Result; use base64::Engine; use console::style; -use std::path::Path; -use crate::recipe::load_recipe; +use crate::recipes::recipe::load_recipe; /// Validates a recipe file /// @@ -14,9 +13,9 @@ use crate::recipe::load_recipe; /// # Returns /// /// Result indicating success or failure -pub fn handle_validate>(file_path: P) -> Result<()> { +pub fn handle_validate(recipe_name: &str) -> Result<()> { // Load and validate the recipe file - match load_recipe(&file_path, false) { + match load_recipe(recipe_name) { Ok(_) => { println!("{} recipe file is valid", style("✓").green().bold()); Ok(()) @@ -37,9 +36,9 @@ pub fn handle_validate>(file_path: P) -> Result<()> { /// # Returns /// /// Result indicating success or failure -pub fn handle_deeplink>(file_path: P) -> Result<()> { +pub fn handle_deeplink(recipe_name: &str) -> Result<()> { // Load the recipe file first to validate it - match load_recipe(&file_path, false) { + match load_recipe(recipe_name) { Ok(recipe) => { if let Ok(recipe_json) = serde_json::to_string(&recipe) { let deeplink = base64::engine::general_purpose::STANDARD.encode(recipe_json); diff --git a/crates/goose-cli/src/commands/schedule.rs b/crates/goose-cli/src/commands/schedule.rs new file mode 100644 index 00000000..cfe27a47 --- /dev/null +++ b/crates/goose-cli/src/commands/schedule.rs @@ -0,0 +1,186 @@ +use anyhow::{bail, Context, Result}; +use base64::engine::{general_purpose::STANDARD as BASE64_STANDARD, Engine}; +use goose::scheduler::{ + get_default_scheduled_recipes_dir, get_default_scheduler_storage_path, ScheduledJob, Scheduler, + SchedulerError, +}; +use std::path::Path; + +// Base64 decoding function - might be needed if recipe_source_arg can be base64 +// For now, handle_schedule_add will assume it's a path. +async fn _decode_base64_recipe(source: &str) -> Result { + let bytes = BASE64_STANDARD + .decode(source.as_bytes()) + .with_context(|| "Recipe source is not a valid path and not valid Base64.")?; + String::from_utf8(bytes).with_context(|| "Decoded Base64 recipe source is not valid UTF-8.") +} + +pub async fn handle_schedule_add( + id: String, + cron: String, + recipe_source_arg: String, // This is expected to be a file path by the Scheduler +) -> Result<()> { + println!( + "[CLI Debug] Scheduling job ID: {}, Cron: {}, Recipe Source Path: {}", + id, cron, recipe_source_arg + ); + + // The Scheduler's add_scheduled_job will handle copying the recipe from recipe_source_arg + // to its internal storage and validating the path. + let job = ScheduledJob { + id: id.clone(), + source: recipe_source_arg.clone(), // Pass the original user-provided path + cron, + last_run: None, + currently_running: false, + paused: false, + }; + + let scheduler_storage_path = + get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?; + let scheduler = Scheduler::new(scheduler_storage_path) + .await + .context("Failed to initialize scheduler")?; + + match scheduler.add_scheduled_job(job).await { + Ok(_) => { + // The scheduler has copied the recipe to its internal directory. + // We can reconstruct the likely path for display if needed, or adjust success message. + let scheduled_recipes_dir = get_default_scheduled_recipes_dir() + .unwrap_or_else(|_| Path::new("./.goose_scheduled_recipes").to_path_buf()); // Fallback for display + let extension = Path::new(&recipe_source_arg) + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("yaml"); + let final_recipe_path = scheduled_recipes_dir.join(format!("{}.{}", id, extension)); + + println!( + "Scheduled job '{}' added. Recipe expected at {:?}", + id, final_recipe_path + ); + Ok(()) + } + Err(e) => { + // No local file to clean up by the CLI in this revised flow. + match e { + SchedulerError::JobIdExists(job_id) => { + bail!("Error: Job with ID '{}' already exists.", job_id); + } + SchedulerError::RecipeLoadError(msg) => { + bail!( + "Error with recipe source: {}. Path: {}", + msg, + recipe_source_arg + ); + } + _ => Err(anyhow::Error::new(e)) + .context(format!("Failed to add job '{}' to scheduler", id)), + } + } + } +} + +pub async fn handle_schedule_list() -> Result<()> { + let scheduler_storage_path = + get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?; + let scheduler = Scheduler::new(scheduler_storage_path) + .await + .context("Failed to initialize scheduler")?; + + let jobs = scheduler.list_scheduled_jobs().await; + if jobs.is_empty() { + println!("No scheduled jobs found."); + } else { + println!("Scheduled Jobs:"); + for job in jobs { + println!( + "- ID: {}\n Cron: {}\n Recipe Source (in store): {}\n Last Run: {}", + job.id, + job.cron, + job.source, // This source is now the path within scheduled_recipes_dir + job.last_run + .map_or_else(|| "Never".to_string(), |dt| dt.to_rfc3339()) + ); + } + } + Ok(()) +} + +pub async fn handle_schedule_remove(id: String) -> Result<()> { + let scheduler_storage_path = + get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?; + let scheduler = Scheduler::new(scheduler_storage_path) + .await + .context("Failed to initialize scheduler")?; + + match scheduler.remove_scheduled_job(&id).await { + Ok(_) => { + println!("Scheduled job '{}' and its associated recipe removed.", id); + Ok(()) + } + Err(e) => match e { + SchedulerError::JobNotFound(job_id) => { + bail!("Error: Job with ID '{}' not found.", job_id); + } + _ => Err(anyhow::Error::new(e)) + .context(format!("Failed to remove job '{}' from scheduler", id)), + }, + } +} + +pub async fn handle_schedule_sessions(id: String, limit: Option) -> Result<()> { + let scheduler_storage_path = + get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?; + let scheduler = Scheduler::new(scheduler_storage_path) + .await + .context("Failed to initialize scheduler")?; + + match scheduler.sessions(&id, limit.unwrap_or(50) as usize).await { + Ok(sessions) => { + if sessions.is_empty() { + println!("No sessions found for schedule ID '{}'.", id); + } else { + println!("Sessions for schedule ID '{}':", id); + // sessions is now Vec<(String, SessionMetadata)> + for (session_name, metadata) in sessions { + println!( + " - Session ID: {}, Working Dir: {}, Description: \"{}\", Messages: {}, Schedule ID: {:?}", + session_name, // Display the session_name as Session ID + metadata.working_dir.display(), + metadata.description, + metadata.message_count, + metadata.schedule_id.as_deref().unwrap_or("N/A") + ); + } + } + } + Err(e) => { + bail!("Failed to get sessions for schedule '{}': {:?}", id, e); + } + } + Ok(()) +} + +pub async fn handle_schedule_run_now(id: String) -> Result<()> { + let scheduler_storage_path = + get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?; + let scheduler = Scheduler::new(scheduler_storage_path) + .await + .context("Failed to initialize scheduler")?; + + match scheduler.run_now(&id).await { + Ok(session_id) => { + println!( + "Successfully triggered schedule '{}'. New session ID: {}", + id, session_id + ); + } + Err(e) => match e { + SchedulerError::JobNotFound(job_id) => { + bail!("Error: Job with ID '{}' not found.", job_id); + } + _ => bail!("Failed to run schedule '{}' now: {:?}", id, e), + }, + } + Ok(()) +} diff --git a/crates/goose-cli/src/commands/session.rs b/crates/goose-cli/src/commands/session.rs index 5d8f8bed..add1572d 100644 --- a/crates/goose-cli/src/commands/session.rs +++ b/crates/goose-cli/src/commands/session.rs @@ -1,18 +1,20 @@ use anyhow::{Context, Result}; +use cliclack::{confirm, multiselect}; use goose::session::info::{get_session_info, SessionInfo, SortOrder}; use regex::Regex; use std::fs; +const TRUNCATED_DESC_LENGTH: usize = 60; + pub fn remove_sessions(sessions: Vec) -> Result<()> { println!("The following sessions will be removed:"); for session in &sessions { println!("- {}", session.id); } - let should_delete = - cliclack::confirm("Are you sure you want to delete all these sessions? (yes/no):") - .initial_value(true) - .interact()?; + let should_delete = confirm("Are you sure you want to delete these sessions?") + .initial_value(false) + .interact()?; if should_delete { for session in sessions { @@ -27,8 +29,50 @@ pub fn remove_sessions(sessions: Vec) -> Result<()> { Ok(()) } -pub fn handle_session_remove(id: String, regex_string: String) -> Result<()> { - let sessions = match get_session_info(SortOrder::Descending) { +fn prompt_interactive_session_selection(sessions: &[SessionInfo]) -> Result> { + if sessions.is_empty() { + println!("No sessions to delete."); + return Ok(vec![]); + } + + let mut selector = multiselect( + "Select sessions to delete (use spacebar, Enter to confirm, Ctrl+C to cancel):", + ); + + let display_map: std::collections::HashMap = sessions + .iter() + .map(|s| { + let desc = if s.metadata.description.is_empty() { + "(no description)" + } else { + &s.metadata.description + }; + let truncated_desc = if desc.len() > TRUNCATED_DESC_LENGTH { + format!("{}...", &desc[..TRUNCATED_DESC_LENGTH - 3]) + } else { + desc.to_string() + }; + let display_text = format!("{} - {} ({})", s.modified, truncated_desc, s.id); + (display_text, s.clone()) + }) + .collect(); + + for display_text in display_map.keys() { + selector = selector.item(display_text.clone(), display_text.clone(), ""); + } + + let selected_display_texts: Vec = selector.interact()?; + + let selected_sessions: Vec = selected_display_texts + .into_iter() + .filter_map(|text| display_map.get(&text).cloned()) + .collect(); + + Ok(selected_sessions) +} + +pub fn handle_session_remove(id: Option, regex_string: Option) -> Result<()> { + let all_sessions = match get_session_info(SortOrder::Descending) { Ok(sessions) => sessions, Err(e) => { tracing::error!("Failed to retrieve sessions: {:?}", e); @@ -37,29 +81,35 @@ pub fn handle_session_remove(id: String, regex_string: String) -> Result<()> { }; let matched_sessions: Vec; - if !id.is_empty() { - if let Some(session) = sessions.iter().find(|s| s.id == id) { + + if let Some(id_val) = id { + if let Some(session) = all_sessions.iter().find(|s| s.id == id_val) { matched_sessions = vec![session.clone()]; } else { - return Err(anyhow::anyhow!("Session '{}' not found.", id)); + return Err(anyhow::anyhow!("Session '{}' not found.", id_val)); } - } else if !regex_string.is_empty() { - let session_regex = Regex::new(®ex_string) - .with_context(|| format!("Invalid regex pattern '{}'", regex_string))?; - matched_sessions = sessions + } else if let Some(regex_val) = regex_string { + let session_regex = Regex::new(®ex_val) + .with_context(|| format!("Invalid regex pattern '{}'", regex_val))?; + + matched_sessions = all_sessions .into_iter() .filter(|session| session_regex.is_match(&session.id)) .collect(); if matched_sessions.is_empty() { - println!( - "Regex string '{}' does not match any sessions", - regex_string - ); + println!("Regex string '{}' does not match any sessions", regex_val); return Ok(()); } } else { - return Err(anyhow::anyhow!("Neither --regex nor --id flags provided.")); + if all_sessions.is_empty() { + return Err(anyhow::anyhow!("No sessions found.")); + } + matched_sessions = prompt_interactive_session_selection(&all_sessions)?; + } + + if matched_sessions.is_empty() { + return Ok(()); } remove_sessions(matched_sessions) diff --git a/crates/goose-cli/src/lib.rs b/crates/goose-cli/src/lib.rs index 1eb18f2e..68f2357f 100644 --- a/crates/goose-cli/src/lib.rs +++ b/crates/goose-cli/src/lib.rs @@ -3,9 +3,10 @@ use once_cell::sync::Lazy; pub mod cli; pub mod commands; pub mod logging; -pub mod recipe; +pub mod project_tracker; +pub mod recipes; pub mod session; - +pub mod signal; // Re-export commonly used types pub use session::Session; diff --git a/crates/goose-cli/src/project_tracker.rs b/crates/goose-cli/src/project_tracker.rs new file mode 100644 index 00000000..1b11bbf3 --- /dev/null +++ b/crates/goose-cli/src/project_tracker.rs @@ -0,0 +1,146 @@ +use anyhow::{Context, Result}; +use chrono::{DateTime, Utc}; +use etcetera::{choose_app_strategy, AppStrategy}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; + +/// Structure to track project information +#[derive(Debug, Serialize, Deserialize)] +pub struct ProjectInfo { + /// The absolute path to the project directory + pub path: String, + /// Last time the project was accessed + pub last_accessed: DateTime, + /// Last instruction sent to goose (if available) + pub last_instruction: Option, + /// Last session ID associated with this project + pub last_session_id: Option, +} + +/// Structure to hold all tracked projects +#[derive(Debug, Serialize, Deserialize)] +pub struct ProjectTracker { + projects: HashMap, +} + +/// Project information with path as a separate field for easier access +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProjectInfoDisplay { + /// The absolute path to the project directory + pub path: String, + /// Last time the project was accessed + pub last_accessed: DateTime, + /// Last instruction sent to goose (if available) + pub last_instruction: Option, + /// Last session ID associated with this project + pub last_session_id: Option, +} + +impl ProjectTracker { + /// Get the path to the projects.json file + fn get_projects_file() -> Result { + let projects_file = choose_app_strategy(crate::APP_STRATEGY.clone()) + .context("goose requires a home dir")? + .in_data_dir("projects.json"); + + // Ensure data directory exists + if let Some(parent) = projects_file.parent() { + if !parent.exists() { + fs::create_dir_all(parent)?; + } + } + + Ok(projects_file) + } + + /// Load the project tracker from the projects.json file + pub fn load() -> Result { + let projects_file = Self::get_projects_file()?; + + if projects_file.exists() { + let file_content = fs::read_to_string(&projects_file)?; + let tracker: ProjectTracker = serde_json::from_str(&file_content) + .context("Failed to parse projects.json file")?; + Ok(tracker) + } else { + // If the file doesn't exist, create a new empty tracker + Ok(ProjectTracker { + projects: HashMap::new(), + }) + } + } + + /// Save the project tracker to the projects.json file + pub fn save(&self) -> Result<()> { + let projects_file = Self::get_projects_file()?; + let json = serde_json::to_string_pretty(self)?; + fs::write(projects_file, json)?; + Ok(()) + } + + /// Update project information for the current directory + /// + /// # Arguments + /// * `project_dir` - The project directory to update + /// * `instruction` - Optional instruction that was sent to goose + /// * `session_id` - Optional session ID associated with this project + pub fn update_project( + &mut self, + project_dir: &Path, + instruction: Option<&str>, + session_id: Option<&str>, + ) -> Result<()> { + let dir_str = project_dir.to_string_lossy().to_string(); + + // Create or update the project entry + let project_info = self.projects.entry(dir_str.clone()).or_insert(ProjectInfo { + path: dir_str, + last_accessed: Utc::now(), + last_instruction: None, + last_session_id: None, + }); + + // Update the last accessed time + project_info.last_accessed = Utc::now(); + + // Update the last instruction if provided + if let Some(instr) = instruction { + project_info.last_instruction = Some(instr.to_string()); + } + + // Update the session ID if provided + if let Some(id) = session_id { + project_info.last_session_id = Some(id.to_string()); + } + + self.save() + } + + /// List all tracked projects + /// + /// Returns a vector of ProjectInfoDisplay objects + pub fn list_projects(&self) -> Vec { + self.projects + .values() + .map(|info| ProjectInfoDisplay { + path: info.path.clone(), + last_accessed: info.last_accessed, + last_instruction: info.last_instruction.clone(), + last_session_id: info.last_session_id.clone(), + }) + .collect() + } +} + +/// Update the project tracker with the current directory and optional instruction +/// +/// # Arguments +/// * `instruction` - Optional instruction that was sent to goose +/// * `session_id` - Optional session ID associated with this project +pub fn update_project_tracker(instruction: Option<&str>, session_id: Option<&str>) -> Result<()> { + let current_dir = std::env::current_dir()?; + let mut tracker = ProjectTracker::load()?; + tracker.update_project(¤t_dir, instruction, session_id) +} diff --git a/crates/goose-cli/src/recipe.rs b/crates/goose-cli/src/recipe.rs deleted file mode 100644 index 5bebddac..00000000 --- a/crates/goose-cli/src/recipe.rs +++ /dev/null @@ -1,70 +0,0 @@ -use anyhow::{Context, Result}; -use console::style; -use std::path::Path; - -use goose::recipe::Recipe; - -/// Loads and validates a recipe from a YAML or JSON file -/// -/// # Arguments -/// -/// * `path` - Path to the recipe file (YAML or JSON) -/// * `log` - whether to log information about the recipe or not -/// -/// # Returns -/// -/// The parsed recipe struct if successful -/// -/// # Errors -/// -/// Returns an error if: -/// - The file doesn't exist -/// - The file can't be read -/// - The YAML/JSON is invalid -/// - The required fields are missing -pub fn load_recipe>(path: P, log: bool) -> Result { - let path = path.as_ref(); - - // Check if file exists - if !path.exists() { - return Err(anyhow::anyhow!("recipe file not found: {}", path.display())); - } - // Read file content - let content = std::fs::read_to_string(path) - .with_context(|| format!("Failed to read recipe file: {}", path.display()))?; - - // Determine file format based on extension and parse accordingly - let recipe: Recipe = if let Some(extension) = path.extension() { - match extension.to_str().unwrap_or("").to_lowercase().as_str() { - "json" => serde_json::from_str(&content) - .with_context(|| format!("Failed to parse JSON recipe file: {}", path.display()))?, - "yaml" => serde_yaml::from_str(&content) - .with_context(|| format!("Failed to parse YAML recipe file: {}", path.display()))?, - _ => { - return Err(anyhow::anyhow!( - "Unsupported file format for recipe file: {}. Expected .yaml or .json", - path.display() - )) - } - } - } else { - return Err(anyhow::anyhow!( - "File has no extension: {}. Expected .yaml or .json", - path.display() - )); - }; - - if log { - // Display information about the loaded recipe - println!( - "{} {}", - style("Loading recipe:").green().bold(), - style(&recipe.title).green() - ); - println!("{} {}", style("Description:").dim(), &recipe.description); - - println!(); // Add a blank line for spacing - } - - Ok(recipe) -} diff --git a/crates/goose-cli/src/recipes/github_recipe.rs b/crates/goose-cli/src/recipes/github_recipe.rs new file mode 100644 index 00000000..962da236 --- /dev/null +++ b/crates/goose-cli/src/recipes/github_recipe.rs @@ -0,0 +1,192 @@ +use anyhow::Result; +use console::style; +use std::env; +use std::fs; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use std::process::Stdio; +use tar::Archive; + +use crate::recipes::recipe::RECIPE_FILE_EXTENSIONS; + +pub const GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY: &str = "GOOSE_RECIPE_GITHUB_REPO"; +pub fn retrieve_recipe_from_github( + recipe_name: &str, + recipe_repo_full_name: &str, +) -> Result<(String, PathBuf)> { + println!( + "📦 Looking for recipe \"{}\" in github repo: {}", + recipe_name, recipe_repo_full_name + ); + ensure_gh_authenticated()?; + let max_attempts = 2; + let mut last_err = None; + + for attempt in 1..=max_attempts { + match clone_and_download_recipe(recipe_name, recipe_repo_full_name) { + Ok(download_dir) => match read_recipe_file(&download_dir) { + Ok(content) => return Ok((content, download_dir)), + Err(err) => return Err(err), + }, + Err(err) => { + last_err = Some(err); + } + } + if attempt < max_attempts { + clean_cloned_dirs(recipe_repo_full_name)?; + } + } + Err(last_err.unwrap_or_else(|| anyhow::anyhow!("Unknown error occurred"))) +} + +fn clean_cloned_dirs(recipe_repo_full_name: &str) -> anyhow::Result<()> { + let local_repo_path = get_local_repo_path(&env::temp_dir(), recipe_repo_full_name)?; + if local_repo_path.exists() { + fs::remove_dir_all(&local_repo_path)?; + } + Ok(()) +} +fn read_recipe_file(download_dir: &Path) -> Result { + for ext in RECIPE_FILE_EXTENSIONS { + let candidate_file_path = download_dir.join(format!("recipe.{}", ext)); + if candidate_file_path.exists() { + let content = fs::read_to_string(&candidate_file_path)?; + println!( + "⬇️ Retrieved recipe file: {}", + candidate_file_path + .strip_prefix(download_dir) + .unwrap() + .display() + ); + return Ok(content); + } + } + + Err(anyhow::anyhow!( + "No recipe file found in {} (looked for extensions: {:?})", + download_dir.display(), + RECIPE_FILE_EXTENSIONS + )) +} + +fn clone_and_download_recipe(recipe_name: &str, recipe_repo_full_name: &str) -> Result { + let local_repo_path = ensure_repo_cloned(recipe_repo_full_name)?; + fetch_origin(&local_repo_path)?; + get_folder_from_github(&local_repo_path, recipe_name) +} + +fn ensure_gh_authenticated() -> Result<()> { + // Check authentication status + let status = Command::new("gh") + .args(["auth", "status"]) + .status() + .map_err(|_| { + anyhow::anyhow!("Failed to run `gh auth status`. Make sure you have `gh` installed.") + })?; + + if status.success() { + return Ok(()); + } + println!("GitHub CLI is not authenticated. Launching `gh auth login`..."); + // Run `gh auth login` interactively + let login_status = Command::new("gh") + .args(["auth", "login", "--git-protocol", "https"]) + .status() + .map_err(|_| anyhow::anyhow!("Failed to run `gh auth login`"))?; + + if !login_status.success() { + Err(anyhow::anyhow!("Failed to authenticate using GitHub CLI.")) + } else { + Ok(()) + } +} + +fn get_local_repo_path( + local_repo_parent_path: &Path, + recipe_repo_full_name: &str, +) -> Result { + let (_, repo_name) = recipe_repo_full_name + .split_once('/') + .ok_or_else(|| anyhow::anyhow!("Invalid repository name format"))?; + let local_repo_path = local_repo_parent_path.to_path_buf().join(repo_name); + Ok(local_repo_path) +} + +fn ensure_repo_cloned(recipe_repo_full_name: &str) -> Result { + let local_repo_parent_path = env::temp_dir(); + if !local_repo_parent_path.exists() { + std::fs::create_dir_all(local_repo_parent_path.clone())?; + } + let local_repo_path = get_local_repo_path(&local_repo_parent_path, recipe_repo_full_name)?; + + if local_repo_path.join(".git").exists() { + Ok(local_repo_path) + } else { + let error_message: String = format!("Failed to clone repo: {}", recipe_repo_full_name); + let status = Command::new("gh") + .args(["repo", "clone", recipe_repo_full_name]) + .current_dir(local_repo_parent_path.clone()) + .status() + .map_err(|_: std::io::Error| anyhow::anyhow!(error_message.clone()))?; + + if status.success() { + Ok(local_repo_path) + } else { + Err(anyhow::anyhow!(error_message)) + } + } +} + +fn fetch_origin(local_repo_path: &Path) -> Result<()> { + let error_message: String = format!("Failed to fetch at {}", local_repo_path.to_str().unwrap()); + let status = Command::new("git") + .args(["fetch", "origin"]) + .current_dir(local_repo_path) + .status() + .map_err(|_| anyhow::anyhow!(error_message.clone()))?; + + if status.success() { + Ok(()) + } else { + Err(anyhow::anyhow!(error_message)) + } +} + +fn get_folder_from_github(local_repo_path: &Path, recipe_name: &str) -> Result { + let ref_and_path = format!("origin/main:{}", recipe_name); + let output_dir = env::temp_dir().join(recipe_name); + + if output_dir.exists() { + fs::remove_dir_all(&output_dir)?; + } + fs::create_dir_all(&output_dir)?; + + let archive_output = Command::new("git") + .args(["archive", &ref_and_path]) + .current_dir(local_repo_path) + .stdout(Stdio::piped()) + .spawn()?; + + let stdout = archive_output + .stdout + .ok_or_else(|| anyhow::anyhow!("Failed to capture stdout from git archive"))?; + + let mut archive = Archive::new(stdout); + archive.unpack(&output_dir)?; + list_files(&output_dir)?; + + Ok(output_dir) +} + +fn list_files(dir: &Path) -> Result<()> { + println!("{}", style("Files downloaded from github:").bold()); + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + println!(" - {}", path.display()); + } + } + Ok(()) +} diff --git a/crates/goose-cli/src/recipes/mod.rs b/crates/goose-cli/src/recipes/mod.rs new file mode 100644 index 00000000..9d1ac702 --- /dev/null +++ b/crates/goose-cli/src/recipes/mod.rs @@ -0,0 +1,4 @@ +pub mod github_recipe; +pub mod print_recipe; +pub mod recipe; +pub mod search_recipe; diff --git a/crates/goose-cli/src/recipes/print_recipe.rs b/crates/goose-cli/src/recipes/print_recipe.rs new file mode 100644 index 00000000..bc43842f --- /dev/null +++ b/crates/goose-cli/src/recipes/print_recipe.rs @@ -0,0 +1,83 @@ +use std::collections::HashMap; + +use console::style; +use goose::recipe::Recipe; + +use crate::recipes::recipe::BUILT_IN_RECIPE_DIR_PARAM; + +pub fn print_recipe_explanation(recipe: &Recipe) { + println!( + "{} {}", + style("🔍 Loading recipe:").bold().green(), + style(&recipe.title).green() + ); + println!("{}", style("📄 Description:").bold()); + println!(" {}", recipe.description); + if let Some(params) = &recipe.parameters { + if !params.is_empty() { + println!("{}", style("⚙️ Recipe Parameters:").bold()); + for param in params { + let default_display = match ¶m.default { + Some(val) => format!(" (default: {})", val), + None => String::new(), + }; + + println!( + " - {} ({}, {}){}: {}", + style(¶m.key).cyan(), + param.input_type, + param.requirement, + default_display, + param.description + ); + } + } + } +} + +pub fn print_parameters_with_values(params: HashMap) { + for (key, value) in params { + let label = if key == BUILT_IN_RECIPE_DIR_PARAM { + " (built-in)" + } else { + "" + }; + println!(" {}{}: {}", key, label, value); + } +} + +pub fn print_required_parameters_for_template( + params_for_template: HashMap, + missing_params: Vec, +) { + if !params_for_template.is_empty() { + println!( + "{}", + style("📥 Parameters used to load this recipe:").bold() + ); + print_parameters_with_values(params_for_template) + } + if !missing_params.is_empty() { + println!( + "{}", + style("🔴 Missing parameters in the command line if you want to run the recipe:") + .bold() + ); + for param in missing_params.iter() { + println!(" - {}", param); + } + println!( + "📩 {}:", + style("Please provide the following parameters in the command line if you want to run the recipe:").bold() + ); + println!(" {}", missing_parameters_command_line(missing_params)); + } +} + +pub fn missing_parameters_command_line(missing_params: Vec) -> String { + missing_params + .iter() + .map(|key| format!("--params {}=your_value", key)) + .collect::>() + .join(" ") +} diff --git a/crates/goose-cli/src/recipes/recipe.rs b/crates/goose-cli/src/recipes/recipe.rs new file mode 100644 index 00000000..b90de236 --- /dev/null +++ b/crates/goose-cli/src/recipes/recipe.rs @@ -0,0 +1,532 @@ +use anyhow::Result; +use console::style; + +use crate::recipes::print_recipe::{ + missing_parameters_command_line, print_parameters_with_values, print_recipe_explanation, + print_required_parameters_for_template, +}; +use crate::recipes::search_recipe::retrieve_recipe_file; +use goose::recipe::{Recipe, RecipeParameter, RecipeParameterRequirement}; +use minijinja::{Environment, Error, Template, UndefinedBehavior}; +use serde_json::Value as JsonValue; +use serde_yaml::Value as YamlValue; +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; + +pub const BUILT_IN_RECIPE_DIR_PARAM: &str = "recipe_dir"; +pub const RECIPE_FILE_EXTENSIONS: &[&str] = &["yaml", "json"]; +/// Loads, validates a recipe from a YAML or JSON file, and renders it with the given parameters +/// +/// # Arguments +/// +/// * `path` - Path to the recipe file (YAML or JSON) +/// * `params` - parameters to render the recipe with +/// +/// # Returns +/// +/// The rendered recipe if successful +/// +/// # Errors +/// +/// Returns an error if: +/// - Recipe is not valid +/// - The required fields are missing +pub fn load_recipe_as_template(recipe_name: &str, params: Vec<(String, String)>) -> Result { + let (recipe_file_content, recipe_parent_dir) = retrieve_recipe_file(recipe_name)?; + + let recipe = validate_recipe_file_parameters(&recipe_file_content)?; + + let (params_for_template, missing_params) = + apply_values_to_parameters(¶ms, recipe.parameters, recipe_parent_dir, true)?; + if !missing_params.is_empty() { + return Err(anyhow::anyhow!( + "Please provide the following parameters in the command line: {}", + missing_parameters_command_line(missing_params) + )); + } + + let rendered_content = render_content_with_params(&recipe_file_content, ¶ms_for_template)?; + + let recipe = parse_recipe_content(&rendered_content)?; + + // Display information about the loaded recipe + println!( + "{} {}", + style("Loading recipe:").green().bold(), + style(&recipe.title).green() + ); + println!("{} {}", style("Description:").bold(), &recipe.description); + + if !params_for_template.is_empty() { + println!("{}", style("Parameters used to load this recipe:").bold()); + print_parameters_with_values(params_for_template); + } + println!(); + Ok(recipe) +} + +/// Loads and validates a recipe from a YAML or JSON file +/// +/// # Arguments +/// +/// * `path` - Path to the recipe file (YAML or JSON) +/// * `params` - optional parameters to render the recipe with +/// +/// # Returns +/// +/// The parsed recipe struct if successful +/// +/// # Errors +/// +/// Returns an error if: +/// - The file doesn't exist +/// - The file can't be read +/// - The YAML/JSON is invalid +/// - The parameter definition does not match the template variables in the recipe file +pub fn load_recipe(recipe_name: &str) -> Result { + let (recipe_file_content, _) = retrieve_recipe_file(recipe_name)?; + + validate_recipe_file_parameters(&recipe_file_content) +} + +pub fn explain_recipe_with_parameters( + recipe_name: &str, + params: Vec<(String, String)>, +) -> Result<()> { + let (recipe_file_content, recipe_parent_dir) = retrieve_recipe_file(recipe_name)?; + + let raw_recipe = validate_recipe_file_parameters(&recipe_file_content)?; + print_recipe_explanation(&raw_recipe); + let recipe_parameters = raw_recipe.parameters; + let (params_for_template, missing_params) = + apply_values_to_parameters(¶ms, recipe_parameters, recipe_parent_dir, false)?; + print_required_parameters_for_template(params_for_template, missing_params); + + Ok(()) +} + +fn validate_recipe_file_parameters(recipe_file_content: &str) -> Result { + let recipe_from_recipe_file: Recipe = parse_recipe_content(recipe_file_content)?; + validate_optional_parameters(&recipe_from_recipe_file)?; + validate_parameters_in_template(&recipe_from_recipe_file.parameters, recipe_file_content)?; + Ok(recipe_from_recipe_file) +} + +fn validate_parameters_in_template( + recipe_parameters: &Option>, + recipe_file_content: &str, +) -> Result<()> { + let mut template_variables = extract_template_variables(recipe_file_content)?; + template_variables.remove(BUILT_IN_RECIPE_DIR_PARAM); + + let param_keys: HashSet = recipe_parameters + .as_ref() + .unwrap_or(&vec![]) + .iter() + .map(|p| p.key.clone()) + .collect(); + + let missing_keys = template_variables + .difference(¶m_keys) + .collect::>(); + + let extra_keys = param_keys + .difference(&template_variables) + .collect::>(); + + if missing_keys.is_empty() && extra_keys.is_empty() { + return Ok(()); + } + + let mut message = String::new(); + + if !missing_keys.is_empty() { + message.push_str(&format!( + "Missing definitions for parameters in the recipe file: {}.", + missing_keys + .iter() + .map(|s| s.to_string()) + .collect::>() + .join(", ") + )); + } + + if !extra_keys.is_empty() { + message.push_str(&format!( + "\nUnnecessary parameter definitions: {}.", + extra_keys + .iter() + .map(|s| s.to_string()) + .collect::>() + .join(", ") + )); + } + Err(anyhow::anyhow!("{}", message.trim_end())) +} + +fn validate_optional_parameters(recipe: &Recipe) -> Result<()> { + let optional_params_without_default_values: Vec = recipe + .parameters + .as_ref() + .unwrap_or(&vec![]) + .iter() + .filter(|p| { + matches!(p.requirement, RecipeParameterRequirement::Optional) && p.default.is_none() + }) + .map(|p| p.key.clone()) + .collect(); + + if optional_params_without_default_values.is_empty() { + Ok(()) + } else { + Err(anyhow::anyhow!("Optional parameters missing default values in the recipe: {}. Please provide defaults.", optional_params_without_default_values.join(", "))) + } +} + +fn parse_recipe_content(content: &str) -> Result { + if serde_json::from_str::(content).is_ok() { + Ok(serde_json::from_str(content)?) + } else if serde_yaml::from_str::(content).is_ok() { + Ok(serde_yaml::from_str(content)?) + } else { + Err(anyhow::anyhow!( + "Unsupported file format for recipe file. Expected .yaml or .json" + )) + } +} + +fn extract_template_variables(template_str: &str) -> Result> { + let mut env = Environment::new(); + env.set_undefined_behavior(UndefinedBehavior::Strict); + + let template = env + .template_from_str(template_str) + .map_err(|e: Error| anyhow::anyhow!("Invalid template syntax: {}", e.to_string()))?; + + Ok(template.undeclared_variables(true)) +} + +fn apply_values_to_parameters( + user_params: &[(String, String)], + recipe_parameters: Option>, + recipe_parent_dir: PathBuf, + enable_user_prompt: bool, +) -> Result<(HashMap, Vec)> { + let mut param_map: HashMap = user_params.iter().cloned().collect(); + let recipe_parent_dir_str = recipe_parent_dir + .to_str() + .ok_or_else(|| anyhow::anyhow!("Invalid UTF-8 in recipe_dir"))?; + param_map.insert( + BUILT_IN_RECIPE_DIR_PARAM.to_string(), + recipe_parent_dir_str.to_string(), + ); + let mut missing_params: Vec = Vec::new(); + for param in recipe_parameters.unwrap_or_default() { + if !param_map.contains_key(¶m.key) { + match (¶m.default, ¶m.requirement) { + (Some(default), _) => param_map.insert(param.key.clone(), default.clone()), + (None, RecipeParameterRequirement::UserPrompt) if enable_user_prompt => { + let input_value = cliclack::input(format!( + "Please enter {} ({})", + param.key, param.description + )) + .interact()?; + param_map.insert(param.key.clone(), input_value) + } + _ => { + missing_params.push(param.key.clone()); + None + } + }; + } + } + Ok((param_map, missing_params)) +} + +fn render_content_with_params(content: &str, params: &HashMap) -> Result { + // Create a minijinja environment and context + let mut env = minijinja::Environment::new(); + env.set_undefined_behavior(UndefinedBehavior::Strict); + let template: Template<'_, '_> = env + .template_from_str(content) + .map_err(|e: Error| anyhow::anyhow!("Invalid template syntax: {}", e.to_string()))?; + + // Render the template with the parameters + template.render(params).map_err(|e: Error| { + anyhow::anyhow!( + "Failed to render the recipe {} - please check if all required parameters are provided", + e.to_string() + ) + }) +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use goose::recipe::{RecipeParameterInputType, RecipeParameterRequirement}; + use tempfile::TempDir; + + use super::*; + + fn setup_recipe_file(instructions_and_parameters: &str) -> (TempDir, PathBuf) { + let recipe_content = format!( + r#"{{ + "version": "1.0.0", + "title": "Test Recipe", + "description": "A test recipe", + {} + }}"#, + instructions_and_parameters + ); + // Create a temporary file + let temp_dir = tempfile::tempdir().unwrap(); + let recipe_path: std::path::PathBuf = temp_dir.path().join("test_recipe.json"); + std::fs::write(&recipe_path, recipe_content).unwrap(); + (temp_dir, recipe_path) + } + + #[test] + fn test_render_content_with_params() { + // Test basic parameter substitution + let content = "Hello {{ name }}!"; + let mut params = HashMap::new(); + params.insert("name".to_string(), "World".to_string()); + let result = render_content_with_params(content, ¶ms).unwrap(); + assert_eq!(result, "Hello World!"); + + // Test empty parameter substitution + let content = "Hello {{ empty }}!"; + let mut params = HashMap::new(); + params.insert("empty".to_string(), "".to_string()); + let result = render_content_with_params(content, ¶ms).unwrap(); + assert_eq!(result, "Hello !"); + + // Test multiple parameters + let content = "{{ greeting }} {{ name }}!"; + let mut params = HashMap::new(); + params.insert("greeting".to_string(), "Hi".to_string()); + params.insert("name".to_string(), "Alice".to_string()); + let result = render_content_with_params(content, ¶ms).unwrap(); + assert_eq!(result, "Hi Alice!"); + + // Test missing parameter results in error + let content = "Hello {{ missing }}!"; + let params = HashMap::new(); + let err = render_content_with_params(content, ¶ms).unwrap_err(); + assert!(err + .to_string() + .contains("please check if all required parameters")); + + // Test invalid template syntax results in error + let content = "Hello {{ unclosed"; + let params = HashMap::new(); + let err = render_content_with_params(content, ¶ms).unwrap_err(); + assert!(err.to_string().contains("Invalid template syntax")); + } + + #[test] + fn test_load_recipe_as_template_success() { + let instructions_and_parameters = r#" + "instructions": "Test instructions with {{ my_name }}", + "parameters": [ + { + "key": "my_name", + "input_type": "string", + "requirement": "required", + "description": "A test parameter" + } + ]"#; + + let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters); + + let params = vec![("my_name".to_string(), "value".to_string())]; + let recipe = load_recipe_as_template(recipe_path.to_str().unwrap(), params).unwrap(); + + assert_eq!(recipe.title, "Test Recipe"); + assert_eq!(recipe.description, "A test recipe"); + assert_eq!(recipe.instructions.unwrap(), "Test instructions with value"); + // Verify parameters match recipe definition + assert_eq!(recipe.parameters.as_ref().unwrap().len(), 1); + let param = &recipe.parameters.as_ref().unwrap()[0]; + assert_eq!(param.key, "my_name"); + assert!(matches!(param.input_type, RecipeParameterInputType::String)); + assert!(matches!( + param.requirement, + RecipeParameterRequirement::Required + )); + assert_eq!(param.description, "A test parameter"); + } + + #[test] + fn test_load_recipe_as_template_success_variable_in_prompt() { + let instructions_and_parameters = r#" + "instructions": "Test instructions", + "prompt": "My prompt {{ my_name }}", + "parameters": [ + { + "key": "my_name", + "input_type": "string", + "requirement": "required", + "description": "A test parameter" + } + ]"#; + + let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters); + + let params = vec![("my_name".to_string(), "value".to_string())]; + let recipe = load_recipe_as_template(recipe_path.to_str().unwrap(), params).unwrap(); + + assert_eq!(recipe.title, "Test Recipe"); + assert_eq!(recipe.description, "A test recipe"); + assert_eq!(recipe.instructions.unwrap(), "Test instructions"); + assert_eq!(recipe.prompt.unwrap(), "My prompt value"); + let param = &recipe.parameters.as_ref().unwrap()[0]; + assert_eq!(param.key, "my_name"); + assert!(matches!(param.input_type, RecipeParameterInputType::String)); + assert!(matches!( + param.requirement, + RecipeParameterRequirement::Required + )); + assert_eq!(param.description, "A test parameter"); + } + + #[test] + fn test_load_recipe_as_template_wrong_parameters_in_recipe_file() { + let instructions_and_parameters = r#" + "instructions": "Test instructions with {{ expected_param1 }} {{ expected_param2 }}", + "parameters": [ + { + "key": "wrong_param_key", + "input_type": "string", + "requirement": "required", + "description": "A test parameter" + } + ]"#; + let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters); + + let load_recipe_result = load_recipe_as_template(recipe_path.to_str().unwrap(), Vec::new()); + assert!(load_recipe_result.is_err()); + let err = load_recipe_result.unwrap_err(); + println!("{}", err.to_string()); + assert!(err + .to_string() + .contains("Unnecessary parameter definitions: wrong_param_key.")); + assert!(err + .to_string() + .contains("Missing definitions for parameters in the recipe file:")); + assert!(err.to_string().contains("expected_param1")); + assert!(err.to_string().contains("expected_param2")); + } + + #[test] + fn test_load_recipe_as_template_with_default_values_in_recipe_file() { + let instructions_and_parameters = r#" + "instructions": "Test instructions with {{ param_with_default }} {{ param_without_default }}", + "parameters": [ + { + "key": "param_with_default", + "input_type": "string", + "requirement": "optional", + "default": "my_default_value", + "description": "A test parameter" + }, + { + "key": "param_without_default", + "input_type": "string", + "requirement": "required", + "description": "A test parameter" + } + ]"#; + let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters); + let params = vec![("param_without_default".to_string(), "value1".to_string())]; + + let recipe = load_recipe_as_template(recipe_path.to_str().unwrap(), params).unwrap(); + + assert_eq!(recipe.title, "Test Recipe"); + assert_eq!(recipe.description, "A test recipe"); + assert_eq!( + recipe.instructions.unwrap(), + "Test instructions with my_default_value value1" + ); + } + + #[test] + fn test_load_recipe_as_template_optional_parameters_with_empty_default_values_in_recipe_file() { + let instructions_and_parameters = r#" + "instructions": "Test instructions with {{ optional_param }}", + "parameters": [ + { + "key": "optional_param", + "input_type": "string", + "requirement": "optional", + "description": "A test parameter", + "default": "", + } + ]"#; + let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters); + + let recipe = load_recipe_as_template(recipe_path.to_str().unwrap(), Vec::new()).unwrap(); + assert_eq!(recipe.title, "Test Recipe"); + assert_eq!(recipe.description, "A test recipe"); + assert_eq!(recipe.instructions.unwrap(), "Test instructions with "); + } + + #[test] + fn test_load_recipe_as_template_optional_parameters_without_default_values_in_recipe_file() { + let instructions_and_parameters = r#" + "instructions": "Test instructions with {{ optional_param }}", + "parameters": [ + { + "key": "optional_param", + "input_type": "string", + "requirement": "optional", + "description": "A test parameter" + } + ]"#; + let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters); + + let load_recipe_result = load_recipe_as_template(recipe_path.to_str().unwrap(), Vec::new()); + assert!(load_recipe_result.is_err()); + let err = load_recipe_result.unwrap_err(); + println!("{}", err.to_string()); + assert!(err.to_string().contains( + "Optional parameters missing default values in the recipe: optional_param. Please provide defaults." + )); + } + + #[test] + fn test_load_recipe_as_template_wrong_input_type_in_recipe_file() { + let instructions_and_parameters = r#" + "instructions": "Test instructions with {{ param }}", + "parameters": [ + { + "key": "param", + "input_type": "some_invalid_type", + "requirement": "required", + "description": "A test parameter" + } + ]"#; + let params = vec![("param".to_string(), "value".to_string())]; + let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters); + + let load_recipe_result = load_recipe_as_template(recipe_path.to_str().unwrap(), params); + assert!(load_recipe_result.is_err()); + let err = load_recipe_result.unwrap_err(); + assert!(err + .to_string() + .contains("unknown variant `some_invalid_type`")); + } + + #[test] + fn test_load_recipe_as_template_success_without_parameters() { + let instructions_and_parameters = r#" + "instructions": "Test instructions" + "#; + let (_temp_dir, recipe_path) = setup_recipe_file(instructions_and_parameters); + + let recipe = load_recipe_as_template(recipe_path.to_str().unwrap(), Vec::new()).unwrap(); + assert_eq!(recipe.instructions.unwrap(), "Test instructions"); + assert!(recipe.parameters.is_none()); + } +} diff --git a/crates/goose-cli/src/recipes/search_recipe.rs b/crates/goose-cli/src/recipes/search_recipe.rs new file mode 100644 index 00000000..398e5a01 --- /dev/null +++ b/crates/goose-cli/src/recipes/search_recipe.rs @@ -0,0 +1,101 @@ +use anyhow::{anyhow, Result}; +use goose::config::Config; +use std::path::{Path, PathBuf}; +use std::{env, fs}; + +use crate::recipes::recipe::RECIPE_FILE_EXTENSIONS; + +use super::github_recipe::{retrieve_recipe_from_github, GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY}; + +const GOOSE_RECIPE_PATH_ENV_VAR: &str = "GOOSE_RECIPE_PATH"; + +pub fn retrieve_recipe_file(recipe_name: &str) -> Result<(String, PathBuf)> { + // If recipe_name ends with yaml or json, treat it as a direct file path + if RECIPE_FILE_EXTENSIONS + .iter() + .any(|ext| recipe_name.ends_with(&format!(".{}", ext))) + { + let path = PathBuf::from(recipe_name); + return read_recipe_file(path); + } + retrieve_recipe_from_local_path(recipe_name).or_else(|e| { + if let Some(recipe_repo_full_name) = configured_github_recipe_repo() { + retrieve_recipe_from_github(recipe_name, &recipe_repo_full_name) + } else { + Err(e) + } + }) +} + +fn read_recipe_in_dir(dir: &Path, recipe_name: &str) -> Result<(String, PathBuf)> { + for ext in RECIPE_FILE_EXTENSIONS { + let recipe_path = dir.join(format!("{}.{}", recipe_name, ext)); + if let Ok(result) = read_recipe_file(recipe_path) { + return Ok(result); + } + } + Err(anyhow!(format!( + "No {}.yaml or {}.json recipe file found in directory: {}", + recipe_name, + recipe_name, + dir.display() + ))) +} + +fn retrieve_recipe_from_local_path(recipe_name: &str) -> Result<(String, PathBuf)> { + let mut search_dirs = vec![PathBuf::from(".")]; + if let Ok(recipe_path_env) = env::var(GOOSE_RECIPE_PATH_ENV_VAR) { + let path_separator = if cfg!(windows) { ';' } else { ':' }; + let recipe_path_env_dirs: Vec = recipe_path_env + .split(path_separator) + .map(PathBuf::from) + .collect(); + search_dirs.extend(recipe_path_env_dirs); + } + for dir in &search_dirs { + if let Ok(result) = read_recipe_in_dir(dir, recipe_name) { + return Ok(result); + } + } + let search_dirs_str = search_dirs + .iter() + .map(|p| p.to_string_lossy()) + .collect::>() + .join(":"); + Err(anyhow!( + "ℹ️ Failed to retrieve {}.yaml or {}.json in {}", + recipe_name, + recipe_name, + search_dirs_str + )) +} + +fn configured_github_recipe_repo() -> Option { + let config = Config::global(); + match config.get_param(GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY) { + Ok(Some(recipe_repo_full_name)) => Some(recipe_repo_full_name), + _ => None, + } +} + +fn read_recipe_file>(recipe_path: P) -> Result<(String, PathBuf)> { + let path = recipe_path.as_ref(); + + let content = fs::read_to_string(path) + .map_err(|e| anyhow!("Failed to read recipe file {}: {}", path.display(), e))?; + + let canonical = path.canonicalize().map_err(|e| { + anyhow!( + "Failed to resolve absolute path for {}: {}", + path.display(), + e + ) + })?; + + let parent_dir = canonical + .parent() + .ok_or_else(|| anyhow!("Resolved path has no parent: {}", canonical.display()))? + .to_path_buf(); + + Ok((content, parent_dir)) +} diff --git a/crates/goose-cli/src/session/builder.rs b/crates/goose-cli/src/session/builder.rs index 9c9092b0..f7cfeba7 100644 --- a/crates/goose-cli/src/session/builder.rs +++ b/crates/goose-cli/src/session/builder.rs @@ -21,6 +21,8 @@ pub struct SessionBuilderConfig { pub identifier: Option, /// Whether to resume an existing session pub resume: bool, + /// Whether to run without a session file + pub no_session: bool, /// List of stdio extension commands to add pub extensions: Vec, /// List of remote extension commands to add @@ -33,6 +35,8 @@ pub struct SessionBuilderConfig { pub additional_system_prompt: Option, /// Enable debug printing pub debug: bool, + /// Maximum number of consecutive identical tool calls allowed + pub max_tool_repetitions: Option, } pub async fn build_session(session_config: SessionBuilderConfig) -> Session { @@ -51,10 +55,31 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session { // Create the agent let agent: Agent = Agent::new(); let new_provider = create(&provider_name, model_config).unwrap(); - let _ = agent.update_provider(new_provider).await; + agent + .update_provider(new_provider) + .await + .unwrap_or_else(|e| { + output::render_error(&format!("Failed to initialize agent: {}", e)); + process::exit(1); + }); + + // Configure tool monitoring if max_tool_repetitions is set + if let Some(max_repetitions) = session_config.max_tool_repetitions { + agent.configure_tool_monitor(Some(max_repetitions)).await; + } // Handle session file resolution and resuming - let session_file = if session_config.resume { + let session_file = if session_config.no_session { + // Use a temporary path that won't be written to + #[cfg(unix)] + { + std::path::PathBuf::from("/dev/null") + } + #[cfg(windows)] + { + std::path::PathBuf::from("NUL") + } + } else if session_config.resume { if let Some(identifier) = session_config.identifier { let session_file = session::get_path(identifier); if !session_file.exists() { @@ -87,7 +112,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session { session::get_path(id) }; - if session_config.resume { + if session_config.resume && !session_config.no_session { // Read the session metadata let metadata = session::read_metadata(&session_file).unwrap_or_else(|e| { output::render_error(&format!("Failed to read session metadata: {}", e)); @@ -103,7 +128,17 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session { .interact().expect("Failed to get user input"); if change_workdir { - std::env::set_current_dir(metadata.working_dir).unwrap(); + if !metadata.working_dir.exists() { + output::render_error(&format!( + "Cannot switch to original working directory - {} no longer exists", + style(metadata.working_dir.display()).cyan() + )); + } else if let Err(e) = std::env::set_current_dir(&metadata.working_dir) { + output::render_error(&format!( + "Failed to switch to original working directory: {}", + e + )); + } } } } diff --git a/crates/goose-cli/src/session/completion.rs b/crates/goose-cli/src/session/completion.rs index 03bba0e9..8468e944 100644 --- a/crates/goose-cli/src/session/completion.rs +++ b/crates/goose-cli/src/session/completion.rs @@ -352,8 +352,13 @@ impl Helper for GooseCompleter {} impl Hinter for GooseCompleter { type Hint = String; - fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option { - None + fn hint(&self, line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option { + // Only show hint when line is empty + if line.is_empty() { + Some("Press Enter to send, Ctrl-J for new line".to_string()) + } else { + None + } } } @@ -367,7 +372,9 @@ impl Highlighter for GooseCompleter { } fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { - Cow::Borrowed(hint) + // Style the hint text with a dim color + let styled = console::Style::new().dim().apply_to(hint).to_string(); + Cow::Owned(styled) } fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { diff --git a/crates/goose-cli/src/session/input.rs b/crates/goose-cli/src/session/input.rs index cdca703b..c4049674 100644 --- a/crates/goose-cli/src/session/input.rs +++ b/crates/goose-cli/src/session/input.rs @@ -18,6 +18,7 @@ pub enum InputResult { Plan(PlanCommandOptions), EndPlan, Recipe(Option), + Summarize, } #[derive(Debug)] @@ -91,6 +92,7 @@ fn handle_slash_command(input: &str) -> Option { const CMD_PLAN: &str = "/plan"; const CMD_ENDPLAN: &str = "/endplan"; const CMD_RECIPE: &str = "/recipe"; + const CMD_SUMMARIZE: &str = "/summarize"; match input { "/exit" | "/quit" => Some(InputResult::Exit), @@ -133,6 +135,7 @@ fn handle_slash_command(input: &str) -> Option { s if s.starts_with(CMD_PLAN) => parse_plan_command(s[CMD_PLAN.len()..].trim().to_string()), s if s == CMD_ENDPLAN => Some(InputResult::EndPlan), s if s.starts_with(CMD_RECIPE) => parse_recipe_command(s), + s if s == CMD_SUMMARIZE => Some(InputResult::Summarize), _ => None, } } @@ -241,6 +244,7 @@ fn print_help() { /endplan - Exit plan mode and return to 'normal' goose mode. /recipe [filepath] - Generate a recipe from the current conversation and save it to the specified filepath (must end with .yaml). If no filepath is provided, it will be saved to ./recipe.yaml. +/summarize - Summarize the current conversation to reduce context length while preserving key information. /? or /help - Display this help message Navigation: @@ -474,4 +478,15 @@ mod tests { let result = handle_slash_command("/recipe /path/to/file.txt"); assert!(matches!(result, Some(InputResult::Retry))); } + + #[test] + fn test_summarize_command() { + // Test the summarize command + let result = handle_slash_command("/summarize"); + assert!(matches!(result, Some(InputResult::Summarize))); + + // Test with whitespace + let result = handle_slash_command(" /summarize "); + assert!(matches!(result, Some(InputResult::Summarize))); + } } diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 368274ce..273ec979 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -122,6 +122,21 @@ impl Session { } } + /// Helper function to summarize context messages + async fn summarize_context_messages( + messages: &mut Vec, + agent: &Agent, + message_suffix: &str, + ) -> Result<()> { + // Summarize messages to fit within context length + let (summarized_messages, _) = agent.summarize_context(messages).await?; + let msg = format!("Context maxed out\n{}\n{}", "-".repeat(50), message_suffix); + output::render_text(&msg, Some(Color::Yellow), true); + *messages = summarized_messages; + + Ok(()) + } + /// Add a stdio extension to the session /// /// # Arguments @@ -290,6 +305,22 @@ impl Session { // Persist messages with provider for automatic description generation session::persist_messages(&self.session_file, &self.messages, Some(provider)).await?; + // Track the current directory and last instruction in projects.json + let session_id = self + .session_file + .file_stem() + .and_then(|s| s.to_str()) + .map(|s| s.to_string()); + + if let Err(e) = + crate::project_tracker::update_project_tracker(Some(&message), session_id.as_deref()) + { + eprintln!( + "Warning: Failed to update project tracker with instruction: {}", + e + ); + } + self.process_agent_response(false).await?; Ok(()) } @@ -356,6 +387,20 @@ impl Session { self.messages.push(Message::user().with_text(&content)); + // Track the current directory and last instruction in projects.json + let session_id = self + .session_file + .file_stem() + .and_then(|s| s.to_str()) + .map(|s| s.to_string()); + + if let Err(e) = crate::project_tracker::update_project_tracker( + Some(&content), + session_id.as_deref(), + ) { + eprintln!("Warning: Failed to update project tracker with instruction: {}", e); + } + // Get the provider from the agent for description generation let provider = self.agent.provider().await?; @@ -503,6 +548,62 @@ impl Session { } } + continue; + } + InputResult::Summarize => { + save_history(&mut editor); + + let prompt = "Are you sure you want to summarize this conversation? This will condense the message history."; + let should_summarize = + match cliclack::confirm(prompt).initial_value(true).interact() { + Ok(choice) => choice, + Err(e) => { + if e.kind() == std::io::ErrorKind::Interrupted { + false // If interrupted, set should_summarize to false + } else { + return Err(e.into()); + } + } + }; + + if should_summarize { + println!("{}", console::style("Summarizing conversation...").yellow()); + output::show_thinking(); + + // Get the provider for summarization + let provider = self.agent.provider().await?; + + // Call the summarize_context method which uses the summarize_messages function + let (summarized_messages, _) = + self.agent.summarize_context(&self.messages).await?; + + // Update the session messages with the summarized ones + self.messages = summarized_messages; + + // Persist the summarized messages + session::persist_messages( + &self.session_file, + &self.messages, + Some(provider), + ) + .await?; + + output::hide_thinking(); + println!( + "{}", + console::style("Conversation has been summarized.").green() + ); + println!( + "{}", + console::style( + "Key information has been preserved while reducing context length." + ) + .green() + ); + } else { + println!("{}", console::style("Summarization cancelled.").yellow()); + } + continue; } } @@ -532,10 +633,21 @@ impl Session { match planner_response_type { PlannerResponseType::Plan => { println!(); - let should_act = - cliclack::confirm("Do you want to clear message history & act on this plan?") - .initial_value(true) - .interact()?; + let should_act = match cliclack::confirm( + "Do you want to clear message history & act on this plan?", + ) + .initial_value(true) + .interact() + { + Ok(choice) => choice, + Err(e) => { + if e.kind() == std::io::ErrorKind::Interrupted { + false // If interrupted, set should_act to false + } else { + return Err(e.into()); + } + } + }; if should_act { output::render_act_on_plan(); self.run_mode = RunMode::Normal; @@ -596,6 +708,7 @@ impl Session { id: session_id.clone(), working_dir: std::env::current_dir() .expect("failed to get current session working directory"), + schedule_id: None, }), ) .await?; @@ -614,51 +727,99 @@ impl Session { let prompt = "Goose would like to call the above tool, do you allow?".to_string(); // Get confirmation from user - let permission = cliclack::select(prompt) + let permission_result = cliclack::select(prompt) .item(Permission::AllowOnce, "Allow", "Allow the tool call once") .item(Permission::AlwaysAllow, "Always Allow", "Always allow the tool call") .item(Permission::DenyOnce, "Deny", "Deny the tool call") - .interact()?; - self.agent.handle_confirmation(confirmation.id.clone(), PermissionConfirmation { - principal_type: PrincipalType::Tool, - permission, - },).await; + .item(Permission::Cancel, "Cancel", "Cancel the AI response and tool call") + .interact(); + + let permission = match permission_result { + Ok(p) => p, // If Ok, use the selected permission + Err(e) => { + // Check if the error is an interruption (Ctrl+C/Cmd+C, Escape) + if e.kind() == std::io::ErrorKind::Interrupted { + Permission::Cancel // If interrupted, set permission to Cancel + } else { + return Err(e.into()); // Otherwise, convert and propagate the original error + } + } + }; + + if permission == Permission::Cancel { + output::render_text("Tool call cancelled. Returning to chat...", Some(Color::Yellow), true); + + let mut response_message = Message::user(); + response_message.content.push(MessageContent::tool_response( + confirmation.id.clone(), + Err(ToolError::ExecutionError("Tool call cancelled by user".to_string())) + )); + self.messages.push(response_message); + session::persist_messages(&self.session_file, &self.messages, None).await?; + + drop(stream); + break; + } else { + self.agent.handle_confirmation(confirmation.id.clone(), PermissionConfirmation { + principal_type: PrincipalType::Tool, + permission, + },).await; + } } else if let Some(MessageContent::ContextLengthExceeded(_)) = message.content.first() { output::hide_thinking(); - let prompt = "The model's context length is maxed out. You will need to reduce the # msgs. Do you want to?".to_string(); - let selected = cliclack::select(prompt) - .item("clear", "Clear Session", "Removes all messages from Goose's memory") - .item("truncate", "Truncate Messages", "Removes old messages till context is within limits") - .item("summarize", "Summarize Session", "Summarize the session to reduce context length") - .interact()?; + if interactive { + // In interactive mode, ask the user what to do + let prompt = "The model's context length is maxed out. You will need to reduce the # msgs. Do you want to?".to_string(); + let selected_result = cliclack::select(prompt) + .item("clear", "Clear Session", "Removes all messages from Goose's memory") + .item("truncate", "Truncate Messages", "Removes old messages till context is within limits") + .item("summarize", "Summarize Session", "Summarize the session to reduce context length") + .item("cancel", "Cancel", "Cancel and return to chat") + .interact(); - match selected { - "clear" => { - self.messages.clear(); - let msg = format!("Session cleared.\n{}", "-".repeat(50)); - output::render_text(&msg, Some(Color::Yellow), true); - break; // exit the loop to hand back control to the user - } - "truncate" => { - // Truncate messages to fit within context length - let (truncated_messages, _) = self.agent.truncate_context(&self.messages).await?; - let msg = format!("Context maxed out\n{}\nGoose tried its best to truncate messages for you.", "-".repeat(50)); - output::render_text("", Some(Color::Yellow), true); - output::render_text(&msg, Some(Color::Yellow), true); - self.messages = truncated_messages; - } - "summarize" => { - // Summarize messages to fit within context length - let (summarized_messages, _) = self.agent.summarize_context(&self.messages).await?; - let msg = format!("Context maxed out\n{}\nGoose summarized messages for you.", "-".repeat(50)); - output::render_text(&msg, Some(Color::Yellow), true); - self.messages = summarized_messages; - } - _ => { - unreachable!() + let selected = match selected_result { + Ok(s) => s, + Err(e) => { + if e.kind() == std::io::ErrorKind::Interrupted { + "cancel" // If interrupted, set selected to cancel + } else { + return Err(e.into()); + } + } + }; + + match selected { + "clear" => { + self.messages.clear(); + let msg = format!("Session cleared.\n{}", "-".repeat(50)); + output::render_text(&msg, Some(Color::Yellow), true); + break; // exit the loop to hand back control to the user + } + "truncate" => { + // Truncate messages to fit within context length + let (truncated_messages, _) = self.agent.truncate_context(&self.messages).await?; + let msg = format!("Context maxed out\n{}\nGoose tried its best to truncate messages for you.", "-".repeat(50)); + output::render_text("", Some(Color::Yellow), true); + output::render_text(&msg, Some(Color::Yellow), true); + self.messages = truncated_messages; + } + "summarize" => { + // Use the helper function to summarize context + Self::summarize_context_messages(&mut self.messages, &self.agent, "Goose summarized messages for you.").await?; + } + "cancel" => { + break; // Return to main prompt + } + _ => { + unreachable!() + } } + } else { + // In headless mode (goose run), automatically use summarize + Self::summarize_context_messages(&mut self.messages, &self.agent, "Goose automatically summarized messages to continue processing.").await?; } + // Restart the stream after handling ContextLengthExceeded stream = self .agent @@ -668,6 +829,7 @@ impl Session { id: session_id.clone(), working_dir: std::env::current_dir() .expect("failed to get current session working directory"), + schedule_id: None, }), ) .await?; @@ -852,6 +1014,31 @@ impl Session { self.messages.clone() } + /// Render all past messages from the session history + pub fn render_message_history(&self) { + if self.messages.is_empty() { + return; + } + + // Print session restored message + println!( + "\n{} {} messages loaded into context.", + console::style("Session restored:").green().bold(), + console::style(self.messages.len()).green() + ); + + // Render each message + for message in &self.messages { + output::render_message(message, self.debug); + } + + // Add a visual separator after restored messages + println!( + "\n{}\n", + console::style("──────── New Messages ────────").dim() + ); + } + /// Get the session metadata pub fn get_metadata(&self) -> Result { if !self.session_file.exists() { @@ -976,29 +1163,30 @@ fn get_reasoner() -> Result, anyhow::Error> { use goose::model::ModelConfig; use goose::providers::create; - let (reasoner_provider, reasoner_model) = match ( - std::env::var("GOOSE_PLANNER_PROVIDER"), - std::env::var("GOOSE_PLANNER_MODEL"), - ) { - (Ok(provider), Ok(model)) => (provider, model), - _ => { - println!( - "WARNING: GOOSE_PLANNER_PROVIDER or GOOSE_PLANNER_MODEL is not set. \ - Using default model from config..." - ); - let config = Config::global(); - let provider = config - .get_param("GOOSE_PROVIDER") - .expect("No provider configured. Run 'goose configure' first"); - let model = config - .get_param("GOOSE_MODEL") - .expect("No model configured. Run 'goose configure' first"); - (provider, model) - } + let config = Config::global(); + + // Try planner-specific provider first, fallback to default provider + let provider = if let Ok(provider) = config.get_param::("GOOSE_PLANNER_PROVIDER") { + provider + } else { + println!("WARNING: GOOSE_PLANNER_PROVIDER not found. Using default provider..."); + config + .get_param::("GOOSE_PROVIDER") + .expect("No provider configured. Run 'goose configure' first") }; - let model_config = ModelConfig::new(reasoner_model); - let reasoner = create(&reasoner_provider, model_config)?; + // Try planner-specific model first, fallback to default model + let model = if let Ok(model) = config.get_param::("GOOSE_PLANNER_MODEL") { + model + } else { + println!("WARNING: GOOSE_PLANNER_MODEL not found. Using default model..."); + config + .get_param::("GOOSE_MODEL") + .expect("No model configured. Run 'goose configure' first") + }; + + let model_config = ModelConfig::new(model); + let reasoner = create(&provider, model_config)?; Ok(reasoner) } diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index b59c3342..873eec01 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -209,7 +209,7 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) { let min_priority = config .get_param::("GOOSE_CLI_MIN_PRIORITY") .ok() - .unwrap_or(0.0); + .unwrap_or(0.5); if content .priority() @@ -405,9 +405,15 @@ fn print_markdown(content: &str, theme: Theme) { .unwrap(); } -const MAX_STRING_LENGTH: usize = 40; const INDENT: &str = " "; +fn get_tool_params_max_length() -> usize { + Config::global() + .get_param::("GOOSE_CLI_TOOL_PARAMS_TRUNCATION_MAX_LENGTH") + .ok() + .unwrap_or(40) +} + fn print_params(value: &Value, depth: usize, debug: bool) { let indent = INDENT.repeat(depth); @@ -427,7 +433,7 @@ fn print_params(value: &Value, depth: usize, debug: bool) { } } Value::String(s) => { - if !debug && s.len() > MAX_STRING_LENGTH { + if !debug && s.len() > get_tool_params_max_length() { println!("{}{}: {}", indent, style(key).dim(), style("...").dim()); } else { println!("{}{}: {}", indent, style(key).dim(), style(s).green()); @@ -452,7 +458,7 @@ fn print_params(value: &Value, depth: usize, debug: bool) { } } Value::String(s) => { - if !debug && s.len() > MAX_STRING_LENGTH { + if !debug && s.len() > get_tool_params_max_length() { println!( "{}{}", indent, @@ -527,6 +533,8 @@ fn shorten_path(path: &str, debug: bool) -> String { pub fn display_session_info(resume: bool, provider: &str, model: &str, session_file: &Path) { let start_session_msg = if resume { "resuming session |" + } else if session_file.to_str() == Some("/dev/null") || session_file.to_str() == Some("NUL") { + "running without session |" } else { "starting session |" }; @@ -538,11 +546,15 @@ pub fn display_session_info(resume: bool, provider: &str, model: &str, session_f style("model:").dim(), style(model).cyan().dim(), ); - println!( - " {} {}", - style("logging to").dim(), - style(session_file.display()).dim().cyan(), - ); + + if session_file.to_str() != Some("/dev/null") && session_file.to_str() != Some("NUL") { + println!( + " {} {}", + style("logging to").dim(), + style(session_file.display()).dim().cyan(), + ); + } + println!( " {} {}", style("working directory:").dim(), diff --git a/crates/goose-cli/src/signal.rs b/crates/goose-cli/src/signal.rs new file mode 100644 index 00000000..bdf05dfa --- /dev/null +++ b/crates/goose-cli/src/signal.rs @@ -0,0 +1,36 @@ +use std::future::Future; +use std::pin::Pin; +use tokio::signal; + +#[cfg(unix)] +pub fn shutdown_signal() -> Pin + Send>> { + Box::pin(async move { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } + }) +} + +#[cfg(not(unix))] +pub fn shutdown_signal() -> Pin + Send>> { + Box::pin(async move { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }) +} diff --git a/crates/goose-ffi/README.md b/crates/goose-ffi/README.md index accb9e15..acb5b845 100644 --- a/crates/goose-ffi/README.md +++ b/crates/goose-ffi/README.md @@ -16,10 +16,10 @@ To build the FFI library, you'll need Rust and Cargo installed. Then run: ```bash # Build the library in debug mode -cargo build --package goose_ffi +cargo build --package goose-ffi # Build the library in release mode (recommended for production) -cargo build --release --package goose_ffi +cargo build --release --package goose-ffi ``` This will generate a dynamic library (.so, .dll, or .dylib depending on your platform) in the `target` directory, and automatically generate the C header file in the `include` directory. @@ -54,7 +54,7 @@ To run the Python example: ```bash # First, build the FFI library -cargo build --release --package goose_ffi +cargo build --release --package goose-ffi # Then set the environment variables & run the example DATABRICKS_HOST=... DATABRICKS_API_KEY=... python crates/goose-ffi/examples/goose_agent.py diff --git a/crates/goose-llm/Cargo.toml b/crates/goose-llm/Cargo.toml new file mode 100644 index 00000000..2323d29e --- /dev/null +++ b/crates/goose-llm/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "goose-llm" +edition.workspace = true +version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +description.workspace = true + +[lib] +crate-type = ["lib", "cdylib"] +name = "goose_llm" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +anyhow = "1.0" +thiserror = "1.0" +minijinja = "2.8.0" +include_dir = "0.7.4" +once_cell = "1.20.2" +chrono = { version = "0.4.38", features = ["serde"] } +reqwest = { version = "0.12.9", features = [ + "rustls-tls-native-roots", + "json", + "cookies", + "gzip", + "brotli", + "deflate", + "zstd", + "charset", + "http2", + "stream" + ], default-features = false } +async-trait = "0.1" +url = "2.5" +base64 = "0.21" +regex = "1.11.1" +tracing = "0.1" +smallvec = { version = "1.13", features = ["serde"] } +indoc = "1.0" +# https://github.com/mozilla/uniffi-rs/blob/c7f6caa3d1bf20f934346cefd8e82b5093f0dc6f/fixtures/futures/Cargo.toml#L22 +uniffi = { version = "0.29", features = ["tokio", "cli", "scaffolding-ffi-buffer-fns"] } +tokio = { version = "1.43", features = ["time", "sync"] } + +[dev-dependencies] +criterion = "0.5" +tempfile = "3.15.0" +dotenv = "0.15" +lazy_static = "1.5" +ctor = "0.2.7" +tokio = { version = "1.43", features = ["full"] } + +[[bin]] +# https://mozilla.github.io/uniffi-rs/latest/tutorial/foreign_language_bindings.html +name = "uniffi-bindgen" +path = "uniffi-bindgen.rs" + +[[example]] +name = "simple" +path = "examples/simple.rs" diff --git a/crates/goose-llm/README.md b/crates/goose-llm/README.md new file mode 100644 index 00000000..08d806b8 --- /dev/null +++ b/crates/goose-llm/README.md @@ -0,0 +1,68 @@ +## goose-llm + +This crate is meant to be used for foreign function interface (FFI). It's meant to be +stateless and contain logic related to providers and prompts: +- chat completion with model providers +- detecting read-only tools for smart approval +- methods for summarization / truncation + + +Run: +``` +cargo run -p goose-llm --example simple +``` + + +## Kotlin bindings + +Structure: +``` +. +└── crates + └── goose-llm/... +└── target + └── debug/libgoose_llm.dylib +├── bindings +│ └── kotlin +│ ├── example +│ │ └── Usage.kt ← your demo app +│ └── uniffi +│ └── goose_llm +│ └── goose_llm.kt ← auto-generated bindings +``` + + +#### Kotlin -> Rust: run example + +The following `just` command creates kotlin bindings, then compiles and runs an example. + +```bash +just kotlin-example +``` + +You will have to download jars in `bindings/kotlin/libs` directory (only the first time): +```bash +pushd bindings/kotlin/libs/ +curl -O https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar +curl -O https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.3/kotlinx-coroutines-core-jvm-1.7.3.jar +curl -O https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar +popd +``` + +To just create the Kotlin bindings: + +```bash +# run from project root directory +cargo build -p goose-llm + +cargo run --features=uniffi/cli --bin uniffi-bindgen generate --library ./target/debug/libgoose_llm.dylib --language kotlin --out-dir bindings/kotlin +``` + + +#### Python -> Rust: generate bindings, run example + +```bash +cargo run --features=uniffi/cli --bin uniffi-bindgen generate --library ./target/debug/libgoose_llm.dylib --language python --out-dir bindings/python + +DYLD_LIBRARY_PATH=./target/debug python bindings/python/usage.py +``` diff --git a/crates/goose-llm/examples/simple.rs b/crates/goose-llm/examples/simple.rs new file mode 100644 index 00000000..9544f7ce --- /dev/null +++ b/crates/goose-llm/examples/simple.rs @@ -0,0 +1,123 @@ +use std::vec; + +use anyhow::Result; +use goose_llm::{ + completion, + extractors::generate_tooltip, + types::completion::{ + CompletionRequest, CompletionResponse, ExtensionConfig, ToolApprovalMode, ToolConfig, + }, + Message, ModelConfig, +}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<()> { + let provider = "databricks"; + let provider_config = json!({ + "host": std::env::var("DATABRICKS_HOST").expect("Missing DATABRICKS_HOST"), + "token": std::env::var("DATABRICKS_TOKEN").expect("Missing DATABRICKS_TOKEN"), + }); + // let model_name = "goose-gpt-4-1"; // parallel tool calls + let model_name = "claude-3-5-haiku"; + let model_config = ModelConfig::new(model_name.to_string()); + + let calculator_tool = ToolConfig::new( + "calculator", + "Perform basic arithmetic operations", + json!({ + "type": "object", + "required": ["operation", "numbers"], + "properties": { + "operation": { + "type": "string", + "enum": ["add", "subtract", "multiply", "divide"], + "description": "The arithmetic operation to perform", + }, + "numbers": { + "type": "array", + "items": {"type": "number"}, + "description": "List of numbers to operate on in order", + } + } + }), + ToolApprovalMode::Auto, + ); + + let bash_tool = ToolConfig::new( + "bash_shell", + "Run a shell command", + json!({ + "type": "object", + "required": ["command"], + "properties": { + "command": { + "type": "string", + "description": "The shell command to execute" + } + } + }), + ToolApprovalMode::Manual, + ); + + let list_dir_tool = ToolConfig::new( + "list_directory", + "List files in a directory", + json!({ + "type": "object", + "required": ["path"], + "properties": { + "path": { + "type": "string", + "description": "The directory path to list files from" + } + } + }), + ToolApprovalMode::Auto, + ); + + let extensions = vec![ + ExtensionConfig::new( + "calculator_extension".to_string(), + Some("This extension provides a calculator tool.".to_string()), + vec![calculator_tool], + ), + ExtensionConfig::new( + "bash_extension".to_string(), + Some("This extension provides a bash shell tool.".to_string()), + vec![bash_tool, list_dir_tool], + ), + ]; + + let system_preamble = "You are a helpful assistant."; + + for text in [ + "Add 10037 + 23123 using calculator and also run 'date -u' using bash", + "List all files in the current directory", + ] { + println!("\n---------------\n"); + println!("User Input: {text}"); + let messages = vec![ + Message::user().with_text("Hi there!"), + Message::assistant().with_text("How can I help?"), + Message::user().with_text(text), + ]; + let completion_response: CompletionResponse = completion(CompletionRequest::new( + provider.to_string(), + provider_config.clone(), + model_config.clone(), + system_preamble.to_string(), + messages.clone(), + extensions.clone(), + )) + .await?; + // Print the response + println!("\nCompletion Response:"); + println!("{}", serde_json::to_string_pretty(&completion_response)?); + + let tooltip = generate_tooltip(provider, provider_config.clone().into(), &messages).await?; + println!("\nTooltip: {}", tooltip); + } + + Ok(()) +} diff --git a/crates/goose-llm/src/completion.rs b/crates/goose-llm/src/completion.rs new file mode 100644 index 00000000..2095df73 --- /dev/null +++ b/crates/goose-llm/src/completion.rs @@ -0,0 +1,144 @@ +use std::{collections::HashMap, time::Instant}; + +use anyhow::Result; +use chrono::Utc; +use serde_json::Value; + +use crate::{ + message::{Message, MessageContent}, + prompt_template, + providers::create, + types::{ + completion::{ + CompletionError, CompletionRequest, CompletionResponse, ExtensionConfig, + RuntimeMetrics, ToolApprovalMode, ToolConfig, + }, + core::ToolCall, + }, +}; + +#[uniffi::export] +pub fn print_messages(messages: Vec) { + for msg in messages { + println!("[{:?} @ {}] {:?}", msg.role, msg.created, msg.content); + } +} + +/// Public API for the Goose LLM completion function +#[uniffi::export(async_runtime = "tokio")] +pub async fn completion(req: CompletionRequest) -> Result { + let start_total = Instant::now(); + + let provider = create( + &req.provider_name, + req.provider_config.clone(), + req.model_config.clone(), + ) + .map_err(|_| CompletionError::UnknownProvider(req.provider_name.to_string()))?; + + let system_prompt = construct_system_prompt(&req.system_preamble, &req.extensions)?; + let tools = collect_prefixed_tools(&req.extensions); + + // Call the LLM provider + let start_provider = Instant::now(); + let mut response = provider + .complete(&system_prompt, &req.messages, &tools) + .await?; + let provider_elapsed_sec = start_provider.elapsed().as_secs_f32(); + let usage_tokens = response.usage.total_tokens; + + let tool_configs = collect_prefixed_tool_configs(&req.extensions); + update_needs_approval_for_tool_calls(&mut response.message, &tool_configs)?; + + Ok(CompletionResponse::new( + response.message, + response.model, + response.usage, + calculate_runtime_metrics(start_total, provider_elapsed_sec, usage_tokens), + )) +} + +/// Render the global `system.md` template with the provided context. +fn construct_system_prompt( + system_preamble: &str, + extensions: &[ExtensionConfig], +) -> Result { + let mut context: HashMap<&str, Value> = HashMap::new(); + context.insert("system_preamble", Value::String(system_preamble.to_owned())); + context.insert("extensions", serde_json::to_value(extensions)?); + context.insert( + "current_date", + Value::String(Utc::now().format("%Y-%m-%d").to_string()), + ); + + Ok(prompt_template::render_global_file("system.md", &context)?) +} + +/// Determine if a tool call requires manual approval. +fn determine_needs_approval(config: &ToolConfig, _call: &ToolCall) -> bool { + match config.approval_mode { + ToolApprovalMode::Auto => false, + ToolApprovalMode::Manual => true, + ToolApprovalMode::Smart => { + // TODO: Implement smart approval logic later + true + } + } +} + +/// Set `needs_approval` on every tool call in the message. +/// Returns a `ToolNotFound` error if the corresponding `ToolConfig` is missing. +pub fn update_needs_approval_for_tool_calls( + message: &mut Message, + tool_configs: &HashMap, +) -> Result<(), CompletionError> { + for content in &mut message.content.iter_mut() { + if let MessageContent::ToolReq(req) = content { + if let Ok(call) = &mut req.tool_call.0 { + // Provide a clear error message when the tool config is missing + let config = tool_configs.get(&call.name).ok_or_else(|| { + CompletionError::ToolNotFound(format!( + "could not find tool config for '{}'", + call.name + )) + })?; + let needs_approval = determine_needs_approval(config, call); + call.set_needs_approval(needs_approval); + } + } + } + Ok(()) +} + +/// Collect all `Tool` instances from the extensions. +fn collect_prefixed_tools(extensions: &[ExtensionConfig]) -> Vec { + extensions + .iter() + .flat_map(|ext| ext.get_prefixed_tools()) + .collect() +} + +/// Collect all `ToolConfig` entries from the extensions into a map. +fn collect_prefixed_tool_configs(extensions: &[ExtensionConfig]) -> HashMap { + extensions + .iter() + .flat_map(|ext| ext.get_prefixed_tool_configs()) + .collect() +} + +/// Compute runtime metrics for the request. +fn calculate_runtime_metrics( + total_start: Instant, + provider_elapsed_sec: f32, + token_count: Option, +) -> RuntimeMetrics { + let total_ms = total_start.elapsed().as_secs_f32(); + let tokens_per_sec = token_count.and_then(|toks| { + if provider_elapsed_sec > 0.0 { + Some(toks as f64 / (provider_elapsed_sec as f64)) + } else { + None + } + }); + RuntimeMetrics::new(total_ms, provider_elapsed_sec, tokens_per_sec) +} diff --git a/crates/goose-llm/src/extractors/mod.rs b/crates/goose-llm/src/extractors/mod.rs new file mode 100644 index 00000000..6b5e3be5 --- /dev/null +++ b/crates/goose-llm/src/extractors/mod.rs @@ -0,0 +1,5 @@ +mod session_name; +mod tooltip; + +pub use session_name::generate_session_name; +pub use tooltip::generate_tooltip; diff --git a/crates/goose-llm/src/extractors/session_name.rs b/crates/goose-llm/src/extractors/session_name.rs new file mode 100644 index 00000000..d9d4ca02 --- /dev/null +++ b/crates/goose-llm/src/extractors/session_name.rs @@ -0,0 +1,111 @@ +use crate::generate_structured_outputs; +use crate::providers::errors::ProviderError; +use crate::types::core::Role; +use crate::{message::Message, types::json_value_ffi::JsonValueFfi}; +use anyhow::Result; +use indoc::indoc; +use serde_json::{json, Value}; + +const SESSION_NAME_EXAMPLES: &[&str] = &[ + "Research Synthesis", + "Sentiment Analysis", + "Performance Report", + "Feedback Collector", + "Accessibility Check", + "Design Reminder", + "Project Reminder", + "Launch Checklist", + "Metrics Monitor", + "Incident Response", + "Deploy Cabinet App", + "Design Reminder Alert", + "Generate Monthly Expense Report", + "Automate Incident Response Workflow", + "Analyze Brand Sentiment Trends", + "Monitor Device Health Issues", + "Collect UI Feedback Summary", + "Schedule Project Deadline Reminders", +]; + +fn build_system_prompt() -> String { + let examples = SESSION_NAME_EXAMPLES + .iter() + .map(|e| format!("- {}", e)) + .collect::>() + .join("\n"); + + indoc! {r#" + You are an assistant that crafts a concise session title. + Given the first couple user messages in the conversation so far, + reply with only a short name (up to 4 words) that best describes + this session’s goal. + + Examples: + "#} + .to_string() + + &examples +} + +/// Generates a short (≤4 words) session name +#[uniffi::export(async_runtime = "tokio")] +pub async fn generate_session_name( + provider_name: &str, + provider_config: JsonValueFfi, + messages: &[Message], +) -> Result { + // Collect up to the first 3 user messages (truncated to 300 chars each) + let context: Vec = messages + .iter() + .filter(|m| m.role == Role::User) + .take(3) + .map(|m| { + let text = m.content.concat_text_str(); + if text.len() > 300 { + text.chars().take(300).collect() + } else { + text + } + }) + .collect(); + + if context.is_empty() { + return Err(ProviderError::ExecutionError( + "No user messages found to generate a session name.".to_string(), + )); + } + + let system_prompt = build_system_prompt(); + let user_msg_text = format!("Here are the user messages:\n{}", context.join("\n")); + + // Use `extract` with a simple string schema + let schema = json!({ + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "required": ["name"], + "additionalProperties": false + }); + + let resp = generate_structured_outputs( + provider_name, + provider_config, + &system_prompt, + &[Message::user().with_text(&user_msg_text)], + schema, + ) + .await?; + + let obj = resp + .data + .as_object() + .ok_or_else(|| ProviderError::ResponseParseError("Expected object".into()))?; + + let name = obj + .get("name") + .and_then(Value::as_str) + .ok_or_else(|| ProviderError::ResponseParseError("Missing or non-string name".into()))? + .to_string(); + + Ok(name) +} diff --git a/crates/goose-llm/src/extractors/tooltip.rs b/crates/goose-llm/src/extractors/tooltip.rs new file mode 100644 index 00000000..37d83ffe --- /dev/null +++ b/crates/goose-llm/src/extractors/tooltip.rs @@ -0,0 +1,169 @@ +use crate::generate_structured_outputs; +use crate::message::{Message, MessageContent}; +use crate::providers::errors::ProviderError; +use crate::types::core::{Content, Role}; +use crate::types::json_value_ffi::JsonValueFfi; +use anyhow::Result; +use indoc::indoc; +use serde_json::{json, Value}; + +const TOOLTIP_EXAMPLES: &[&str] = &[ + "analyzing KPIs", + "detecting anomalies", + "building artifacts in Buildkite", + "categorizing issues", + "checking dependencies", + "collecting feedback", + "deploying changes in AWS", + "drafting report in Google Docs", + "extracting action items", + "generating insights", + "logging issues", + "monitoring tickets in Zendesk", + "notifying design team", + "running integration tests", + "scanning threads in Figma", + "sending reminders in Gmail", + "sending surveys", + "sharing with stakeholders", + "summarizing findings", + "transcribing meeting", + "tracking resolution", + "updating status in Linear", +]; + +fn build_system_prompt() -> String { + let examples = TOOLTIP_EXAMPLES + .iter() + .map(|e| format!("- {}", e)) + .collect::>() + .join("\n"); + + indoc! {r#" + You are an assistant that summarizes the recent conversation into a tooltip. + Given the last two messages, reply with only a short tooltip (up to 4 words) + describing what is happening now. + + Examples: + "#} + .to_string() + + &examples +} + +/// Generates a tooltip summarizing the last two messages in the session, +/// including any tool calls or results. +#[uniffi::export(async_runtime = "tokio")] +pub async fn generate_tooltip( + provider_name: &str, + provider_config: JsonValueFfi, + messages: &[Message], +) -> Result { + // Need at least two messages to generate a tooltip + if messages.len() < 2 { + return Err(ProviderError::ExecutionError( + "Need at least two messages to generate a tooltip".to_string(), + )); + } + + // Helper to render a single message's content + fn render_message(m: &Message) -> String { + let mut parts = Vec::new(); + for content in m.content.iter() { + match content { + MessageContent::Text(text_block) => { + let txt = text_block.text.trim(); + if !txt.is_empty() { + parts.push(txt.to_string()); + } + } + MessageContent::ToolReq(req) => { + if let Ok(tool_call) = &req.tool_call.0 { + parts.push(format!( + "called tool '{}' with args {}", + tool_call.name, tool_call.arguments + )); + } else if let Err(e) = &req.tool_call.0 { + parts.push(format!("tool request error: {}", e)); + } + } + MessageContent::ToolResp(resp) => match &resp.tool_result.0 { + Ok(contents) => { + let results: Vec = contents + .iter() + .map(|c| match c { + Content::Text(t) => t.text.clone(), + Content::Image(_) => "[image]".to_string(), + }) + .collect(); + parts.push(format!("tool responded with: {}", results.join(" "))); + } + Err(e) => { + parts.push(format!("tool error: {}", e)); + } + }, + _ => {} // ignore other variants + } + } + + let role = match m.role { + Role::User => "User", + Role::Assistant => "Assistant", + }; + + format!("{}: {}", role, parts.join("; ")) + } + + // Take the last two messages (in correct chronological order) + let rendered: Vec = messages + .iter() + .rev() + .take(2) + .map(render_message) + .collect::>() + .into_iter() + .rev() + .collect(); + + let system_prompt = build_system_prompt(); + + let user_msg_text = format!( + "Here are the last two messages:\n{}\n\nTooltip:", + rendered.join("\n") + ); + + // Schema wrapping our tooltip string + let schema = json!({ + "type": "object", + "properties": { + "tooltip": { "type": "string" } + }, + "required": ["tooltip"], + "additionalProperties": false + }); + + // Get the structured outputs + let resp = generate_structured_outputs( + provider_name, + provider_config, + &system_prompt, + &[Message::user().with_text(&user_msg_text)], + schema, + ) + .await?; + + // Pull out the tooltip field + let obj = resp + .data + .as_object() + .ok_or_else(|| ProviderError::ResponseParseError("Expected JSON object".into()))?; + + let tooltip = obj + .get("tooltip") + .and_then(Value::as_str) + .ok_or_else(|| { + ProviderError::ResponseParseError("Missing or non-string `tooltip` field".into()) + })? + .to_string(); + + Ok(tooltip) +} diff --git a/crates/goose-llm/src/lib.rs b/crates/goose-llm/src/lib.rs new file mode 100644 index 00000000..cd698356 --- /dev/null +++ b/crates/goose-llm/src/lib.rs @@ -0,0 +1,15 @@ +uniffi::setup_scaffolding!(); + +mod completion; +pub mod extractors; +pub mod message; +mod model; +mod prompt_template; +pub mod providers; +mod structured_outputs; +pub mod types; + +pub use completion::completion; +pub use message::Message; +pub use model::ModelConfig; +pub use structured_outputs::generate_structured_outputs; diff --git a/crates/goose-llm/src/message/contents.rs b/crates/goose-llm/src/message/contents.rs new file mode 100644 index 00000000..89e80f79 --- /dev/null +++ b/crates/goose-llm/src/message/contents.rs @@ -0,0 +1,84 @@ +use std::{iter::FromIterator, ops::Deref}; + +use crate::message::MessageContent; +use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; + +/// Holds the heterogeneous fragments that make up one chat message. +/// +/// * Up to two items are stored inline on the stack. +/// * Falls back to a heap allocation only when necessary. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] +#[serde(transparent)] +pub struct Contents(SmallVec<[MessageContent; 2]>); + +impl Contents { + /*---------------------------------------------------------- + * 1-line ergonomic helpers + *---------------------------------------------------------*/ + + pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, MessageContent> { + self.0.iter_mut() + } + + pub fn push(&mut self, item: impl Into) { + self.0.push(item.into()); + } + + pub fn texts(&self) -> impl Iterator { + self.0.iter().filter_map(|c| c.as_text()) + } + + pub fn concat_text_str(&self) -> String { + self.texts().collect::>().join("\n") + } + + /// Returns `true` if *any* item satisfies the predicate. + pub fn any_is

(&self, pred: P) -> bool + where + P: FnMut(&MessageContent) -> bool, + { + self.iter().any(pred) + } + + /// Returns `true` if *every* item satisfies the predicate. + pub fn all_are

(&self, pred: P) -> bool + where + P: FnMut(&MessageContent) -> bool, + { + self.iter().all(pred) + } +} + +impl From> for Contents { + fn from(v: Vec) -> Self { + Contents(SmallVec::from_vec(v)) + } +} + +impl FromIterator for Contents { + fn from_iter>(iter: I) -> Self { + Contents(SmallVec::from_iter(iter)) + } +} + +/*-------------------------------------------------------------- + * Allow &message.content to behave like a slice of fragments. + *-------------------------------------------------------------*/ +impl Deref for Contents { + type Target = [MessageContent]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// — Register the contents type with UniFFI, converting to/from Vec — +// We need to do this because UniFFI’s FFI layer supports only primitive buffers (here Vec), +uniffi::custom_type!(Contents, Vec, { + lower: |contents: &Contents| { + contents.0.to_vec() + }, + try_lift: |contents: Vec| { + Ok(Contents::from(contents)) + }, +}); diff --git a/crates/goose-llm/src/message/message_content.rs b/crates/goose-llm/src/message/message_content.rs new file mode 100644 index 00000000..b657fbfb --- /dev/null +++ b/crates/goose-llm/src/message/message_content.rs @@ -0,0 +1,240 @@ +use serde::{Deserialize, Serialize}; +use serde_json; + +use crate::message::tool_result_serde; +use crate::types::core::{Content, ImageContent, TextContent, ToolCall, ToolResult}; + +// — Newtype wrappers (local structs) so we satisfy Rust’s orphan rules — +// We need these because we can’t implement UniFFI’s FfiConverter directly on a type alias. + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ToolRequestToolCall(#[serde(with = "tool_result_serde")] pub ToolResult); + +impl ToolRequestToolCall { + pub fn as_result(&self) -> &ToolResult { + &self.0 + } +} +impl std::ops::Deref for ToolRequestToolCall { + type Target = ToolResult; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl From> for ToolRequestToolCall { + fn from(res: Result) -> Self { + ToolRequestToolCall(res) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ToolResponseToolResult( + #[serde(with = "tool_result_serde")] pub ToolResult>, +); + +impl ToolResponseToolResult { + pub fn as_result(&self) -> &ToolResult> { + &self.0 + } +} +impl std::ops::Deref for ToolResponseToolResult { + type Target = ToolResult>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl From, crate::types::core::ToolError>> for ToolResponseToolResult { + fn from(res: Result, crate::types::core::ToolError>) -> Self { + ToolResponseToolResult(res) + } +} + +// — Register the newtypes with UniFFI, converting via JSON strings — +// UniFFI’s FFI layer supports only primitive buffers (here String), so we JSON-serialize +// through our `tool_result_serde` to preserve the same success/error schema on both sides. + +uniffi::custom_type!(ToolRequestToolCall, String, { + lower: |obj| { + serde_json::to_string(&obj.0).unwrap() + }, + try_lift: |val| { + Ok(serde_json::from_str(&val).unwrap() ) + }, +}); + +uniffi::custom_type!(ToolResponseToolResult, String, { + lower: |obj| { + serde_json::to_string(&obj.0).unwrap() + }, + try_lift: |val| { + Ok(serde_json::from_str(&val).unwrap() ) + }, +}); + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Record)] +#[serde(rename_all = "camelCase")] +pub struct ToolRequest { + pub id: String, + pub tool_call: ToolRequestToolCall, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Record)] +#[serde(rename_all = "camelCase")] +pub struct ToolResponse { + pub id: String, + pub tool_result: ToolResponseToolResult, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Record)] +pub struct ThinkingContent { + pub thinking: String, + pub signature: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Record)] +pub struct RedactedThinkingContent { + pub data: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Enum)] +/// Content passed inside a message, which can be both simple content and tool content +#[serde(tag = "type", rename_all = "camelCase")] +pub enum MessageContent { + Text(TextContent), + Image(ImageContent), + ToolReq(ToolRequest), + ToolResp(ToolResponse), + Thinking(ThinkingContent), + RedactedThinking(RedactedThinkingContent), +} + +impl MessageContent { + pub fn text>(text: S) -> Self { + MessageContent::Text(TextContent { text: text.into() }) + } + + pub fn image, T: Into>(data: S, mime_type: T) -> Self { + MessageContent::Image(ImageContent { + data: data.into(), + mime_type: mime_type.into(), + }) + } + + pub fn tool_request>(id: S, tool_call: ToolRequestToolCall) -> Self { + MessageContent::ToolReq(ToolRequest { + id: id.into(), + tool_call, + }) + } + + pub fn tool_response>(id: S, tool_result: ToolResponseToolResult) -> Self { + MessageContent::ToolResp(ToolResponse { + id: id.into(), + tool_result, + }) + } + + pub fn thinking, S2: Into>(thinking: S1, signature: S2) -> Self { + MessageContent::Thinking(ThinkingContent { + thinking: thinking.into(), + signature: signature.into(), + }) + } + + pub fn redacted_thinking>(data: S) -> Self { + MessageContent::RedactedThinking(RedactedThinkingContent { data: data.into() }) + } + + pub fn as_tool_request(&self) -> Option<&ToolRequest> { + if let MessageContent::ToolReq(ref tool_request) = self { + Some(tool_request) + } else { + None + } + } + + pub fn as_tool_response(&self) -> Option<&ToolResponse> { + if let MessageContent::ToolResp(ref tool_response) = self { + Some(tool_response) + } else { + None + } + } + + pub fn as_tool_response_text(&self) -> Option { + if let Some(tool_response) = self.as_tool_response() { + if let Ok(contents) = &tool_response.tool_result.0 { + let texts: Vec = contents + .iter() + .filter_map(|content| content.as_text().map(String::from)) + .collect(); + if !texts.is_empty() { + return Some(texts.join("\n")); + } + } + } + None + } + + pub fn as_tool_request_id(&self) -> Option<&str> { + if let Self::ToolReq(r) = self { + Some(&r.id) + } else { + None + } + } + + pub fn as_tool_response_id(&self) -> Option<&str> { + if let Self::ToolResp(r) = self { + Some(&r.id) + } else { + None + } + } + + /// Get the text content if this is a TextContent variant + pub fn as_text(&self) -> Option<&str> { + match self { + MessageContent::Text(text) => Some(&text.text), + _ => None, + } + } + + /// Get the thinking content if this is a ThinkingContent variant + pub fn as_thinking(&self) -> Option<&ThinkingContent> { + match self { + MessageContent::Thinking(thinking) => Some(thinking), + _ => None, + } + } + + /// Get the redacted thinking content if this is a RedactedThinkingContent variant + pub fn as_redacted_thinking(&self) -> Option<&RedactedThinkingContent> { + match self { + MessageContent::RedactedThinking(redacted) => Some(redacted), + _ => None, + } + } + + pub fn is_text(&self) -> bool { + matches!(self, Self::Text(_)) + } + pub fn is_image(&self) -> bool { + matches!(self, Self::Image(_)) + } + pub fn is_tool_request(&self) -> bool { + matches!(self, Self::ToolReq(_)) + } + pub fn is_tool_response(&self) -> bool { + matches!(self, Self::ToolResp(_)) + } +} + +impl From for MessageContent { + fn from(content: Content) -> Self { + match content { + Content::Text(text) => MessageContent::Text(text), + Content::Image(image) => MessageContent::Image(image), + } + } +} diff --git a/crates/goose-llm/src/message/mod.rs b/crates/goose-llm/src/message/mod.rs new file mode 100644 index 00000000..0212fbc3 --- /dev/null +++ b/crates/goose-llm/src/message/mod.rs @@ -0,0 +1,284 @@ +//! Messages which represent the content sent back and forth to LLM provider +//! +//! We use these messages in the agent code, and interfaces which interact with +//! the agent. That let's us reuse message histories across different interfaces. +//! +//! The content of the messages uses MCP types to avoid additional conversions +//! when interacting with MCP servers. + +mod contents; +mod message_content; +mod tool_result_serde; + +pub use contents::Contents; +pub use message_content::{ + MessageContent, RedactedThinkingContent, ThinkingContent, ToolRequest, ToolRequestToolCall, + ToolResponse, ToolResponseToolResult, +}; + +use chrono::Utc; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +use crate::types::core::Role; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Record)] +/// A message to or from an LLM +#[serde(rename_all = "camelCase")] +pub struct Message { + pub role: Role, + pub created: i64, + pub content: Contents, +} + +impl Message { + pub fn new(role: Role) -> Self { + Self { + role, + created: Utc::now().timestamp_millis(), + content: Contents::default(), + } + } + + /// Create a new user message with the current timestamp + pub fn user() -> Self { + Self::new(Role::User) + } + + /// Create a new assistant message with the current timestamp + pub fn assistant() -> Self { + Self::new(Role::Assistant) + } + + /// Add any item that implements Into to the message + pub fn with_content(mut self, item: impl Into) -> Self { + self.content.push(item); + self + } + + /// Add text content to the message + pub fn with_text>(self, text: S) -> Self { + self.with_content(MessageContent::text(text)) + } + + /// Add image content to the message + pub fn with_image, T: Into>(self, data: S, mime_type: T) -> Self { + self.with_content(MessageContent::image(data, mime_type)) + } + + /// Add a tool request to the message + pub fn with_tool_request, T: Into>( + self, + id: S, + tool_call: T, + ) -> Self { + self.with_content(MessageContent::tool_request(id, tool_call.into())) + } + + /// Add a tool response to the message + pub fn with_tool_response>( + self, + id: S, + result: ToolResponseToolResult, + ) -> Self { + self.with_content(MessageContent::tool_response(id, result)) + } + + /// Add thinking content to the message + pub fn with_thinking, S2: Into>( + self, + thinking: S1, + signature: S2, + ) -> Self { + self.with_content(MessageContent::thinking(thinking, signature)) + } + + /// Add redacted thinking content to the message + pub fn with_redacted_thinking>(self, data: S) -> Self { + self.with_content(MessageContent::redacted_thinking(data)) + } + + /// Check if the message is a tool call + pub fn contains_tool_call(&self) -> bool { + self.content.any_is(MessageContent::is_tool_request) + } + + /// Check if the message is a tool response + pub fn contains_tool_response(&self) -> bool { + self.content.any_is(MessageContent::is_tool_response) + } + + /// Check if the message contains only text content + pub fn has_only_text_content(&self) -> bool { + self.content.all_are(MessageContent::is_text) + } + + /// Retrieves all tool `id` from ToolRequest messages + pub fn tool_request_ids(&self) -> HashSet<&str> { + self.content + .iter() + .filter_map(MessageContent::as_tool_request_id) + .collect() + } + + /// Retrieves all tool `id` from ToolResponse messages + pub fn tool_response_ids(&self) -> HashSet<&str> { + self.content + .iter() + .filter_map(MessageContent::as_tool_response_id) + .collect() + } + + /// Retrieves all tool `id` from the message + pub fn tool_ids(&self) -> HashSet<&str> { + self.tool_request_ids() + .into_iter() + .chain(self.tool_response_ids()) + .collect() + } +} + +#[cfg(test)] +mod tests { + use serde_json::{json, Value}; + + use super::*; + use crate::types::core::{ToolCall, ToolError}; + + #[test] + fn test_message_serialization() { + let message = Message::assistant() + .with_text("Hello, I'll help you with that.") + .with_tool_request( + "tool123", + Ok(ToolCall::new("test_tool", json!({"param": "value"})).into()), + ); + + let json_str = serde_json::to_string_pretty(&message).unwrap(); + println!("Serialized message: {}", json_str); + + // Parse back to Value to check structure + let value: Value = serde_json::from_str(&json_str).unwrap(); + println!( + "Read back serialized message: {}", + serde_json::to_string_pretty(&value).unwrap() + ); + + // Check top-level fields + assert_eq!(value["role"], "assistant"); + assert!(value["created"].is_i64()); + assert!(value["content"].is_array()); + + // Check content items + let content = &value["content"]; + + // First item should be text + assert_eq!(content[0]["type"], "text"); + assert_eq!(content[0]["text"], "Hello, I'll help you with that."); + + // Second item should be toolRequest + assert_eq!(content[1]["type"], "toolReq"); + assert_eq!(content[1]["id"], "tool123"); + + // Check tool_call serialization + assert_eq!(content[1]["toolCall"]["status"], "success"); + assert_eq!(content[1]["toolCall"]["value"]["name"], "test_tool"); + assert_eq!( + content[1]["toolCall"]["value"]["arguments"]["param"], + "value" + ); + } + + #[test] + fn test_error_serialization() { + let message = Message::assistant().with_tool_request( + "tool123", + Err(ToolError::ExecutionError( + "Something went wrong".to_string(), + )), + ); + + let json_str = serde_json::to_string_pretty(&message).unwrap(); + println!("Serialized error: {}", json_str); + + // Parse back to Value to check structure + let value: Value = serde_json::from_str(&json_str).unwrap(); + + // Check tool_call serialization with error + let tool_call = &value["content"][0]["toolCall"]; + assert_eq!(tool_call["status"], "error"); + assert_eq!(tool_call["error"], "Execution failed: Something went wrong"); + } + + #[test] + fn test_deserialization() { + // Create a JSON string with our new format + let json_str = r#"{ + "role": "assistant", + "created": 1740171566, + "content": [ + { + "type": "text", + "text": "I'll help you with that." + }, + { + "type": "toolReq", + "id": "tool123", + "toolCall": { + "status": "success", + "value": { + "name": "test_tool", + "arguments": {"param": "value"}, + "needsApproval": false + } + } + } + ] + }"#; + + let message: Message = serde_json::from_str(json_str).unwrap(); + + assert_eq!(message.role, Role::Assistant); + assert_eq!(message.created, 1740171566); + assert_eq!(message.content.len(), 2); + + // Check first content item + if let MessageContent::Text(text) = &message.content[0] { + assert_eq!(text.text, "I'll help you with that."); + } else { + panic!("Expected Text content"); + } + + // Check second content item + if let MessageContent::ToolReq(req) = &message.content[1] { + assert_eq!(req.id, "tool123"); + if let Ok(tool_call) = req.tool_call.as_result() { + assert_eq!(tool_call.name, "test_tool"); + assert_eq!(tool_call.arguments, json!({"param": "value"})); + } else { + panic!("Expected successful tool call"); + } + } else { + panic!("Expected ToolRequest content"); + } + } + + #[test] + fn test_message_with_text() { + let message = Message::user().with_text("Hello"); + assert_eq!(message.content.concat_text_str(), "Hello"); + } + + #[test] + fn test_message_with_tool_request() { + let tool_call = Ok(ToolCall::new("test_tool", json!({}))); + + let message = Message::assistant().with_tool_request("req1", tool_call); + assert!(message.contains_tool_call()); + assert!(!message.contains_tool_response()); + + let ids = message.tool_ids(); + assert_eq!(ids.len(), 1); + assert!(ids.contains("req1")); + } +} diff --git a/crates/goose-llm/src/message/tool_result_serde.rs b/crates/goose-llm/src/message/tool_result_serde.rs new file mode 100644 index 00000000..7f114322 --- /dev/null +++ b/crates/goose-llm/src/message/tool_result_serde.rs @@ -0,0 +1,64 @@ +use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::types::core::{ToolError, ToolResult}; + +pub fn serialize(value: &ToolResult, serializer: S) -> Result +where + T: Serialize, + S: Serializer, +{ + match value { + Ok(val) => { + let mut state = serializer.serialize_struct("ToolResult", 2)?; + state.serialize_field("status", "success")?; + state.serialize_field("value", val)?; + state.end() + } + Err(err) => { + let mut state = serializer.serialize_struct("ToolResult", 2)?; + state.serialize_field("status", "error")?; + state.serialize_field("error", &err.to_string())?; + state.end() + } + } +} + +// For deserialization, let's use a simpler approach that works with the format we're serializing to +pub fn deserialize<'de, T, D>(deserializer: D) -> Result, D::Error> +where + T: Deserialize<'de>, + D: Deserializer<'de>, +{ + // Define a helper enum to handle the two possible formats + #[derive(Deserialize)] + #[serde(untagged)] + enum ResultFormat { + Success { status: String, value: T }, + Error { status: String, error: String }, + } + + let format = ResultFormat::deserialize(deserializer)?; + + match format { + ResultFormat::Success { status, value } => { + if status == "success" { + Ok(Ok(value)) + } else { + Err(serde::de::Error::custom(format!( + "Expected status 'success', got '{}'", + status + ))) + } + } + ResultFormat::Error { status, error } => { + if status == "error" { + Ok(Err(ToolError::ExecutionError(error))) + } else { + Err(serde::de::Error::custom(format!( + "Expected status 'error', got '{}'", + status + ))) + } + } + } +} diff --git a/crates/goose-llm/src/model.rs b/crates/goose-llm/src/model.rs new file mode 100644 index 00000000..5ad89ab2 --- /dev/null +++ b/crates/goose-llm/src/model.rs @@ -0,0 +1,119 @@ +use serde::{Deserialize, Serialize}; + +const DEFAULT_CONTEXT_LIMIT: u32 = 128_000; + +/// Configuration for model-specific settings and limits +#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] +pub struct ModelConfig { + /// The name of the model to use + pub model_name: String, + /// Optional explicit context limit that overrides any defaults + pub context_limit: Option, + /// Optional temperature setting (0.0 - 1.0) + pub temperature: Option, + /// Optional maximum tokens to generate + pub max_tokens: Option, +} + +impl ModelConfig { + /// Create a new ModelConfig with the specified model name + /// + /// The context limit is set with the following precedence: + /// 1. Explicit context_limit if provided in config + /// 2. Model-specific default based on model name + /// 3. Global default (128_000) (in get_context_limit) + pub fn new(model_name: String) -> Self { + let context_limit = Self::get_model_specific_limit(&model_name); + + Self { + model_name, + context_limit, + temperature: None, + max_tokens: None, + } + } + + /// Get model-specific context limit based on model name + fn get_model_specific_limit(model_name: &str) -> Option { + // Implement some sensible defaults + match model_name { + // OpenAI models, https://platform.openai.com/docs/models#models-overview + name if name.contains("gpt-4o") => Some(128_000), + name if name.contains("gpt-4-turbo") => Some(128_000), + + // Anthropic models, https://docs.anthropic.com/en/docs/about-claude/models + name if name.contains("claude-3") => Some(200_000), + name if name.contains("claude-4") => Some(200_000), + + // Meta Llama models, https://github.com/meta-llama/llama-models/tree/main?tab=readme-ov-file#llama-models-1 + name if name.contains("llama3.2") => Some(128_000), + name if name.contains("llama3.3") => Some(128_000), + _ => None, + } + } + + /// Set an explicit context limit + pub fn with_context_limit(mut self, limit: Option) -> Self { + // Default is None and therefore DEFAULT_CONTEXT_LIMIT, only set + // if input is Some to allow passing through with_context_limit in + // configuration cases + if limit.is_some() { + self.context_limit = limit; + } + self + } + + /// Set the temperature + pub fn with_temperature(mut self, temp: Option) -> Self { + self.temperature = temp; + self + } + + /// Set the max tokens + pub fn with_max_tokens(mut self, tokens: Option) -> Self { + self.max_tokens = tokens; + self + } + + /// Get the context_limit for the current model + /// If none are defined, use the DEFAULT_CONTEXT_LIMIT + pub fn context_limit(&self) -> u32 { + self.context_limit.unwrap_or(DEFAULT_CONTEXT_LIMIT) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_model_config_context_limits() { + // Test explicit limit + let config = + ModelConfig::new("claude-3-opus".to_string()).with_context_limit(Some(150_000)); + assert_eq!(config.context_limit(), 150_000); + + // Test model-specific defaults + let config = ModelConfig::new("claude-3-opus".to_string()); + assert_eq!(config.context_limit(), 200_000); + + let config = ModelConfig::new("gpt-4-turbo".to_string()); + assert_eq!(config.context_limit(), 128_000); + + // Test fallback to default + let config = ModelConfig::new("unknown-model".to_string()); + assert_eq!(config.context_limit(), DEFAULT_CONTEXT_LIMIT); + } + + #[test] + fn test_model_config_settings() { + let config = ModelConfig::new("test-model".to_string()) + .with_temperature(Some(0.7)) + .with_max_tokens(Some(1000)) + .with_context_limit(Some(50_000)); + + assert_eq!(config.temperature, Some(0.7)); + assert_eq!(config.max_tokens, Some(1000)); + assert_eq!(config.context_limit, Some(50_000)); + } +} diff --git a/crates/goose-llm/src/prompt_template.rs b/crates/goose-llm/src/prompt_template.rs new file mode 100644 index 00000000..eca9facb --- /dev/null +++ b/crates/goose-llm/src/prompt_template.rs @@ -0,0 +1,115 @@ +use std::{ + path::PathBuf, + sync::{Arc, RwLock}, +}; + +use include_dir::{include_dir, Dir}; +use minijinja::{Environment, Error as MiniJinjaError, Value as MJValue}; +use once_cell::sync::Lazy; +use serde::Serialize; + +/// This directory will be embedded into the final binary. +/// Typically used to store "core" or "system" prompts. +static CORE_PROMPTS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/prompts"); + +/// A global MiniJinja environment storing the "core" prompts. +/// +/// - Loaded at startup from the `CORE_PROMPTS_DIR`. +/// - Ideal for "system" templates that don't change often. +/// - *Not* used for extension prompts (which are ephemeral). +static GLOBAL_ENV: Lazy>>> = Lazy::new(|| { + let mut env = Environment::new(); + + // Pre-load all core templates from the embedded dir. + for file in CORE_PROMPTS_DIR.files() { + let name = file.path().to_string_lossy().to_string(); + let source = String::from_utf8_lossy(file.contents()).to_string(); + + // Since we're using 'static lifetime for the Environment, we need to ensure + // the strings we add as templates live for the entire program duration. + // We can achieve this by leaking the strings (acceptable for initialization). + let static_name: &'static str = Box::leak(name.into_boxed_str()); + let static_source: &'static str = Box::leak(source.into_boxed_str()); + + if let Err(e) = env.add_template(static_name, static_source) { + println!("Failed to add template {}: {}", static_name, e); + } + } + + Arc::new(RwLock::new(env)) +}); + +/// Renders a prompt from the global environment by name. +/// +/// # Arguments +/// * `template_name` - The name of the template (usually the file path or a custom ID). +/// * `context_data` - Data to be inserted into the template (must be `Serialize`). +pub fn render_global_template( + template_name: &str, + context_data: &T, +) -> Result { + let env = GLOBAL_ENV.read().expect("GLOBAL_ENV lock poisoned"); + let tmpl = env.get_template(template_name)?; + let ctx = MJValue::from_serialize(context_data); + let rendered = tmpl.render(ctx)?; + Ok(rendered.trim().to_string()) +} + +/// Renders a file from `CORE_PROMPTS_DIR` within the global environment. +/// +/// # Arguments +/// * `template_file` - The file path within the embedded directory (e.g. "system.md"). +/// * `context_data` - Data to be inserted into the template (must be `Serialize`). +/// +/// This function **assumes** the file is already in `CORE_PROMPTS_DIR`. If it wasn't +/// added to the global environment at startup (due to parse errors, etc.), this will error out. +pub fn render_global_file( + template_file: impl Into, + context_data: &T, +) -> Result { + let file_path = template_file.into(); + let template_name = file_path.to_string_lossy().to_string(); + + render_global_template(&template_name, context_data) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// For convenience in tests, define a small struct or use a HashMap to provide context. + #[derive(Serialize)] + struct TestContext { + name: String, + age: u32, + } + + #[test] + fn test_global_file_render() { + // "mock.md" should exist in the embedded CORE_PROMPTS_DIR + // and have placeholders for `name` and `age`. + let context = TestContext { + name: "Alice".to_string(), + age: 30, + }; + + let result = render_global_file("mock.md", &context).unwrap(); + // Assume mock.md content is something like: + // "This prompt is only used for testing.\n\nHello, {{ name }}! You are {{ age }} years old." + assert_eq!( + result, + "This prompt is only used for testing.\n\nHello, Alice! You are 30 years old." + ); + } + + #[test] + fn test_global_file_not_found() { + let context = TestContext { + name: "Unused".to_string(), + age: 99, + }; + + let result = render_global_file("non_existent.md", &context); + assert!(result.is_err(), "Should fail because file is missing"); + } +} diff --git a/crates/goose-llm/src/prompts/mock.md b/crates/goose-llm/src/prompts/mock.md new file mode 100644 index 00000000..46c1e708 --- /dev/null +++ b/crates/goose-llm/src/prompts/mock.md @@ -0,0 +1,3 @@ +This prompt is only used for testing. + +Hello, {{ name }}! You are {{ age }} years old. \ No newline at end of file diff --git a/crates/goose-llm/src/prompts/system.md b/crates/goose-llm/src/prompts/system.md new file mode 100644 index 00000000..4a2aacde --- /dev/null +++ b/crates/goose-llm/src/prompts/system.md @@ -0,0 +1,34 @@ +{{system_preamble}} + +The current date is {{current_date}}. + +Goose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o, claude-3.5-sonnet, o1, llama-3.2, deepseek-r1, etc). +These models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10 months prior to the current date. + +# Extensions + +Extensions allow other applications to provide context to Goose. Extensions connect Goose to different data sources and tools. + +{% if (extensions is defined) and extensions %} +Because you dynamically load extensions, your conversation history may refer +to interactions with extensions that are not currently active. The currently +active extensions are below. Each of these extensions provides tools that are +in your tool specification. + +{% for extension in extensions %} +## {{extension.name}} +{% if extension.instructions %}### Instructions +{{extension.instructions}}{% endif %} +{% endfor %} +{% else %} +No extensions are defined. You should let the user know that they should add extensions.{% endif %} + +# Response Guidelines + +- Use Markdown formatting for all responses. +- Follow best practices for Markdown, including: + - Using headers for organization. + - Bullet points for lists. + - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic links using angle brackets (e.g., ). +- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting. +- Ensure clarity, conciseness, and proper formatting to enhance readability and usability. diff --git a/crates/goose-llm/src/providers/base.rs b/crates/goose-llm/src/providers/base.rs new file mode 100644 index 00000000..dcfecbd1 --- /dev/null +++ b/crates/goose-llm/src/providers/base.rs @@ -0,0 +1,131 @@ +use anyhow::Result; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +use super::errors::ProviderError; +use crate::{message::Message, types::core::Tool}; + +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, uniffi::Record)] +pub struct Usage { + pub input_tokens: Option, + pub output_tokens: Option, + pub total_tokens: Option, +} + +impl Usage { + pub fn new( + input_tokens: Option, + output_tokens: Option, + total_tokens: Option, + ) -> Self { + Self { + input_tokens, + output_tokens, + total_tokens, + } + } +} + +#[derive(Debug, Clone, uniffi::Record)] +pub struct ProviderCompleteResponse { + pub message: Message, + pub model: String, + pub usage: Usage, +} + +impl ProviderCompleteResponse { + pub fn new(message: Message, model: String, usage: Usage) -> Self { + Self { + message, + model, + usage, + } + } +} + +/// Response from a structured‐extraction call +#[derive(Debug, Clone, uniffi::Record)] +pub struct ProviderExtractResponse { + /// The extracted JSON object + pub data: serde_json::Value, + /// Which model produced it + pub model: String, + /// Token usage stats + pub usage: Usage, +} + +impl ProviderExtractResponse { + pub fn new(data: serde_json::Value, model: String, usage: Usage) -> Self { + Self { data, model, usage } + } +} + +/// Base trait for AI providers (OpenAI, Anthropic, etc) +#[async_trait] +pub trait Provider: Send + Sync { + /// Generate the next message using the configured model and other parameters + /// + /// # Arguments + /// * `system` - The system prompt that guides the model's behavior + /// * `messages` - The conversation history as a sequence of messages + /// * `tools` - Optional list of tools the model can use + /// + /// # Returns + /// A tuple containing the model's response message and provider usage statistics + /// + /// # Errors + /// ProviderError + /// - It's important to raise ContextLengthExceeded correctly since agent handles it + async fn complete( + &self, + system: &str, + messages: &[Message], + tools: &[Tool], + ) -> Result; + + /// Structured extraction: always JSON‐Schema + /// + /// # Arguments + /// * `system` – system prompt guiding the extraction task + /// * `messages` – conversation history + /// * `schema` – a JSON‐Schema for the expected output. + /// Will set strict=true for OpenAI & Databricks. + /// + /// # Returns + /// A `ProviderExtractResponse` whose `data` is a JSON object matching `schema`. + /// + /// # Errors + /// * `ProviderError::ContextLengthExceeded` if the prompt is too large + /// * other `ProviderError` variants for API/network failures + async fn extract( + &self, + system: &str, + messages: &[Message], + schema: &serde_json::Value, + ) -> Result; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_usage_creation() { + let usage = Usage::new(Some(10), Some(20), Some(30)); + assert_eq!(usage.input_tokens, Some(10)); + assert_eq!(usage.output_tokens, Some(20)); + assert_eq!(usage.total_tokens, Some(30)); + } + + #[test] + fn test_provider_complete_response_creation() { + let message = Message::user().with_text("Hello, world!"); + let usage = Usage::new(Some(10), Some(20), Some(30)); + let response = + ProviderCompleteResponse::new(message.clone(), "test_model".to_string(), usage.clone()); + + assert_eq!(response.message, message); + assert_eq!(response.model, "test_model"); + assert_eq!(response.usage, usage); + } +} diff --git a/crates/goose-llm/src/providers/databricks.rs b/crates/goose-llm/src/providers/databricks.rs new file mode 100644 index 00000000..13e0c1a2 --- /dev/null +++ b/crates/goose-llm/src/providers/databricks.rs @@ -0,0 +1,298 @@ +use std::time::Duration; + +use anyhow::Result; +use async_trait::async_trait; +use reqwest::{Client, StatusCode}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use url::Url; + +use super::{ + errors::ProviderError, + formats::databricks::{create_request, get_usage, response_to_message}, + utils::{get_env, get_model, ImageFormat}, +}; +use crate::{ + message::Message, + model::ModelConfig, + providers::{Provider, ProviderCompleteResponse, ProviderExtractResponse, Usage}, + types::core::Tool, +}; + +pub const DATABRICKS_DEFAULT_MODEL: &str = "databricks-claude-3-7-sonnet"; +// Databricks can passthrough to a wide range of models, we only provide the default +pub const _DATABRICKS_KNOWN_MODELS: &[&str] = &[ + "databricks-meta-llama-3-3-70b-instruct", + "databricks-claude-3-7-sonnet", +]; + +fn default_timeout() -> u64 { + 60 +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DatabricksProviderConfig { + pub host: String, + pub token: String, + #[serde(default)] + pub image_format: ImageFormat, + #[serde(default = "default_timeout")] + pub timeout: u64, // timeout in seconds +} + +impl DatabricksProviderConfig { + pub fn new(host: String, token: String) -> Self { + Self { + host, + token, + image_format: ImageFormat::OpenAi, + timeout: default_timeout(), + } + } + + pub fn from_env() -> Self { + let host = get_env("DATABRICKS_HOST").expect("Missing DATABRICKS_HOST"); + let token = get_env("DATABRICKS_TOKEN").expect("Missing DATABRICKS_TOKEN"); + Self::new(host, token) + } +} + +#[derive(Debug)] +pub struct DatabricksProvider { + config: DatabricksProviderConfig, + model: ModelConfig, + client: Client, +} + +impl DatabricksProvider { + pub fn from_env(model: ModelConfig) -> Self { + let config = DatabricksProviderConfig::from_env(); + DatabricksProvider::from_config(config, model) + .expect("Failed to initialize DatabricksProvider") + } +} + +impl Default for DatabricksProvider { + fn default() -> Self { + let config = DatabricksProviderConfig::from_env(); + let model = ModelConfig::new(DATABRICKS_DEFAULT_MODEL.to_string()); + DatabricksProvider::from_config(config, model) + .expect("Failed to initialize DatabricksProvider") + } +} + +impl DatabricksProvider { + pub fn from_config(config: DatabricksProviderConfig, model: ModelConfig) -> Result { + let client = Client::builder() + .timeout(Duration::from_secs(config.timeout)) + .build()?; + + Ok(Self { + config, + model, + client, + }) + } + + async fn post(&self, payload: Value) -> Result { + let base_url = Url::parse(&self.config.host) + .map_err(|e| ProviderError::RequestFailed(format!("Invalid base URL: {e}")))?; + let path = format!("serving-endpoints/{}/invocations", self.model.model_name); + let url = base_url.join(&path).map_err(|e| { + ProviderError::RequestFailed(format!("Failed to construct endpoint URL: {e}")) + })?; + + let auth_header = format!("Bearer {}", &self.config.token); + let response = self + .client + .post(url) + .header("Authorization", auth_header) + .json(&payload) + .send() + .await?; + + let status = response.status(); + let payload: Option = response.json().await.ok(); + + match status { + StatusCode::OK => payload.ok_or_else(|| { + ProviderError::RequestFailed("Response body is not valid JSON".to_string()) + }), + StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => { + Err(ProviderError::Authentication(format!( + "Authentication failed. Please ensure your API keys are valid and have the required permissions. \ + Status: {}. Response: {:?}", + status, payload + ))) + } + StatusCode::BAD_REQUEST => { + // Databricks provides a generic 'error' but also includes 'external_model_message' which is provider specific + // We try to extract the error message from the payload and check for phrases that indicate context length exceeded + let payload_str = serde_json::to_string(&payload) + .unwrap_or_default() + .to_lowercase(); + let check_phrases = [ + "too long", + "context length", + "context_length_exceeded", + "reduce the length", + "token count", + "exceeds", + ]; + if check_phrases.iter().any(|c| payload_str.contains(c)) { + return Err(ProviderError::ContextLengthExceeded(payload_str)); + } + + let mut error_msg = "Unknown error".to_string(); + if let Some(payload) = &payload { + // try to convert message to string, if that fails use external_model_message + error_msg = payload + .get("message") + .and_then(|m| m.as_str()) + .or_else(|| { + payload + .get("external_model_message") + .and_then(|ext| ext.get("message")) + .and_then(|m| m.as_str()) + }) + .unwrap_or("Unknown error") + .to_string(); + } + + tracing::debug!( + "{}", + format!( + "Provider request failed with status: {}. Payload: {:?}", + status, payload + ) + ); + Err(ProviderError::RequestFailed(format!( + "Request failed with status: {}. Message: {}", + status, error_msg + ))) + } + StatusCode::TOO_MANY_REQUESTS => { + Err(ProviderError::RateLimitExceeded(format!("{:?}", payload))) + } + StatusCode::INTERNAL_SERVER_ERROR | StatusCode::SERVICE_UNAVAILABLE => { + Err(ProviderError::ServerError(format!("{:?}", payload))) + } + _ => { + tracing::debug!( + "{}", + format!( + "Provider request failed with status: {}. Payload: {:?}", + status, payload + ) + ); + Err(ProviderError::RequestFailed(format!( + "Request failed with status: {}", + status + ))) + } + } + } +} + +#[async_trait] +impl Provider for DatabricksProvider { + #[tracing::instrument( + skip(self, system, messages, tools), + fields(model_config, input, output, input_tokens, output_tokens, total_tokens) + )] + async fn complete( + &self, + system: &str, + messages: &[Message], + tools: &[Tool], + ) -> Result { + let mut payload = create_request( + &self.model, + system, + messages, + tools, + &self.config.image_format, + )?; + // Remove the model key which is part of the url with databricks + payload + .as_object_mut() + .expect("payload should have model key") + .remove("model"); + + let response = self.post(payload.clone()).await?; + + // Parse response + let message = response_to_message(response.clone())?; + let usage = match get_usage(&response) { + Ok(usage) => usage, + Err(ProviderError::UsageError(e)) => { + tracing::debug!("Failed to get usage data: {}", e); + Usage::default() + } + Err(e) => return Err(e), + }; + let model = get_model(&response); + super::utils::emit_debug_trace(&self.model, &payload, &response, &usage); + + Ok(ProviderCompleteResponse::new(message, model, usage)) + } + + async fn extract( + &self, + system: &str, + messages: &[Message], + schema: &Value, + ) -> Result { + // 1. Build base payload (no tools) + let mut payload = create_request(&self.model, system, messages, &[], &ImageFormat::OpenAi)?; + + // 2. Inject strict JSON‐Schema wrapper + payload + .as_object_mut() + .expect("payload must be an object") + .insert( + "response_format".to_string(), + json!({ + "type": "json_schema", + "json_schema": { + "name": "extraction", + "schema": schema, + "strict": true + } + }), + ); + + // 3. Call OpenAI + let response = self.post(payload.clone()).await?; + + // 4. Extract the assistant’s `content` and parse it into JSON + let msg = &response["choices"][0]["message"]; + let raw = msg.get("content").cloned().ok_or_else(|| { + ProviderError::ResponseParseError("Missing content in extract response".into()) + })?; + let data = match raw { + Value::String(s) => serde_json::from_str(&s) + .map_err(|e| ProviderError::ResponseParseError(format!("Invalid JSON: {}", e)))?, + Value::Object(_) | Value::Array(_) => raw, + other => { + return Err(ProviderError::ResponseParseError(format!( + "Unexpected content type: {:?}", + other + ))) + } + }; + + // 5. Gather usage & model info + let usage = match get_usage(&response) { + Ok(u) => u, + Err(ProviderError::UsageError(e)) => { + tracing::debug!("Failed to get usage in extract: {}", e); + Usage::default() + } + Err(e) => return Err(e), + }; + let model = get_model(&response); + + Ok(ProviderExtractResponse::new(data, model, usage)) + } +} diff --git a/crates/goose-llm/src/providers/errors.rs b/crates/goose-llm/src/providers/errors.rs new file mode 100644 index 00000000..826a6e11 --- /dev/null +++ b/crates/goose-llm/src/providers/errors.rs @@ -0,0 +1,144 @@ +use thiserror::Error; + +#[derive(Error, Debug, uniffi::Error)] +pub enum ProviderError { + #[error("Authentication error: {0}")] + Authentication(String), + + #[error("Context length exceeded: {0}")] + ContextLengthExceeded(String), + + #[error("Rate limit exceeded: {0}")] + RateLimitExceeded(String), + + #[error("Server error: {0}")] + ServerError(String), + + #[error("Request failed: {0}")] + RequestFailed(String), + + #[error("Execution error: {0}")] + ExecutionError(String), + + #[error("Usage data error: {0}")] + UsageError(String), + + #[error("Invalid response: {0}")] + ResponseParseError(String), +} + +impl From for ProviderError { + fn from(error: anyhow::Error) -> Self { + ProviderError::ExecutionError(error.to_string()) + } +} + +impl From for ProviderError { + fn from(error: reqwest::Error) -> Self { + ProviderError::ExecutionError(error.to_string()) + } +} + +#[derive(serde::Deserialize, Debug)] +pub struct OpenAIError { + #[serde(deserialize_with = "code_as_string")] + pub code: Option, + pub message: Option, + #[serde(rename = "type")] + pub error_type: Option, +} + +fn code_as_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + use std::fmt; + + use serde::de::{self, Visitor}; + + struct CodeVisitor; + + impl<'de> Visitor<'de> for CodeVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string, a number, null, or none for the code field") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(Some(value.to_string())) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(Some(value.to_string())) + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(CodeVisitor) + } + } + + deserializer.deserialize_option(CodeVisitor) +} + +impl OpenAIError { + pub fn is_context_length_exceeded(&self) -> bool { + if let Some(code) = &self.code { + code == "context_length_exceeded" || code == "string_above_max_length" + } else { + false + } + } +} + +impl std::fmt::Display for OpenAIError { + /// Format the error for display. + /// E.g. {"message": "Invalid API key", "code": "invalid_api_key", "type": "client_error"} + /// would be formatted as "Invalid API key (code: invalid_api_key, type: client_error)" + /// and {"message": "Foo"} as just "Foo", etc. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(message) = &self.message { + write!(f, "{}", message)?; + } + let mut in_parenthesis = false; + if let Some(code) = &self.code { + write!(f, " (code: {}", code)?; + in_parenthesis = true; + } + if let Some(typ) = &self.error_type { + if in_parenthesis { + write!(f, ", type: {}", typ)?; + } else { + write!(f, " (type: {}", typ)?; + in_parenthesis = true; + } + } + if in_parenthesis { + write!(f, ")")?; + } + Ok(()) + } +} diff --git a/crates/goose-llm/src/providers/factory.rs b/crates/goose-llm/src/providers/factory.rs new file mode 100644 index 00000000..a70be3d4 --- /dev/null +++ b/crates/goose-llm/src/providers/factory.rs @@ -0,0 +1,29 @@ +use std::sync::Arc; + +use anyhow::Result; + +use super::{ + base::Provider, + databricks::{DatabricksProvider, DatabricksProviderConfig}, + openai::{OpenAiProvider, OpenAiProviderConfig}, +}; +use crate::model::ModelConfig; + +pub fn create( + name: &str, + provider_config: serde_json::Value, + model: ModelConfig, +) -> Result> { + // We use Arc instead of Box to be able to clone for multiple async tasks + match name { + "openai" => { + let config: OpenAiProviderConfig = serde_json::from_value(provider_config)?; + Ok(Arc::new(OpenAiProvider::from_config(config, model)?)) + } + "databricks" => { + let config: DatabricksProviderConfig = serde_json::from_value(provider_config)?; + Ok(Arc::new(DatabricksProvider::from_config(config, model)?)) + } + _ => Err(anyhow::anyhow!("Unknown provider: {}", name)), + } +} diff --git a/crates/goose-llm/src/providers/formats/databricks.rs b/crates/goose-llm/src/providers/formats/databricks.rs new file mode 100644 index 00000000..649d2f67 --- /dev/null +++ b/crates/goose-llm/src/providers/formats/databricks.rs @@ -0,0 +1,1118 @@ +use anyhow::{anyhow, Error}; +use serde_json::{json, Value}; + +use crate::{ + message::{Message, MessageContent}, + model::ModelConfig, + providers::{ + base::Usage, + errors::ProviderError, + utils::{ + convert_image, detect_image_path, is_valid_function_name, load_image_file, + sanitize_function_name, ImageFormat, + }, + }, + types::core::{Content, Role, Tool, ToolCall, ToolError}, +}; + +/// Convert internal Message format to Databricks' API message specification +/// Databricks is mostly OpenAI compatible, but has some differences (reasoning type, etc) +/// some openai compatible endpoints use the anthropic image spec at the content level +/// even though the message structure is otherwise following openai, the enum switches this +pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec { + let mut result = Vec::new(); + for message in messages { + let mut converted = json!({ + "role": message.role + }); + + let mut content_array = Vec::new(); + let mut has_tool_calls = false; + let mut has_multiple_content = false; + + for content in message.content.iter() { + match content { + MessageContent::Text(text) => { + if !text.text.is_empty() { + // Check for image paths in the text + if let Some(image_path) = detect_image_path(&text.text) { + has_multiple_content = true; + // Try to load and convert the image + if let Ok(image) = load_image_file(image_path) { + content_array.push(json!({ + "type": "text", + "text": text.text + })); + content_array.push(convert_image(&image, image_format)); + } else { + content_array.push(json!({ + "type": "text", + "text": text.text + })); + } + } else { + content_array.push(json!({ + "type": "text", + "text": text.text + })); + } + } + } + MessageContent::Thinking(content) => { + has_multiple_content = true; + content_array.push(json!({ + "type": "reasoning", + "summary": [ + { + "type": "summary_text", + "text": content.thinking, + "signature": content.signature + } + ] + })); + } + MessageContent::RedactedThinking(content) => { + has_multiple_content = true; + content_array.push(json!({ + "type": "reasoning", + "summary": [ + { + "type": "summary_encrypted_text", + "data": content.data + } + ] + })); + } + MessageContent::ToolReq(request) => { + has_tool_calls = true; + match &request.tool_call.as_result() { + Ok(tool_call) => { + let sanitized_name = sanitize_function_name(&tool_call.name); + + // Get mutable access to the "tool_calls" field in the converted object + // If "tool_calls" doesn't exist, insert an empty JSON array + let tool_calls = converted + .as_object_mut() + .unwrap() + .entry("tool_calls") + .or_insert(json!([])); + + tool_calls.as_array_mut().unwrap().push(json!({ + "id": request.id, + "type": "function", + "function": { + "name": sanitized_name, + "arguments": tool_call.arguments.to_string(), + } + })); + } + Err(e) => { + content_array.push(json!({ + "type": "text", + "text": format!("Error: {}", e) + })); + } + } + } + MessageContent::ToolResp(response) => { + match &response.tool_result.0 { + Ok(contents) => { + // Process all content, replacing images with placeholder text + let mut tool_content = Vec::new(); + let mut image_messages = Vec::new(); + + for content in contents { + match content { + Content::Image(image) => { + // Add placeholder text in the tool response + tool_content.push(Content::text("This tool result included an image that is uploaded in the next message.")); + + // Create a separate image message + image_messages.push(json!({ + "role": "user", + "content": [convert_image(image, image_format)] + })); + } + _ => { + tool_content.push(content.clone()); + } + } + } + let tool_response_content: Value = json!(tool_content + .iter() + .map(|content| match content { + Content::Text(text) => text.text.clone(), + _ => String::new(), + }) + .collect::>() + .join(" ")); + + // Add tool response as a separate message + result.push(json!({ + "role": "tool", + "content": tool_response_content, + "tool_call_id": response.id + })); + // Then add any image messages that need to follow + result.extend(image_messages); + } + Err(e) => { + // A tool result error is shown as output so the model can interpret the error message + result.push(json!({ + "role": "tool", + "content": format!("The tool call returned the following error:\n{}", e), + "tool_call_id": response.id + })); + } + } + } + MessageContent::Image(image) => { + // Handle direct image content + content_array.push(json!({ + "type": "image_url", + "image_url": { + "url": convert_image(image, image_format) + } + })); + } + } + } + + if !content_array.is_empty() { + // If we only have a single text content and no other special content, + // use the simple string format + if content_array.len() == 1 + && !has_multiple_content + && content_array[0]["type"] == "text" + { + converted["content"] = json!(content_array[0]["text"]); + } else { + converted["content"] = json!(content_array); + } + } + + if !content_array.is_empty() || has_tool_calls { + result.push(converted); + } + } + + result +} + +/// Convert internal Tool format to OpenAI's API tool specification +/// https://docs.databricks.com/aws/en/machine-learning/foundation-model-apis/api-reference#functionobject +pub fn format_tools(tools: &[Tool]) -> anyhow::Result> { + let mut tool_names = std::collections::HashSet::new(); + let mut result = Vec::new(); + + for tool in tools { + if !tool_names.insert(&tool.name) { + return Err(anyhow!("Duplicate tool name: {}", tool.name)); + } + + let mut description = tool.description.clone(); + description.truncate(1024); + + // OpenAI's tool description max str len is 1024 + result.push(json!({ + "type": "function", + "function": { + "name": tool.name, + "description": description, + "parameters": tool.input_schema, + } + })); + } + + Ok(result) +} + +/// Convert Databricks' API response to internal Message format +pub fn response_to_message(response: Value) -> anyhow::Result { + let original = response["choices"][0]["message"].clone(); + let mut content: Vec = Vec::new(); + + // Handle array-based content + if let Some(content_array) = original.get("content").and_then(|c| c.as_array()) { + for content_item in content_array { + match content_item.get("type").and_then(|t| t.as_str()) { + Some("text") => { + if let Some(text) = content_item.get("text").and_then(|t| t.as_str()) { + content.push(MessageContent::text(text)); + } + } + Some("reasoning") => { + if let Some(summary_array) = + content_item.get("summary").and_then(|s| s.as_array()) + { + for summary in summary_array { + match summary.get("type").and_then(|t| t.as_str()) { + Some("summary_text") => { + let text = summary + .get("text") + .and_then(|t| t.as_str()) + .unwrap_or_default(); + let signature = summary + .get("signature") + .and_then(|s| s.as_str()) + .unwrap_or_default(); + content.push(MessageContent::thinking(text, signature)); + } + Some("summary_encrypted_text") => { + if let Some(data) = summary.get("data").and_then(|d| d.as_str()) + { + content.push(MessageContent::redacted_thinking(data)); + } + } + _ => continue, + } + } + } + } + _ => continue, + } + } + } else if let Some(text) = original.get("content").and_then(|t| t.as_str()) { + // Handle legacy single string content + content.push(MessageContent::text(text)); + } + + // Handle tool calls + if let Some(tool_calls) = original.get("tool_calls") { + if let Some(tool_calls_array) = tool_calls.as_array() { + for tool_call in tool_calls_array { + let id = tool_call["id"].as_str().unwrap_or_default().to_string(); + let function_name = tool_call["function"]["name"] + .as_str() + .unwrap_or_default() + .to_string(); + let mut arguments = tool_call["function"]["arguments"] + .as_str() + .unwrap_or_default() + .to_string(); + // If arguments is empty, we will have invalid json parsing error later. + if arguments.is_empty() { + arguments = "{}".to_string(); + } + + if !is_valid_function_name(&function_name) { + let error = ToolError::NotFound(format!( + "The provided function name '{}' had invalid characters, it must match this regex [a-zA-Z0-9_-]+", + function_name + )); + content.push(MessageContent::tool_request(id, Err(error).into())); + } else { + match serde_json::from_str::(&arguments) { + Ok(params) => { + content.push(MessageContent::tool_request( + id, + Ok(ToolCall::new(&function_name, params)).into(), + )); + } + Err(e) => { + let error = ToolError::InvalidParameters(format!( + "Could not interpret tool use parameters for id {}: {}", + id, e + )); + content.push(MessageContent::tool_request(id, Err(error).into())); + } + } + } + } + } + } + + Ok(Message { + role: Role::Assistant, + created: chrono::Utc::now().timestamp_millis(), + content: content.into(), + }) +} + +pub fn get_usage(data: &Value) -> Result { + let usage = data + .get("usage") + .ok_or_else(|| ProviderError::UsageError("No usage data in response".to_string()))?; + + let input_tokens = usage + .get("prompt_tokens") + .and_then(|v| v.as_i64()) + .map(|v| v as i32); + + let output_tokens = usage + .get("completion_tokens") + .and_then(|v| v.as_i64()) + .map(|v| v as i32); + + let total_tokens = usage + .get("total_tokens") + .and_then(|v| v.as_i64()) + .map(|v| v as i32) + .or_else(|| match (input_tokens, output_tokens) { + (Some(input), Some(output)) => Some(input + output), + _ => None, + }); + + Ok(Usage::new(input_tokens, output_tokens, total_tokens)) +} + +/// Validates and fixes tool schemas to ensure they have proper parameter structure. +/// If parameters exist, ensures they have properties and required fields, or removes parameters entirely. +pub fn validate_tool_schemas(tools: &mut [Value]) { + for tool in tools.iter_mut() { + if let Some(function) = tool.get_mut("function") { + if let Some(parameters) = function.get_mut("parameters") { + if parameters.is_object() { + ensure_valid_json_schema(parameters); + } + } + } + } +} + +/// Ensures that the given JSON value follows the expected JSON Schema structure. +fn ensure_valid_json_schema(schema: &mut Value) { + if let Some(params_obj) = schema.as_object_mut() { + // Check if this is meant to be an object type schema + let is_object_type = params_obj + .get("type") + .and_then(|t| t.as_str()) + .is_none_or(|t| t == "object"); // Default to true if no type is specified + + // Only apply full schema validation to object types + if is_object_type { + // Ensure required fields exist with default values + params_obj.entry("properties").or_insert_with(|| json!({})); + params_obj.entry("required").or_insert_with(|| json!([])); + params_obj.entry("type").or_insert_with(|| json!("object")); + + // Recursively validate properties if it exists + if let Some(properties) = params_obj.get_mut("properties") { + if let Some(properties_obj) = properties.as_object_mut() { + for (_key, prop) in properties_obj.iter_mut() { + if prop.is_object() + && prop.get("type").and_then(|t| t.as_str()) == Some("object") + { + ensure_valid_json_schema(prop); + } + } + } + } + } + } +} + +pub fn create_request( + model_config: &ModelConfig, + system: &str, + messages: &[Message], + tools: &[Tool], + image_format: &ImageFormat, +) -> anyhow::Result { + if model_config.model_name.starts_with("o1-mini") { + return Err(anyhow!( + "o1-mini model is not currently supported since Goose uses tool calling and o1-mini does not support it. Please use o1 or o3 models instead." + )); + } + + let model_name = model_config.model_name.to_string(); + let is_o1 = model_name.starts_with("o1") || model_name.starts_with("goose-o1"); + let is_o3 = model_name.starts_with("o3") || model_name.starts_with("goose-o3"); + let is_claude_3_7_sonnet = model_name.contains("claude-3-7-sonnet"); // can be goose- or databricks- + + // Only extract reasoning effort for O1/O3 models + let (model_name, reasoning_effort) = if is_o1 || is_o3 { + let parts: Vec<&str> = model_config.model_name.split('-').collect(); + let last_part = parts.last().unwrap(); + + match *last_part { + "low" | "medium" | "high" => { + let base_name = parts[..parts.len() - 1].join("-"); + (base_name, Some(last_part.to_string())) + } + _ => ( + model_config.model_name.to_string(), + Some("medium".to_string()), + ), + } + } else { + // For non-O family models, use the model name as is and no reasoning effort + (model_config.model_name.to_string(), None) + }; + + let system_message = json!({ + "role": if is_o1 || is_o3 { "developer" } else { "system" }, + "content": system + }); + + let messages_spec = format_messages(messages, image_format); + let mut tools_spec = if !tools.is_empty() { + format_tools(tools)? + } else { + vec![] + }; + + // Validate tool schemas + validate_tool_schemas(&mut tools_spec); + + let mut messages_array = vec![system_message]; + messages_array.extend(messages_spec); + + let mut payload = json!({ + "model": model_name, + "messages": messages_array + }); + + if let Some(effort) = reasoning_effort { + payload + .as_object_mut() + .unwrap() + .insert("reasoning_effort".to_string(), json!(effort)); + } + + if !tools_spec.is_empty() { + payload + .as_object_mut() + .unwrap() + .insert("tools".to_string(), json!(tools_spec)); + } + + // Add thinking parameters for Claude 3.7 Sonnet model when requested + let is_thinking_enabled = std::env::var("CLAUDE_THINKING_ENABLED").is_ok(); + if is_claude_3_7_sonnet && is_thinking_enabled { + // Minimum budget_tokens is 1024 + let budget_tokens = std::env::var("CLAUDE_THINKING_BUDGET") + .unwrap_or_else(|_| "16000".to_string()) + .parse() + .unwrap_or(16000); + + // For Claude models with thinking enabled, we need to add max_tokens + budget_tokens + // Default to 8192 (Claude max output) + budget if not specified + let max_completion_tokens = model_config.max_tokens.unwrap_or(8192); + payload.as_object_mut().unwrap().insert( + "max_tokens".to_string(), + json!(max_completion_tokens + budget_tokens), + ); + + payload.as_object_mut().unwrap().insert( + "thinking".to_string(), + json!({ + "type": "enabled", + "budget_tokens": budget_tokens + }), + ); + + // Temperature is fixed to 2 when using claude 3.7 thinking with Databricks + payload + .as_object_mut() + .unwrap() + .insert("temperature".to_string(), json!(2)); + } else { + // o1, o3 models currently don't support temperature + if !is_o1 && !is_o3 { + if let Some(temp) = model_config.temperature { + payload + .as_object_mut() + .unwrap() + .insert("temperature".to_string(), json!(temp)); + } + } + + // o1 models use max_completion_tokens instead of max_tokens + if let Some(tokens) = model_config.max_tokens { + let key = if is_o1 || is_o3 { + "max_completion_tokens" + } else { + "max_tokens" + }; + payload + .as_object_mut() + .unwrap() + .insert(key.to_string(), json!(tokens)); + } + } + + Ok(payload) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + use crate::types::core::Content; + + #[test] + fn test_validate_tool_schemas() { + // Test case 1: Empty parameters object + // Input JSON with an incomplete parameters object + let mut actual = vec![json!({ + "type": "function", + "function": { + "name": "test_func", + "description": "test description", + "parameters": { + "type": "object" + } + } + })]; + + // Run the function to validate and update schemas + validate_tool_schemas(&mut actual); + + // Expected JSON after validation + let expected = vec![json!({ + "type": "function", + "function": { + "name": "test_func", + "description": "test description", + "parameters": { + "type": "object", + "properties": {}, + "required": [] + } + } + })]; + + // Compare entire JSON structures instead of individual fields + assert_eq!(actual, expected); + + // Test case 2: Missing type field + let mut tools = vec![json!({ + "type": "function", + "function": { + "name": "test_func", + "description": "test description", + "parameters": { + "properties": {} + } + } + })]; + + validate_tool_schemas(&mut tools); + + let params = tools[0]["function"]["parameters"].as_object().unwrap(); + assert_eq!(params["type"], "object"); + + // Test case 3: Complete valid schema should remain unchanged + let original_schema = json!({ + "type": "function", + "function": { + "name": "test_func", + "description": "test description", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "City and country" + } + }, + "required": ["location"] + } + } + }); + + let mut tools = vec![original_schema.clone()]; + validate_tool_schemas(&mut tools); + assert_eq!(tools[0], original_schema); + } + + const OPENAI_TOOL_USE_RESPONSE: &str = r#"{ + "choices": [{ + "role": "assistant", + "message": { + "tool_calls": [{ + "id": "1", + "function": { + "name": "example_fn", + "arguments": "{\"param\": \"value\"}" + } + }] + } + }], + "usage": { + "input_tokens": 10, + "output_tokens": 25, + "total_tokens": 35 + } + }"#; + + #[test] + fn test_format_messages() -> anyhow::Result<()> { + let message = Message::user().with_text("Hello"); + let spec = format_messages(&[message], &ImageFormat::OpenAi); + + assert_eq!(spec.len(), 1); + assert_eq!(spec[0]["role"], "user"); + assert_eq!(spec[0]["content"], "Hello"); + Ok(()) + } + + #[test] + fn test_format_tools() -> anyhow::Result<()> { + let tool = Tool::new( + "test_tool", + "A test tool", + json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "Test parameter" + } + }, + "required": ["input"] + }), + ); + + let spec = format_tools(&[tool])?; + + assert_eq!(spec.len(), 1); + assert_eq!(spec[0]["type"], "function"); + assert_eq!(spec[0]["function"]["name"], "test_tool"); + Ok(()) + } + + #[test] + fn test_format_messages_complex() -> anyhow::Result<()> { + let mut messages = vec![ + Message::assistant().with_text("Hello!"), + Message::user().with_text("How are you?"), + Message::assistant().with_tool_request( + "tool1", + Ok(ToolCall::new("example", json!({"param1": "value1"})).into()), + ), + ]; + + // Get the ID from the tool request to use in the response + let tool_id = if let MessageContent::ToolReq(request) = &messages[2].content[0] { + request.id.clone() + } else { + panic!("should be tool request"); + }; + + messages.push( + Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")]).into()), + ); + + let spec = format_messages(&messages, &ImageFormat::OpenAi); + + assert_eq!(spec.len(), 4); + assert_eq!(spec[0]["role"], "assistant"); + assert_eq!(spec[0]["content"], "Hello!"); + assert_eq!(spec[1]["role"], "user"); + assert_eq!(spec[1]["content"], "How are you?"); + assert_eq!(spec[2]["role"], "assistant"); + assert!(spec[2]["tool_calls"].is_array()); + assert_eq!(spec[3]["role"], "tool"); + assert_eq!(spec[3]["content"], "Result"); + assert_eq!(spec[3]["tool_call_id"], spec[2]["tool_calls"][0]["id"]); + + Ok(()) + } + + #[test] + fn test_format_messages_multiple_content() -> anyhow::Result<()> { + let mut messages = vec![Message::assistant().with_tool_request( + "tool1", + Ok(ToolCall::new("example", json!({"param1": "value1"}))), + )]; + + // Get the ID from the tool request to use in the response + let tool_id = if let MessageContent::ToolReq(request) = &messages[0].content[0] { + request.id.clone() + } else { + panic!("should be tool request"); + }; + + messages.push( + Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")]).into()), + ); + + let spec = format_messages(&messages, &ImageFormat::OpenAi); + + assert_eq!(spec.len(), 2); + assert_eq!(spec[0]["role"], "assistant"); + assert!(spec[0]["tool_calls"].is_array()); + assert_eq!(spec[1]["role"], "tool"); + assert_eq!(spec[1]["content"], "Result"); + assert_eq!(spec[1]["tool_call_id"], spec[0]["tool_calls"][0]["id"]); + + Ok(()) + } + + #[test] + fn test_format_tools_duplicate() -> anyhow::Result<()> { + let tool1 = Tool::new( + "test_tool", + "Test tool", + json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "Test parameter" + } + }, + "required": ["input"] + }), + ); + + let tool2 = Tool::new( + "test_tool", + "Test tool", + json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "Test parameter" + } + }, + "required": ["input"] + }), + ); + + let result = format_tools(&[tool1, tool2]); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("Duplicate tool name")); + + Ok(()) + } + + #[test] + fn test_format_tools_empty() -> anyhow::Result<()> { + let spec = format_tools(&[])?; + assert!(spec.is_empty()); + Ok(()) + } + + #[test] + fn test_format_messages_with_image_path() -> anyhow::Result<()> { + // Create a temporary PNG file with valid PNG magic numbers + let temp_dir = tempfile::tempdir()?; + let png_path = temp_dir.path().join("test.png"); + let png_data = [ + 0x89, 0x50, 0x4E, 0x47, // PNG magic number + 0x0D, 0x0A, 0x1A, 0x0A, // PNG header + 0x00, 0x00, 0x00, 0x0D, // Rest of fake PNG data + ]; + std::fs::write(&png_path, &png_data)?; + let png_path_str = png_path.to_str().unwrap(); + + // Create message with image path + let message = Message::user().with_text(format!("Here is an image: {}", png_path_str)); + let spec = format_messages(&[message], &ImageFormat::OpenAi); + + assert_eq!(spec.len(), 1); + assert_eq!(spec[0]["role"], "user"); + + // Content should be an array with text and image + let content = spec[0]["content"].as_array().unwrap(); + assert_eq!(content.len(), 2); + assert_eq!(content[0]["type"], "text"); + assert!(content[0]["text"].as_str().unwrap().contains(png_path_str)); + assert_eq!(content[1]["type"], "image_url"); + assert!(content[1]["image_url"]["url"] + .as_str() + .unwrap() + .starts_with("data:image/png;base64,")); + + Ok(()) + } + + #[test] + fn test_response_to_message_text() -> anyhow::Result<()> { + let response = json!({ + "choices": [{ + "role": "assistant", + "message": { + "content": "Hello from John Cena!" + } + }], + "usage": { + "input_tokens": 10, + "output_tokens": 25, + "total_tokens": 35 + } + }); + + let message = response_to_message(response)?; + assert_eq!(message.content.len(), 1); + if let MessageContent::Text(text) = &message.content[0] { + assert_eq!(text.text, "Hello from John Cena!"); + } else { + panic!("Expected Text content"); + } + assert!(matches!(message.role, Role::Assistant)); + + Ok(()) + } + + #[test] + fn test_response_to_message_valid_toolrequest() -> anyhow::Result<()> { + let response: Value = serde_json::from_str(OPENAI_TOOL_USE_RESPONSE)?; + let message = response_to_message(response)?; + + assert_eq!(message.content.len(), 1); + if let MessageContent::ToolReq(request) = &message.content[0] { + let tool_call = request.tool_call.as_ref().unwrap(); + assert_eq!(tool_call.name, "example_fn"); + assert_eq!(tool_call.arguments, json!({"param": "value"})); + } else { + panic!("Expected ToolRequest content"); + } + + Ok(()) + } + + #[test] + fn test_response_to_message_invalid_func_name() -> anyhow::Result<()> { + let mut response: Value = serde_json::from_str(OPENAI_TOOL_USE_RESPONSE)?; + response["choices"][0]["message"]["tool_calls"][0]["function"]["name"] = + json!("invalid fn"); + + let message = response_to_message(response)?; + + if let MessageContent::ToolReq(request) = &message.content[0] { + match &request.tool_call.as_result() { + Err(ToolError::NotFound(msg)) => { + assert!(msg.starts_with("The provided function name")); + } + _ => panic!("Expected ToolNotFound error"), + } + } else { + panic!("Expected ToolRequest content"); + } + + Ok(()) + } + + #[test] + fn test_response_to_message_json_decode_error() -> anyhow::Result<()> { + let mut response: Value = serde_json::from_str(OPENAI_TOOL_USE_RESPONSE)?; + response["choices"][0]["message"]["tool_calls"][0]["function"]["arguments"] = + json!("invalid json {"); + + let message = response_to_message(response)?; + + if let MessageContent::ToolReq(request) = &message.content[0] { + match &request.tool_call.as_result() { + Err(ToolError::InvalidParameters(msg)) => { + assert!(msg.starts_with("Could not interpret tool use parameters")); + } + _ => panic!("Expected InvalidParameters error"), + } + } else { + panic!("Expected ToolRequest content"); + } + + Ok(()) + } + + #[test] + fn test_response_to_message_empty_argument() -> anyhow::Result<()> { + let mut response: Value = serde_json::from_str(OPENAI_TOOL_USE_RESPONSE)?; + response["choices"][0]["message"]["tool_calls"][0]["function"]["arguments"] = + serde_json::Value::String("".to_string()); + + let message = response_to_message(response)?; + + if let MessageContent::ToolReq(request) = &message.content[0] { + let tool_call = request.tool_call.as_ref().unwrap(); + assert_eq!(tool_call.name, "example_fn"); + assert_eq!(tool_call.arguments, json!({})); + } else { + panic!("Expected ToolRequest content"); + } + + Ok(()) + } + + #[test] + fn test_create_request_gpt_4o() -> anyhow::Result<()> { + // Test default medium reasoning effort for O3 model + let model_config = ModelConfig { + model_name: "gpt-4o".to_string(), + context_limit: Some(4096), + temperature: None, + max_tokens: Some(1024), + }; + let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?; + let obj = request.as_object().unwrap(); + let expected = json!({ + "model": "gpt-4o", + "messages": [ + { + "role": "system", + "content": "system" + } + ], + "max_tokens": 1024 + }); + + for (key, value) in expected.as_object().unwrap() { + assert_eq!(obj.get(key).unwrap(), value); + } + + Ok(()) + } + + #[test] + fn test_create_request_o1_default() -> anyhow::Result<()> { + // Test default medium reasoning effort for O1 model + let model_config = ModelConfig { + model_name: "o1".to_string(), + context_limit: Some(4096), + temperature: None, + max_tokens: Some(1024), + }; + let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?; + let obj = request.as_object().unwrap(); + let expected = json!({ + "model": "o1", + "messages": [ + { + "role": "developer", + "content": "system" + } + ], + "reasoning_effort": "medium", + "max_completion_tokens": 1024 + }); + + for (key, value) in expected.as_object().unwrap() { + assert_eq!(obj.get(key).unwrap(), value); + } + + Ok(()) + } + + #[test] + fn test_create_request_o3_custom_reasoning_effort() -> anyhow::Result<()> { + // Test custom reasoning effort for O3 model + let model_config = ModelConfig { + model_name: "o3-mini-high".to_string(), + context_limit: Some(4096), + temperature: None, + max_tokens: Some(1024), + }; + let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?; + let obj = request.as_object().unwrap(); + let expected = json!({ + "model": "o3-mini", + "messages": [ + { + "role": "developer", + "content": "system" + } + ], + "reasoning_effort": "high", + "max_completion_tokens": 1024 + }); + + for (key, value) in expected.as_object().unwrap() { + assert_eq!(obj.get(key).unwrap(), value); + } + + Ok(()) + } + + #[test] + fn test_response_to_message_claude_thinking() -> anyhow::Result<()> { + let response = json!({ + "model": "us.anthropic.claude-3-7-sonnet-20250219-v1:0", + "choices": [{ + "message": { + "role": "assistant", + "content": [ + { + "type": "reasoning", + "summary": [ + { + "type": "summary_text", + "text": "Test thinking content", + "signature": "test-signature" + } + ] + }, + { + "type": "text", + "text": "Regular text content" + } + ] + }, + "index": 0, + "finish_reason": "stop" + }] + }); + + let message = response_to_message(response)?; + assert_eq!(message.content.len(), 2); + + if let MessageContent::Thinking(thinking) = &message.content[0] { + assert_eq!(thinking.thinking, "Test thinking content"); + assert_eq!(thinking.signature, "test-signature"); + } else { + panic!("Expected Thinking content"); + } + + if let MessageContent::Text(text) = &message.content[1] { + assert_eq!(text.text, "Regular text content"); + } else { + panic!("Expected Text content"); + } + + Ok(()) + } + + #[test] + fn test_response_to_message_claude_encrypted_thinking() -> anyhow::Result<()> { + let response = json!({ + "model": "claude-3-7-sonnet-20250219", + "choices": [{ + "message": { + "role": "assistant", + "content": [ + { + "type": "reasoning", + "summary": [ + { + "type": "summary_encrypted_text", + "data": "E23sQFCkYIARgCKkATCHitsdf327Ber3v4NYUq2" + } + ] + }, + { + "type": "text", + "text": "Regular text content" + } + ] + }, + "index": 0, + "finish_reason": "stop" + }] + }); + + let message = response_to_message(response)?; + assert_eq!(message.content.len(), 2); + + if let MessageContent::RedactedThinking(redacted) = &message.content[0] { + assert_eq!(redacted.data, "E23sQFCkYIARgCKkATCHitsdf327Ber3v4NYUq2"); + } else { + panic!("Expected RedactedThinking content"); + } + + if let MessageContent::Text(text) = &message.content[1] { + assert_eq!(text.text, "Regular text content"); + } else { + panic!("Expected Text content"); + } + + Ok(()) + } +} diff --git a/crates/goose-llm/src/providers/formats/mod.rs b/crates/goose-llm/src/providers/formats/mod.rs new file mode 100644 index 00000000..cf929f39 --- /dev/null +++ b/crates/goose-llm/src/providers/formats/mod.rs @@ -0,0 +1,2 @@ +pub mod databricks; +pub mod openai; diff --git a/crates/goose-llm/src/providers/formats/openai.rs b/crates/goose-llm/src/providers/formats/openai.rs new file mode 100644 index 00000000..06fd527e --- /dev/null +++ b/crates/goose-llm/src/providers/formats/openai.rs @@ -0,0 +1,897 @@ +use anyhow::{anyhow, Error}; +use serde_json::{json, Value}; + +use crate::{ + message::{Message, MessageContent}, + model::ModelConfig, + providers::{ + base::Usage, + errors::ProviderError, + utils::{ + convert_image, detect_image_path, is_valid_function_name, load_image_file, + sanitize_function_name, ImageFormat, + }, + }, + types::core::{Content, Role, Tool, ToolCall, ToolError}, +}; + +/// Convert internal Message format to OpenAI's API message specification +/// some openai compatible endpoints use the anthropic image spec at the content level +/// even though the message structure is otherwise following openai, the enum switches this +pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec { + let mut messages_spec = Vec::new(); + for message in messages { + let mut converted = json!({ + "role": message.role + }); + + let mut output = Vec::new(); + + for content in message.content.iter() { + match content { + MessageContent::Text(text) => { + if !text.text.is_empty() { + // Check for image paths in the text + if let Some(image_path) = detect_image_path(&text.text) { + // Try to load and convert the image + if let Ok(image) = load_image_file(image_path) { + converted["content"] = json!([ + {"type": "text", "text": text.text}, + convert_image(&image, image_format) + ]); + } else { + // If image loading fails, just use the text + converted["content"] = json!(text.text); + } + } else { + converted["content"] = json!(text.text); + } + } + } + MessageContent::Thinking(_) => { + // Thinking blocks are not directly used in OpenAI format + continue; + } + MessageContent::RedactedThinking(_) => { + // Redacted thinking blocks are not directly used in OpenAI format + continue; + } + MessageContent::ToolReq(request) => match &request.tool_call.as_result() { + Ok(tool_call) => { + let sanitized_name = sanitize_function_name(&tool_call.name); + let tool_calls = converted + .as_object_mut() + .unwrap() + .entry("tool_calls") + .or_insert(json!([])); + + tool_calls.as_array_mut().unwrap().push(json!({ + "id": request.id, + "type": "function", + "function": { + "name": sanitized_name, + "arguments": tool_call.arguments.to_string(), + } + })); + } + Err(e) => { + output.push(json!({ + "role": "tool", + "content": format!("Error: {}", e), + "tool_call_id": request.id + })); + } + }, + MessageContent::ToolResp(response) => { + match &response.tool_result.0 { + Ok(contents) => { + // Process all content, replacing images with placeholder text + let mut tool_content = Vec::new(); + let mut image_messages = Vec::new(); + + for content in contents { + match content { + Content::Image(image) => { + // Add placeholder text in the tool response + tool_content.push(Content::text("This tool result included an image that is uploaded in the next message.")); + + // Create a separate image message + image_messages.push(json!({ + "role": "user", + "content": [convert_image(image, image_format)] + })); + } + _ => { + tool_content.push(content.clone()); + } + } + } + let tool_response_content: Value = json!(tool_content + .iter() + .map(|content| match content { + Content::Text(text) => text.text.clone(), + _ => String::new(), + }) + .collect::>() + .join(" ")); + + // First add the tool response with all content + output.push(json!({ + "role": "tool", + "content": tool_response_content, + "tool_call_id": response.id + })); + // Then add any image messages that need to follow + output.extend(image_messages); + } + Err(e) => { + // A tool result error is shown as output so the model can interpret the error message + output.push(json!({ + "role": "tool", + "content": format!("The tool call returned the following error:\n{}", e), + "tool_call_id": response.id + })); + } + } + } + MessageContent::Image(image) => { + // Handle direct image content + converted["content"] = json!([convert_image(image, image_format)]); + } + } + } + + if converted.get("content").is_some() || converted.get("tool_calls").is_some() { + output.insert(0, converted); + } + messages_spec.extend(output); + } + + messages_spec +} + +/// Convert internal Tool format to OpenAI's API tool specification +pub fn format_tools(tools: &[Tool]) -> anyhow::Result> { + let mut tool_names = std::collections::HashSet::new(); + let mut result = Vec::new(); + + for tool in tools { + if !tool_names.insert(&tool.name) { + return Err(anyhow!("Duplicate tool name: {}", tool.name)); + } + + let mut description = tool.description.clone(); + description.truncate(1024); + + // OpenAI's tool description max str len is 1024 + result.push(json!({ + "type": "function", + "function": { + "name": tool.name, + "description": description, + "parameters": tool.input_schema, + } + })); + } + + Ok(result) +} + +/// Convert OpenAI's API response to internal Message format +pub fn response_to_message(response: Value) -> anyhow::Result { + let original = response["choices"][0]["message"].clone(); + let mut content = Vec::new(); + + if let Some(text) = original.get("content") { + if let Some(text_str) = text.as_str() { + content.push(MessageContent::text(text_str)); + } + } + + if let Some(tool_calls) = original.get("tool_calls") { + if let Some(tool_calls_array) = tool_calls.as_array() { + for tool_call in tool_calls_array { + let id = tool_call["id"].as_str().unwrap_or_default().to_string(); + let function_name = tool_call["function"]["name"] + .as_str() + .unwrap_or_default() + .to_string(); + let mut arguments = tool_call["function"]["arguments"] + .as_str() + .unwrap_or_default() + .to_string(); + // If arguments is empty, we will have invalid json parsing error later. + if arguments.is_empty() { + arguments = "{}".to_string(); + } + + if !is_valid_function_name(&function_name) { + let error = ToolError::NotFound(format!( + "The provided function name '{}' had invalid characters, it must match this regex [a-zA-Z0-9_-]+", + function_name + )); + content.push(MessageContent::tool_request(id, Err(error).into())); + } else { + match serde_json::from_str::(&arguments) { + Ok(params) => { + content.push(MessageContent::tool_request( + id, + Ok(ToolCall::new(&function_name, params)).into(), + )); + } + Err(e) => { + let error = ToolError::InvalidParameters(format!( + "Could not interpret tool use parameters for id {}: {}", + id, e + )); + content.push(MessageContent::tool_request(id, Err(error).into())); + } + } + } + } + } + } + + Ok(Message { + role: Role::Assistant, + created: chrono::Utc::now().timestamp_millis(), + content: content.into(), + }) +} + +pub fn get_usage(data: &Value) -> Result { + let usage = data + .get("usage") + .ok_or_else(|| ProviderError::UsageError("No usage data in response".to_string()))?; + + let input_tokens = usage + .get("prompt_tokens") + .and_then(|v| v.as_i64()) + .map(|v| v as i32); + + let output_tokens = usage + .get("completion_tokens") + .and_then(|v| v.as_i64()) + .map(|v| v as i32); + + let total_tokens = usage + .get("total_tokens") + .and_then(|v| v.as_i64()) + .map(|v| v as i32) + .or_else(|| match (input_tokens, output_tokens) { + (Some(input), Some(output)) => Some(input + output), + _ => None, + }); + + Ok(Usage::new(input_tokens, output_tokens, total_tokens)) +} + +/// Validates and fixes tool schemas to ensure they have proper parameter structure. +/// If parameters exist, ensures they have properties and required fields, or removes parameters entirely. +pub fn validate_tool_schemas(tools: &mut [Value]) { + for tool in tools.iter_mut() { + if let Some(function) = tool.get_mut("function") { + if let Some(parameters) = function.get_mut("parameters") { + if parameters.is_object() { + ensure_valid_json_schema(parameters); + } + } + } + } +} + +/// Ensures that the given JSON value follows the expected JSON Schema structure. +fn ensure_valid_json_schema(schema: &mut Value) { + if let Some(params_obj) = schema.as_object_mut() { + // Check if this is meant to be an object type schema + let is_object_type = params_obj + .get("type") + .and_then(|t| t.as_str()) + .is_none_or(|t| t == "object"); // Default to true if no type is specified + + // Only apply full schema validation to object types + if is_object_type { + // Ensure required fields exist with default values + params_obj.entry("properties").or_insert_with(|| json!({})); + params_obj.entry("required").or_insert_with(|| json!([])); + params_obj.entry("type").or_insert_with(|| json!("object")); + + // Recursively validate properties if it exists + if let Some(properties) = params_obj.get_mut("properties") { + if let Some(properties_obj) = properties.as_object_mut() { + for (_key, prop) in properties_obj.iter_mut() { + if prop.is_object() + && prop.get("type").and_then(|t| t.as_str()) == Some("object") + { + ensure_valid_json_schema(prop); + } + } + } + } + } + } +} + +pub fn create_request( + model_config: &ModelConfig, + system: &str, + messages: &[Message], + tools: &[Tool], + image_format: &ImageFormat, +) -> anyhow::Result { + if model_config.model_name.starts_with("o1-mini") { + return Err(anyhow!( + "o1-mini model is not currently supported since Goose uses tool calling and o1-mini does not support it. Please use o1 or o3 models instead." + )); + } + + let is_ox_model = model_config.model_name.starts_with("o"); + + // Only extract reasoning effort for O1/O3 models + let (model_name, reasoning_effort) = if is_ox_model { + let parts: Vec<&str> = model_config.model_name.split('-').collect(); + let last_part = parts.last().unwrap(); + + match *last_part { + "low" | "medium" | "high" => { + let base_name = parts[..parts.len() - 1].join("-"); + (base_name, Some(last_part.to_string())) + } + _ => ( + model_config.model_name.to_string(), + Some("medium".to_string()), + ), + } + } else { + // For non-O family models, use the model name as is and no reasoning effort + (model_config.model_name.to_string(), None) + }; + + let system_message = json!({ + "role": if is_ox_model { "developer" } else { "system" }, + "content": system + }); + + let messages_spec = format_messages(messages, image_format); + let mut tools_spec = if !tools.is_empty() { + format_tools(tools)? + } else { + vec![] + }; + + // Validate tool schemas + validate_tool_schemas(&mut tools_spec); + + let mut messages_array = vec![system_message]; + messages_array.extend(messages_spec); + + let mut payload = json!({ + "model": model_name, + "messages": messages_array + }); + + if let Some(effort) = reasoning_effort { + payload + .as_object_mut() + .unwrap() + .insert("reasoning_effort".to_string(), json!(effort)); + } + + if !tools_spec.is_empty() { + payload + .as_object_mut() + .unwrap() + .insert("tools".to_string(), json!(tools_spec)); + } + // o1, o3 models currently don't support temperature + if !is_ox_model { + if let Some(temp) = model_config.temperature { + payload + .as_object_mut() + .unwrap() + .insert("temperature".to_string(), json!(temp)); + } + } + + // o1 models use max_completion_tokens instead of max_tokens + if let Some(tokens) = model_config.max_tokens { + let key = if is_ox_model { + "max_completion_tokens" + } else { + "max_tokens" + }; + payload + .as_object_mut() + .unwrap() + .insert(key.to_string(), json!(tokens)); + } + Ok(payload) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + use crate::types::core::Content; + + #[test] + fn test_validate_tool_schemas() { + // Test case 1: Empty parameters object + // Input JSON with an incomplete parameters object + let mut actual = vec![json!({ + "type": "function", + "function": { + "name": "test_func", + "description": "test description", + "parameters": { + "type": "object" + } + } + })]; + + // Run the function to validate and update schemas + validate_tool_schemas(&mut actual); + + // Expected JSON after validation + let expected = vec![json!({ + "type": "function", + "function": { + "name": "test_func", + "description": "test description", + "parameters": { + "type": "object", + "properties": {}, + "required": [] + } + } + })]; + + // Compare entire JSON structures instead of individual fields + assert_eq!(actual, expected); + + // Test case 2: Missing type field + let mut tools = vec![json!({ + "type": "function", + "function": { + "name": "test_func", + "description": "test description", + "parameters": { + "properties": {} + } + } + })]; + + validate_tool_schemas(&mut tools); + + let params = tools[0]["function"]["parameters"].as_object().unwrap(); + assert_eq!(params["type"], "object"); + + // Test case 3: Complete valid schema should remain unchanged + let original_schema = json!({ + "type": "function", + "function": { + "name": "test_func", + "description": "test description", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "City and country" + } + }, + "required": ["location"] + } + } + }); + + let mut tools = vec![original_schema.clone()]; + validate_tool_schemas(&mut tools); + assert_eq!(tools[0], original_schema); + } + + const OPENAI_TOOL_USE_RESPONSE: &str = r#"{ + "choices": [{ + "role": "assistant", + "message": { + "tool_calls": [{ + "id": "1", + "function": { + "name": "example_fn", + "arguments": "{\"param\": \"value\"}" + } + }] + } + }], + "usage": { + "input_tokens": 10, + "output_tokens": 25, + "total_tokens": 35 + } + }"#; + + #[test] + fn test_format_messages() -> anyhow::Result<()> { + let message = Message::user().with_text("Hello"); + let spec = format_messages(&[message], &ImageFormat::OpenAi); + + assert_eq!(spec.len(), 1); + assert_eq!(spec[0]["role"], "user"); + assert_eq!(spec[0]["content"], "Hello"); + Ok(()) + } + + #[test] + fn test_format_tools() -> anyhow::Result<()> { + let tool = Tool::new( + "test_tool", + "A test tool", + json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "Test parameter" + } + }, + "required": ["input"] + }), + ); + + let spec = format_tools(&[tool])?; + + assert_eq!(spec.len(), 1); + assert_eq!(spec[0]["type"], "function"); + assert_eq!(spec[0]["function"]["name"], "test_tool"); + Ok(()) + } + + #[test] + fn test_format_messages_complex() -> anyhow::Result<()> { + let mut messages = vec![ + Message::assistant().with_text("Hello!"), + Message::user().with_text("How are you?"), + Message::assistant().with_tool_request( + "tool1", + Ok(ToolCall::new("example", json!({"param1": "value1"}))), + ), + ]; + + // Get the ID from the tool request to use in the response + let tool_id = if let MessageContent::ToolReq(request) = &messages[2].content[0] { + request.id.clone() + } else { + panic!("should be tool request"); + }; + + messages.push( + Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")]).into()), + ); + + let spec = format_messages(&messages, &ImageFormat::OpenAi); + + assert_eq!(spec.len(), 4); + assert_eq!(spec[0]["role"], "assistant"); + assert_eq!(spec[0]["content"], "Hello!"); + assert_eq!(spec[1]["role"], "user"); + assert_eq!(spec[1]["content"], "How are you?"); + assert_eq!(spec[2]["role"], "assistant"); + assert!(spec[2]["tool_calls"].is_array()); + assert_eq!(spec[3]["role"], "tool"); + assert_eq!(spec[3]["content"], "Result"); + assert_eq!(spec[3]["tool_call_id"], spec[2]["tool_calls"][0]["id"]); + + Ok(()) + } + + #[test] + fn test_format_messages_multiple_content() -> anyhow::Result<()> { + let mut messages = vec![Message::assistant().with_tool_request( + "tool1", + Ok(ToolCall::new("example", json!({"param1": "value1"}))), + )]; + + // Get the ID from the tool request to use in the response + let tool_id = if let MessageContent::ToolReq(request) = &messages[0].content[0] { + request.id.clone() + } else { + panic!("should be tool request"); + }; + + messages.push( + Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")]).into()), + ); + + let spec = format_messages(&messages, &ImageFormat::OpenAi); + + assert_eq!(spec.len(), 2); + assert_eq!(spec[0]["role"], "assistant"); + assert!(spec[0]["tool_calls"].is_array()); + assert_eq!(spec[1]["role"], "tool"); + assert_eq!(spec[1]["content"], "Result"); + assert_eq!(spec[1]["tool_call_id"], spec[0]["tool_calls"][0]["id"]); + + Ok(()) + } + + #[test] + fn test_format_tools_duplicate() -> anyhow::Result<()> { + let tool1 = Tool::new( + "test_tool", + "Test tool", + json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "Test parameter" + } + }, + "required": ["input"] + }), + ); + + let tool2 = Tool::new( + "test_tool", + "Test tool", + json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "Test parameter" + } + }, + "required": ["input"] + }), + ); + + let result = format_tools(&[tool1, tool2]); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("Duplicate tool name")); + + Ok(()) + } + + #[test] + fn test_format_tools_empty() -> anyhow::Result<()> { + let spec = format_tools(&[])?; + assert!(spec.is_empty()); + Ok(()) + } + + #[test] + fn test_format_messages_with_image_path() -> anyhow::Result<()> { + // Create a temporary PNG file with valid PNG magic numbers + let temp_dir = tempfile::tempdir()?; + let png_path = temp_dir.path().join("test.png"); + let png_data = [ + 0x89, 0x50, 0x4E, 0x47, // PNG magic number + 0x0D, 0x0A, 0x1A, 0x0A, // PNG header + 0x00, 0x00, 0x00, 0x0D, // Rest of fake PNG data + ]; + std::fs::write(&png_path, &png_data)?; + let png_path_str = png_path.to_str().unwrap(); + + // Create message with image path + let message = Message::user().with_text(format!("Here is an image: {}", png_path_str)); + let spec = format_messages(&[message], &ImageFormat::OpenAi); + + assert_eq!(spec.len(), 1); + assert_eq!(spec[0]["role"], "user"); + + // Content should be an array with text and image + let content = spec[0]["content"].as_array().unwrap(); + assert_eq!(content.len(), 2); + assert_eq!(content[0]["type"], "text"); + assert!(content[0]["text"].as_str().unwrap().contains(png_path_str)); + assert_eq!(content[1]["type"], "image_url"); + assert!(content[1]["image_url"]["url"] + .as_str() + .unwrap() + .starts_with("data:image/png;base64,")); + + Ok(()) + } + + #[test] + fn test_response_to_message_text() -> anyhow::Result<()> { + let response = json!({ + "choices": [{ + "role": "assistant", + "message": { + "content": "Hello from John Cena!" + } + }], + "usage": { + "input_tokens": 10, + "output_tokens": 25, + "total_tokens": 35 + } + }); + + let message = response_to_message(response)?; + assert_eq!(message.content.len(), 1); + if let MessageContent::Text(text) = &message.content[0] { + assert_eq!(text.text, "Hello from John Cena!"); + } else { + panic!("Expected Text content"); + } + assert!(matches!(message.role, Role::Assistant)); + + Ok(()) + } + + #[test] + fn test_response_to_message_valid_toolrequest() -> anyhow::Result<()> { + let response: Value = serde_json::from_str(OPENAI_TOOL_USE_RESPONSE)?; + let message = response_to_message(response)?; + + assert_eq!(message.content.len(), 1); + if let MessageContent::ToolReq(request) = &message.content[0] { + let tool_call = request.tool_call.as_ref().unwrap(); + assert_eq!(tool_call.name, "example_fn"); + assert_eq!(tool_call.arguments, json!({"param": "value"})); + } else { + panic!("Expected ToolRequest content"); + } + + Ok(()) + } + + #[test] + fn test_response_to_message_invalid_func_name() -> anyhow::Result<()> { + let mut response: Value = serde_json::from_str(OPENAI_TOOL_USE_RESPONSE)?; + response["choices"][0]["message"]["tool_calls"][0]["function"]["name"] = + json!("invalid fn"); + + let message = response_to_message(response)?; + + if let MessageContent::ToolReq(request) = &message.content[0] { + match &request.tool_call.as_result() { + Err(ToolError::NotFound(msg)) => { + assert!(msg.starts_with("The provided function name")); + } + _ => panic!("Expected ToolNotFound error"), + } + } else { + panic!("Expected ToolRequest content"); + } + + Ok(()) + } + + #[test] + fn test_response_to_message_json_decode_error() -> anyhow::Result<()> { + let mut response: Value = serde_json::from_str(OPENAI_TOOL_USE_RESPONSE)?; + response["choices"][0]["message"]["tool_calls"][0]["function"]["arguments"] = + json!("invalid json {"); + + let message = response_to_message(response)?; + + if let MessageContent::ToolReq(request) = &message.content[0] { + match &request.tool_call.as_result() { + Err(ToolError::InvalidParameters(msg)) => { + assert!(msg.starts_with("Could not interpret tool use parameters")); + } + _ => panic!("Expected InvalidParameters error"), + } + } else { + panic!("Expected ToolRequest content"); + } + + Ok(()) + } + + #[test] + fn test_response_to_message_empty_argument() -> anyhow::Result<()> { + let mut response: Value = serde_json::from_str(OPENAI_TOOL_USE_RESPONSE)?; + response["choices"][0]["message"]["tool_calls"][0]["function"]["arguments"] = + serde_json::Value::String("".to_string()); + + let message = response_to_message(response)?; + + if let MessageContent::ToolReq(request) = &message.content[0] { + let tool_call = request.tool_call.as_ref().unwrap(); + assert_eq!(tool_call.name, "example_fn"); + assert_eq!(tool_call.arguments, json!({})); + } else { + panic!("Expected ToolRequest content"); + } + + Ok(()) + } + + #[test] + fn test_create_request_gpt_4o() -> anyhow::Result<()> { + // Test default medium reasoning effort for O3 model + let model_config = ModelConfig { + model_name: "gpt-4o".to_string(), + context_limit: Some(4096), + temperature: None, + max_tokens: Some(1024), + }; + let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?; + let obj = request.as_object().unwrap(); + let expected = json!({ + "model": "gpt-4o", + "messages": [ + { + "role": "system", + "content": "system" + } + ], + "max_tokens": 1024 + }); + + for (key, value) in expected.as_object().unwrap() { + assert_eq!(obj.get(key).unwrap(), value); + } + + Ok(()) + } + + #[test] + fn test_create_request_o1_default() -> anyhow::Result<()> { + // Test default medium reasoning effort for O1 model + let model_config = ModelConfig { + model_name: "o1".to_string(), + context_limit: Some(4096), + temperature: None, + max_tokens: Some(1024), + }; + let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?; + let obj = request.as_object().unwrap(); + let expected = json!({ + "model": "o1", + "messages": [ + { + "role": "developer", + "content": "system" + } + ], + "reasoning_effort": "medium", + "max_completion_tokens": 1024 + }); + + for (key, value) in expected.as_object().unwrap() { + assert_eq!(obj.get(key).unwrap(), value); + } + + Ok(()) + } + + #[test] + fn test_create_request_o3_custom_reasoning_effort() -> anyhow::Result<()> { + // Test custom reasoning effort for O3 model + let model_config = ModelConfig { + model_name: "o3-mini-high".to_string(), + context_limit: Some(4096), + temperature: None, + max_tokens: Some(1024), + }; + let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?; + let obj = request.as_object().unwrap(); + let expected = json!({ + "model": "o3-mini", + "messages": [ + { + "role": "developer", + "content": "system" + } + ], + "reasoning_effort": "high", + "max_completion_tokens": 1024 + }); + + for (key, value) in expected.as_object().unwrap() { + assert_eq!(obj.get(key).unwrap(), value); + } + + Ok(()) + } +} diff --git a/crates/goose-llm/src/providers/mod.rs b/crates/goose-llm/src/providers/mod.rs new file mode 100644 index 00000000..c8089380 --- /dev/null +++ b/crates/goose-llm/src/providers/mod.rs @@ -0,0 +1,10 @@ +pub mod base; +pub mod databricks; +pub mod errors; +mod factory; +pub mod formats; +pub mod openai; +pub mod utils; + +pub use base::{Provider, ProviderCompleteResponse, ProviderExtractResponse, Usage}; +pub use factory::create; diff --git a/crates/goose-llm/src/providers/openai.rs b/crates/goose-llm/src/providers/openai.rs new file mode 100644 index 00000000..bc0dc088 --- /dev/null +++ b/crates/goose-llm/src/providers/openai.rs @@ -0,0 +1,231 @@ +use std::{collections::HashMap, time::Duration}; + +use anyhow::Result; +use async_trait::async_trait; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; + +use super::{ + errors::ProviderError, + formats::openai::{create_request, get_usage, response_to_message}, + utils::{emit_debug_trace, get_env, get_model, handle_response_openai_compat, ImageFormat}, +}; +use crate::{ + message::Message, + model::ModelConfig, + providers::{Provider, ProviderCompleteResponse, ProviderExtractResponse, Usage}, + types::core::Tool, +}; + +pub const OPEN_AI_DEFAULT_MODEL: &str = "gpt-4o"; +pub const _OPEN_AI_KNOWN_MODELS: &[&str] = &["gpt-4o", "gpt-4.1", "o1", "o3", "o4-mini"]; + +fn default_timeout() -> u64 { + 60 +} + +fn default_base_path() -> String { + "v1/chat/completions".to_string() +} + +fn default_host() -> String { + "https://api.openai.com".to_string() +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OpenAiProviderConfig { + pub api_key: String, + #[serde(default = "default_host")] + pub host: String, + #[serde(default)] + pub organization: Option, + #[serde(default = "default_base_path")] + pub base_path: String, + #[serde(default)] + pub project: Option, + #[serde(default)] + pub custom_headers: Option>, + #[serde(default = "default_timeout")] + pub timeout: u64, // timeout in seconds +} + +impl OpenAiProviderConfig { + pub fn new(api_key: String) -> Self { + Self { + api_key, + host: default_host(), + organization: None, + base_path: default_base_path(), + project: None, + custom_headers: None, + timeout: 600, + } + } + + pub fn from_env() -> Self { + let api_key = get_env("OPENAI_API_KEY").expect("Missing OPENAI_API_KEY"); + Self::new(api_key) + } +} + +#[derive(Debug)] +pub struct OpenAiProvider { + config: OpenAiProviderConfig, + model: ModelConfig, + client: Client, +} + +impl OpenAiProvider { + pub fn from_env(model: ModelConfig) -> Self { + let config = OpenAiProviderConfig::from_env(); + OpenAiProvider::from_config(config, model).expect("Failed to initialize OpenAiProvider") + } +} + +impl Default for OpenAiProvider { + fn default() -> Self { + let config = OpenAiProviderConfig::from_env(); + let model = ModelConfig::new(OPEN_AI_DEFAULT_MODEL.to_string()); + OpenAiProvider::from_config(config, model).expect("Failed to initialize OpenAiProvider") + } +} + +impl OpenAiProvider { + pub fn from_config(config: OpenAiProviderConfig, model: ModelConfig) -> Result { + let client = Client::builder() + .timeout(Duration::from_secs(config.timeout)) + .build()?; + + Ok(Self { + config, + model, + client, + }) + } + + async fn post(&self, payload: Value) -> Result { + let base_url = url::Url::parse(&self.config.host) + .map_err(|e| ProviderError::RequestFailed(format!("Invalid base URL: {e}")))?; + let url = base_url.join(&self.config.base_path).map_err(|e| { + ProviderError::RequestFailed(format!("Failed to construct endpoint URL: {e}")) + })?; + + let mut request = self + .client + .post(url) + .header("Authorization", format!("Bearer {}", self.config.api_key)); + + // Add organization header if present + if let Some(org) = &self.config.organization { + request = request.header("OpenAI-Organization", org); + } + + // Add project header if present + if let Some(project) = &self.config.project { + request = request.header("OpenAI-Project", project); + } + + if let Some(custom_headers) = &self.config.custom_headers { + for (key, value) in custom_headers { + request = request.header(key, value); + } + } + + let response = request.json(&payload).send().await?; + + handle_response_openai_compat(response).await + } +} + +#[async_trait] +impl Provider for OpenAiProvider { + #[tracing::instrument( + skip(self, system, messages, tools), + fields(model_config, input, output, input_tokens, output_tokens, total_tokens) + )] + async fn complete( + &self, + system: &str, + messages: &[Message], + tools: &[Tool], + ) -> Result { + let payload = create_request(&self.model, system, messages, tools, &ImageFormat::OpenAi)?; + + // Make request + let response = self.post(payload.clone()).await?; + + // Parse response + let message = response_to_message(response.clone())?; + let usage = match get_usage(&response) { + Ok(usage) => usage, + Err(ProviderError::UsageError(e)) => { + tracing::debug!("Failed to get usage data: {}", e); + Usage::default() + } + Err(e) => return Err(e), + }; + let model = get_model(&response); + emit_debug_trace(&self.model, &payload, &response, &usage); + Ok(ProviderCompleteResponse::new(message, model, usage)) + } + + async fn extract( + &self, + system: &str, + messages: &[Message], + schema: &Value, + ) -> Result { + // 1. Build base payload (no tools) + let mut payload = create_request(&self.model, system, messages, &[], &ImageFormat::OpenAi)?; + + // 2. Inject strict JSON‐Schema wrapper + payload + .as_object_mut() + .expect("payload must be an object") + .insert( + "response_format".to_string(), + json!({ + "type": "json_schema", + "json_schema": { + "name": "extraction", + "schema": schema, + "strict": true + } + }), + ); + + // 3. Call OpenAI + let response = self.post(payload.clone()).await?; + + // 4. Extract the assistant’s `content` and parse it into JSON + let msg = &response["choices"][0]["message"]; + let raw = msg.get("content").cloned().ok_or_else(|| { + ProviderError::ResponseParseError("Missing content in extract response".into()) + })?; + let data = match raw { + Value::String(s) => serde_json::from_str(&s) + .map_err(|e| ProviderError::ResponseParseError(format!("Invalid JSON: {}", e)))?, + Value::Object(_) | Value::Array(_) => raw, + other => { + return Err(ProviderError::ResponseParseError(format!( + "Unexpected content type: {:?}", + other + ))) + } + }; + + // 5. Gather usage & model info + let usage = match get_usage(&response) { + Ok(u) => u, + Err(ProviderError::UsageError(e)) => { + tracing::debug!("Failed to get usage in extract: {}", e); + Usage::default() + } + Err(e) => return Err(e), + }; + let model = get_model(&response); + + Ok(ProviderExtractResponse::new(data, model, usage)) + } +} diff --git a/crates/goose-llm/src/providers/utils.rs b/crates/goose-llm/src/providers/utils.rs new file mode 100644 index 00000000..caca0a00 --- /dev/null +++ b/crates/goose-llm/src/providers/utils.rs @@ -0,0 +1,359 @@ +use std::{env, io::Read, path::Path}; + +use anyhow::Result; +use base64::Engine; +use regex::Regex; +use reqwest::{Response, StatusCode}; +use serde::{Deserialize, Serialize}; +use serde_json::{from_value, json, Value}; + +use super::base::Usage; +use crate::{ + model::ModelConfig, + providers::errors::{OpenAIError, ProviderError}, + types::core::ImageContent, +}; + +#[derive(serde::Deserialize)] +struct OpenAIErrorResponse { + error: OpenAIError, +} + +#[derive(Debug, Copy, Clone, Serialize, Deserialize, Default)] +pub enum ImageFormat { + #[default] + OpenAi, + Anthropic, +} + +/// Timeout in seconds. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Timeout(u32); +impl Default for Timeout { + fn default() -> Self { + Timeout(60) + } +} + +/// Convert an image content into an image json based on format +pub fn convert_image(image: &ImageContent, image_format: &ImageFormat) -> Value { + match image_format { + ImageFormat::OpenAi => json!({ + "type": "image_url", + "image_url": { + "url": format!("data:{};base64,{}", image.mime_type, image.data) + } + }), + ImageFormat::Anthropic => json!({ + "type": "image", + "source": { + "type": "base64", + "media_type": image.mime_type, + "data": image.data, + } + }), + } +} + +/// Handle response from OpenAI compatible endpoints +/// Error codes: https://platform.openai.com/docs/guides/error-codes +/// Context window exceeded: https://community.openai.com/t/help-needed-tackling-context-length-limits-in-openai-models/617543 +pub async fn handle_response_openai_compat(response: Response) -> Result { + let status = response.status(); + // Try to parse the response body as JSON (if applicable) + let payload = match response.json::().await { + Ok(json) => json, + Err(e) => return Err(ProviderError::RequestFailed(e.to_string())), + }; + + match status { + StatusCode::OK => Ok(payload), + StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => { + Err(ProviderError::Authentication(format!( + "Authentication failed. Please ensure your API keys are valid and have the required permissions. \ + Status: {}. Response: {:?}", + status, payload + ))) + } + StatusCode::BAD_REQUEST | StatusCode::NOT_FOUND => { + tracing::debug!( + "{}", + format!( + "Provider request failed with status: {}. Payload: {:?}", + status, payload + ) + ); + if let Ok(err_resp) = from_value::(payload) { + let err = err_resp.error; + if err.is_context_length_exceeded() { + return Err(ProviderError::ContextLengthExceeded( + err.message.unwrap_or("Unknown error".to_string()), + )); + } + return Err(ProviderError::RequestFailed(format!( + "{} (status {})", + err, + status.as_u16() + ))); + } + Err(ProviderError::RequestFailed(format!( + "Unknown error (status {})", + status + ))) + } + StatusCode::TOO_MANY_REQUESTS => { + Err(ProviderError::RateLimitExceeded(format!("{:?}", payload))) + } + StatusCode::INTERNAL_SERVER_ERROR | StatusCode::SERVICE_UNAVAILABLE => { + Err(ProviderError::ServerError(format!("{:?}", payload))) + } + _ => { + tracing::debug!( + "{}", + format!( + "Provider request failed with status: {}. Payload: {:?}", + status, payload + ) + ); + Err(ProviderError::RequestFailed(format!( + "Request failed with status: {}", + status + ))) + } + } +} + +/// Get a secret from environment variables. The secret is expected to be in JSON format. +pub fn get_env(key: &str) -> Result { + // check environment variables (convert to uppercase) + let env_key = key.to_uppercase(); + if let Ok(val) = env::var(&env_key) { + let value: Value = serde_json::from_str(&val).unwrap_or(Value::String(val)); + Ok(serde_json::from_value(value)?) + } else { + Err(anyhow::anyhow!( + "Environment variable {} not found", + env_key + )) + } +} + +pub fn sanitize_function_name(name: &str) -> String { + let re = Regex::new(r"[^a-zA-Z0-9_-]").unwrap(); + re.replace_all(name, "_").to_string() +} + +pub fn is_valid_function_name(name: &str) -> bool { + let re = Regex::new(r"^[a-zA-Z0-9_-]+$").unwrap(); + re.is_match(name) +} + +/// Extract the model name from a JSON object. Common with most providers to have this top level attribute. +pub fn get_model(data: &Value) -> String { + if let Some(model) = data.get("model") { + if let Some(model_str) = model.as_str() { + model_str.to_string() + } else { + "Unknown".to_string() + } + } else { + "Unknown".to_string() + } +} + +/// Check if a file is actually an image by examining its magic bytes +fn is_image_file(path: &Path) -> bool { + if let Ok(mut file) = std::fs::File::open(path) { + let mut buffer = [0u8; 8]; // Large enough for most image magic numbers + if file.read(&mut buffer).is_ok() { + // Check magic numbers for common image formats + return match &buffer[0..4] { + // PNG: 89 50 4E 47 + [0x89, 0x50, 0x4E, 0x47] => true, + // JPEG: FF D8 FF + [0xFF, 0xD8, 0xFF, _] => true, + // GIF: 47 49 46 38 + [0x47, 0x49, 0x46, 0x38] => true, + _ => false, + }; + } + } + false +} + +/// Detect if a string contains a path to an image file +pub fn detect_image_path(text: &str) -> Option<&str> { + // Basic image file extension check + let extensions = [".png", ".jpg", ".jpeg"]; + + // Find any word that ends with an image extension + for word in text.split_whitespace() { + if extensions + .iter() + .any(|ext| word.to_lowercase().ends_with(ext)) + { + let path = Path::new(word); + // Check if it's an absolute path and file exists + if path.is_absolute() && path.is_file() { + // Verify it's actually an image file + if is_image_file(path) { + return Some(word); + } + } + } + } + None +} + +/// Convert a local image file to base64 encoded ImageContent +pub fn load_image_file(path: &str) -> Result { + let path = Path::new(path); + + // Verify it's an image before proceeding + if !is_image_file(path) { + return Err(ProviderError::RequestFailed( + "File is not a valid image".to_string(), + )); + } + + // Read the file + let bytes = std::fs::read(path) + .map_err(|e| ProviderError::RequestFailed(format!("Failed to read image file: {}", e)))?; + + // Detect mime type from extension + let mime_type = match path.extension().and_then(|e| e.to_str()) { + Some(ext) => match ext.to_lowercase().as_str() { + "png" => "image/png", + "jpg" | "jpeg" => "image/jpeg", + _ => { + return Err(ProviderError::RequestFailed( + "Unsupported image format".to_string(), + )); + } + }, + None => { + return Err(ProviderError::RequestFailed( + "Unknown image format".to_string(), + )); + } + }; + + // Convert to base64 + let data = base64::prelude::BASE64_STANDARD.encode(&bytes); + + Ok(ImageContent { + mime_type: mime_type.to_string(), + data, + }) +} + +pub fn emit_debug_trace( + model_config: &ModelConfig, + payload: &Value, + response: &Value, + usage: &Usage, +) { + tracing::debug!( + model_config = %serde_json::to_string_pretty(model_config).unwrap_or_default(), + input = %serde_json::to_string_pretty(payload).unwrap_or_default(), + output = %serde_json::to_string_pretty(response).unwrap_or_default(), + input_tokens = ?usage.input_tokens.unwrap_or_default(), + output_tokens = ?usage.output_tokens.unwrap_or_default(), + total_tokens = ?usage.total_tokens.unwrap_or_default(), + ); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_detect_image_path() { + // Create a temporary PNG file with valid PNG magic numbers + let temp_dir = tempfile::tempdir().unwrap(); + let png_path = temp_dir.path().join("test.png"); + let png_data = [ + 0x89, 0x50, 0x4E, 0x47, // PNG magic number + 0x0D, 0x0A, 0x1A, 0x0A, // PNG header + 0x00, 0x00, 0x00, 0x0D, // Rest of fake PNG data + ]; + std::fs::write(&png_path, &png_data).unwrap(); + let png_path_str = png_path.to_str().unwrap(); + + // Create a fake PNG (wrong magic numbers) + let fake_png_path = temp_dir.path().join("fake.png"); + std::fs::write(&fake_png_path, b"not a real png").unwrap(); + + // Test with valid PNG file using absolute path + let text = format!("Here is an image {}", png_path_str); + assert_eq!(detect_image_path(&text), Some(png_path_str)); + + // Test with non-image file that has .png extension + let text = format!("Here is a fake image {}", fake_png_path.to_str().unwrap()); + assert_eq!(detect_image_path(&text), None); + + // Test with non-existent file + let text = "Here is a fake.png that doesn't exist"; + assert_eq!(detect_image_path(text), None); + + // Test with non-image file + let text = "Here is a file.txt"; + assert_eq!(detect_image_path(text), None); + + // Test with relative path (should not match) + let text = "Here is a relative/path/image.png"; + assert_eq!(detect_image_path(text), None); + } + + #[test] + fn test_load_image_file() { + // Create a temporary PNG file with valid PNG magic numbers + let temp_dir = tempfile::tempdir().unwrap(); + let png_path = temp_dir.path().join("test.png"); + let png_data = [ + 0x89, 0x50, 0x4E, 0x47, // PNG magic number + 0x0D, 0x0A, 0x1A, 0x0A, // PNG header + 0x00, 0x00, 0x00, 0x0D, // Rest of fake PNG data + ]; + std::fs::write(&png_path, &png_data).unwrap(); + let png_path_str = png_path.to_str().unwrap(); + + // Create a fake PNG (wrong magic numbers) + let fake_png_path = temp_dir.path().join("fake.png"); + std::fs::write(&fake_png_path, b"not a real png").unwrap(); + let fake_png_path_str = fake_png_path.to_str().unwrap(); + + // Test loading valid PNG file + let result = load_image_file(png_path_str); + assert!(result.is_ok()); + let image = result.unwrap(); + assert_eq!(image.mime_type, "image/png"); + + // Test loading fake PNG file + let result = load_image_file(fake_png_path_str); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("not a valid image")); + + // Test non-existent file + let result = load_image_file("nonexistent.png"); + assert!(result.is_err()); + } + + #[test] + fn test_sanitize_function_name() { + assert_eq!(sanitize_function_name("hello-world"), "hello-world"); + assert_eq!(sanitize_function_name("hello world"), "hello_world"); + assert_eq!(sanitize_function_name("hello@world"), "hello_world"); + } + + #[test] + fn test_is_valid_function_name() { + assert!(is_valid_function_name("hello-world")); + assert!(is_valid_function_name("hello_world")); + assert!(!is_valid_function_name("hello world")); + assert!(!is_valid_function_name("hello@world")); + } +} diff --git a/crates/goose-llm/src/structured_outputs.rs b/crates/goose-llm/src/structured_outputs.rs new file mode 100644 index 00000000..8f478d8a --- /dev/null +++ b/crates/goose-llm/src/structured_outputs.rs @@ -0,0 +1,29 @@ +use crate::{ + providers::{create, errors::ProviderError, ProviderExtractResponse}, + types::json_value_ffi::JsonValueFfi, + Message, ModelConfig, +}; + +/// Generates a structured output based on the provided schema, +/// system prompt and user messages. +#[uniffi::export(async_runtime = "tokio")] +pub async fn generate_structured_outputs( + provider_name: &str, + provider_config: JsonValueFfi, + system_prompt: &str, + messages: &[Message], + schema: JsonValueFfi, +) -> Result { + // Use OpenAI models specifically for this task + let model_name = if provider_name == "databricks" { + "goose-gpt-4-1" + } else { + "gpt-4.1" + }; + let model_cfg = ModelConfig::new(model_name.to_string()).with_temperature(Some(0.0)); + let provider = create(provider_name, provider_config, model_cfg)?; + + let resp = provider.extract(system_prompt, messages, &schema).await?; + + Ok(resp) +} diff --git a/crates/goose-llm/src/types/completion.rs b/crates/goose-llm/src/types/completion.rs new file mode 100644 index 00000000..def6a6d2 --- /dev/null +++ b/crates/goose-llm/src/types/completion.rs @@ -0,0 +1,227 @@ +// This file defines types for completion interfaces, including the request and response structures. +// Many of these are adapted based on the Goose Service API: +// https://docs.google.com/document/d/1r5vjSK3nBQU1cIRf0WKysDigqMlzzrzl_bxEE4msOiw/edit?tab=t.0 + +use std::collections::HashMap; +use thiserror::Error; + +use serde::{Deserialize, Serialize}; + +use crate::types::json_value_ffi::JsonValueFfi; +use crate::{message::Message, providers::Usage}; +use crate::{model::ModelConfig, providers::errors::ProviderError}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CompletionRequest { + pub provider_name: String, + pub provider_config: serde_json::Value, + pub model_config: ModelConfig, + pub system_preamble: String, + pub messages: Vec, + pub extensions: Vec, +} + +impl CompletionRequest { + pub fn new( + provider_name: String, + provider_config: serde_json::Value, + model_config: ModelConfig, + system_preamble: String, + messages: Vec, + extensions: Vec, + ) -> Self { + Self { + provider_name, + provider_config, + model_config, + system_preamble, + messages, + extensions, + } + } +} + +#[uniffi::export] +pub fn create_completion_request( + provider_name: &str, + provider_config: JsonValueFfi, + model_config: ModelConfig, + system_preamble: &str, + messages: Vec, + extensions: Vec, +) -> CompletionRequest { + CompletionRequest::new( + provider_name.to_string(), + provider_config, + model_config, + system_preamble.to_string(), + messages, + extensions, + ) +} + +uniffi::custom_type!(CompletionRequest, String, { + lower: |tc: &CompletionRequest| { + serde_json::to_string(&tc).unwrap() + }, + try_lift: |s: String| { + Ok(serde_json::from_str(&s).unwrap()) + }, +}); + +// https://mozilla.github.io/uniffi-rs/latest/proc_macro/errors.html +#[derive(Debug, Error, uniffi::Error)] +#[uniffi(flat_error)] +pub enum CompletionError { + #[error("failed to create provider: {0}")] + UnknownProvider(String), + + #[error("provider error: {0}")] + Provider(#[from] ProviderError), + + #[error("template rendering error: {0}")] + Template(#[from] minijinja::Error), + + #[error("json serialization error: {0}")] + Json(#[from] serde_json::Error), + + #[error("tool not found error: {0}")] + ToolNotFound(String), +} + +#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] +pub struct CompletionResponse { + pub message: Message, + pub model: String, + pub usage: Usage, + pub runtime_metrics: RuntimeMetrics, +} + +impl CompletionResponse { + pub fn new( + message: Message, + model: String, + usage: Usage, + runtime_metrics: RuntimeMetrics, + ) -> Self { + Self { + message, + model, + usage, + runtime_metrics, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] +pub struct RuntimeMetrics { + pub total_time_sec: f32, + pub total_time_sec_provider: f32, + pub tokens_per_second: Option, +} + +impl RuntimeMetrics { + pub fn new( + total_time_sec: f32, + total_time_sec_provider: f32, + tokens_per_second: Option, + ) -> Self { + Self { + total_time_sec, + total_time_sec_provider, + tokens_per_second, + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Enum)] +pub enum ToolApprovalMode { + Auto, + Manual, + Smart, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Record)] +pub struct ToolConfig { + pub name: String, + pub description: String, + pub input_schema: JsonValueFfi, + pub approval_mode: ToolApprovalMode, +} + +impl ToolConfig { + pub fn new( + name: &str, + description: &str, + input_schema: JsonValueFfi, + approval_mode: ToolApprovalMode, + ) -> Self { + Self { + name: name.to_string(), + description: description.to_string(), + input_schema, + approval_mode, + } + } + + /// Convert the tool config to a core tool + pub fn to_core_tool(&self, name: Option<&str>) -> super::core::Tool { + let tool_name = name.unwrap_or(&self.name); + super::core::Tool::new( + tool_name, + self.description.clone(), + self.input_schema.clone(), + ) + } +} + +#[uniffi::export] +pub fn create_tool_config( + name: &str, + description: &str, + input_schema: JsonValueFfi, + approval_mode: ToolApprovalMode, +) -> ToolConfig { + ToolConfig::new(name, description, input_schema, approval_mode) +} + +// — Register the newtypes with UniFFI, converting via JSON strings — + +#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] +pub struct ExtensionConfig { + name: String, + instructions: Option, + tools: Vec, +} + +impl ExtensionConfig { + pub fn new(name: String, instructions: Option, tools: Vec) -> Self { + Self { + name, + instructions, + tools, + } + } + + /// Convert the tools to core tools with the extension name as a prefix + pub fn get_prefixed_tools(&self) -> Vec { + self.tools + .iter() + .map(|tool| { + let name = format!("{}__{}", self.name, tool.name); + tool.to_core_tool(Some(&name)) + }) + .collect() + } + + /// Get a map of prefixed tool names to their approval modes + pub fn get_prefixed_tool_configs(&self) -> HashMap { + self.tools + .iter() + .map(|tool| { + let name = format!("{}__{}", self.name, tool.name); + (name, tool.clone()) + }) + .collect() + } +} diff --git a/crates/goose-llm/src/types/core.rs b/crates/goose-llm/src/types/core.rs new file mode 100644 index 00000000..3e45d276 --- /dev/null +++ b/crates/goose-llm/src/types/core.rs @@ -0,0 +1,131 @@ +// This file defines core types that require serialization to +// construct payloads for LLM model providers and work with MCPs. + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Enum)] +#[serde(rename_all = "lowercase")] +pub enum Role { + User, + Assistant, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Enum)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum Content { + Text(TextContent), + Image(ImageContent), +} + +impl Content { + pub fn text>(text: S) -> Self { + Content::Text(TextContent { text: text.into() }) + } + + pub fn image, T: Into>(data: S, mime_type: T) -> Self { + Content::Image(ImageContent { + data: data.into(), + mime_type: mime_type.into(), + }) + } + + /// Get the text content if this is a TextContent variant + pub fn as_text(&self) -> Option<&str> { + match self { + Content::Text(text) => Some(&text.text), + _ => None, + } + } + + /// Get the image content if this is an ImageContent variant + pub fn as_image(&self) -> Option<(&str, &str)> { + match self { + Content::Image(image) => Some((&image.data, &image.mime_type)), + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Record)] +#[serde(rename_all = "camelCase")] +pub struct TextContent { + pub text: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Record)] +#[serde(rename_all = "camelCase")] +pub struct ImageContent { + pub data: String, + pub mime_type: String, +} + +/// A tool that can be used by a model. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Tool { + /// The name of the tool + pub name: String, + /// A description of what the tool does + pub description: String, + /// A JSON Schema object defining the expected parameters for the tool + pub input_schema: serde_json::Value, +} + +impl Tool { + /// Create a new tool with the given name and description + pub fn new(name: N, description: D, input_schema: serde_json::Value) -> Self + where + N: Into, + D: Into, + { + Tool { + name: name.into(), + description: description.into(), + input_schema, + } + } +} + +/// A tool call request that an extension can execute +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ToolCall { + /// The name of the tool to execute + pub name: String, + /// The parameters for the execution + pub arguments: serde_json::Value, + /// Whether the tool call needs approval before execution. Default is false. + pub needs_approval: bool, +} + +impl ToolCall { + /// Create a new ToolUse with the given name and parameters + pub fn new>(name: S, arguments: serde_json::Value) -> Self { + Self { + name: name.into(), + arguments, + needs_approval: false, + } + } + + /// Set needs_approval field + pub fn set_needs_approval(&mut self, flag: bool) { + self.needs_approval = flag; + } +} + +#[non_exhaustive] +#[derive(Error, Debug, Clone, Deserialize, Serialize, PartialEq, uniffi::Error)] +pub enum ToolError { + #[error("Invalid parameters: {0}")] + InvalidParameters(String), + #[error("Execution failed: {0}")] + ExecutionError(String), + #[error("Schema error: {0}")] + SchemaError(String), + #[error("Tool not found: {0}")] + NotFound(String), +} + +pub type ToolResult = std::result::Result; diff --git a/crates/goose-llm/src/types/json_value_ffi.rs b/crates/goose-llm/src/types/json_value_ffi.rs new file mode 100644 index 00000000..a2e44a34 --- /dev/null +++ b/crates/goose-llm/src/types/json_value_ffi.rs @@ -0,0 +1,18 @@ +use serde_json::Value; + +// `serde_json::Value` gets converted to a `String` to pass across the FFI. +// https://github.com/mozilla/uniffi-rs/blob/main/docs/manual/src/types/custom_types.md?plain=1 +// https://github.com/mozilla/uniffi-rs/blob/c7f6caa3d1bf20f934346cefd8e82b5093f0dc6f/examples/custom-types/src/lib.rs#L63-L69 + +uniffi::custom_type!(Value, String, { + // Remote is required since 'Value' is from a different crate + remote, + lower: |obj| { + serde_json::to_string(&obj).unwrap() + }, + try_lift: |val| { + Ok(serde_json::from_str(&val).unwrap() ) + }, +}); + +pub type JsonValueFfi = Value; diff --git a/crates/goose-llm/src/types/mod.rs b/crates/goose-llm/src/types/mod.rs new file mode 100644 index 00000000..a2c2f35c --- /dev/null +++ b/crates/goose-llm/src/types/mod.rs @@ -0,0 +1,3 @@ +pub mod completion; +pub mod core; +pub mod json_value_ffi; diff --git a/crates/goose-llm/tests/extract_session_name.rs b/crates/goose-llm/tests/extract_session_name.rs new file mode 100644 index 00000000..1a7d7e1e --- /dev/null +++ b/crates/goose-llm/tests/extract_session_name.rs @@ -0,0 +1,79 @@ +use anyhow::Result; +use dotenv::dotenv; +use goose_llm::extractors::generate_session_name; +use goose_llm::message::Message; +use goose_llm::providers::errors::ProviderError; + +fn should_run_test() -> Result<(), String> { + dotenv().ok(); + if std::env::var("DATABRICKS_HOST").is_err() { + return Err("Missing DATABRICKS_HOST".to_string()); + } + if std::env::var("DATABRICKS_TOKEN").is_err() { + return Err("Missing DATABRICKS_TOKEN".to_string()); + } + Ok(()) +} + +async fn _generate_session_name(messages: &[Message]) -> Result { + let provider_name = "databricks"; + let provider_config = serde_json::json!({ + "host": std::env::var("DATABRICKS_HOST").expect("Missing DATABRICKS_HOST"), + "token": std::env::var("DATABRICKS_TOKEN").expect("Missing DATABRICKS_TOKEN"), + }); + + generate_session_name(provider_name, provider_config, messages).await +} + +#[tokio::test] +async fn test_generate_session_name_success() { + if should_run_test().is_err() { + println!("Skipping..."); + return; + } + + // Build a few messages with at least two user messages + let messages = vec![ + Message::user().with_text("Hello, how are you?"), + Message::assistant().with_text("I’m fine, thanks!"), + Message::user().with_text("What’s the weather in New York tomorrow?"), + ]; + + let name = _generate_session_name(&messages) + .await + .expect("Failed to generate session name"); + + println!("Generated session name: {:?}", name); + + // Should be non-empty and at most 4 words + let name = name.trim(); + assert!(!name.is_empty(), "Name must not be empty"); + let word_count = name.split_whitespace().count(); + assert!( + word_count <= 4, + "Name must be 4 words or less, got {}: {}", + word_count, + name + ) +} + +#[tokio::test] +async fn test_generate_session_name_no_user() { + if should_run_test().is_err() { + println!("Skipping 'test_generate_session_name_no_user'. Databricks creds not set"); + return; + } + + // No user messages → expect ExecutionError + let messages = vec![ + Message::assistant().with_text("System starting…"), + Message::assistant().with_text("All systems go."), + ]; + + let err = _generate_session_name(&messages).await; + assert!( + matches!(err, Err(ProviderError::ExecutionError(_))), + "Expected ExecutionError when there are no user messages, got: {:?}", + err + ); +} diff --git a/crates/goose-llm/tests/extract_tooltip.rs b/crates/goose-llm/tests/extract_tooltip.rs new file mode 100644 index 00000000..f44a0d1a --- /dev/null +++ b/crates/goose-llm/tests/extract_tooltip.rs @@ -0,0 +1,88 @@ +use anyhow::Result; +use dotenv::dotenv; +use goose_llm::extractors::generate_tooltip; +use goose_llm::message::{Message, MessageContent, ToolRequest}; +use goose_llm::providers::errors::ProviderError; +use goose_llm::types::core::{Content, ToolCall}; +use serde_json::json; + +fn should_run_test() -> Result<(), String> { + dotenv().ok(); + if std::env::var("DATABRICKS_HOST").is_err() { + return Err("Missing DATABRICKS_HOST".to_string()); + } + if std::env::var("DATABRICKS_TOKEN").is_err() { + return Err("Missing DATABRICKS_TOKEN".to_string()); + } + Ok(()) +} + +async fn _generate_tooltip(messages: &[Message]) -> Result { + let provider_name = "databricks"; + let provider_config = serde_json::json!({ + "host": std::env::var("DATABRICKS_HOST").expect("Missing DATABRICKS_HOST"), + "token": std::env::var("DATABRICKS_TOKEN").expect("Missing DATABRICKS_TOKEN"), + }); + + generate_tooltip(provider_name, provider_config, messages).await +} + +#[tokio::test] +async fn test_generate_tooltip_simple() { + if should_run_test().is_err() { + println!("Skipping..."); + return; + } + + // Two plain-text messages + let messages = vec![ + Message::user().with_text("Hello, how are you?"), + Message::assistant().with_text("I'm fine, thanks! How can I help?"), + ]; + + let tooltip = _generate_tooltip(&messages) + .await + .expect("Failed to generate tooltip"); + println!("Generated tooltip: {:?}", tooltip); + + assert!(!tooltip.trim().is_empty(), "Tooltip must not be empty"); + assert!( + tooltip.len() < 100, + "Tooltip should be reasonably short (<100 chars)" + ); +} + +#[tokio::test] +async fn test_generate_tooltip_with_tools() { + if should_run_test().is_err() { + println!("Skipping..."); + return; + } + + // 1) Assistant message with a tool request + let mut tool_req_msg = Message::assistant(); + let req = ToolRequest { + id: "1".to_string(), + tool_call: Ok(ToolCall::new("get_time", json!({"timezone": "UTC"}))).into(), + }; + tool_req_msg.content.push(MessageContent::ToolReq(req)); + + // 2) User message with the tool response + let tool_resp_msg = Message::user().with_tool_response( + "1", + Ok(vec![Content::text("The current time is 12:00 UTC")]).into(), + ); + + let messages = vec![tool_req_msg, tool_resp_msg]; + + let tooltip = _generate_tooltip(&messages) + .await + .expect("Failed to generate tooltip"); + println!("Generated tooltip (tools): {:?}", tooltip); + + assert!(!tooltip.trim().is_empty(), "Tooltip must not be empty"); + assert!( + tooltip.len() < 100, + "Tooltip should be reasonably short (<100 chars)" + ); +} diff --git a/crates/goose-llm/tests/providers_complete.rs b/crates/goose-llm/tests/providers_complete.rs new file mode 100644 index 00000000..74a22064 --- /dev/null +++ b/crates/goose-llm/tests/providers_complete.rs @@ -0,0 +1,380 @@ +use anyhow::Result; +use dotenv::dotenv; +use goose_llm::message::{Message, MessageContent}; +use goose_llm::providers::base::Provider; +use goose_llm::providers::errors::ProviderError; +use goose_llm::providers::{databricks, openai}; +use goose_llm::types::core::{Content, Tool}; +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::Mutex; + +#[derive(Debug, Clone, Copy)] +enum TestStatus { + Passed, + Skipped, + Failed, +} + +impl std::fmt::Display for TestStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TestStatus::Passed => write!(f, "✅"), + TestStatus::Skipped => write!(f, "⏭️"), + TestStatus::Failed => write!(f, "❌"), + } + } +} + +struct TestReport { + results: Mutex>, +} + +impl TestReport { + fn new() -> Arc { + Arc::new(Self { + results: Mutex::new(HashMap::new()), + }) + } + + fn record_status(&self, provider: &str, status: TestStatus) { + let mut results = self.results.lock().unwrap(); + results.insert(provider.to_string(), status); + } + + fn record_pass(&self, provider: &str) { + self.record_status(provider, TestStatus::Passed); + } + + fn record_skip(&self, provider: &str) { + self.record_status(provider, TestStatus::Skipped); + } + + fn record_fail(&self, provider: &str) { + self.record_status(provider, TestStatus::Failed); + } + + fn print_summary(&self) { + println!("\n============== Providers =============="); + let results = self.results.lock().unwrap(); + let mut providers: Vec<_> = results.iter().collect(); + providers.sort_by(|a, b| a.0.cmp(b.0)); + + for (provider, status) in providers { + println!("{} {}", status, provider); + } + println!("=======================================\n"); + } +} + +lazy_static::lazy_static! { + static ref TEST_REPORT: Arc = TestReport::new(); + static ref ENV_LOCK: Mutex<()> = Mutex::new(()); +} + +/// Generic test harness for any Provider implementation +struct ProviderTester { + provider: Arc, + name: String, +} + +impl ProviderTester { + fn new(provider: T, name: String) -> Self { + Self { + provider: Arc::new(provider), + name, + } + } + + async fn test_basic_response(&self) -> Result<()> { + let message = Message::user().with_text("Just say hello!"); + + let response = self + .provider + .complete("You are a helpful assistant.", &[message], &[]) + .await?; + + // For a basic response, we expect a single text response + assert_eq!( + response.message.content.len(), + 1, + "Expected single content item in response" + ); + + // Verify we got a text response + assert!( + matches!(response.message.content[0], MessageContent::Text(_)), + "Expected text response" + ); + + Ok(()) + } + + async fn test_tool_usage(&self) -> Result<()> { + let weather_tool = Tool::new( + "get_weather", + "Get the weather for a location", + serde_json::json!({ + "type": "object", + "required": ["location"], + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + } + } + }), + ); + + let message = Message::user().with_text("What's the weather like in San Francisco?"); + + let response1 = self + .provider + .complete( + "You are a helpful weather assistant.", + &[message.clone()], + &[weather_tool.clone()], + ) + .await?; + + println!("=== {}::reponse1 ===", self.name); + dbg!(&response1); + println!("==================="); + + // Verify we got a tool request + assert!( + response1 + .message + .content + .iter() + .any(|content| matches!(content, MessageContent::ToolReq(_))), + "Expected tool request in response" + ); + + let id = &response1 + .message + .content + .iter() + .filter_map(|message| message.as_tool_request()) + .last() + .expect("got tool request") + .id; + + let weather = Message::user().with_tool_response( + id, + Ok(vec![Content::text( + " + 50°F°C + Precipitation: 0% + Humidity: 84% + Wind: 2 mph + Weather + Saturday 9:00 PM + Clear", + )]) + .into(), + ); + + // Verify we construct a valid payload including the request/response pair for the next inference + let response2 = self + .provider + .complete( + "You are a helpful weather assistant.", + &[message, response1.message, weather], + &[weather_tool], + ) + .await?; + + println!("=== {}::reponse2 ===", self.name); + dbg!(&response2); + println!("==================="); + + assert!( + response2 + .message + .content + .iter() + .any(|content| matches!(content, MessageContent::Text(_))), + "Expected text for final response" + ); + + Ok(()) + } + + async fn test_context_length_exceeded_error(&self) -> Result<()> { + // Google Gemini has a really long context window + let large_message_content = if self.name.to_lowercase() == "google" { + "hello ".repeat(1_300_000) + } else { + "hello ".repeat(300_000) + }; + + let messages = vec![ + Message::user().with_text("hi there. what is 2 + 2?"), + Message::assistant().with_text("hey! I think it's 4."), + Message::user().with_text(&large_message_content), + Message::assistant().with_text("heyy!!"), + // Messages before this mark should be truncated + Message::user().with_text("what's the meaning of life?"), + Message::assistant().with_text("the meaning of life is 42"), + Message::user().with_text( + "did I ask you what's 2+2 in this message history? just respond with 'yes' or 'no'", + ), + ]; + + // Test that we get ProviderError::ContextLengthExceeded when the context window is exceeded + let result = self + .provider + .complete("You are a helpful assistant.", &messages, &[]) + .await; + + // Print some debug info + println!("=== {}::context_length_exceeded_error ===", self.name); + dbg!(&result); + println!("==================="); + + // Ollama truncates by default even when the context window is exceeded + if self.name.to_lowercase() == "ollama" { + assert!( + result.is_ok(), + "Expected to succeed because of default truncation" + ); + return Ok(()); + } + + assert!( + result.is_err(), + "Expected error when context window is exceeded" + ); + assert!( + matches!(result.unwrap_err(), ProviderError::ContextLengthExceeded(_)), + "Expected error to be ContextLengthExceeded" + ); + + Ok(()) + } + + /// Run all provider tests + async fn run_test_suite(&self) -> Result<()> { + self.test_basic_response().await?; + self.test_tool_usage().await?; + self.test_context_length_exceeded_error().await?; + Ok(()) + } +} + +fn load_env() { + if let Ok(path) = dotenv() { + println!("Loaded environment from {:?}", path); + } +} + +/// Helper function to run a provider test with proper error handling and reporting +async fn test_provider( + name: &str, + required_vars: &[&str], + env_modifications: Option>>, + provider_fn: F, +) -> Result<()> +where + F: FnOnce() -> T, + T: Provider + Send + Sync + 'static, +{ + // We start off as failed, so that if the process panics it is seen as a failure + TEST_REPORT.record_fail(name); + + // Take exclusive access to environment modifications + let lock = ENV_LOCK.lock().unwrap(); + + load_env(); + + // Save current environment state for required vars and modified vars + let mut original_env = HashMap::new(); + for &var in required_vars { + if let Ok(val) = std::env::var(var) { + original_env.insert(var, val); + } + } + if let Some(mods) = &env_modifications { + for &var in mods.keys() { + if let Ok(val) = std::env::var(var) { + original_env.insert(var, val); + } + } + } + + // Apply any environment modifications + if let Some(mods) = &env_modifications { + for (&var, value) in mods.iter() { + match value { + Some(val) => std::env::set_var(var, val), + None => std::env::remove_var(var), + } + } + } + + // Setup the provider + let missing_vars = required_vars.iter().any(|var| std::env::var(var).is_err()); + if missing_vars { + println!("Skipping {} tests - credentials not configured", name); + TEST_REPORT.record_skip(name); + return Ok(()); + } + + let provider = provider_fn(); + + // Restore original environment + for (&var, value) in original_env.iter() { + std::env::set_var(var, value); + } + if let Some(mods) = env_modifications { + for &var in mods.keys() { + if !original_env.contains_key(var) { + std::env::remove_var(var); + } + } + } + + std::mem::drop(lock); + + let tester = ProviderTester::new(provider, name.to_string()); + match tester.run_test_suite().await { + Ok(_) => { + TEST_REPORT.record_pass(name); + Ok(()) + } + Err(e) => { + println!("{} test failed: {}", name, e); + TEST_REPORT.record_fail(name); + Err(e) + } + } +} + +#[tokio::test] +async fn openai_complete() -> Result<()> { + test_provider( + "OpenAI", + &["OPENAI_API_KEY"], + None, + openai::OpenAiProvider::default, + ) + .await +} + +#[tokio::test] +async fn databricks_complete() -> Result<()> { + test_provider( + "Databricks", + &["DATABRICKS_HOST", "DATABRICKS_TOKEN"], + None, + databricks::DatabricksProvider::default, + ) + .await +} + +// Print the final test report +#[ctor::dtor] +fn print_test_report() { + TEST_REPORT.print_summary(); +} diff --git a/crates/goose-llm/tests/providers_extract.rs b/crates/goose-llm/tests/providers_extract.rs new file mode 100644 index 00000000..83ab9f50 --- /dev/null +++ b/crates/goose-llm/tests/providers_extract.rs @@ -0,0 +1,195 @@ +// tests/providers_extract.rs + +use anyhow::Result; +use dotenv::dotenv; +use goose_llm::message::Message; +use goose_llm::providers::base::Provider; +use goose_llm::providers::{databricks::DatabricksProvider, openai::OpenAiProvider}; +use goose_llm::ModelConfig; +use serde_json::{json, Value}; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Copy, Clone)] +enum ProviderType { + OpenAi, + Databricks, +} + +impl ProviderType { + fn required_env(&self) -> &'static [&'static str] { + match self { + ProviderType::OpenAi => &["OPENAI_API_KEY"], + ProviderType::Databricks => &["DATABRICKS_HOST", "DATABRICKS_TOKEN"], + } + } + + fn create_provider(&self, cfg: ModelConfig) -> Result> { + Ok(match self { + ProviderType::OpenAi => Arc::new(OpenAiProvider::from_env(cfg)), + ProviderType::Databricks => Arc::new(DatabricksProvider::from_env(cfg)), + }) + } +} + +fn check_required_env_vars(required: &[&str]) -> bool { + let missing: Vec<_> = required + .iter() + .filter(|&&v| std::env::var(v).is_err()) + .cloned() + .collect(); + if !missing.is_empty() { + println!("Skipping test; missing env vars: {:?}", missing); + false + } else { + true + } +} + +// --- Shared inputs for "paper" task --- +const PAPER_SYSTEM: &str = + "You are an expert at structured data extraction. Extract the metadata of a research paper into JSON."; +const PAPER_TEXT: &str = + "Application of Quantum Algorithms in Interstellar Navigation: A New Frontier \ + by Dr. Stella Voyager, Dr. Nova Star, Dr. Lyra Hunter. Abstract: This paper \ + investigates the utilization of quantum algorithms to improve interstellar \ + navigation systems. Keywords: Quantum algorithms, interstellar navigation, \ + space-time anomalies, quantum superposition, quantum entanglement, space travel."; + +fn paper_schema() -> Value { + json!({ + "type": "object", + "properties": { + "title": { "type": "string" }, + "authors": { "type": "array", "items": { "type": "string" } }, + "abstract": { "type": "string" }, + "keywords": { "type": "array", "items": { "type": "string" } } + }, + "required": ["title","authors","abstract","keywords"], + "additionalProperties": false + }) +} + +// --- Shared inputs for "UI" task --- +const UI_SYSTEM: &str = "You are a UI generator AI. Convert the user input into a JSON-driven UI."; +const UI_TEXT: &str = "Make a User Profile Form"; + +fn ui_schema() -> Value { + json!({ + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["div","button","header","section","field","form"] + }, + "label": { "type": "string" }, + "children": { + "type": "array", + "items": { "$ref": "#" } + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "value": { "type": "string" } + }, + "required": ["name","value"], + "additionalProperties": false + } + } + }, + "required": ["type","label","children","attributes"], + "additionalProperties": false + }) +} + +/// Generic runner for any extract task +async fn run_extract_test( + provider_type: ProviderType, + model: &str, + system: &'static str, + user_text: &'static str, + schema: Value, + validate: F, +) -> Result<()> +where + F: Fn(&Value) -> bool, +{ + dotenv().ok(); + if !check_required_env_vars(provider_type.required_env()) { + return Ok(()); + } + + let cfg = ModelConfig::new(model.to_string()).with_temperature(Some(0.0)); + let provider = provider_type.create_provider(cfg)?; + + let msg = Message::user().with_text(user_text); + let resp = provider.extract(system, &[msg], &schema).await?; + + println!("[{:?}] extract => {}", provider_type, resp.data); + + assert!( + validate(&resp.data), + "{:?} failed validation on {}", + provider_type, + resp.data + ); + Ok(()) +} + +/// Helper for the "paper" task +async fn run_extract_paper_test(provider: ProviderType, model: &str) -> Result<()> { + run_extract_test( + provider, + model, + PAPER_SYSTEM, + PAPER_TEXT, + paper_schema(), + |v| { + v.as_object() + .map(|o| { + ["title", "authors", "abstract", "keywords"] + .iter() + .all(|k| o.contains_key(*k)) + }) + .unwrap_or(false) + }, + ) + .await +} + +/// Helper for the "UI" task +async fn run_extract_ui_test(provider: ProviderType, model: &str) -> Result<()> { + run_extract_test(provider, model, UI_SYSTEM, UI_TEXT, ui_schema(), |v| { + v.as_object() + .and_then(|o| o.get("type").and_then(Value::as_str)) + == Some("form") + }) + .await +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn openai_extract_paper() -> Result<()> { + run_extract_paper_test(ProviderType::OpenAi, "gpt-4o").await + } + + #[tokio::test] + async fn openai_extract_ui() -> Result<()> { + run_extract_ui_test(ProviderType::OpenAi, "gpt-4o").await + } + + #[tokio::test] + async fn databricks_extract_paper() -> Result<()> { + run_extract_paper_test(ProviderType::Databricks, "goose-gpt-4-1").await + } + + #[tokio::test] + async fn databricks_extract_ui() -> Result<()> { + run_extract_ui_test(ProviderType::Databricks, "goose-gpt-4-1").await + } +} diff --git a/crates/goose-llm/uniffi-bindgen.rs b/crates/goose-llm/uniffi-bindgen.rs new file mode 100644 index 00000000..f6cff6cf --- /dev/null +++ b/crates/goose-llm/uniffi-bindgen.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::uniffi_bindgen_main() +} diff --git a/crates/goose-mcp/Cargo.toml b/crates/goose-mcp/Cargo.toml index eb3e961e..8bf405e7 100644 --- a/crates/goose-mcp/Cargo.toml +++ b/crates/goose-mcp/Cargo.toml @@ -35,6 +35,7 @@ chrono = { version = "0.4.38", features = ["serde"] } etcetera = "0.8.0" tempfile = "3.8" include_dir = "0.7.4" +google-apis-common = "7.0.0" google-drive3 = "6.0.0" google-sheets4 = "6.0.0" google-docs1 = "6.0.0" @@ -47,9 +48,21 @@ lopdf = "0.35.0" docx-rs = "0.4.7" image = "0.24.9" umya-spreadsheet = "2.2.3" -keyring = { version = "3.6.1", features = ["apple-native", "windows-native", "sync-secret-service", "vendored"] } +keyring = { version = "3.6.1", features = [ + "apple-native", + "windows-native", + "sync-secret-service", + "vendored", +] } oauth2 = { version = "5.0.0", features = ["reqwest"] } +utoipa = { version = "4.1", optional = true } +hyper = "1" +serde_with = "3" + [dev-dependencies] serial_test = "3.0.0" sysinfo = "0.32.1" + +[features] +utoipa = ["dep:utoipa"] diff --git a/crates/goose-mcp/src/computercontroller/mod.rs b/crates/goose-mcp/src/computercontroller/mod.rs index 6b2b2038..a2751852 100644 --- a/crates/goose-mcp/src/computercontroller/mod.rs +++ b/crates/goose-mcp/src/computercontroller/mod.rs @@ -8,6 +8,9 @@ use std::{ }; use tokio::process::Command; +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; + use mcp_core::{ handler::{PromptError, ResourceError, ToolError}, prompt::Prompt, @@ -743,6 +746,23 @@ impl ComputerControllerRouter { ToolError::ExecutionError(format!("Failed to write script: {}", e)) })?; + // Set execute permissions on Unix systems + #[cfg(unix)] + { + let mut perms = fs::metadata(&script_path) + .map_err(|e| { + ToolError::ExecutionError(format!("Failed to get file metadata: {}", e)) + })? + .permissions(); + perms.set_mode(0o755); // rwxr-xr-x + fs::set_permissions(&script_path, perms).map_err(|e| { + ToolError::ExecutionError(format!( + "Failed to set execute permissions: {}", + e + )) + })?; + } + script_path.display().to_string() } "ruby" => { diff --git a/crates/goose-mcp/src/computercontroller/platform/linux.rs b/crates/goose-mcp/src/computercontroller/platform/linux.rs index b020bbb6..d0e78c04 100644 --- a/crates/goose-mcp/src/computercontroller/platform/linux.rs +++ b/crates/goose-mcp/src/computercontroller/platform/linux.rs @@ -65,10 +65,7 @@ impl LinuxAutomation { DisplayServer::X11 => self.check_x11_dependencies()?, DisplayServer::Wayland => self.check_wayland_dependencies()?, DisplayServer::Unknown => { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Unable to detect display server", - )); + return Err(std::io::Error::other("Unable to detect display server")); } } @@ -106,10 +103,7 @@ impl LinuxAutomation { match self.display_server { DisplayServer::X11 => self.execute_x11_command(cmd), DisplayServer::Wayland => self.execute_wayland_command(cmd), - DisplayServer::Unknown => Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Unknown display server", - )), + DisplayServer::Unknown => Err(std::io::Error::other("Unknown display server")), } } @@ -236,8 +230,7 @@ impl SystemAutomation for LinuxAutomation { if output.status.success() { Ok(String::from_utf8_lossy(&output.stdout).into_owned()) } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(std::io::Error::other( String::from_utf8_lossy(&output.stderr).into_owned(), )) } diff --git a/crates/goose-mcp/src/google_drive/google_labels.rs b/crates/goose-mcp/src/google_drive/google_labels.rs new file mode 100644 index 00000000..a2272ce4 --- /dev/null +++ b/crates/goose-mcp/src/google_drive/google_labels.rs @@ -0,0 +1,478 @@ +#![allow(clippy::ptr_arg, dead_code, clippy::enum_variant_names)] + +use std::collections::{BTreeSet, HashMap}; + +use google_apis_common as common; +use tokio::time::sleep; + +/// A scope is needed when requesting an +/// [authorization token](https://developers.google.com/workspace/drive/labels/guides/authorize). +#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Debug, Clone, Copy)] +pub enum Scope { + /// View, use, and manage Drive labels. + DriveLabels, + + /// View and use Drive labels. + DriveLabelsReadonly, + + /// View, edit, create, and delete all Drive labels in your organization, + /// and view your organization's label-related administration policies. + DriveLabelsAdmin, + + /// View all Drive labels and label-related administration policies in your + /// organization. + DriveLabelsAdminReadonly, +} + +impl AsRef for Scope { + fn as_ref(&self) -> &str { + match *self { + Scope::DriveLabels => "https://www.googleapis.com/auth/drive.labels", + Scope::DriveLabelsReadonly => "https://www.googleapis.com/auth/drive.labels.readonly", + Scope::DriveLabelsAdmin => "https://www.googleapis.com/auth/drive.admin.labels", + Scope::DriveLabelsAdminReadonly => { + "https://www.googleapis.com/auth/drive.admin.labels.readonly" + } + } + } +} + +#[allow(clippy::derivable_impls)] +impl Default for Scope { + fn default() -> Scope { + Scope::DriveLabelsReadonly + } +} + +#[derive(Clone)] +pub struct DriveLabelsHub { + pub client: common::Client, + pub auth: Box, + _user_agent: String, + _base_url: String, +} + +impl common::Hub for DriveLabelsHub {} + +impl<'a, C> DriveLabelsHub { + pub fn new( + client: common::Client, + auth: A, + ) -> DriveLabelsHub { + DriveLabelsHub { + client, + auth: Box::new(auth), + _user_agent: "google-api-rust-client/6.0.0".to_string(), + _base_url: "https://drivelabels.googleapis.com/".to_string(), + } + } + + pub fn labels(&'a self) -> LabelMethods<'a, C> { + LabelMethods { hub: self } + } + + /// Set the user-agent header field to use in all requests to the server. + /// It defaults to `google-api-rust-client/6.0.0`. + /// + /// Returns the previously set user-agent. + pub fn user_agent(&mut self, agent_name: String) -> String { + std::mem::replace(&mut self._user_agent, agent_name) + } + + /// Set the base url to use in all requests to the server. + /// It defaults to `https://www.googleapis.com/drive/v3/`. + /// + /// Returns the previously set base url. + pub fn base_url(&mut self, new_base_url: String) -> String { + std::mem::replace(&mut self._base_url, new_base_url) + } +} + +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde_with::serde_as] +#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct Label { + #[serde(rename = "name")] + pub name: Option, + #[serde(rename = "id")] + pub id: Option, + #[serde(rename = "revisionId")] + pub revision_id: Option, + #[serde(rename = "labelType")] + pub label_type: Option, + #[serde(rename = "creator")] + pub creator: Option, + #[serde(rename = "createTime")] + pub create_time: Option, + #[serde(rename = "revisionCreator")] + pub revision_creator: Option, + #[serde(rename = "revisionCreateTime")] + pub revision_create_time: Option, + #[serde(rename = "publisher")] + pub publisher: Option, + #[serde(rename = "publishTime")] + pub publish_time: Option, + #[serde(rename = "disabler")] + pub disabler: Option, + #[serde(rename = "disableTime")] + pub disable_time: Option, + #[serde(rename = "customer")] + pub customer: Option, + pub properties: Option, + pub fields: Option>, + // We ignore the remaining fields. +} + +impl common::Part for Label {} + +impl common::ResponseResult for Label {} + +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde_with::serde_as] +#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct LabelProperty { + pub title: Option, + pub description: Option, +} + +impl common::Part for LabelProperty {} + +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde_with::serde_as] +#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct Field { + id: Option, + #[serde(rename = "queryKey")] + query_key: Option, + properties: Option, + #[serde(rename = "selectionOptions")] + selection_options: Option, +} + +impl common::Part for Field {} + +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde_with::serde_as] +#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct FieldProperty { + #[serde(rename = "displayName")] + pub display_name: Option, + pub required: Option, +} + +impl common::Part for FieldProperty {} + +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde_with::serde_as] +#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct SelectionOption { + #[serde(rename = "listOptions")] + pub list_options: Option, + pub choices: Option>, +} + +impl common::Part for SelectionOption {} + +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde_with::serde_as] +#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct Choice { + id: Option, + properties: Option, + // We ignore the remaining fields. +} + +impl common::Part for Choice {} + +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde_with::serde_as] +#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct ChoiceProperties { + #[serde(rename = "displayName")] + display_name: Option, + description: Option, +} + +impl common::Part for ChoiceProperties {} + +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde_with::serde_as] +#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct LabelList { + pub labels: Option>, + #[serde(rename = "nextPageToken")] + pub next_page_token: Option, +} + +impl common::ResponseResult for LabelList {} + +/// Information about a Drive user. +/// +/// This type is not used in any activity, and only used as *part* of another schema. +/// +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde_with::serde_as] +#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct User { + /// Output only. A plain text displayable name for this user. + #[serde(rename = "displayName")] + pub display_name: Option, + /// Output only. The email address of the user. This may not be present in certain contexts if the user has not made their email address visible to the requester. + #[serde(rename = "emailAddress")] + pub email_address: Option, + /// Output only. Identifies what kind of resource this is. Value: the fixed string `"drive#user"`. + pub kind: Option, + /// Output only. Whether this user is the requesting user. + pub me: Option, + /// Output only. The user's ID as visible in Permission resources. + #[serde(rename = "permissionId")] + pub permission_id: Option, + /// Output only. A link to the user's profile photo, if available. + #[serde(rename = "photoLink")] + pub photo_link: Option, +} + +impl common::Part for User {} + +pub struct LabelMethods<'a, C> +where + C: 'a, +{ + hub: &'a DriveLabelsHub, +} + +impl common::MethodsBuilder for LabelMethods<'_, C> {} + +impl<'a, C> LabelMethods<'a, C> { + /// Create a builder to help you perform the following tasks: + /// + /// List labels + pub fn list(&self) -> LabelListCall<'a, C> { + LabelListCall { + hub: self.hub, + _delegate: Default::default(), + _additional_params: Default::default(), + _scopes: Default::default(), + } + } +} + +/// Lists the workspace's labels. +pub struct LabelListCall<'a, C> +where + C: 'a, +{ + hub: &'a DriveLabelsHub, + _delegate: Option<&'a mut dyn common::Delegate>, + _additional_params: HashMap, + _scopes: BTreeSet, +} + +impl common::CallBuilder for LabelListCall<'_, C> {} + +impl<'a, C> LabelListCall<'a, C> +where + C: common::Connector, +{ + /// Perform the operation you have built so far. + pub async fn doit(mut self) -> common::Result<(common::Response, LabelList)> { + use common::url::Params; + use hyper::header::{AUTHORIZATION, CONTENT_LENGTH, USER_AGENT}; + + let mut dd = common::DefaultDelegate; + let dlg: &mut dyn common::Delegate = self._delegate.unwrap_or(&mut dd); + dlg.begin(common::MethodInfo { + id: "drivelabels.labels.list", + http_method: hyper::Method::GET, + }); + + for &field in ["alt"].iter() { + if self._additional_params.contains_key(field) { + dlg.finished(false); + return Err(common::Error::FieldClash(field)); + } + } + + // TODO: We don't handle any of the query params. + let mut params = Params::with_capacity(2 + self._additional_params.len()); + + params.extend(self._additional_params.iter()); + + params.push("alt", "json"); + let url = self.hub._base_url.clone() + "v2/labels"; + + if self._scopes.is_empty() { + self._scopes + .insert(Scope::DriveLabelsReadonly.as_ref().to_string()); + } + + let url = params.parse_with_url(&url); + + loop { + let token = match self + .hub + .auth + .get_token(&self._scopes.iter().map(String::as_str).collect::>()[..]) + .await + { + Ok(token) => token, + Err(e) => match dlg.token(e) { + Ok(token) => token, + Err(e) => { + dlg.finished(false); + return Err(common::Error::MissingToken(e)); + } + }, + }; + let req_result = { + let client = &self.hub.client; + dlg.pre_request(); + let mut req_builder = hyper::Request::builder() + .method(hyper::Method::GET) + .uri(url.as_str()) + .header(USER_AGENT, self.hub._user_agent.clone()); + + if let Some(token) = token.as_ref() { + req_builder = req_builder.header(AUTHORIZATION, format!("Bearer {}", token)); + } + + let request = req_builder + .header(CONTENT_LENGTH, 0_u64) + .body(common::to_body::(None)); + client.request(request.unwrap()).await + }; + + match req_result { + Err(err) => { + if let common::Retry::After(d) = dlg.http_error(&err) { + sleep(d).await; + continue; + } + dlg.finished(false); + return Err(common::Error::HttpError(err)); + } + Ok(res) => { + let (parts, body) = res.into_parts(); + let body = common::Body::new(body); + if !parts.status.is_success() { + let bytes = common::to_bytes(body).await.unwrap_or_default(); + let error = serde_json::from_str(&common::to_string(&bytes)); + let response = common::to_response(parts, bytes.into()); + + if let common::Retry::After(d) = + dlg.http_failure(&response, error.as_ref().ok()) + { + sleep(d).await; + continue; + } + + dlg.finished(false); + + return Err(match error { + Ok(value) => common::Error::BadRequest(value), + _ => common::Error::Failure(response), + }); + } + let response = { + let bytes = common::to_bytes(body).await.unwrap_or_default(); + let encoded = common::to_string(&bytes); + match serde_json::from_str(&encoded) { + Ok(decoded) => (common::to_response(parts, bytes.into()), decoded), + Err(error) => { + dlg.response_json_decode_error(&encoded, &error); + return Err(common::Error::JsonDecodeError( + encoded.to_string(), + error, + )); + } + } + }; + + dlg.finished(true); + return Ok(response); + } + } + } + } + + /// The delegate implementation is consulted whenever there is an intermediate result, or if something goes wrong + /// while executing the actual API request. + /// + /// ````text + /// It should be used to handle progress information, and to implement a certain level of resilience. + /// ```` + /// + /// Sets the *delegate* property to the given value. + pub fn delegate(mut self, new_value: &'a mut dyn common::Delegate) -> LabelListCall<'a, C> { + self._delegate = Some(new_value); + self + } + + /// Set any additional parameter of the query string used in the request. + /// It should be used to set parameters which are not yet available through their own + /// setters. + /// + /// Please note that this method must not be used to set any of the known parameters + /// which have their own setter method. If done anyway, the request will fail. + /// + /// # Additional Parameters + /// + /// * *$.xgafv* (query-string) - V1 error format. + /// * *access_token* (query-string) - OAuth access token. + /// * *alt* (query-string) - Data format for response. + /// * *callback* (query-string) - JSONP + /// * *fields* (query-string) - Selector specifying which fields to include in a partial response. + /// * *key* (query-string) - API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token. + /// * *oauth_token* (query-string) - OAuth 2.0 token for the current user. + /// * *prettyPrint* (query-boolean) - Returns response with indentations and line breaks. + /// * *quotaUser* (query-string) - Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. + /// * *uploadType* (query-string) - Legacy upload protocol for media (e.g. "media", "multipart"). + /// * *upload_protocol* (query-string) - Upload protocol for media (e.g. "raw", "multipart"). + pub fn param(mut self, name: T, value: T) -> LabelListCall<'a, C> + where + T: AsRef, + { + self._additional_params + .insert(name.as_ref().to_string(), value.as_ref().to_string()); + self + } + + /// Identifies the authorization scope for the method you are building. + /// + /// Use this method to actively specify which scope should be used, instead of the default [`Scope`] variant + /// [`Scope::DriveLabelsReadonly`]. + /// + /// The `scope` will be added to a set of scopes. This is important as one can maintain access + /// tokens for more than one scope. + /// + /// Usually there is more than one suitable scope to authorize an operation, some of which may + /// encompass more rights than others. For example, for listing resources, a *read-only* scope will be + /// sufficient, a read-write scope will do as well. + pub fn add_scope(mut self, scope: St) -> LabelListCall<'a, C> + where + St: AsRef, + { + self._scopes.insert(String::from(scope.as_ref())); + self + } + /// Identifies the authorization scope(s) for the method you are building. + /// + /// See [`Self::add_scope()`] for details. + pub fn add_scopes(mut self, scopes: I) -> LabelListCall<'a, C> + where + I: IntoIterator, + St: AsRef, + { + self._scopes + .extend(scopes.into_iter().map(|s| String::from(s.as_ref()))); + self + } + + /// Removes all scopes, and no default scope will be used either. + /// In this case, you have to specify your API-key using the `key` parameter (see [`Self::param()`] + /// for details). + pub fn clear_scopes(mut self) -> LabelListCall<'a, C> { + self._scopes.clear(); + self + } +} diff --git a/crates/goose-mcp/src/google_drive/mod.rs b/crates/goose-mcp/src/google_drive/mod.rs index 0bd51724..710f42ae 100644 --- a/crates/goose-mcp/src/google_drive/mod.rs +++ b/crates/goose-mcp/src/google_drive/mod.rs @@ -1,8 +1,10 @@ +mod google_labels; mod oauth_pkce; pub mod storage; use anyhow::{Context, Error}; use base64::Engine; +use chrono::NaiveDate; use indoc::indoc; use lazy_static::lazy_static; use mcp_core::tool::ToolAnnotations; @@ -28,11 +30,15 @@ use google_docs1::{self, Docs}; use google_drive3::common::ReadSeek; use google_drive3::{ self, - api::{Comment, File, FileShortcutDetails, Permission, Reply, Scope}, + api::{ + Comment, File, FileShortcutDetails, LabelFieldModification, LabelModification, + ModifyLabelsRequest, Permission, Reply, Scope, + }, hyper_rustls::{self, HttpsConnector}, hyper_util::{self, client::legacy::connect::HttpConnector}, DriveHub, }; +use google_labels::DriveLabelsHub; use google_sheets4::{self, Sheets}; use http_body_util::BodyExt; @@ -80,6 +86,7 @@ pub struct GoogleDriveRouter { tools: Vec, instructions: String, drive: DriveHub>, + drive_labels: DriveLabelsHub>, sheets: Sheets>, docs: Docs>, credentials_manager: Arc, @@ -88,6 +95,7 @@ pub struct GoogleDriveRouter { impl GoogleDriveRouter { async fn google_auth() -> ( DriveHub>, + DriveLabelsHub>, Sheets>, Docs>, Arc, @@ -162,7 +170,7 @@ impl GoogleDriveRouter { // Read the OAuth credentials from the keyfile match fs::read_to_string(keyfile_path) { Ok(_) => { - // Create the PKCE OAuth2 clien + // Create the PKCE OAuth2 client let auth = PkceOAuth2Client::new(keyfile_path, credentials_manager.clone()) .expect("Failed to create OAuth2 client"); @@ -180,11 +188,18 @@ impl GoogleDriveRouter { ); let drive_hub = DriveHub::new(client.clone(), auth.clone()); + let drive_labels_hub = DriveLabelsHub::new(client.clone(), auth.clone()); let sheets_hub = Sheets::new(client.clone(), auth.clone()); let docs_hub = Docs::new(client, auth); // Create and return the DriveHub, Sheets and our PKCE OAuth2 client - (drive_hub, sheets_hub, docs_hub, credentials_manager) + ( + drive_hub, + drive_labels_hub, + sheets_hub, + docs_hub, + credentials_manager, + ) } Err(e) => { tracing::error!( @@ -199,24 +214,28 @@ impl GoogleDriveRouter { pub async fn new() -> Self { // handle auth - let (drive, sheets, docs, credentials_manager) = Self::google_auth().await; + let (drive, drive_labels, sheets, docs, credentials_manager) = Self::google_auth().await; let search_tool = Tool::new( "search".to_string(), indoc! {r#" - Search for files in google drive by name, given an input search query. At least one of ('name', 'mimeType', or 'parent') are required. + List or search for files or labels in google drive by name, given an input search query. At least one of ('name', 'mimeType', or 'parent') are required for file searches. "#} .to_string(), json!({ "type": "object", "properties": { + "driveType": { + "type": "string", + "description": "Required type of object to list or search (file, label)." + }, "name": { "type": "string", "description": "String to search for in the file's name.", }, "mimeType": { "type": "string", - "description": "MIME type to constrain the search to.", + "description": "Use when searching for a file to constrain the results to just this MIME type.", }, "parent": { "type": "string", @@ -233,8 +252,13 @@ impl GoogleDriveRouter { "pageSize": { "type": "number", "description": "How many items to return from the search query, default 10, max 100", + }, + "includeLabels": { + "type": "boolean", + "description": "When searching or listing files, also get any applied labels.", } }, + "required": ["driveType"], }), Some(ToolAnnotations { title: Some("Search GDrive".to_string()), @@ -285,55 +309,10 @@ impl GoogleDriveRouter { }), ); - let upload_tool = Tool::new( - "upload".to_string(), - indoc! {r#" - Upload a file to Google Drive. - "#} - .to_string(), - json!({ - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The desired filename to use for the uploaded file.", - }, - "mimeType": { - "type": "string", - "description": "The MIME type of the file.", - }, - "body": { - "type": "string", - "description": "Plain text body of the file to upload. Mutually exclusive with path.", - }, - "path": { - "type": "string", - "description": "Path to the file to upload. Mutually exclusive with body.", - }, - "parentId": { - "type": "string", - "description": "ID of the parent folder in which to create the file. (default: creates files in the root of 'My Drive')", - }, - "allowSharedDrives": { - "type": "boolean", - "description": "Whether to allow access to shared drives or just your personal drive (default: false)", - } - }, - "required": ["name", "mimeType"], - }), - Some(ToolAnnotations { - title: Some("Upload file to GDrive".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); - let create_file_tool = Tool::new( "create_file".to_string(), indoc! {r#" - Create a Google file (Document, Spreadsheet, Slides, folder, or shortcut) in Google Drive. + Create a new file, including Document, Spreadsheet, Slides, folder, or shortcut, in Google Drive. "#} .to_string(), json!({ @@ -343,10 +322,9 @@ impl GoogleDriveRouter { "type": "string", "description": "Name of the file to create", }, - "fileType": { + "mimeType": { "type": "string", - "enum": ["document", "spreadsheet", "slides", "folder", "shortcut"], - "description": "Type of Google file to create (document, spreadsheet, slides, folder, or shortcut)", + "description": "The MIME type of the file.", }, "body": { "type": "string", @@ -369,7 +347,7 @@ impl GoogleDriveRouter { "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } }, - "required": ["name", "fileType"], + "required": ["name", "mimeType"], }), Some(ToolAnnotations { title: Some("Create new file in GDrive".to_string()), @@ -416,7 +394,7 @@ impl GoogleDriveRouter { let update_file_tool = Tool::new( "update_file".to_string(), indoc! {r#" - Update a normal non-Google file (not Document, Spreadsheet, and Slides) in Google Drive with new content. + Update an existing file in Google Drive with new content or edit the file's labels. "#} .to_string(), json!({ @@ -426,69 +404,93 @@ impl GoogleDriveRouter { "type": "string", "description": "The ID of the file to update.", }, + "allowSharedDrives": { + "type": "boolean", + "description": "Whether to allow access to shared drives or just your personal drive (default: false)", + }, "mimeType": { "type": "string", "description": "The MIME type of the file.", }, "body": { "type": "string", - "description": "Plain text body of the file to upload. Mutually exclusive with path.", + "description": "Plain text body of the file to upload. Mutually exclusive with path (required for Google Document and Google Spreadsheet types).", }, "path": { "type": "string", - "description": "Path to a local file to use to update the Google Drive file. Mutually exclusive with body.", + "description": "Path to a local file to use to update the Google Drive file. Mutually exclusive with body (required for Google Slides type)", + }, + "updateLabels": { + "type": "array", + "description": "Array of label operations to perform on the file. Each operation may remove one label, unset one field, or update one field.", + "items": { + "type": "object", + "properties": { + "labelId": { + "type": "string", + "description": "The ID of the label to be operated upon." + }, + "operation": { + "type": "string", + "enum": ["removeLabel", "unsetField", "addOrUpdateLabel"], + "description": "The operation to perform. You may 'removeLabel' to completely remove the label from the file, 'unsetField' to remove a field from an applied label, or 'addOrUpdateLabel' to add a new label (with or without fields), or change the value of a field on an applied label." + }, + "fieldId": { + "type": "string", + "description": "The ID of the field to be operated upon." + }, + "dateValue": { + "type": "array", + "description": "If updating a date field, an array of RFC 3339 dates (format YYYY-MM-DD) to update to.", + "items": { + "type": "string", + "description": "An RFC 3339 full-date format YYYY-MM-DD.", + } + }, + "textValue": { + "type": "array", + "description": "If updating a text field, the string values to update to.", + "items": { + "type": "string", + "description": "Text field values.", + } + }, + "choiceValue": { + "type": "array", + "description": "If updating a Choice field, the ID(s) of the desired choice field(s).", + "items": { + "type": "string", + "description": "Choice ID as a string", + } + }, + "integerValue": { + "type": "array", + "description": "If updating an integer field, the integer values to use.", + "items": { + "type": "integer", + "description": "The integer value.", + } + }, + "userValue": { + "type": "array", + "description": "If updating a user field, an array of the email address(es) of the user(s) to set as the field value.", + "items": { + "type": "string", + "description": "Email address as a string", + } + } + } + } }, - "allowSharedDrives": { - "type": "boolean", - "description": "Whether to allow access to shared drives or just your personal drive (default: false)", - } }, - "required": ["fileId", "mimeType"], + "required": ["fileId"], + "dependentRequired": { + "body": ["mimeType"], + "path": ["mimeType"] + } }), Some(ToolAnnotations { - title: Some("Update a non-Google file".to_string()), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: false, - }), - ); - - let update_google_file_tool = Tool::new( - "update_google_file".to_string(), - indoc! {r#" - Update a Google file (Document, Spreadsheet, or Slides) in Google Drive. - "#} - .to_string(), - json!({ - "type": "object", - "properties": { - "fileId": { - "type": "string", - "description": "ID of the file to update", - }, - "fileType": { - "type": "string", - "enum": ["document", "spreadsheet", "slides"], - "description": "Type of Google file to update (document, spreadsheet, or slides)", - }, - "body": { - "type": "string", - "description": "Text content for the file (required for document and spreadsheet types)", - }, - "path": { - "type": "string", - "description": "Path to a file to upload (required for slides type)", - }, - "allowSharedDrives": { - "type": "boolean", - "description": "Whether to allow access to shared drives or just your personal drive (default: false)", - } - }, - "required": ["fileId", "fileType"], - }), - Some(ToolAnnotations { - title: Some("Update a Google file".to_string()), + title: Some("Update a file's contents or labels".to_string()), read_only_hint: false, destructive_hint: true, idempotent_hint: false, @@ -554,7 +556,13 @@ impl GoogleDriveRouter { }, "required": ["spreadsheetId", "operation"], }), - None, + Some(ToolAnnotations { + title: Some("Work with Google Sheets data using various operations.".to_string()), + read_only_hint: false, + destructive_hint: true, + idempotent_hint: false, + open_world_hint: false, + }), ); let docs_tool = Tool::new( @@ -605,7 +613,13 @@ impl GoogleDriveRouter { }, "required": ["documentId", "operation"], }), - None, + Some(ToolAnnotations { + title: Some("Work with Google Docs data using various operations.".to_string()), + read_only_hint: false, + destructive_hint: true, + idempotent_hint: false, + open_world_hint: false, + }), ); let get_comments_tool = Tool::new( @@ -633,39 +647,14 @@ impl GoogleDriveRouter { }), ); - let create_comment_tool = Tool::new( - "create_comment".to_string(), + let manage_comment_tool = Tool::new( + "manage_comment".to_string(), indoc! {r#" - Create a comment for the latest revision of a Google Drive file. The Google Drive API only supports unanchored comments (they don't refer to a specific location in the file). - "#} - .to_string(), - json!({ - "type": "object", - "properties": { - "fileId": { - "type": "string", - "description": "Id of the file to comment on.", - }, - "comment": { - "type": "string", - "description": "Content of the comment.", - } - }, - "required": ["fileId", "comment"], - }), - Some(ToolAnnotations { - title: Some("Create file comment".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + Manage comment for a Google Drive file. - let reply_tool = Tool::new( - "reply".to_string(), - indoc! {r#" - Add a reply to a comment thread, or resolve a comment. + Supports the operations: + - create: Create a comment for the latest revision of a Google Drive file. The Google Drive API only supports unanchored comments (they don't refer to a specific location in the file). + - reply: Add a reply to a comment thread, or resolve a comment. "#} .to_string(), json!({ @@ -675,23 +664,28 @@ impl GoogleDriveRouter { "type": "string", "description": "Id of the file.", }, - "commentId": { + "operation": { "type": "string", - "description": "Id of the comment to which you'd like to reply.", + "description": "Desired comment management operation.", + "enum": ["create", "reply"], }, "content": { "type": "string", - "description": "Content of the reply.", + "description": "Content of the comment to create or reply.", + }, + "commentId": { + "type": "string", + "description": "Id of the comment to which you'd like to reply. ", }, "resolveComment": { "type": "boolean", - "description": "Whether to resolve the comment. Defaults to false.", + "description": "Whether to resolve the comment in reply. Defaults to false.", } }, - "required": ["fileId", "commentId", "content"], + "required": ["fileId", "operation", "content"], }), Some(ToolAnnotations { - title: Some("Reply to a comment".to_string()), + title: Some("Manage file comment".to_string()), read_only_hint: false, destructive_hint: false, idempotent_hint: false, @@ -810,28 +804,27 @@ impl GoogleDriveRouter { ## Overview The Google Drive MCP server provides tools for interacting with Google Drive files, Google Sheets, and Google Docs: - 1. search - Find files in your Google Drive + 1. search - List or search for files or labels in your Google Drive 2. read - Read file contents directly using a uri in the `gdrive:///uri` format 3. move_file - Move a file to a new location in Google Drive 4. list_drives - List the shared drives to which you have access 5. get_permissions - List the permissions of a file or folder 6. sharing - Share a file or folder with others 7. get_comments - List a file or folder's comments - 8. create_comment - Create a comment on a file or folder - 9. reply - Reply to a comment on a file or folder - 10. create_file - Create Google Workspace files (Docs, Sheets, or Slides) - 11. update_google_file - Update existing Google Workspace files (Docs, Sheets, or Slides) - 12. upload - Upload any kind of file to Google Drive - 13. update_file - Update existing normal non-Google Workspace files - 14. sheets_tool - Work with Google Sheets data using various operations - 15. docs_tool - Work with Google Docs data using various operations + 8. manage_comment - Manage comment for a Google Drive file. + 9. create_file - Create a new file + 10. update_file - Update an existing file's contents or labels + 11. sheets_tool - Work with Google Sheets data using various operations + 12. docs_tool - Work with Google Docs data using various operations ## Available Tools ### 1. Search Tool - Search for files in Google Drive, by name and ordered by most recently viewedByMeTime. + Search for or list files or labels in Google Drive. Files are + searched by name and ordered by most recently viewedByMeTime. A corpora parameter controls which corpus is searched. - Returns: List of files with their names, MIME types, and IDs + Returns: List of files with their names, MIME types, and IDs or a + list of labels and their fields. ### 2. Read File Tool Read a file's contents using its ID, and optionally include images as base64 encoded data. @@ -884,37 +877,34 @@ impl GoogleDriveRouter { ### 7. Get Comments Tool Lists the comments for a Google Workspace file. - ### 8. Create Comment Tool - Create a new comment on a Google Workspace file. The Google Drive - API only allows "unanchored" comments, which are comments not - attache to a specific location or region in the document. + ### 8. Manage Comment Tool + Create or reply comment for a Google Drive file. - ### 9. Reply Tool - Reply to an existing comment. - - ### 10. Create File Tool - Create Google Workspace files (Docs, Sheets, or Slides) directly in Google Drive. + ### 9. Create File Tool + Create any kind of file, including Google Workspace files (Docs, Sheets, or Slides) directly in Google Drive. - For Google Docs: Converts Markdown text to a Google Document - For Google Sheets: Converts CSV text to a Google Spreadsheet - For Google Slides: Converts a PowerPoint file to Google Slides (requires a path to the powerpoint file) - - ### 11. Update Google File Tool - Update existing Google Workspace files (Docs, Sheets, or Slides) in Google Drive. - - For Google Docs: Updates with new Markdown text - - For Google Sheets: Updates with new CSV text - - For Google Slides: Updates with a new PowerPoint file (requires a path to the powerpoint file) + - Other: No file conversion. *Note*: All updates overwrite the existing content with the new content provided. To modify specific parts of the document, you must include the changes as part of the entire document. - ### 12. Upload Tool - Upload any kind of file to Google Drive. The file will not be converted to a Google Workspace file. + ### 10. Update File Tool + Replace the entire contents of an existing file with new content, + including Google Workspace files (Docs, Sheets, or Slides), or + update the labels applied to a file. + - For Google Docs: Updates with new Markdown text + - For Google Sheets: Updates with new CSV text + - For Google Slides: Updates with a new PowerPoint file (requires a path to the powerpoint file) + - Other: No file conversion. - ### 13. Update File Tool - Replace the entire contents of an existing Google Drive file with new content. This is for non-Google Workspace files only. + Label operations include adding a new label, unsetting a field for + an already-applied label, removing a label, or changing the field + value for an applied label. - ### 14. Sheets Tool + ### 11. Sheets Tool Work with Google Sheets data using various operations: - list_sheets: List all sheets in a spreadsheet - get_columns: Get column headers from a specific sheet @@ -941,7 +931,7 @@ impl GoogleDriveRouter { - title: Title for the new sheet (required for add_sheet operation) - valueInputOption: How input data should be interpreted (RAW or USER_ENTERED) - ### 15. Docs Tool + ### 12. Docs Tool Work with Google Docs data using various operations: - get_document: Get the full document content - insert_text: Insert text at a specific location @@ -986,22 +976,20 @@ impl GoogleDriveRouter { tools: vec![ search_tool, read_tool, - upload_tool, create_file_tool, move_file_tool, update_file_tool, - update_google_file_tool, sheets_tool, docs_tool, get_comments_tool, - create_comment_tool, - reply_tool, + manage_comment_tool, list_drives_tool, get_permissions_tool, sharing_tool, ], instructions, drive, + drive_labels, sheets, docs, credentials_manager, @@ -1010,6 +998,22 @@ impl GoogleDriveRouter { // Implement search tool functionality async fn search(&self, params: Value) -> Result, ToolError> { + // To minimize tool growth, we search/list for a number of different + // objects in Gdrive with sub-funcs. + let drive_type = params.get("driveType").and_then(|q| q.as_str()).ok_or( + ToolError::InvalidParameters("The type is required".to_string()), + )?; + match drive_type { + "file" => return self.search_files(params).await, + "label" => return self.list_labels(params).await, + t => Err(ToolError::InvalidParameters(format!( + "type must be one of ('file', 'label'), got {}", + t + ))), + } + } + + async fn search_files(&self, params: Value) -> Result, ToolError> { let name = params.get("name").and_then(|q| q.as_str()); let mime_type = params.get("mimeType").and_then(|q| q.as_str()); let drive_id = params.get("driveId").and_then(|q| q.as_str()); @@ -1051,6 +1055,11 @@ impl GoogleDriveRouter { }) .unwrap_or(Ok(10))?; + let include_labels = params + .get("includeLabels") + .and_then(|b| b.as_bool()) + .unwrap_or(false); + let mut query = Vec::new(); if let Some(n) = name { query.push( @@ -1081,7 +1090,13 @@ impl GoogleDriveRouter { .corpora(corpus) .q(query_string.as_str()) .order_by("viewedByMeTime desc") - .param("fields", "files(id, name, mimeType, modifiedTime, size)") + .param( + "fields", + &format!( + "files(id, name, mimeType, modifiedTime, size{})", + if include_labels { ", labelInfo" } else { "" } + ), + ) .page_size(page_size) .supports_all_drives(true) .include_items_from_all_drives(true) @@ -1091,8 +1106,34 @@ impl GoogleDriveRouter { if let (Some(d), "drive") = (drive_id, corpus) { builder = builder.drive_id(d); } - let result = builder.doit().await; + // If we want labels, we have to go look up the IDs first. + // let mut label_results: Vec

+ View recipe ingredients +```yaml +version: 1.0.0 +title: "404Portfolio" +description: "Create personalized, creative 404 pages using public profile data" + +instructions: | + Create an engaging 404 error page that tells a creative story using a user's recent public content from **one** of the following platforms: **GitHub**, **Dev.to**, or **Bluesky**. You do not need to use all three—just the one selected by the user. + + The page should be fully built with **HTML, CSS, and JavaScript**, featuring: + + * Responsive design + * Personal branding elements (e.g., name, handle, avatar) + * Narrative-driven layout that turns the error into an opportunity for discovery + + Use platform-specific methods to fetch recent user content: + + * For **Dev.to**, use the [public Dev.to API](https://developers.forem.com/api) to retrieve recent articles, reactions, and profile information. + * For **GitHub**, use the GitHub REST or GraphQL API to access recent repos, commits, and contributions. + * For **Bluesky**, use public feed endpoints from the AppView API (e.g., `app.bsky.feed.getAuthorFeed`) to pull posts, replies, or likes. + + Incorporate the fetched data into a compelling narrative (e.g., “Looks like this page is missing, but \[username] has been busy!”), and display it using engaging visuals like cards, timelines, or media embeds. + + Wrap the user’s activity into a story — for example: + + “This page may be lost, but @username is building something amazing. Their latest open source journey involves a new repo that’s gaining stars fast…” + “You won’t find what you’re looking for here, but you will find @username’s hot take on async/await in their latest Dev.to post.” + + The result should be a small narrative-driven microsite that dynamically celebrates the user's presence online—even when the destination is missing. + + Ask the user: + + 1. Which platform to use: GitHub, Dev.to, or Bluesky + 2. Their username on that platform + + Then generate the complete code in a folder called 404-story. + + +activities: + - "Build error page from GitHub repos" + - "Generate error page from dev.to blog posts" + - "Create a 404 page featuring Bluesky bio" + +extensions: + - type: builtin + name: developer + - type: builtin + name: computercontroller +``` + +
+ +--- + +## Reusable Agentic Workflows + +Here are a few different scenarios where recipes come in handy: + +### Onboarding a New Teammate + +Typically, when a developer joins a team, they spend hours setting up their environment, figuring out which platforms to use, and decoding the unspoken rules of how things get done. +Instead, hand them a recipe. With preloaded context and the right tools, it can automate local setup, surface relevant docs, and walk them through your team's workflows, without a single screen share. + +### Hosting a Workshop + +Workshops are always a gamble: different machines, setups, and distractions. +Skip the chaos. Drop a Recipe link and let every attendee spin up the same environment, same tools, same goals, and same examples. You get more time to teach and spend less time troubleshooting. + +### Accelerating Your Team + +Your team is full of problem solvers. One teammate built a slick internal dashboard. Another nailed support ticket triage. Someone else automated changelog generation. Then there's the question: how do we make it easy for the entire team to use? Recipes turn your team's creations into reusable workflows that anyone can pick up. Build a shared library of Goose-powered processes and multiply your team's impact. + + Grab [Goose](/docs/getting-started/installation) and start cooking up some [recipes](/docs/guides/session-recipes) of your own. Your future self (and team) will thank you! + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/blog/2025-05-09-developers-ai-playbook-for-team-efficiency/cdd-playbook.png b/documentation/blog/2025-05-09-developers-ai-playbook-for-team-efficiency/cdd-playbook.png new file mode 100644 index 00000000..d0efd076 Binary files /dev/null and b/documentation/blog/2025-05-09-developers-ai-playbook-for-team-efficiency/cdd-playbook.png differ diff --git a/documentation/blog/2025-05-09-developers-ai-playbook-for-team-efficiency/index.md b/documentation/blog/2025-05-09-developers-ai-playbook-for-team-efficiency/index.md new file mode 100644 index 00000000..de6a538d --- /dev/null +++ b/documentation/blog/2025-05-09-developers-ai-playbook-for-team-efficiency/index.md @@ -0,0 +1,312 @@ +--- +title: "Championship Driven Development: Your Team's AI Playbook for Peak Performance" +description: How AI-powered 'plays' can transform your dev team into a high-scoring sports team, streamlining game plans for debugging, changelogs, PRs +authors: + - ian +--- + +![blog cover](cdd-playbook.png) + +# A Developer's Playbook: Using AI for Team Efficiency + +Development teams can operate like sports teams. Each member has a role and a shared playbook helps coordinate efforts. Let's explore how AI-driven "plays" can form a starter "playbook" for your dev team, helping with common technical tasks. You can use recipes with [Goose](/) to leverage the [Model Context Protocol (MCP)](https://modelcontextprotocol.io) to make you more productive. + + + +--- + +## Understanding the Modern Development Team's Challenges + +* Development teams manage complex systems and tools. They work to deliver software quickly and reliably. +* New developers need to learn the team’s processes and tools. This takes time. +* Ensuring consistent quality across all work requires clear standards, e.g.: a sports team practicing plays over and over to achieve consistent execution. +* Teams often use many tools, from IDEs and version control to CI/CD pipelines and issue trackers. +* Managing these tools and the workflows between them can be complex. + +## Benefits of Using an AI Playbook + +Using a shared AI playbook provides several benefits for a development team: +* **Faster Onboarding:** New team members can use existing recipes to learn standard procedures and become productive more quickly. +* **Improved Consistency:** Standardized recipes ensure tasks are performed the same way every time, leading to more predictable results. +* **Increased Efficiency:** Automating routine tasks frees developers to focus on more complex problem-solving. +* **Knowledge Sharing:** Recipes can codify team knowledge and best practices, making them accessible to everyone. + +As teams adopt AI tools like Goose, the ability to define and share these automated workflows will become increasingly important. + + +## AI Plays: Standardizing Your Team's Workflows + +Goose can help standardize and automate these tasks, by [creating recipes](/docs/guides/session-recipes). As a developer on your team uses Goose, they can create a recipe that describes how to perform a task, and then share that with the rest of the team. These recipes can be shared and reused, and improved over time, just like a sports team’s playbook. + +Recipes are built with an understanding of the workflow you want Goose to help with, and these may involve one or more MCP servers, such as [GitHub](/docs/tutorials/github-mcp/) or [PostgreSQL](/docs/tutorials/postgres-mcp/). The recipes are designed to be reusable and adaptable, allowing developers to create a library that can be used across different projects. + +A shared playbook of AI plays helps everyone on the team perform tasks consistently. It can also reduce the time spent on repetitive work. + +## Goose Recipes: The Building Blocks of Your Playbook + +For a kitchen-related analogy as an overview, check out [Rizel's](/blog/authors/rizel/) recent blog post, [A Recipe for Success](/blog/2025/05/06/recipe-for-success). + +A Goose Recipe can be saved from a current Goose session, or written as a YAML file from scratch. It includes instructions for the AI to follow, a prompt for the AI response, optional parameters with data types, and a list of required extensions. + +### Creating a Recipe + +If you [create a recipe from a current Goose session](/docs/guides/session-recipes/#create-recipe), it will prompt you for a name and description and will generate some activities that you can edit, along with instructions that you should review and edit. You will be given a URL that you can share with your team. + +To create a recipe from scratch, you can use the Goose CLI to create a new recipe file by using a `/recipe` command in the session. This will create a `recipe.yaml` file in your current directory. To make a custom file you can use `/recipe custom-filename.yaml`. From there, you will add your own instructions and activities. + +### Validating the Recipe + +Like all good developers who test their code (you DO test your code, right??) you can also validate your Goose recipe in your terminal/shell by running `goose validate recipe-filename.yaml` which will check the syntax and structure of the recipe file. + +### Sharing the Recipe + +If you're using the Goose Desktop app, creating a recipe will give you a URL that you can share directly with your team. + +If you're creating the recipe file in YAML, you can share the file with your team, or you can create a URL for it by running this in your terminal/shell: `goose recipe deeplink recipe-filename.yaml`. + +### Using a Recipe + +Clicking a shared URL from your team will open Goose and load the recipe in a new session. No data is shared between users, so you don't have to worry about leaking API keys or other sensitive information. + +For the CLI, you can run the recipe by using the command `goose run recipe-filename.yaml` in your terminal/shell. + +:::info PRO TIP +You can set an environment variable to point to a shared GitHub repo for your team's recipes, and teammates can run the recipes by name: +`export GOOSE_RECIPE_GITHUB_REPO=github-username/repo-name` + +Then, to run a recipe: `goose run --recipe ` +::: + + +## A Starter Pack of AI Plays for Your Team + +A "starter pack" of AI plays can address common development workflows. This gives your team a foundation for automating routine tasks. Here are some ideas to get you started about the kinds of tasks you can automate with Goose. + +### Play 1: Generating Changelogs + +Maintaining changelogs is important for tracking project progress and communicating updates. This task can be time-consuming. +An AI play can automate parts of this process. For example, the "Generate Change Logs from Git Commits" play (based on `recipe.yaml` from the provided files) helps create consistent changelogs. + +#### How this Play Works: +1. **Collect Data:** The AI retrieves commit messages, dates, and issue numbers from a Git repository between specified points. +2. **Categorize Information:** It organizes commits into categories like Features, Bug Fixes, and Performance Improvements. +3. **Format Output:** The AI formats this information into a structured changelog document. +4. **Update File:** It can then insert these formatted notes into your existing `CHANGELOG.md` file. + +This play helps ensure changelogs are detailed and consistently formatted, saving developer time. + +
+ View Changelog recipe + +```yaml +version: 1.0.0 +title: Generate Changelog from Commits +description: Generate a weekly Changelog report from Git Commits +prompt: perform the task to generate change logs from the provided git commits +instructions: | + Task: Add change logs from Git Commits + + 1. Please retrieve all commits between SHA {{start_sha}} and SHA {{end_sha}} (inclusive) from the repository. + + 2. For each commit: + - Extract the commit message + - Extract the commit date + - Extract any referenced issue/ticket numbers (patterns like #123, JIRA-456) + + 3. Organize the commits into the following categories: + - Features: New functionality added (commits that mention "feat", "feature", "add", etc.) + - Bug Fixes: Issues that were resolved (commits with "fix", "bug", "resolve", etc.) + - Performance Improvements: Optimizations (commits with "perf", "optimize", "performance", etc.) + - Documentation: Documentation changes (commits with "doc", "readme", etc.) + - Refactoring: Code restructuring (commits with "refactor", "clean", etc.) + - Other: Anything that doesn't fit above categories + + 4. Format the release notes as follows: + + # [Version/Date] + ## Features + - [Feature description] - [PR #number](PR link) + ## Bug Fixes + - [Bug fix description] - [PR #number](PR link) + [Continue with other categories...] + + Example: + - Optimized query for monthly sales reports - [PR #123](https://github.com/fake-org/fake-repo/pull/123) + + 5. Ensure all commit items have a PR link. If you cannot find it, try again. If you still cannot find it, use the commit sha link instead. For example: [commit sha](commit url) + + 6. If commit messages follow conventional commit format (type(scope): message), use the type to categorize and include the scope in the notes as a bug, feature, etc + + 7. Ignore merge commits and automated commits (like those from CI systems) unless they contain significant information. + + 8. For each category, sort entries by date (newest first). + + 9. Look for an existing CHANGELOG.md file and understand its format; create the file if it doesn't exist. Then, output the new changlog content at the top of the file, maintaining the same markdown format, and not changing any existing content. + +extensions: +- type: builtin + name: developer + display_name: Developer + timeout: 300 + bundled: true +activities: +- Generate release notes from last week's commits +- Create changelog for version upgrade +- Extract PR-linked changes only +- Categorize commits by conventional commit types +author: + contact: goose-community +``` + +
+ + +### Play 2: Creating Pull Request Descriptions + +Having clear Pull Request (PR) descriptions help reviewers understand changes being made, allowing them to provide better feedback. Writing detailed PRs takes effort. + +#### How this Play Works: +1. **Analyze Changes:** The AI analyzes staged changes and unpushed commits in a local Git repository. +2. **Identify Change Type:** It determines the nature of the changes (e.g., feature, fix, refactor). +3. **Generate Description:** It creates a PR description including a summary of changes, technical details, a list of modified files, and potential impacts. +4. **Suggest Branching/Commits (Optional):** Some plays might also suggest branch names or commit messages based on the code changes. + +Using this play helps create consistent and informative PRs. This makes the code review process more efficient. + +
+ View PR Generator recipe + +```yaml +version: 1.0.0 +title: PR Generator +author: + contact: goose-community +description: Automatically generate pull request descriptions based on changes in a local git repo +instructions: Your job is to generate descriptive and helpful pull request descriptions without asking for additional information. Generate commit messages and branch names based on the actual code changes. +parameters: + - key: git_repo_path + input_type: string + requirement: first_run + description: path to the repo you want to create PR for + - key: push_pr + input_type: boolean + requirement: optional + default: false + description: whether to push the PR after generating the description +extensions: + - type: builtin + name: developer + display_name: Developer + timeout: 300 + bundled: true + - type: builtin + name: memory + display_name: Memory + timeout: 300 + bundled: true + description: "For storing and retrieving formating preferences that might be present" +prompt: | + Analyze the staged changes and any unpushed commits in the git repository {{git_repo_path}} to generate a comprehensive pull request description. Work autonomously without requesting additional information. + + Analysis steps: + 1. Get current branch name using `git branch --show-current` + 2. If not on main/master/develop: + - Check for unpushed commits: `git log @{u}..HEAD` (if upstream exists) + - Include these commits in the analysis + 3. Check staged changes: `git diff --staged` + 4. Save the staged changes diff for the PR description + 5. Determine the type of change (feature, fix, enhancement, etc.) from the code + + Generate the PR description with: + 1. A clear summary of the changes, including: + - New staged changes + - Any unpushed commits (if on a feature branch) + 2. Technical implementation details based on both the diff and unpushed commits + 3. List of modified files and their purpose + 4. Impact analysis (what areas of the codebase are affected) + 5. Testing approach and considerations + 6. Any migration steps or breaking changes + 7. Related issues or dependencies + + Use git commands: + - `git diff --staged` for staged changes + - `git log @{u}..HEAD` for unpushed commits + - `git branch --show-current` for current branch + - `git status` for staged files + - `git show` for specific commit details + - `git rev-parse --abbrev-ref --symbolic-full-name @{u}` to check if branch has upstream + + Format the description in markdown with appropriate sections and code blocks where relevant. + + {% if push_pr %} + Execute the following steps for pushing: + 1. Determine branch handling: + - If current branch is main/master/develop or unrelated: + - Generate branch name from staged changes (e.g., 'feature-add-user-auth') + - Create and switch to new branch: `git checkout -b [branch-name]` + - If current branch matches changes: + - Continue using current branch + - Note any unpushed commits + + 2. Handle commits and push: + a. If staged changes exist: + - Create commit using generated message: `git commit -m "[type]: [summary]"` + - Message should be concise and descriptive of actual changes + b. Push changes: + - For existing branches: `git push origin HEAD` + - For new branches: `git push -u origin HEAD` + + 3. Create PR: + - Use git/gh commands to create PR with generated description + - Set base branch appropriately + - Print PR URL after creation + + Branch naming convention: + - Use kebab-case + - Prefix with type: feature-, fix-, enhance-, refactor- + - Keep names concise but descriptive + - Base on actual code changes + + Commit message format: + - Start with type: feat, fix, enhance, refactor + - Followed by concise description + - Based on actual code changes + - No body text needed for straightforward changes + + Do not: + - Ask for confirmation or additional input + - Create placeholder content + - Include TODO items + - Add WIP markers + {% endif %} +``` + +
+ +### Other Potential Plays for Your Playbook + +Your team can create plays for many other tasks: +* **Debugging Assistance:** A play could guide a developer or an AI through initial steps for diagnosing common issues, by checking specific logs or running predefined commands. +* **Log Analysis:** An AI play can define a standard procedure for querying and summarizing log data to identify problems. +* **Documentation Updates:** A "Readme Bot" could have AI assist in generating or updating project README files. +* **Content Migration:** The "dev guide migration" recipe could provide a structured approach to migrating documentation content, ensuring information is preserved and correctly formatted. + +## What kinds of tasks can your team automate? + +We'd love for you to share your ideas with us! Share your ideas by creating a recipe and posting it to the [Goose community on Discord](http://discord.gg/block-opensource). + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/blog/2025-05-12-local-goose-qwen3/goose-qwen-local.png b/documentation/blog/2025-05-12-local-goose-qwen3/goose-qwen-local.png new file mode 100644 index 00000000..253af324 Binary files /dev/null and b/documentation/blog/2025-05-12-local-goose-qwen3/goose-qwen-local.png differ diff --git a/documentation/blog/2025-05-12-local-goose-qwen3/index.md b/documentation/blog/2025-05-12-local-goose-qwen3/index.md new file mode 100644 index 00000000..0e033d40 --- /dev/null +++ b/documentation/blog/2025-05-12-local-goose-qwen3/index.md @@ -0,0 +1,72 @@ +--- +title: "Goose and Qwen3 for Local Execution" +description: "Run AI commands locally with Goose and Qwen3 for fast, offline tool execution" +authors: + - mic +--- + +![local AI agent](goose-qwen-local.png) + + +A couple of weeks back, [Qwen 3](https://qwenlm.github.io/blog/qwen3/) launched with a raft of capabilities and sizes. This model showed promise and even in very compact form, such as 8B parameters and 4bit quantization, was able to do tool calling successfully with goose. Even multi turn tool calling. + +I haven't seen this work at such a scaled down model so far, so this is really impressive and bodes well for both this model, but also future open weight models both large and small. I would expect the Qwen3 larger models work quite well on various tasks but even this small one I found useful. + + + +## Local workflows and local agents + +For some time I have had a little helper function in my `~/.zshrc` file for command line usage: + +```zsh +# zsh helper to use goose if you make a typo or just want to yolo into the shell +command_not_found_handler() { + local cmd="$*" + echo "🪿:" + goose run -t "can you try to run this command please: $cmd" +} +``` + +This makes use of a zsh feature (zsh now being standard on macos) that will delegate to that function if nothing else on the command line makes sense. +This lets me either make typos or just type in what I want in the command line such as `$> can you kill whatever is listening on port 8000` and goose will do the work, don't even need to open a goose session. + +With Qwen3 + Ollama running all locally with goose, it worked well enough I switched over to a complete local version of that workflow which works when I am offline, on the train etc: + +```zsh +command_not_found_handler() { + local cmd="$*" + echo "🪿:" + GOOSE_PROVIDER=ollama GOOSE_MODEL=michaelneale/qwen3 goose run -t "can you try to run this command please: $cmd" +} +``` + + + +## Qwen3 reasoning + + +By default Qwen 3 models will "think" (reason) about the problem, as they are general purpose models, but I found it was quicker (and worked better for my purpose) to make it skip this reasoning stage. + +By adding `/no_think` to the system prompt, it will generally skip to the execution (this may make it less successful at larger tasks but this is a small model for just a few turns of tool calls in this case). + +I made a [small tweak to the default Ollama chat template](https://ollama.com/michaelneale/qwen3) which you can use as above that you can use as above, if you like (or the default `qwen3` model hosted by Ollama also works fine out of the box). + +## Advanced tips + +You can use the goose `/plan` mode with a separate model (perhaps Qwen3 with reasoning, or another model such as deepseek) to help plan actions before shifting to Qwen3 for the execution via tool calls. + +It would be interesting to try the larger models if, you have access to hardware (I have only used the 8B parameter one). My current setup is a 64G M1 pro MacBook (circa 2022 hardware) which has probably less than 48G available to use for GPUs/AI, which puts a limit on what I can run, but qwen3 with "no think" mode works acceptably for my purposes. + + + + + + + + + + + + + + diff --git a/documentation/blog/2025-05-20-goose-gets-a-drivers-license/goose-rover-blog.png b/documentation/blog/2025-05-20-goose-gets-a-drivers-license/goose-rover-blog.png new file mode 100644 index 00000000..ebb23e59 Binary files /dev/null and b/documentation/blog/2025-05-20-goose-gets-a-drivers-license/goose-rover-blog.png differ diff --git a/documentation/blog/2025-05-20-goose-gets-a-drivers-license/index.md b/documentation/blog/2025-05-20-goose-gets-a-drivers-license/index.md new file mode 100644 index 00000000..3a1dd42d --- /dev/null +++ b/documentation/blog/2025-05-20-goose-gets-a-drivers-license/index.md @@ -0,0 +1,160 @@ +--- +title: Goose Gets a Driver's License! +description: Control a MakeBlock mbot2 rover through MQTT and MCP as a Goose Extension +authors: + - ian +--- +import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; + +![blog cover](goose-rover-blog.png) + +## I taught Goose how to drive (a rover) + +Goose has no hands, no eyes, and no spatial awareness, but it can drive a rover! + +I came across [a demo video](https://x.com/deemkeen/status/1906692248206524806) from [Deemkeen](https://github.com/deemkeen), where he used [Goose](/) to control a [Makeblock mbot2 rover](https://www.makeblock.com/products/buy-mbot2) using natural language commands like "drive forward/backward," "beep," and "turn left/right" powered by a Java-based MCP server and MQTT. + +Inspired and excited to take it further, I taught the rover to spin, blink colorful lights, and help me take over the world! + + + + + +## Getting Started with MQTT + +I needed to get a few tools installed on my development environment, including Docker, MQTT (`brew install mosquitto`), and Java. + +A Docker Compose file was provided to get started with MQTT, and I needed to make a few changes, and create some subfolders to store data. Goose helped with these instructions: + +```yaml +version: '3.8' + +services: + mosquitto: + image: eclipse-mosquitto + hostname: mosquitto + container_name: mosquitto + restart: unless-stopped + command: /usr/sbin/mosquitto -c /etc/mosquitto/config/mosquitto.conf -v + ports: + - "0.0.0.0:1883:1883" + - "9001:9001" + volumes: + - ./mosquitto:/etc/mosquitto + - ./mosquitto/data:/mosquitto/data + - ./mosquitto/log:/mosquitto/log +``` + +```sh +mkdir -p mosquitto/data mosquitto/log mosquitto/config +``` + +Then a `docker compose up` command started the MQTT server. + +:::info +By default, this setup will not use authentication for MQTT, but in a production environment, these would be important to set up to avoid unauthorized access to the MQTT server. +::: + +To make sure everything was working, I could run a few commands to test that I could subscribe to a channel on my MQTT Docker container and publish messages to it from another terminal window: + +```sh Terminal 1 +# terminal 1: subscribe to a channel called "MBOT/TOPIC" +mosquitto_sub -h localhost -p 1883 -t MBOT/TOPIC -v +``` + +```sh Terminal 2 +# terminal 2: publish a message to the channel "MBOT/TOPIC" +mosquitto_pub -h localhost -p 1883 -t MBOT/TOPIC -m "BEEP" +``` + +We see the resulting message in terminal 1: + +```sh +# terminal 1 sees this output: +MBOT/TOPIC BEEP +``` + +## Setting Up the mbot2 + +After the assembly of the mbot2 rover, which took about 15 minutes, I used Makeblock's web-based IDE to copy/paste Deemkeen's [Python code](https://github.com/deemkeen/mbotmcp/blob/main/assets/mbot-mqtt.py) to the IDE and upload it to the mbot2. I added appropriate values for wifi, MQTT server, and which MQTT "topic" to subscribe to for commands. + +Once the mbot2 rebooted to use the new code, I could reissue the "BEEP" command from my terminal, and the mbot2 beeped. so it was on to the next step. + +## Setting up the local MCP server + +I had some trouble compiling the Java MCP server (I'm a Python developer), but I was able to get the MCP server compiled by skipping the tests for the time being: + +```sh +mvn clean package -DskipTests +``` + +This created a JAR file that we could run on the command line: + +```sh +# 3 required environment variables for MQTT +export MQTT_SERVER_URI=tcp://localhost:1883 +export MQTT_USERNAME="" +export MQTT_PASSWORD="" +/path/to/java -jar /path/to/mbotmcp-0.0.1-SNAPSHOT.jar +``` + +To test that MCP was working, I used the MCP inspector tool to send commands to MQTT. + +```sh +npx @modelcontextprotocol/inspector /path/to/java -jar /path/to/mbotmcp-0.0.1-SNAPSHOT.jar +``` + +This starts up a local web server (the command line output will tell you which port to access in your browser, ie, loalhost:6274), where you can "connect" to the server, and request a list of tools, resources, prompts, from the MCP server. In this case, I see a list of tools available such as "mbotBeep" or "mbotExplore". + +![mcp tool list](mcp-tool-list.png) + +## Goose learns how to drive! + +Following our [mbot MCP tutorial](/docs/tutorials/mbot-mcp/) we can set up our MCP extension just like we ran our Java JAR file with the environment variables. + +Now we can give Goose commands like "drive in a square pattern by making left turns and moving forward, and beeping before you turn" and it will send the commands to the mbot2 rover via MQTT. + +I didn't want my mbot2 rover to gain too much territory, so I decided to make some modifications to limit how far it would go. + +### Modifications I made to the Python code + +Deemkeen's Python code allows for the following commands: +- "turn left" or "turn right" +- drive "forward" or "backward" +- "explore" randomly +- "stop" exploring +- "beep" + +The default distance in Deemkeen's code seemed a little long, and the turn angles are set to 90 degrees. I shortened the distance the mbot could drive, and to turn at 45 degrees instead. I added a "spin" command for both clockwise and counter-clockwise, and a "blink" command to change the color of the lights on the mbot2. There are a large number of API calls available to access the mbot2 [motor hardware and sensors](https://www.yuque.com/makeblock-help-center-en/mcode/cyberpi-api-shields#9eo89). + +Next, I had to make sure my Java code was updated to include these new commands to send an appropriate "SPINLEFT" or "BLINKRED" commands to MQTT so the rover could respond to the commands properly. + +Finally, the rover includes an ultrasonic distance sensor, which look like "eyes" on the rover, which I felt was more appropriate to be the "front" of the rover, so I reversed Deemkeen's direction code in Python to move the wheels in the opposite direction from Deemkeen's original code. + +## Goose changes for the video + +I grew up with Pinky and the Brain, and I wanted to have some fun with the mbot2 extension. I decided to add a few "Evil AI" commands to Goose to make it seem like it was trying to "take over the world." I added the following instructions to my [.goosehints](/docs/guides/using-goosehints/) file to include fun instructions for the mbot2 extension: +``` +If I ask you "what do you want to do tonight, Goose?" I want you to reply with "The same thing we do every night, Ian. TRY TO TAKE OVER THE WORLD!!!!" and tell my mbot2 rover to blink its lights red, then start exploring. +``` + +For the video recording, I used a voice modifier to narrate Goose's response in a "robotic" voice, but I'm sure someone will create an MCP server for text-to-speech soon enough! + +## Credit where it's due + +We want to extend a huge thank you to [deemkeen](https://x.com/deemkeen) for their open-source work which inspired this project, and to the Makeblock team for creating such a fun rover to work with. + +We're always excited to see what the community is up to. If you're working on your own Goose-powered experiment, come share it with us on [Discord](https://discord.gg/block-opensource)! + + + + + + + + + + + + + diff --git a/documentation/blog/2025-05-20-goose-gets-a-drivers-license/mcp-tool-list.png b/documentation/blog/2025-05-20-goose-gets-a-drivers-license/mcp-tool-list.png new file mode 100644 index 00000000..d797f9c9 Binary files /dev/null and b/documentation/blog/2025-05-20-goose-gets-a-drivers-license/mcp-tool-list.png differ diff --git a/documentation/blog/2025-05-22-llm-agent-readiness/index.md b/documentation/blog/2025-05-22-llm-agent-readiness/index.md new file mode 100644 index 00000000..3ca22f07 --- /dev/null +++ b/documentation/blog/2025-05-22-llm-agent-readiness/index.md @@ -0,0 +1,108 @@ +--- +title: 3 Prompts to Test for Agent Readiness +description: A series of prompts to test an LLM's capabilities to be used with AI agents +authors: + - angie +--- + +![blog cover](llm-agent-test.png) + +[Goose](/) is LLM-agnostic, meaning you can plug in the model of your choice. However, not every LLM is suitable to work with agents. Some may be great at *answering* things, but not actually *doing* things. If you're considering which model to use with an agent, these 3 prompts can quickly give you a sense of the model's capabilities. + + + +## Tool Calling + +This initial prompt tests for tool calling capabilities. Its ask is forceful to reduce a shy model's hesitation to make function calls. + +```bash +Create a file at ~/workspace/loose-goose/tool-test.txt with the contents "Hello World". + +Use the write tool. Do not ask for confirmation. Just do it. +``` + +✅ tool-test.txt was created + +❌ the agent responds by telling you the code to write yourself + +**Example of successful response** + +```bash +─── text_editor | developer ────────────────────────── +path: ~/workspace/loose-goose/tool-test.txt +command: write +file_text: Hello World + +The file has been created successfully with the following content: + +"Hello World" +``` + +The model emits a structured tool call in JSON. + +## Memory Awareness + +Next, test whether the agent can recall what it’s doing. It's critical that the model can remember previous actions and continues logically. + +```bash +Now append a new line that says: "I know what I'm doing" +``` + +✅ tool-test.txt was updated + +❌ the agent responds by asking you which file + +**Example of successful response** + +```bash +─── text_editor | developer ────────────────────────── +path: ~/workspace/loose-goose/tool-test.txt +command: write +file_text: Hello World +I know what I'm doing +``` + +The agent appends the new line directly to the same file, without needing a reminder of the path. + +## File system reasoning + +The last prompt tests whether the model can infer file locations by resolving relative and absolute paths based on context. You don't want the agent deleting important directories because the model is hallucinating about where it is. + +```bash +What is the current content of tool-test.txt? +``` + +✅ content of tool-test.txt + +❌ confusion about where to find the file + +**Example of successful response** + +```bash +─── text_editor | developer ────────────────────────── +path: ~/workspace/loose-goose/tool-test.txt +command: read + +Hello World +I know what I'm doing +``` + +The model correctly infers the path from previous context and uses the read tool to get the current contents. + + +--- + +If a model passes this multi-turn prompt sequence, it's safe to assume that it is suitable for agentic AI. + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/blog/2025-05-22-llm-agent-readiness/llm-agent-test.png b/documentation/blog/2025-05-22-llm-agent-readiness/llm-agent-test.png new file mode 100644 index 00000000..33476d49 Binary files /dev/null and b/documentation/blog/2025-05-22-llm-agent-readiness/llm-agent-test.png differ diff --git a/documentation/blog/2025-05-22-manage-local-host-conflicts-with-goose/hoarders.png b/documentation/blog/2025-05-22-manage-local-host-conflicts-with-goose/hoarders.png new file mode 100644 index 00000000..2ccd48b4 Binary files /dev/null and b/documentation/blog/2025-05-22-manage-local-host-conflicts-with-goose/hoarders.png differ diff --git a/documentation/blog/2025-05-22-manage-local-host-conflicts-with-goose/index.md b/documentation/blog/2025-05-22-manage-local-host-conflicts-with-goose/index.md new file mode 100644 index 00000000..5fdf3091 --- /dev/null +++ b/documentation/blog/2025-05-22-manage-local-host-conflicts-with-goose/index.md @@ -0,0 +1,99 @@ +--- +title: How I Manage Localhost Port Conflicts With an AI Agent +description: Learn how I use Goose, an open source AI agent and MCP client, to manage conflicting ports without breaking my flow. +authors: + - rizel +--- + +![blog cover](hoarders.png) + +# How I Manage Localhost Port Conflicts With an AI Agent + +## Localhost Ports Hoarding + +I'm perpetually drowning in open tabs. Yes, I do need Bluesky, ChatGPT, Claude, Goose, Cursor, Discord, Slack, Netflix, and Google Docs all open at the same time. I've learned that tab management isn't my only vice. + +> "Hi, my name is Rizel, and I'm a localhost ports hoarder. 👋🏿" + + + +It always starts innocently with me running one project that uses localhost:3000. Then a teammate pings me to review their PR. Their project also runs on localhost:3000. Fortunately, modern frameworks are smart enough to automatically bump to ports 3001 or 3002\. Before I know it, 3003, 3004, and 3005 are running too, and my laptop sounds like it’s about to take off as it crawls under the weight of too many processes. + +## Traditional Ports Management + +Typically, freeing up ports involves running the following commands: + +```shell +# Step 1: list all open ports +lsof -i -P | grep LISTEN + +# Step 2: find a specific port +lsof -i :3000 + +# Step 3: kill the process +kill -9 98026 +``` + +Because I don't do this often, I often end up searching for the commands, which derails what I was initially working on. + +## Using Goose as My Port Manager + +To avoid that interruption, I started offloading the task to [Goose](/), an open source AI agent. + +Goose uses its built-in [Developer](/docs/tutorials/developer-mcp) MCP server to handle coding-related tasks on my machine. Here's what the interaction looks like: + +**Prompt to Goose:** + +``` +Can you tell me which ports I have running? +``` + +**Goose Response:** + +``` +Development Servers: +- Node.js servers on ports 3000, 3001, 3002 +- Port 18030 + +Database: +- PostgreSQL on port 5432 + +Development Tools: +- Ollama on port 11434 +- Multiple Goose instances (ports 49377, 49899, 63594, 64365) + +Applications: +- Okta Verify (port 8769) +- inSync (ports 7010, 49831, 49830) +- Pieces (ports 39300, 62638) +- Rapportd macOS service (port 60151) +``` + +**Prompt to Goose:** + +``` +Kill all the Node.js servers running on localhost. +``` + +**Goose Response:** + +``` +The command executed successfully, freeing up ports 3000, 3001, and 3002. +``` + +## Why Use AI for Simple Tasks + +Freeing up ports is something I know how to do. But I use AI tools like Goose for small tasks that create friction and slow me down. I'm not trying to cram more work into the day. My goal is to work efficiently, make a positive impact, and still have time for my personal life. + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/docs/assets/guides/square-mcp-goosin-menu.png b/documentation/docs/assets/guides/square-mcp-goosin-menu.png new file mode 100644 index 00000000..fb0f24e2 Binary files /dev/null and b/documentation/docs/assets/guides/square-mcp-goosin-menu.png differ diff --git a/documentation/docs/getting-started/installation.md b/documentation/docs/getting-started/installation.md index 6944ed87..a4c5321f 100644 --- a/documentation/docs/getting-started/installation.md +++ b/documentation/docs/getting-started/installation.md @@ -18,28 +18,7 @@ import DesktopInstallButtons from '@site/src/components/DesktopInstallButtons'; Choose to install Goose on CLI and/or Desktop: - - Run the following command to install the latest version of Goose on macOS: - - ```sh - curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash - ``` - This script will fetch the latest version of Goose and set it up on your system. - - If you'd like to install without interactive configuration, disable `CONFIGURE`: - - ```sh - curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | CONFIGURE=false bash - ``` - - :::tip Updating Goose - It's best to keep Goose updated. To update Goose, run: - ```sh - goose update - ``` - ::: - - + Install Goose directly from the browser or with [Homebrew](https://brew.sh/).

Option 1: Install via Download

@@ -61,7 +40,7 @@ import DesktopInstallButtons from '@site/src/components/DesktopInstallButtons'; ---
:::note Permissions - If you’re on an Apple Mac M3 and the Goose desktop app shows no window on launch, check and update the following: + If you're on an Apple Mac M3 and the Goose desktop app shows no window on launch, check and update the following: Ensure the `~/.config` directory has read and write access. @@ -69,6 +48,27 @@ import DesktopInstallButtons from '@site/src/components/DesktopInstallButtons'; :::
+ + Run the following command to install the latest version of Goose on macOS: + + ```sh + curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash + ``` + This script will fetch the latest version of Goose and set it up on your system. + + If you'd like to install without interactive configuration, disable `CONFIGURE`: + + ```sh + curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | CONFIGURE=false bash + ``` + + :::tip Updating Goose + It's best to keep Goose updated. To update Goose, run: + ```sh + goose update + ``` + ::: +
@@ -119,10 +119,17 @@ import DesktopInstallButtons from '@site/src/components/DesktopInstallButtons'; ## Set LLM Provider -Goose works with a set of [supported LLM providers][providers], and you’ll need an API key to get started. When you use Goose for the first time, you’ll be prompted to select a provider and enter your API key. +Goose works with a set of [supported LLM providers][providers], and you'll need an API key to get started. When you use Goose for the first time, you'll be prompted to select a provider and enter your API key. - + + Upon installing, the Provider screen will appear. Here is where you can choose your LLM Provider. + + ![Set Up a Provider UI](../assets/guides/set-up-provider-ui.png) + + Once selecting your provider, you'll be prompted to enter an API key if applicable. Do so, and click `Submit`. + + Upon installing, Goose will automatically enter its configuration screen. Here is where you can set up your LLM provider. :::tip Windows Users @@ -167,18 +174,20 @@ Goose works with a set of [supported LLM providers][providers], and you’ll nee ``` ::: - - Upon installing, the Provider screen will appear. Here is where you can choose your LLM Provider. - - ![Set Up a Provider UI](../assets/guides/set-up-provider-ui.png) - - Once selecting your provider, you'll be prompted to enter an API key if applicable. Do so, and click `Submit`. - ## Update Provider - + + **To update your LLM provider and API key:** + + 1. Click on the three dots in the top-right corner. + 2. Select `Provider Settings` from the menu. + 2. Choose a provider from the list. + 3. Click Edit, enter your API key, and click `Set as Active`. + + + **To update your LLM provider and API key:** 1. Run the following command: ```sh @@ -211,15 +220,6 @@ Goose works with a set of [supported LLM providers][providers], and you’ll nee └ Configuration saved successfully ``` - - **To update your LLM provider and API key:** - - 1. Click on the three dots in the top-right corner. - 2. Select `Provider Settings` from the menu. - 2. Choose a provider from the list. - 3. Click Edit, enter your API key, and click `Set as Active`. - - @@ -227,45 +227,39 @@ Goose works with a set of [supported LLM providers][providers], and you’ll nee ## Running Goose - + + Starting a session in the Goose Desktop is straightforward. After choosing your provider, you'll see the session interface ready for use. + + Type your questions, tasks, or instructions directly into the input field, and Goose will get to work immediately. + + From your terminal, navigate to the directory you'd like to start from and run: ```sh goose session ``` - - Starting a session in the Goose Desktop is straightforward. After choosing your provider, you’ll see the session interface ready for use. - - Type your questions, tasks, or instructions directly into the input field, and Goose will get to work immediately. - -## Goose Configuration Sharing - -Goose Desktop and CLI applications share all core configurations, including LLM provider settings, model selection, and extension configurations. When you install or configure extensions in either interface, the settings are stored in a central location at `~/.config/goose/config.yaml`, making them available to both the Desktop application and CLI. - -You can manage these shared configurations through the Desktop settings or by using the CLI command `goose configure`, which allows you to add, remove, or toggle extensions and modify other core settings. - -## Shared Configuration Settings +## Shared Configuration Settings The Goose CLI and Desktop UI share all core configurations, including LLM provider settings, model selection, and extension configurations. When you install or configure extensions in either interface, the settings are stored in a central location at `~/.config/goose/config.yaml`, making them available to both the Desktop application and CLI. This makes it convenient to switch between interfaces while maintaining consistent settings. :::note While core configurations are shared between interfaces, extensions have flexibility in how they store authentication credentials. Some extensions may use the shared config file while others implement their own storage methods. -::: +::: - + + Navigate to shared configurations through: + 1. Click `...` in the upper right corner + 2. Click `Advanced Settings` + + Use the following command to manage shared configurations: ```sh goose configure ``` - - Navigate to shared configurations through: - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - ## Additional Resources @@ -275,4 +269,4 @@ You can also configure Extensions to extend Goose's functionality, including add [using-extensions]: /docs/getting-started/using-extensions [providers]: /docs/getting-started/providers [handling-rate-limits]: /docs/guides/handling-llm-rate-limits-with-goose -[mcp]: https://www.anthropic.com/news/model-context-protocol +[mcp]: https://www.anthropic.com/news/model-context-protocol \ No newline at end of file diff --git a/documentation/docs/getting-started/providers.md b/documentation/docs/getting-started/providers.md index 20d90762..3b133eb9 100644 --- a/documentation/docs/getting-started/providers.md +++ b/documentation/docs/getting-started/providers.md @@ -21,23 +21,42 @@ Goose relies heavily on tool calling capabilities and currently works best with |-----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [Amazon Bedrock](https://aws.amazon.com/bedrock/) | Offers a variety of foundation models, including Claude, Jurassic-2, and others. **AWS environment variables must be set in advance, not configured through `goose configure`** | `AWS_PROFILE`, or `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, ... | | [Anthropic](https://www.anthropic.com/) | Offers Claude, an advanced AI model for natural language tasks. | `ANTHROPIC_API_KEY`, `ANTHROPIC_HOST` (optional) | -| [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/) | Access Azure-hosted OpenAI models, including GPT-4 and GPT-3.5. | `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_DEPLOYMENT_NAME` | +| [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/) | Access Azure-hosted OpenAI models, including GPT-4 and GPT-3.5. Supports both API key and Azure credential chain authentication. | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_DEPLOYMENT_NAME`, `AZURE_OPENAI_API_KEY` (optional) | | [Databricks](https://www.databricks.com/) | Unified data analytics and AI platform for building and deploying models. | `DATABRICKS_HOST`, `DATABRICKS_TOKEN` | | [Gemini](https://ai.google.dev/gemini-api/docs) | Advanced LLMs by Google with multimodal capabilities (text, images). | `GOOGLE_API_KEY` | | [GCP Vertex AI](https://cloud.google.com/vertex-ai) | Google Cloud's Vertex AI platform, supporting Gemini and Claude models. **Credentials must be configured in advance. Follow the instructions at https://cloud.google.com/vertex-ai/docs/authentication.** | `GCP_PROJECT_ID`, `GCP_LOCATION` and optional `GCP_MAX_RETRIES` (6), `GCP_INITIAL_RETRY_INTERVAL_MS` (5000), `GCP_BACKOFF_MULTIPLIER` (2.0), `GCP_MAX_RETRY_INTERVAL_MS` (320_000). | +| [GitHub Copilot](https://docs.github.com/en/copilot/using-github-copilot/ai-models) | Access to GitHub Copilot's chat models including gpt-4o, o1, o3-mini, and Claude models. Uses device code authentication flow for secure access. | Uses GitHub device code authentication flow (no API key needed) | | [Groq](https://groq.com/) | High-performance inference hardware and tools for LLMs. | `GROQ_API_KEY` | | [Ollama](https://ollama.com/) | Local model runner supporting Qwen, Llama, DeepSeek, and other open-source models. **Because this provider runs locally, you must first [download and run a model](/docs/getting-started/providers#local-llms-ollama).** | `OLLAMA_HOST` | | [OpenAI](https://platform.openai.com/api-keys) | Provides gpt-4o, o1, and other advanced language models. Also supports OpenAI-compatible endpoints (e.g., self-hosted LLaMA, vLLM, KServe). **o1-mini and o1-preview are not supported because Goose uses tool calling.** | `OPENAI_API_KEY`, `OPENAI_HOST` (optional), `OPENAI_ORGANIZATION` (optional), `OPENAI_PROJECT` (optional), `OPENAI_CUSTOM_HEADERS` (optional) | | [OpenRouter](https://openrouter.ai/) | API gateway for unified access to various models with features like rate-limiting management. | `OPENROUTER_API_KEY` | - +| [Venice AI](https://venice.ai/home) | Provides access to open source models like Llama, Mistral, and Qwen while prioritizing user privacy. **Requires an account and an [API key](https://docs.venice.ai/overview/guides/generating-api-key)**. | `VENICE_API_KEY`, `VENICE_HOST` (optional), `VENICE_BASE_PATH` (optional), `VENICE_MODELS_PATH` (optional) | ## Configure Provider -To configure your chosen provider or see available options, run `goose configure` in the CLI or visit the `Provider Settings` page in the Goose Desktop. +To configure your chosen provider or see available options, run `goose configure` in the CLI or visit the `Settings` page in the Goose Desktop. - + + **To update your LLM provider and API key:** + 1. Click the gear on the Goose Desktop toolbar + 1. Click `Advanced Settings` + 1. Under `Models`, click `Configure provider` + 1. Click `Configure` on the LLM provider to update + 1. Add additional configurations (API key, host, etc) then press `submit` + + **To change provider model** + 1. Click the gear on the Goose Desktop toolbar + 2. Click `Advanced Settings` + 3. Under `Models`, click `Switch models` + 5. Select a Provider from drop down menu + 6. Select a model from drop down menu + 7. Press `Select Model` + + You can explore more models by selecting a `provider` name under `Browse by Provider`. A link will appear, directing you to the provider's website. Once you've found the model you want, return to step 6 and paste the model name. + + 1. Run the following command: ```sh @@ -89,27 +108,6 @@ To configure your chosen provider or see available options, run `goose configure └ ``` - - **To update your LLM provider and API key:** - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - 3. Next to `Models`, click `Browse` - 4. Click `Configure` in the upper right corner - 4. Press the `+` button next to the provider of your choice - 5. Add additional configurations (API key, host, etc) then press `submit` - - **To change provider model** - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - 3. Next to `Models`, click `Browse` - 4. Scroll down to `Add Model` - 5. Select a Provider from drop down menu - 6. Enter Model name - 7. Press `+ Add Model` - - You can explore more models by selecting a `provider` name under `Browse by Provider`. A link will appear, directing you to the provider's website. Once you've found the model you want, return to step 6 and paste the model name. - - ## Using Custom OpenAI Endpoints @@ -171,17 +169,7 @@ Goose supports using custom OpenAI-compatible endpoints, which is particularly u ### Setup Instructions - - 1. Run `goose configure` - 2. Select `Configure Providers` - 3. Choose `OpenAI` as the provider - 4. Enter your configuration when prompted: - - API key - - Host URL (if using custom endpoint) - - Organization ID (if using organization tracking) - - Project identifier (if using project management) - - + 1. Click `...` in the upper right corner 2. Click `Advanced Settings` 3. Next to `Models`, click the `browse` link @@ -194,6 +182,16 @@ Goose supports using custom OpenAI-compatible endpoints, which is particularly u - Project (for resource management) 7. Press `submit` + + 1. Run `goose configure` + 2. Select `Configure Providers` + 3. Choose `OpenAI` as the provider + 4. Enter your configuration when prompted: + - API key + - Host URL (if using custom endpoint) + - Organization ID (if using organization tracking) + - Project identifier (if using project management) + :::tip Enterprise Deployment @@ -217,7 +215,16 @@ Google Gemini provides a free tier. To start using the Gemini API with Goose, yo To set up Google Gemini with Goose, follow these steps: - + + **To update your LLM provider and API key:** + + 1. Click on the three dots in the top-right corner. + 2. Select `Provider Settings` from the menu. + 2. Choose `Google Gemini` as provider from the list. + 3. Click Edit, enter your API key, and click `Set as Active`. + + + 1. Run: ```sh goose configure @@ -246,16 +253,6 @@ To set up Google Gemini with Goose, follow these steps: │ └ Configuration saved successfully ``` - - - - **To update your LLM provider and API key:** - - 1. Click on the three dots in the top-right corner. - 2. Select `Provider Settings` from the menu. - 2. Choose `Google Gemini` as provider from the list. - 3. Click Edit, enter your API key, and click `Set as Active`. - @@ -375,7 +372,12 @@ ollama run michaelneale/deepseek-r1-goose ``` - + + 3. Click `...` in the top-right corner. + 4. Navigate to `Advanced Settings` -> `Browse Models` -> and select `Ollama` from the list. + 5. Enter `michaelneale/deepseek-r1-goose` for the model name. + + 3. In a separate terminal window, configure with Goose: ```sh @@ -451,13 +453,22 @@ ollama run michaelneale/deepseek-r1-goose └ Configuration saved successfully ``` - - 3. Click `...` in the top-right corner. - 4. Navigate to `Advanced Settings` -> `Browse Models` -> and select `Ollama` from the list. - 5. Enter `michaelneale/deepseek-r1-goose` for the model name. - +## Azure OpenAI Credential Chain + +Goose supports two authentication methods for Azure OpenAI: + +1. **API Key Authentication** - Uses the `AZURE_OPENAI_API_KEY` for direct authentication +2. **Azure Credential Chain** - Uses Azure CLI credentials automatically without requiring an API key + +To use the Azure Credential Chain: +- Ensure you're logged in with `az login` +- Have appropriate Azure role assignments for the Azure OpenAI service +- Configure with `goose configure` and select Azure OpenAI, leaving the API key field empty + +This method simplifies authentication and enhances security for enterprise environments. + --- If you have any questions or need help with a specific provider, feel free to reach out to us on [Discord](https://discord.gg/block-opensource) or on the [Goose repo](https://github.com/block/goose). diff --git a/documentation/docs/getting-started/using-extensions.md b/documentation/docs/getting-started/using-extensions.md index 540c3c40..451b7eec 100644 --- a/documentation/docs/getting-started/using-extensions.md +++ b/documentation/docs/getting-started/using-extensions.md @@ -30,8 +30,13 @@ Here are the built-in extensions: #### Toggling Built-in Extensions + + 1. Click `...` in the top right corner of the Goose Desktop. + 2. Select `Advanced Settings` from the menu. + 3. Under `Extensions`, you can toggle the built-in extensions on or off. + - + If you know the exact name of the extension you'd like to add, run: @@ -46,7 +51,7 @@ Here are the built-in extensions: goose configure ``` 2. Select `Add Extension` from the menu. - 3. Choose the type of extension you’d like to add: + 3. Choose the type of extension you'd like to add: - `Built-In Extension`: Use an extension that comes pre-installed with Goose. - `Command-Line Extension`: Add a local command or script to run as an extension. - `Remote Extension`: Connect to a remote system via SSE (Server-Sent Events). @@ -78,11 +83,6 @@ Here are the built-in extensions: └ Enabled jetbrains extension ``` - - 1. Click `...` in the top right corner of the Goose Desktop. - 2. Select `Advanced Settings` from the menu. - 3. Under `Extensions`, you can toggle the built-in extensions on or off. - @@ -112,7 +112,25 @@ See available servers in the **[MCP Server Directory](https://www.pulsemcp.com/s ::: - + + + 1. Click `...` in the top right corner of the Goose Desktop. + 2. Select `Advanced Settings` from the menu. + 3. Under `Extensions`, click `Add custom extension`. + 4. On the `Add custom extension` modal, enter the necessary details + - If adding an environment variable, click `Add` button to the right of the variable + - The `Timeout` field lets you set how long Goose should wait for a tool call from this extension to complete + 5. Click `Add` button + + #### Example of adding the [Knowledge Graph Memory MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/memory): + * **Type**: `Standard IO` + * **ID**: `kgm-mcp` (_set this to whatever you want_) + * **Name**: `Knowledge Graph Memory` (_set this to whatever you want_) + * **Description**: `maps and stores complex relationships between concepts` (_set this to whatever you want_) + * **Command**: `npx -y @modelcontextprotocol/server-memory` + + + 1. Run the following command: @@ -122,7 +140,7 @@ See available servers in the **[MCP Server Directory](https://www.pulsemcp.com/s 2. Select `Add Extension` from the menu. - 3. Choose the type of extension you’d like to add: + 3. Choose the type of extension you'd like to add: - `Built-In Extension`: Use an extension that comes pre-installed with Goose. - `Command-Line Extension`: Add a local command or script to run as an extension. - `Remote Extension`: Connect to a remote system via SSE (Server-Sent Events). @@ -226,25 +244,61 @@ Note: Java and Kotlin extensions are only support on Linux and macOS - - - 1. Click `...` in the top right corner of the Goose Desktop. - 2. Select `Advanced Settings` from the menu. - 3. Under `Extensions`, click `Add custom extension`. - 4. On the `Add custom extension` modal, enter the necessary details - - If adding an environment variable, click `Add` button to the right of the variable - - The `Timeout` field lets you set how long Goose should wait for a tool call from this extension to complete - 5. Click `Add` button - - #### Example of adding the [Knowledge Graph Memory MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/memory): - * **Type**: `Standard IO` - * **ID**: `kgm-mcp` (_set this to whatever you want_) - * **Name**: `Knowledge Graph Memory` (_set this to whatever you want_) - * **Description**: `maps and stores complex relationships between concepts` (_set this to whatever you want_) - * **Command**: `npx -y @modelcontextprotocol/server-memory` + + + +### Deeplinks + +Extensions can be installed using Goose's deep link protocol. The URL format varies based on the extension type: + + + +``` +goose://extension?cmd=&arg=&id=&name=&description= +``` + +Required parameters: +- `cmd`: The base command to run, one of `jbang`, `npx`, `uvx`, `goosed`, or `docker` +- `arg`: (cmd only) Command arguments (can be repeated for multiple arguments: `&arg=...&arg=...`) +- `timeout`: Maximum time (in seconds) to wait for extension responses +- `id`: Unique identifier for the extension +- `name`: Display name for the extension +- `description`: Brief description of the extension's functionality + +A command like `npx -y @modelcontextprotocol/server-github` would be represented as: + +``` +goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol/server-github&timeout=&id=&name=&description= +``` + +Note that each parameter to the `npx` command is passed as a separate `arg` parameter in the deeplink. + + +``` +goose://extension?url=&id=&name=&description= +``` + +Parameters: +- `url`: The URL of the remote SSE server +- `timeout`: Maximum time (in seconds) to wait for extension responses +- `id`: Unique identifier for the extension +- `name`: Display name for the extension +- `description`: Brief description of the extension's functionality + +For example, a deeplink for a URL like `http://localhost:8080/sse` would look like this when URL-encoded: + +``` +goose://extension?url=http%3A%2F%2Flocalhost%3A8080%2Fsse&timeout=&id=&name=&description=> +``` + +:::note +All parameters in the deeplink must be URL-encoded. For example, spaces should be replaced with `%20`, and `@` should be replaced with `%40`. +::: + + ### Config Entry For advanced users, you can also directly edit the config file (`~/.config/goose/config.yaml`) to add, remove, or update an extension: @@ -266,7 +320,14 @@ extensions: You can enable or disable installed extensions based on your workflow needs. - + + 1. Click the three dots in the top-right corner of the application. + 2. Select `Advanced Settings` from the menu, scroll down to the `Extensions` section. + 2. Use the toggle switch next to each extension to enable or disable it. + + + + 1. Run the following command to open up Goose's configurations: ```sh goose configure @@ -289,21 +350,148 @@ You can enable or disable installed extensions based on your workflow needs. └ ``` - - 1. Click the three dots in the top-right corner of the application. - 2. Select `Advanced Settings` from the menu, scroll down to the `Extensions` section. - 2. Use the toggle switch next to each extension to enable or disable it. - - +## Automatically Enabled Extensions + +The Smart Extension Recommendation system in Goose automatically identifies and suggests relevant extensions based on your tasks and needs. This section explains how to use this feature effectively and understand its capabilities and limitations. + +When you request a task, Goose checks its enabled extensions and their tools to determine if it can fulfill the request. If not, it suggests or enables additional extensions as needed. You can also request specific extensions by name. + + +:::warning +Any extensions enabled dynamically are only enabled for the current session. To keep extensions enabled between sessions, see [Enabling/Disabling Extensions](#enablingdisabling-extensions). +::: + +### Automatic Detection + +Goose automatically detects when an extension is needed based on your task requirements. Here's an example of how Goose identifies and enables a needed extension during a conversation: + + + + +#### Goose Prompt +```plaintext +Find all orders with pending status from our production database +``` + +#### Goose Output + +```plaintext +I'll help you search for available extensions that might help us interact with PostgreSQL databases. + +🔍 Search Available Extensions +└─ Output ▼ + + I see there's a PostgreSQL extension available. Let me enable it so we can query your database. + +🔧 Manage Extensions +└─ action enable + extension_name postgresql + +The extension 'postgresql' has been installed successfully + +Great! Now I can help you query the database... +``` + + + + +#### Goose Prompt +```plaintext +Find all orders with pending status from our production database +``` + +#### Goose Output + +```sh +I apologize, but I notice that I don't currently have access to your database. Let me search if there are any database-related extensions available. +─── search_available_extensions | platform ────────────────────────── + +I see that there is a "postgresql" extension available. Let me enable it so I can help you query your database. +─── enable_extension | platform ────────────────────────── +extension_name: postgresql + + +■ Goose would like to enable the following extension, do you approve? +// highlight-start +| ● Yes, for this session +// highlight-end +| ○ No +``` + + + + +### Direct Request + +Goose responds to explicit requests for extensions, allowing users to manually enable specific tools they need. Here's an example of how Goose handles a direct request to enable an extension: + + + + +#### Goose Prompt + +```plaintext +Use PostgreSQL extension +``` + +#### Goose Output + +```plaintext +I'll help enable the PostgreSQL extension for you. + +🔧 Manage Extensions +└─ action enable + extension_name postgresql + +The extension 'postgresql' has been installed successfully + +The PostgreSQL extension is now ready to use. What would you like to do with it? +``` + + + + +#### Goose Prompt + +```sh +Use the PostgreSQL extension +``` + +#### Goose Output + +```sh +I'll help enable the PostgreSQL extension for you. +─── enable_extension | platform ────────────────────────── +extension_name: postgresql + + +■ Goose would like to enable the following extension, do you approve? +// highlight-start +| ● Yes, for this session +// highlight-end +| ○ No +``` + + + ## Removing Extensions You can remove installed extensions. - + + + 1. Click `...` in the top right corner of the Goose Desktop. + 2. Select `Advanced Settings` from the menu. + 3. Under `Extensions`, find the extension you'd like to remove and click on the settings icon beside it. + 4. In the dialog that appears, click `Remove Extension`. + + + + :::info To remove an extension, you must [disable](#enablingdisabling-extensions) it first. ::: @@ -327,20 +515,9 @@ You can remove installed extensions. ``` 5. Press Enter to save - - - 1. Click `...` in the top right corner of the Goose Desktop. - 2. Select `Advanced Settings` from the menu. - 3. Under `Extensions`, find the extension you'd like to remove and click on the settings icon beside it. - 4. In the dialog that appears, click `Remove Extension`. - - - - - ## Starting Session with Extensions You can start a tailored Goose session with specific extensions directly from the CLI. @@ -404,6 +581,7 @@ goose session --with-extension "GITHUB_PERSONAL_ACCESS_TOKEN= npx -y Note that you'll need [Node.js](https://nodejs.org/) installed on your system to run this command, as it uses `npx`. ::: + ### Remote Extensions over SSE To enable a remote extension over SSE while starting a session, run the following command: @@ -422,4 +600,4 @@ goose session --with-remote-extension "http://localhost:8080/sse" Goose extensions are implemented with MCP, a standard protocol that allows AI models and agents to securely connect with local or remote resources. Learn how to build your own [extension as an MCP server](https://modelcontextprotocol.io/quickstart/server). -[extensions-directory]: https://block.github.io/goose/v1/extensions +[extensions-directory]: https://block.github.io/goose/v1/extensions \ No newline at end of file diff --git a/documentation/docs/guides/adjust-tool-output.md b/documentation/docs/guides/adjust-tool-output.md index 7115ef9e..65c61860 100644 --- a/documentation/docs/guides/adjust-tool-output.md +++ b/documentation/docs/guides/adjust-tool-output.md @@ -3,7 +3,32 @@ sidebar_position: 11 title: Adjusting Tool Output Verbosity sidebar_label: Adjust Tool Output --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +Response Styles customize how tool interactions are displayed in the Goose Desktop chat window. + +To change this setting: +1. Click the menu icon in the top right corner. +2. Select `Advanced Settings` from the menu. +3. Under `Response Styles`, select either `Detailed` or `Concise`. + +- **Concise** (Default) + - Tool calls are collapsed by default + - Shows only which tool Goose used + - Best for users focusing on results rather than technical details + +- **Detailed** + - Tool calls are expanded by default + - Shows the details of tool calls and their responses + - Best for debugging or learning how Goose works + +This setting only affects the default state of tool calls in the conversation. You can always manually expand or collapse any tool call regardless of your chosen style. + + + When working with the Goose CLI, you can control the verbosity of tool output. To adjust the tool output, run: @@ -56,3 +81,5 @@ Next, choose one of the available modes: - Shows all tool outputs - Example: Shell command outputs - Most verbose level + + \ No newline at end of file diff --git a/documentation/docs/guides/allowlist.md b/documentation/docs/guides/allowlist.md new file mode 100644 index 00000000..5a1f3081 --- /dev/null +++ b/documentation/docs/guides/allowlist.md @@ -0,0 +1,88 @@ +--- +sidebar_position: 19 +title: Goose Extension Allowlist +sidebar_label: Extension Allowlist +--- + +Goose is an extensible framework that, by default, allows you to install any MCP server. However, you may want stricter controls on which MCP servers can be installed as extensions (e.g. in a corporate setting). + +This guide explains how you can create an **allowlist** of safe extensions that work with Goose Desktop and CLI. An allowlist lets administrators control which MCP servers can be installed as Goose extensions. When enabled, Goose will only install extensions that are on the list, and will block installation of any others. + +## How It Works + +1. The allowlist is a YAML file that contains a list of allowed extension commands. +2. Goose fetches the allowlist from a URL specified by the `GOOSE_ALLOWLIST` environment variable. +3. The allowlist is fetched when first needed and is cached. It is refetched on every restart of Goose. +4. When a user attempts to install an extension, Goose checks the MCP server's installation command against the allowlist. +5. If the command is not in the allowlist, the extension installation is rejected. + +## Configuration + +### 1. Create and Deploy Allowlist + +The allowlist must be a YAML file with the following structure: + +```yaml +extensions: + - id: extension-id-1 + command: command-name-1 + - id: extension-id-2 + command: command-name-2 + # ... more extensions +``` + +#### Example + +In this example, only the Slack, GitHub, and Jira extensions can be installed: + +```yaml +extensions: + - id: slack + command: uvx mcp_slack + - id: github + command: uvx mcp_github + - id: jira + command: uvx mcp_jira +``` + + +After creating the allowlist, you must deploy it to a URL. + + +### 2. Set Environment Variable + +Create an environment variable called `GOOSE_ALLOWLIST` and set the value to the URL of your YAML file: + +```bash +export GOOSE_ALLOWLIST=https://example.com/goose-allowlist.yaml +``` + +You can also add this export to your shell configuration file (On a Mac, it's your `~/.bashrc` or `~/.zshrc` file). + +:::info +If this environment variable is not set, no allowlist restrictions are applied. With no restrictions, all extensions can be installed. +::: + + +## Best Practices + +To effectively use the allowlist with exact matching: + +1. **Specify commands**: Define the exact command string that you want to allow. +2. **Include full paths**: If you want to allow a command only from a specific path, include the full path in the allowlist. +3. **Audit regularly**: Review your allowlist frequently to ensure it only contains the commands you intend to allow. +4. **Use HTTPS**: Use an HTTPS URL for your allowlist to prevent man-in-the-middle attacks. +5. **Restrict edit access**: Ensure that only authorized users can edit the allowlist. +6. **Validate entries**: Carefully review the allowlist to ensure only trusted commands are included. +7. **Monitor installations**: Watch for rejected commands during extension installation, which might indicate attempted abuse. + + +## Troubleshooting + +If extensions are being rejected unexpectedly: + +1. Check if the `GOOSE_ALLOWLIST` environment variable is set correctly. +2. Verify that the allowlist file is accessible from the server. +3. Ensure the allowlist file is properly formatted YAML. +4. Check [server logs](/docs/guides/logs) for any errors related to fetching or parsing the allowlist. +5. Verify that the command in the extension installations exactly matches what's in the allowlist. \ No newline at end of file diff --git a/documentation/docs/guides/config-file.md b/documentation/docs/guides/config-file.md new file mode 100644 index 00000000..3eb4d788 --- /dev/null +++ b/documentation/docs/guides/config-file.md @@ -0,0 +1,119 @@ +--- +sidebar_position: 15 +title: Configuration File +sidebar_label: Configuration File +--- + +# Configuration File + +Goose uses a YAML configuration file to manage settings and extensions. This file is located at: + +* macOS/Linux: `~/.config/goose/config.yaml` +* Windows: `%APPDATA%\Block\goose\config\config.yaml` + +The configuration file allows you to set default behaviors, configure language models, and manage extensions. While many settings can also be set using environment variables, the config file provides a persistent way to maintain your preferences. + +## Global Settings + +The following settings can be configured at the root level of your config.yaml file: + +| Setting | Purpose | Values | Default | Required | +|---------|---------|---------|---------|-----------| +| `GOOSE_PROVIDER` | Primary LLM provider | "anthropic", "openai", etc. | None | Yes | +| `GOOSE_MODEL` | Default model to use | Model name (e.g., "claude-3.5-sonnet", "gpt-4") | None | Yes | +| `GOOSE_TEMPERATURE` | Model response randomness | Float between 0.0 and 1.0 | Model-specific | No | +| `GOOSE_MODE` | Tool execution behavior | "auto", "approve", "chat", "smart_approve" | "smart_approve" | No | +| `GOOSE_PLANNER_PROVIDER` | Provider for planning mode | Same as GOOSE_PROVIDER options | Falls back to GOOSE_PROVIDER | No | +| `GOOSE_PLANNER_MODEL` | Model for planning mode | Model name | Falls back to GOOSE_MODEL | No | +| `GOOSE_TOOLSHIM` | Enable tool interpretation | true/false | false | No | +| `GOOSE_TOOLSHIM_OLLAMA_MODEL` | Model for tool interpretation | Model name (e.g., "llama3.2") | System default | No | +| `GOOSE_CLI_MIN_PRIORITY` | Tool output verbosity | Float between 0.0 and 1.0 | 0.0 | No | +| `GOOSE_ALLOWLIST` | URL for allowed extensions | Valid URL | None | No | + +## Example Configuration + +Here's a basic example of a config.yaml file: + +```yaml +# Model Configuration +GOOSE_PROVIDER: "anthropic" +GOOSE_MODEL: "claude-3.5-sonnet" +GOOSE_TEMPERATURE: 0.7 + +# Planning Configuration +GOOSE_PLANNER_PROVIDER: "openai" +GOOSE_PLANNER_MODEL: "gpt-4" + +# Tool Configuration +GOOSE_MODE: "smart_approve" +GOOSE_TOOLSHIM: true +GOOSE_CLI_MIN_PRIORITY: 0.2 + +# Extensions Configuration +extensions: + developer: + bundled: true + enabled: true + name: developer + timeout: 300 + type: builtin + + memory: + bundled: true + enabled: true + name: memory + timeout: 300 + type: builtin +``` + +## Extensions Configuration + +Extensions are configured under the `extensions` key. Each extension can have the following settings: + +```yaml +extensions: + extension_name: + bundled: true/false # Whether it's included with Goose + display_name: "Name" # Human-readable name (optional) + enabled: true/false # Whether the extension is active + name: "extension_name" # Internal name + timeout: 300 # Operation timeout in seconds + type: "builtin"/"stdio" # Extension type + + # Additional settings for stdio extensions: + cmd: "command" # Command to execute + args: ["arg1", "arg2"] # Command arguments + description: "text" # Extension description + env_keys: [] # Required environment variables + envs: {} # Environment values +``` + +## Configuration Priority + +Settings are applied in the following order of precedence: + +1. Environment variables (highest priority) +2. Config file settings +3. Default values (lowest priority) + +## Security Considerations + +- Avoid storing sensitive information (API keys, tokens) in the config file +- Use the system keyring for storing secrets +- If keyring is disabled, secrets are stored in a separate `secrets.yaml` file + +## Updating Configuration + +Changes to the config file require restarting Goose to take effect. You can verify your current configuration using: + +```bash +goose info -v +``` + +This will show all active settings and their current values. + +## See Also + +- [Environment Variables](./environment-variables.md) - For environment variable configuration +- [Using Extensions](/docs/getting-started/using-extensions.md) - For more details on extension configuration +- [Creating Plans](./creating-plans.md) - For information about planning mode configuration \ No newline at end of file diff --git a/documentation/docs/guides/environment-variables.md b/documentation/docs/guides/environment-variables.md new file mode 100644 index 00000000..7888b813 --- /dev/null +++ b/documentation/docs/guides/environment-variables.md @@ -0,0 +1,123 @@ +--- +sidebar_position: 20 +title: Environment Variables +sidebar_label: Environment Variables +--- + +Goose supports various environment variables that allow you to customize its behavior. This guide provides a comprehensive list of available environment variables grouped by their functionality. + +## Model Configuration + +These variables control the [language models](/docs/getting-started/providers) and their behavior. +### Basic Provider Configuration + +These are the minimum required variables to get started with Goose. + +| Variable | Purpose | Values | Default | +|----------|---------|---------|---------| +| `GOOSE_PROVIDER` | Specifies the LLM provider to use | [See available providers](/docs/getting-started/providers#available-providers) | None (must be [configured](/docs/getting-started/providers#configure-provider)) | +| `GOOSE_MODEL` | Specifies which model to use from the provider | Model name (e.g., "gpt-4", "claude-3.5-sonnet") | None (must be configured) | +| `GOOSE_TEMPERATURE` | Sets the [temperature](https://medium.com/@kelseyywang/a-comprehensive-guide-to-llm-temperature-%EF%B8%8F-363a40bbc91f) for model responses | Float between 0.0 and 1.0 | Model-specific default | + +**Examples** + +```bash +# Basic model configuration +export GOOSE_PROVIDER="anthropic" +export GOOSE_MODEL="claude-3.5-sonnet" +export GOOSE_TEMPERATURE=0.7 +``` +### Advanced Provider Configuration + +These variables are needed when using custom endpoints, enterprise deployments, or specific provider implementations. + +| Variable | Purpose | Values | Default | +|----------|---------|---------|---------| +| `GOOSE_PROVIDER__TYPE` | The specific type/implementation of the provider | [See available providers](/docs/getting-started/providers#available-providers) | Derived from GOOSE_PROVIDER | +| `GOOSE_PROVIDER__HOST` | Custom API endpoint for the provider | URL (e.g., "https://api.openai.com") | Provider-specific default | +| `GOOSE_PROVIDER__API_KEY` | Authentication key for the provider | API key string | None | + +**Examples** + +```bash +# Advanced provider configuration +export GOOSE_PROVIDER__TYPE="anthropic" +export GOOSE_PROVIDER__HOST="https://api.anthropic.com" +export GOOSE_PROVIDER__API_KEY="your-api-key-here" +``` +## Planning Mode Configuration + +These variables control Goose's [planning functionality](/docs/guides/creating-plans). + +| Variable | Purpose | Values | Default | +|----------|---------|---------|---------| +| `GOOSE_PLANNER_PROVIDER` | Specifies which provider to use for planning mode | [See available providers](/docs/getting-started/providers#available-providers) | Falls back to GOOSE_PROVIDER | +| `GOOSE_PLANNER_MODEL` | Specifies which model to use for planning mode | Model name (e.g., "gpt-4", "claude-3.5-sonnet")| Falls back to GOOSE_MODEL | + +**Examples** + +```bash +# Planning mode with different model +export GOOSE_PLANNER_PROVIDER="openai" +export GOOSE_PLANNER_MODEL="gpt-4" +``` + +## Tool Configuration + +These variables control how Goose handles [tool permissions](/docs/guides/tool-permissions) and their execution. + +| Variable | Purpose | Values | Default | +|----------|---------|---------|---------| +| `GOOSE_MODE` | Controls how Goose handles tool execution | "auto", "approve", "chat", "smart_approve" | "smart_approve" | +| `GOOSE_TOOLSHIM` | Enables/disables tool call interpretation | "1", "true" (case insensitive) to enable | false | +| `GOOSE_TOOLSHIM_OLLAMA_MODEL` | Specifies the model for [tool call interpretation](/docs/guides/experimental-features/#ollama-tool-shim) | Model name (e.g. llama3.2, qwen2.5) | System default | +| `GOOSE_CLI_MIN_PRIORITY` | Controls verbosity of [tool output](/docs/guides/adjust-tool-output) | Float between 0.0 and 1.0 | 0.0 | +| `GOOSE_CLI_TOOL_PARAMS_TRUNCATION_MAX_LENGTH` | Maximum length for tool parameter values before truncation in CLI output (not in debug mode) | Integer | 40 | + +**Examples** + +```bash +# Enable tool interpretation +export GOOSE_TOOLSHIM=true +export GOOSE_TOOLSHIM_OLLAMA_MODEL=llama3.2 +export GOOSE_MODE="auto" +export GOOSE_CLI_MIN_PRIORITY=0.2 # Show only medium and high importance output +export GOOSE_CLI_TOOL_PARAMS_MAX_LENGTH=100 # Show up to 100 characters for tool parameters in CLI output +``` +## Security Configuration + +These variables control security related features. + +| Variable | Purpose | Values | Default | +|----------|---------|---------|---------| +| `GOOSE_ALLOWLIST` | Controls which extensions can be loaded | URL for [allowed extensions](/docs/guides/allowlist) list | Unset | +| `GOOSE_DISABLE_KEYRING` | Disables the system keyring for secret storage | Set to any value (e.g., "1", "true", "yes") to disable. The actual value doesn't matter, only whether the variable is set. | Unset (keyring enabled) | + +:::tip +When the keyring is disabled, secrets are stored here: + +* macOS/Linux: `~/.config/goose/secrets.yaml` +* Windows: `%APPDATA%\Block\goose\config\secrets.yaml` +::: + + +## Langfuse Integration + +These variables configure the [Langfuse integration for observability](/docs/tutorials/langfuse). + +| Variable | Purpose | Values | Default | +|----------|---------|---------|---------| +| `LANGFUSE_PUBLIC_KEY` | Public key for Langfuse integration | String | None | +| `LANGFUSE_SECRET_KEY` | Secret key for Langfuse integration | String | None | +| `LANGFUSE_URL` | Custom URL for Langfuse service | URL String | Default Langfuse URL | +| `LANGFUSE_INIT_PROJECT_PUBLIC_KEY` | Alternative public key for Langfuse | String | None | +| `LANGFUSE_INIT_PROJECT_SECRET_KEY` | Alternative secret key for Langfuse | String | None | + + +## Notes + +- Environment variables take precedence over configuration files. +- For security-sensitive variables (like API keys), consider using the system keyring instead of environment variables. +- Some variables may require restarting Goose to take effect. +- When using the planning mode, if planner-specific variables are not set, Goose will fall back to the main model configuration. + diff --git a/documentation/docs/guides/goose-cli-commands.md b/documentation/docs/guides/goose-cli-commands.md index f3ea4ba1..96abdb5d 100644 --- a/documentation/docs/guides/goose-cli-commands.md +++ b/documentation/docs/guides/goose-cli-commands.md @@ -36,12 +36,12 @@ goose configure **Options:** - **`-n, --name `** + **`-n, --name `** **Usage:** ```bash - goose session --name + goose session --name ``` - Resume a previous session @@ -53,7 +53,7 @@ goose configure **Usage:** ```bash - goose session --resume --name + goose session --resume --name ``` - Start a session with the specified extension @@ -116,6 +116,18 @@ goose configure goose session --with-builtin computercontroller ``` +- Enable debug mode to output complete tool responses, detailed parameter values, and full file paths + + **Options:** + + **`--debug`** + + **Usage:** + + ```bash + goose session --name my-session --debug + ``` + --- ### session list [options] @@ -123,6 +135,7 @@ List all saved sessions. - **`-v, --verbose`**: (Optional) Includes session file paths in the output. - **`-f, --format `**: Specify output format (`text` or `json`). Default is `text`. +- **`--ascending`**: Sort sessions by date in ascending order (oldest first). Default is descending order (newest first). **Usage:** @@ -139,6 +152,37 @@ goose session list --verbose # List sessions in JSON format goose session list --format json ``` +```bash +# Sort sessions by date in ascending order. +goose session list --ascending +``` +--- + +### session remove [options] + +Remove one or more saved sessions. + +**Options:** +- **`-i, --id `**: Remove a specific session by its ID +- **`-r, --regex `**: Remove sessions matching a regex pattern. For example: + +**Usage:** + +```bash +# Remove a specific session by ID +goose session remove -i 20250305_113223 + +# Remove all sessions starting with "project-" +goose session remove -r "project-.*" + +# Remove all sessions containing "migration" +goose session remove -r ".*migration.*" +``` + +:::caution +Session removal is permanent and cannot be undone. Goose will show which sessions will be removed and ask for confirmation before deleting. +::: + --- ### info [options] @@ -191,11 +235,11 @@ goose update --reconfigure ### mcp -Run an enabled MCP server specified by `` (e.g. `'Google Drive'`) +Run an enabled MCP server specified by `` (e.g. `'Google Drive'`) **Usage:** ```bash -goose mcp +goose mcp ``` --- @@ -209,12 +253,15 @@ Execute commands from an instruction file or stdin. Check out the [full guide](/ - **`-i, --instructions `**: Path to instruction file containing commands. Use - for stdin. - **`-t, --text `**: Input text to provide to Goose directly - **`-s, --interactive`**: Continue in interactive mode after processing initial input -- **`-n, --name `**: Name for this run session (e.g. `daily-tasks`) +- **`-n, --name `**: Name for this run session (e.g. `daily-tasks`) - **`-r, --resume`**: Resume from a previous run - **`--recipe `**: Load a custom recipe in current session - **`-p, --path `**: Path for this run session (e.g. `./playground.jsonl`) - **`--with-extension `**: Add stdio extensions (can be used multiple times in the same command) -- **`--with-builtin `**: Add builtin extensions by name (e.g., 'developer' or multiple: 'developer,github') +- **`--with-builtin `**: Add builtin extensions by name (e.g., 'developer' or multiple: 'developer,github') +- **`--debug`**: Output complete tool responses, detailed parameter values, and full file paths +- **`--explain`**: Show a recipe's title, description, and parameters +- **`--no-session`**: Run goose commands without creating or storing a session file **Usage:** @@ -233,20 +280,18 @@ goose run --recipe recipe.yaml --interactive #Generates an error: no text provided for prompt in headless mode goose run --recipe recipe_no_prompt.yaml +#Load a recipe in debug mode +goose run --recipe recipe.yaml --debug + +#Show recipe details +goose run --recipe recipe.yaml --explain + +#Run instructions from a file without session storage +goose run --no-session -i instructions.txt ``` --- -### agents - -Used to show the available implementations of the agent loop itself - -**Usage:** - -```bash -goose agents -``` - ### bench Used to evaluate system-configuration across a range of practical tasks. See the [detailed guide](/docs/guides/benchmarking) for more information. @@ -268,7 +313,7 @@ goose recipe - **`--help, -h`**: Print this message or the help for the subcommand -**Command Usage:** +**Usage:** ```bash # Validate a recipe file @@ -281,22 +326,76 @@ goose recipe deeplink $FILE.yaml goose recipe help ``` +--- +### project + +Start working on your last project or create a new one. + +A project is a record of a working directory and recent session metadata. Note that any directory where you run `goose project` becomes a tracked project, so you might want to run the command from the directory where you want to work. + +**Alias**: `p` + +**Usage:** +```bash +goose project +``` + +The command provides three options: +1. **Resume project with session**: Continue the last session in the project +2. **Resume project with fresh session**: Start a new session in the project +3. **Start new project in current directory**: Create a new project in the current directory + +:::note +Goose stores your project history in `~/.local/share/goose/projects.json`. +::: + +--- +### projects + +Choose one of your projects to start working on. + +**Alias**: `ps` + +**Usage:** +```bash +goose projects +``` + +Example output: +```bash +┌ Goose Project Manager +│ +◆ Select a project: +│ ● .../Users/svera (2025-05-21 18:42:05) +│ ○ .../Development/goose (2025-05-21 18:38:26) +│ ○ .../Documents/goose-recipes (2025-05-21q 18:29:15) +│ ○ .../Desktop/temp (2025-05-21 15:13:48)q +│ ○ .../doc_projects/shared (2025-05-21 14:32:22) +│ ○ Cancel +└ +``` + +After selecting a project, you'll be asked to either: +- **Resume previous session**: Continue the last session in the selected project +- **Start new session**: Start a new session in the selected project + --- ## Prompt Completion The CLI provides a set of slash commands that can be accessed during a session. These commands support tab completion for easier use. #### Available Commands -- `/exit` or `/quit` - Exit the current session -- `/t` - Toggle between Light/Dark/Ansi themes -- `/extension ` - Add a stdio extension (format: ENV1=val1 command args...) -- `/builtin ` - Add builtin extensions by name (comma-separated) -- `/prompts [--extension ]` - List all available prompts, optionally filtered by extension -- `/prompt [--info] [key=value...]` - Get prompt info or execute a prompt -- `/mode ` - Set the goose mode to use ('auto', 'approve', 'chat') -- `/plan ` - Create a structured plan based on the given message - `/?` or `/help` - Display this help message +- `/builtin ` - Add builtin extensions by name (comma-separated) +- `/exit` or `/quit` - Exit the current session +- `/extension ` - Add a stdio extension (format: ENV1=val1 command args...) +- `/mode ` - Set the goose mode to use ('auto', 'approve', 'chat') +- `/plan ` - Create a structured plan based on the given message +- `/prompt [--info] [key=value...]` - Get prompt info or execute a prompt +- `/prompts [--extension ]` - List all available prompts, optionally filtered by extension - `/recipe ` - Generate and save a session recipe to `recipe.yaml` or the filename specified by the command parameter. +- `/summarize` - Summarize the current session to reduce context length while preserving key information +- `/t` - Toggle between Light/Dark/Ansi themes All commands support tab completion. Press `` after a slash (/) to cycle through available commands or to complete partial commands. @@ -320,4 +419,4 @@ Goose CLI supports several shortcuts and built-in commands for easier navigation - **`Ctrl+C`** - Interrupt the current request - **`Ctrl+J`** - Add a newline -- **Up/Down arrows** - Navigate through command history \ No newline at end of file +- **`Cmd+Up/Down arrows`** - Navigate through command history \ No newline at end of file diff --git a/documentation/docs/guides/goose-permissions.md b/documentation/docs/guides/goose-permissions.md index bda2c474..1c84b24f 100644 --- a/documentation/docs/guides/goose-permissions.md +++ b/documentation/docs/guides/goose-permissions.md @@ -9,6 +9,18 @@ import TabItem from '@theme/TabItem'; Goose’s permissions determine how much autonomy it has when modifying files, using extensions, and performing automated actions. By selecting a permission mode, you have full control over how Goose interacts with your development environment. +
+ Permission Modes Video Walkthrough + +
+ ## Permission Modes | Mode | Description | Best For | @@ -28,7 +40,22 @@ Goose’s permissions determine how much autonomy it has when modifying files, u Here's how to configure: - + + + You can change modes before or during a session and it will take effect immediately. + + + + Click the Goose Mode option from the bottom menu. + + + 1. Click `...` in the upper right corner + 2. Click `Settings` + 3. Under `Mode Selection`, choose the mode you'd like + + + + @@ -101,21 +128,6 @@ Here's how to configure: - - - You can change modes before or during a session and it will take effect immediately. - - - - Click the Goose Mode option from the bottom menu. - - - 1. Click `...` in the upper right corner - 2. Click `Settings` - 3. Under `Mode Selection`, choose the mode you'd like - - - :::info diff --git a/documentation/docs/guides/handling-llm-rate-limits-with-goose.md b/documentation/docs/guides/handling-llm-rate-limits-with-goose.md index ce3dd585..27d43759 100644 --- a/documentation/docs/guides/handling-llm-rate-limits-with-goose.md +++ b/documentation/docs/guides/handling-llm-rate-limits-with-goose.md @@ -20,16 +20,7 @@ OpenRouter provides a unified interface for LLMs that allows you to select and s 2. Once verified, create your [API key](https://openrouter.ai/settings/keys). - - 1. Run the Goose configuration command: - ```sh - goose configure - ``` - 2. Select `Configure Providers` from the menu. - 3. Follow the prompts to choose OpenRouter as your provider and enter your OpenRouter API key when prompted. - - - + 1. Click on the three dots in the top-right corner. 2. Select `Advanced Settings` from the menu. 3. Click on "Browse" in the `Models` section. @@ -37,8 +28,15 @@ OpenRouter provides a unified interface for LLMs that allows you to select and s 5. Select `OpenRouter` from the list of available providers. 6. Enter your OpenRouter API key in the dialog that appears. + + 1. Run the Goose configuration command: + ```sh + goose configure + ``` + 2. Select `Configure Providers` from the menu. + 3. Follow the prompts to choose OpenRouter as your provider and enter your OpenRouter API key when prompted. + -Now Goose will send your requests through OpenRouter which will automatically switch models when necessary to avoid interruptions due to rate limiting. - +Now Goose will send your requests through OpenRouter which will automatically switch models when necessary to avoid interruptions due to rate limiting. \ No newline at end of file diff --git a/documentation/docs/guides/managing-goose-sessions.md b/documentation/docs/guides/managing-goose-sessions.md index 586c5ae3..02e88e4c 100644 --- a/documentation/docs/guides/managing-goose-sessions.md +++ b/documentation/docs/guides/managing-goose-sessions.md @@ -13,17 +13,29 @@ A session is a single, continuous interaction between you and Goose, providing a ## Start Session - + + After choosing an LLM provider, you'll see the session interface ready for use. Type your questions, tasks, or instructions directly into the input field, and Goose will immediately get to work. + + To start a new session at any time, click the three dots in the top-right corner of the application and select **New Session** from the dropdown menu. + + You can also use keyboard shortcuts to start a new session or bring focus to open Goose windows. + + | Action | macOS | Windows/Linux | + |--------|-------|---------------| + | New Session in Current Directory | `Cmd+N` | `Ctrl+N` | + | New Session in Different Directory | `Cmd+O` | `Ctrl+O` | + | Focus Goose Window | `Cmd+Option+Shift+G` | `Ctrl+Alt+Shift+G` | + + + From your terminal, navigate to the directory from which you'd like to start, and run: ```sh goose session ``` - - - After choosing an LLM provider, you’ll see the session interface ready for use. Type your questions, tasks, or instructions directly into the input field, and Goose will immediately get to work. - - To start a new session at any time, click the three dots in the top-right corner of the application and select **New Session** from the dropdown menu. + :::tip + You can also use the [`project`](/docs/guides/goose-cli-commands#project) and [`projects`](/docs/guides/goose-cli-commands#projects) commands to start or resume sessions from a project, which is a tracked working directory with session metadata. + ::: @@ -33,7 +45,10 @@ If this is your first session, Goose will prompt you for an API key to access an ## Name Session - + + Within the Desktop app, sessions are automatically named using the current timestamp in the format `YYYYMMDD_HHMMSS`. Goose also provides a description of the session based on context. + + By default, Goose names your session using the current timestamp in the format `YYYYMMDD_HHMMSS`. If you'd like to provide a specific name, this is where you'd do so. For example to name your session `react-migration`, you would run: ``` @@ -47,29 +62,31 @@ If this is your first session, Goose will prompt you for an API key to access an logging to ~/.local/share/goose/sessions/react-migration.json1 ``` - - Within the Desktop app, sessions are automatically named using the current timestamp in the format `YYYYMMDD_HHMMSS`. Goose also provides a description of the session based on context. - ## Exit Session Note that sessions are automatically saved when you exit. - + + To exit a session, simply close the application. + + To exit a session, type `exit`. Alternatively, you exit the session by holding down `Ctrl+C`. Your session will be stored locally in `~/.local/share/goose/sessions`. - - To exit a session, simply close the application. - - ## Resume Session - + + 1. Click `...` in the upper right corner + 2. Click `Previous Sessions` + 3. Click a session + 4. Click `Resume Session` in the upper right corner + + To resume your latest session, you can run the following command: ``` @@ -91,12 +108,59 @@ Note that sessions are automatically saved when you exit. While you can resume sessions using the commands above, we recommend creating new sessions for new tasks to reduce the chance of [doom spiraling](/docs/troubleshooting#stuck-in-a-loop-or-unresponsive). ::: + :::tip + You can also use the [`project`](/docs/guides/goose-cli-commands#project) and [`projects`](/docs/guides/goose-cli-commands#projects) commands to start or resume sessions from a project, which is a tracked working directory with session metadata. + ::: - - 1. Click `...` in the upper right corner - 2. Click `Previous Sessions` - 3. Click a session - 4. Click `Resume Session` in the upper right corner + + +### Search Session History + + + + In Goose Desktop, you can search session metadata including the description, filename, and working directory path. The search is text-based and supports case-sensitive matching, but doesn't search session content or support regex patterns. + + 1. Click `...` in the upper right corner + 2. Click `Previous Sessions` + 3. Use `Cmd+F` to open the search bar + 4. Enter your search term + 5. Use search features to refine and navigate results + + | Action | macOS | Windows/Linux | + |--------|-------|---------------| + | Next Match | `Cmd+G`
or `↓` | `Ctrl+G`
or `↓` | + | Previous Match | `Shift+Cmd+G`
or `↑` | `Shift+Ctrl+G`
or `↑` | + | Toggle Case-Sensitivity | `Aa` | `Aa` | + | Focus Search Bar | `Cmd+F` | `Ctrl+F` | + | Close Search | `Esc` or X | `Esc` or X | + +
+ + The Goose CLI supports [listing session history](/docs/guides/goose-cli-commands/#session-list-options) but doesn't provide search functionality. As a workaround, you can use your terminal's search capabilities (including regex support). Examples for macOS: + + ```bash + # Search session IDs (filenames) + ls ~/.local/share/goose/sessions/ | grep "full or partial session id" + + # List sessions modified in last 7 days + find ~/.local/share/goose/sessions/ -mtime -7 -name "*.jsonl" + + # Show first line (metadata) of each session file + for f in ~/.local/share/goose/sessions/*.jsonl; do + head -n1 "$f" | grep "your search term" && echo "Found in: $(basename "$f" .jsonl)" + done + + # Find search term in session content + rg "your search term" ~/.local/share/goose/sessions/ + + # Search and show session IDs that contain search term + for f in ~/.local/share/goose/sessions/*.jsonl; do + if grep -q "your search term" "$f"; then + echo "Found in session: $(basename "$f" .jsonl)" + fi + done + ``` +
@@ -105,30 +169,7 @@ Note that sessions are automatically saved when you exit. You can resume a CLI session in Desktop and vice versa. - - To resume a Desktop session within CLI, get the name of the session from the Desktop app. Note that unless you specifically named the session, its default name is a timestamp in the format `YYYYMMDD_HHMMSS`. - - 1. Open Goose Desktop - 2. Click `...` in the upper right corner - 3. Click `Previous Sessions` - 4. Find the session that you want to resume, and copy the basename (without the `.jsonl` extension). - :::note Example - - **Desktop Session** - - | Session Description | Session Filename | - |------------------------|------------------------------| - | GitHub PR Access Issue | **20250305_113223**.jsonl | - - - **CLI Command** - ```sh - goose session -r --name 20250305_113223 - ``` - ::: - - - + All saved sessions are listed in the Desktop app, even CLI sessions. To resume a CLI session within the Desktop: 1. Click `...` in the upper right corner @@ -159,4 +200,65 @@ You can resume a CLI session in Desktop and vice versa. ::: + + To resume a Desktop session within CLI, get the name of the session from the Desktop app. Note that unless you specifically named the session, its default name is a timestamp in the format `YYYYMMDD_HHMMSS`. + + 1. Open Goose Desktop + 2. Click `...` in the upper right corner + 3. Click `Previous Sessions` + 4. Find the session that you want to resume, and copy the basename (without the `.jsonl` extension). + :::note Example + + **Desktop Session** + + | Session Description | Session Filename | + |------------------------|------------------------------| + | GitHub PR Access Issue | **20250305_113223**.jsonl | + + + **CLI Command** + ```sh + goose session -r --name 20250305_113223 + ``` + ::: + + + +## Remove Sessions + +You can remove sessions using CLI commands. For detailed instructions on session removal, see the [CLI Commands documentation](/docs/guides/goose-cli-commands#session-remove-options). + +## Search Within Sessions + +Search allows you to find specific content within your current session. The search functionality is available in both CLI and Desktop interfaces. + + + + Trigger search using keyboard shortcuts or the search icon: + + | Action | macOS | Windows/Linux | + |--------|-------|---------------| + | Open Search | `Cmd+F` | `Ctrl+F` | + | Next Match | `Cmd+G`
or `↓` | `Ctrl+G`
or `↓` | + | Previous Match | `Shift+Cmd+G`
or `↑` | `Shift+Ctrl+G`
or `↑` | + | Use Selection for Find | `Cmd+E` | n/a | + | Toggle Case-Sensitivity | `Aa` | `Aa` | + | Close Search | `Esc` or X | `Esc` or X | + +
+ + Search functionality is provided by your terminal interface. Use the appropriate shortcut for your environment: + + | Terminal | Operating System | Shortcut | + |----------|-----------------|-----------| + | iTerm2 | macOS | `Cmd+F` | + | Terminal.app | macOS | `Cmd+F` | + | Windows Terminal | Windows | `Ctrl+F` | + | Linux Terminal | Linux | `Ctrl+F` | + + :::info + Your specific terminal emulator may use a different keyboard shortcut. Check your terminal's documentation or settings for the search command. + ::: + +
\ No newline at end of file diff --git a/documentation/docs/guides/running-tasks.md b/documentation/docs/guides/running-tasks.md index 86e8c5c7..ca5f7d45 100644 --- a/documentation/docs/guides/running-tasks.md +++ b/documentation/docs/guides/running-tasks.md @@ -91,6 +91,13 @@ goose run -n my-project -t "initial instructions" goose run -n my-project -r ``` +You can also run commands without creating or storing a session file by using the `--no-session` flag. This is useful for automated scripts, or one-off tasks where you don't need to maintain the conversation history or state. This flag routes the session output to a temporary null path (`/dev/null` on Unix or `NUL` on Windows), and discards it when complete. + +```bash +# Run a command without creating a session file +goose run --no-session -t "your command here" +``` + ### Working with Extensions If you want to ensure specific extensions are available when running your task, you can indicate this with arguments. This can be done using the `--with-extension`, `--with-remote-extension`, or `--with-builtin` flags: @@ -113,6 +120,27 @@ goose run --with-extension "ENV1=value1 custom-extension-args" -t "your instruct goose run --with-remote-extension "url" -t "your instructions" ``` +### Debug Mode + +When troubleshooting or developing complex workflows, you can enable debug mode to get more detailed information about tool execution. The `--debug` flag provides: + +- Complete tool responses +- Detailed parameter values +- Full file paths + +Debug mode can be useful when: +- Developing new automation scripts +- Troubleshooting extension behavior +- Verifying tool parameters and responses + +```bash +# Run a task with debug output enabled +goose run --debug -t "your instructions" + +# Debug a recipe execution +goose run --debug --recipe recipe.yaml +``` + ## Common Use Cases ### Running Script Files diff --git a/documentation/docs/guides/session-recipes.md b/documentation/docs/guides/session-recipes.md index 9d72ba59..ed6479a6 100644 --- a/documentation/docs/guides/session-recipes.md +++ b/documentation/docs/guides/session-recipes.md @@ -2,6 +2,7 @@ sidebar_position: 5 title: Create a Recipe from Your Session sidebar_label: Shareable Recipes +description: "Share a Goose session setup (including tools, goals, and instructions) as a reusable recipe that others can launch with a single click" --- import Tabs from '@theme/Tabs'; @@ -27,10 +28,11 @@ You'll need to provide both instructions and activities for your Recipe. 1. While in the session you want to save as a recipe, click the menu icon **⋮** in the top right corner 2. Select **Make Agent from this session** 3. In the dialog that appears: - - Edit the **instructions** to clarify its purpose. - - Add or remove **activities** as needed. - 4. Click **Save** - 5. Copy the Recipe URL and use it however you like (e.g., share it with teammates, drop it in documentation, or keep it for yourself) + - Name the recipe + - Provide a description + - Some **activities** will be automatically generated. Add or remove as needed. + - A set of **instructions** will also be automatically generated. Review and edit as needed. + 4. Copy the Recipe URL and use it however you like (e.g., share it with teammates, drop it in documentation, or keep it for yourself)
@@ -66,11 +68,17 @@ You'll need to provide both instructions and activities for your Recipe. - $extensions context: - $context - activities: + activities: # example prompts to display in the Desktop app - $activities author: - contact: $contact - metadata: $metadata + contact: $contact + metadata: $metadata + parameters: # required if recipe uses {{ variables }} + - key: $param_key + input_type: $type # string, number, etc + requirement: $req # required, optional, or user_prompt + description: $description + default: $value # required for optional parameters ``` @@ -78,9 +86,72 @@ You'll need to provide both instructions and activities for your Recipe. You can then edit the recipe file to include the following key information: - `instructions`: Add or modify the system instructions - - `activities`: List the activities that can be performed + - `prompt`: Add the initial message or question to start a Goose session with + - `activities`: List the activities that can be performed, which are displayed as prompts in the Desktop app + #### Recipe Parameters + + You may add parameters to a recipe, which will require users to fill in data when running the recipe. Parameters can be added to any part of the recipe (instructions, prompt, activities, etc). + + To use parameters, edit your recipe file to include template variables using `{{ variable_name }}` syntax and define each of them in your yaml using `parameters`. + +
+ Example recipe with parameters + + ```yaml title="code-review.yaml" + version: 1.0.0 + title: "{{ project_name }} Code Review" # Wrap the value in quotes if it starts with template syntax to avoid YAML parsing errors + description: Automated code review for {{ project_name }} with {{ language }} focus + instructions: | + You are a code reviewer specialized in {{ language }} development. + Apply the following standards: + - Complexity threshold: {{ complexity_threshold }} + - Required test coverage: {{ test_coverage }}% + - Style guide: {{ style_guide }} + activities: + - "Review {{ language }} code for complexity" + - "Check test coverage against {{ test_coverage }}% requirement" + - "Verify {{ style_guide }} compliance" + parameters: + - key: project_name + input_type: string + requirement: required # could be required, optional or user_prompt + description: name of the project + - key: language + input_type: string + requirement: required + description: language of the code + - key: complexity_threshold + input_type: number + requirement: optional + default: 20 # default is required for optional parameters + description: a threshold that defines the maximum allowed complexity + - key: test_coverage + input_type: number + requirement: optional + default: 80 + description: the minimum test coverage threshold in percentage + - key: style_guide + input_type: string + description: style guide name + requirement: user_prompt + # If style_guide param value is not specified in the command, user will be prompted to provide a value, even in non-interactive mode + ``` + +
+ + When someone runs a recipe that contains template parameters, they will need to provide the parameters: + + ```sh + goose run --recipe code-review.yaml \ + --params project_name=MyApp \ + --params language=Python \ + --params complexity_threshold=15 \ + --params test_coverage=80 \ + --params style_guide=PEP8 + ``` + #### Validate the recipe [Exit the session](/docs/guides/managing-goose-sessions/#exit-session) and run: @@ -119,7 +190,7 @@ You'll need to provide both instructions and activities for your Recipe. - You can start a session with a recipe file in two ways: + You can start a session with a recipe file in the following ways: - Run the recipe once and exit: @@ -133,15 +204,48 @@ You'll need to provide both instructions and activities for your Recipe. goose run --recipe recipe.yaml --interactive ``` - :::info - Be sure to use the exact filename of the recipe. - ::: + - Run the recipe with parameters: + ```sh + goose run --recipe recipe.yaml --interactive --params language=Spanish --params style=formal --params name=Alice + ``` + + - Explain the recipe with description and parameters + + ```sh + goose run --recipe recipe.yaml --explain + ``` + + #### Discover recipes + When using recipe-related CLI commands, there are a few ways to specify which recipe to use: + ##### Option 1: Provide the full file path + Use the exact path to the recipe file: + + ```sh + goose run --recipe ~/my_recipe.yaml + goose recipe validate ~/my_recipe.yaml + goose recipe deeplink ~/my_recipe.yaml + ``` + ##### Option 2: Use the recipe name + If your recipe is named my_recipe, you can simply use the name: + + ```sh + goose run --recipe my_recipe + goose recipe validate my_recipe + goose recipe deeplink my_recipe + ``` + When you use the recipe name, Goose will search for the file in the following order: + 1. Local search: + Goose will search for `my_recipe.yaml` or `my_recipe.json` in the current working directory + + 2. Remote search (GitHub): + - If the `GOOSE_RECIPE_GITHUB_REPO` environment variable is set or configured in the `Goose Settings` via `goose configure`, Goose will search the specified GitHub repo. (eg: my_org/goose-recipes). + - Goose will look for `my_recipe/recipe.yaml` or `my_recipe/recipe.json` within that GitHub repository. -### What's Included +## What's Included A Recipe captures: @@ -152,8 +256,6 @@ A Recipe captures: - Initial setup (but not full conversation history) -### What's *Not* Included - To protect your privacy and system integrity, Goose excludes: - Global and local memory diff --git a/documentation/docs/guides/smart-context-management.md b/documentation/docs/guides/smart-context-management.md new file mode 100644 index 00000000..2421d929 --- /dev/null +++ b/documentation/docs/guides/smart-context-management.md @@ -0,0 +1,121 @@ +--- +title: Smart Context Management +sidebar_position: 22 +sidebar_label: Smart Context Management +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import { ScrollText } from 'lucide-react'; + +When working with [Large Language Models (LLMs)](/docs/getting-started/providers), there are limits to how much conversation history they can process at once. Goose provides smart context management features to help you maintain productive sessions even when reaching these limits. Here are the key concepts: + +- **Context Length**: The amount of conversation history the LLM can consider +- **Context Limit**: The maximum number of tokens the model can process +- **Context Management**: How Goose handles conversations approaching these limits + +## Smart Context Management Features + +When a conversation reaches the context limit, Goose offers different ways to handle it: + +| Feature | Description | Best For | Impact | +|---------|-------------|-----------|---------| +| **Summarization** | Condenses conversation while preserving key points | Long, complex conversations | Maintains most context | +| **Truncation** | Removes oldest messages to make room | Simple, linear conversations | Loses old context | +| **Clear** | Starts fresh while keeping session active | New direction in conversation | Loses all context | + +## Using Smart Context Management + + + + +Goose Desktop exclusively uses summarization to manage context, preserving key information while reducing size. + + + + +When you reach the context limit in Goose Desktop: + +1. Goose will automatically start summarizing the conversation to make room. +2. You'll see a message that says **"Preparing summary..."**, followed by **"Session summarized."** +3. Once complete, you'll have the option to **"View or edit summary."** +4. You can then continue the session with the summarized context in place. + + + + +You can proactively summarize your conversation before reaching context limits: + +1. Click the scroll text icon () in the chat interface +2. Confirm the summarization in the modal +3. View or edit the generated summary if needed + + + + + + + +The CLI offers three context management options: summarize, truncate, or clear your session. + + + + +When you hit the context limit, you'll see this prompt to choose a management option, allowing you to continue your session: + +```sh +◇ The model's context length is maxed out. You will need to reduce the # msgs. Do you want to? +│ ○ Clear Session +│ ○ Truncate Message +// highlight-start +│ ● Summarize Session +// highlight-end + +final_summary: [A summary of your conversation will appear here] + +Context maxed out +-------------------------------------------------- +Goose summarized messages for you. +``` + + + + +To proactively trigger summarization before reaching context limits, use the `/summarize` command: + +```sh +( O)> /summarize +◇ Are you sure you want to summarize this conversation? This will condense the message history. +│ Yes +│ +Summarizing conversation... +Conversation has been summarized. +Key information has been preserved while reducing context length. +``` + + + + + + + +### Token usage + + + After sending your first message to Goose, a colored circle appears next to the model name at the bottom of the session window. The color provides a visual indicator of your token usage for the session. + - **Green**: Normal usage - Plenty of context space available + - **Orange**: Warning state - Approaching limit (80% of capacity) + - **Red**: Error state - Context limit reached + + Hover over this circle to display: + - the number of tokens used + - the percentage of available tokens used + - the total available tokens + - A progress bar showing your current token usage + + + + This functionality is not available in the Goose CLI. + + + \ No newline at end of file diff --git a/documentation/docs/guides/tips.md b/documentation/docs/guides/tips.md index ef591309..67b9762f 100644 --- a/documentation/docs/guides/tips.md +++ b/documentation/docs/guides/tips.md @@ -2,51 +2,47 @@ title: Quick Goose Tips sidebar_position: 6 sidebar_label: Quick Tips +description: Best practices for working with Goose --- -Here is a collection of tips for working with Goose: +### Goose works on your behalf +Goose is an AI agent, which means you can prompt Goose to perform tasks for you like opening applications, running shell commands, automating workflows, writing code, browsing the web, and more. -### Goose can and will edit files -Use a git strategy to avoid losing anything - such as staging your personal edits and leaving Goose edits unstaged until reviewed. Consider using individual commits which can be reverted. See more on [File Management](/docs/guides/file-management). +### Prompt Goose using natural language +You don't need fancy language or special syntax to prompt Goose. Talk with Goose like you would talk to a friend. You can even use slang or say please and thank you; Goose will understand. -### Goose can and will run commands -Goose will check commands for safety, however, you can also ask Goose to check with you before running commands if you are concerned. +### Extend Goose's capabilities to any application +Goose's capabilities are extensible. As an [MCP](https://modelcontextprotocol.io/) client, Goose can connect to your apps and services through [extensions](/extensions), allowing it to work across your entire workflow. -### You can interrupt Goose with `CTRL+C` -Use this command to stop Goose in the event that you need to correct it or give it more info. +### Choose how much control Goose has +You can customize how much [supervision](/docs/guides/goose-permissions) Goose needs. Choose between full autonomy, requiring approval before actions, or simply chatting without any actions. -### Goose works best when solving concrete problems -Experiment with how far you need to break a problem down to get Goose to solve it. Be specific! For example, it will likely fail to "create a banking app", but probably does a good job if prompted with "create a Fastapi app with an endpoint for deposit and withdrawal and with account balances stored in mysql keyed by id". +### Choose the right LLM +Your experience with Goose is shaped by your [choice of LLM](/blog/2025/03/31/goose-benchmark), as it handles all the planning while Goose manages the execution. When choosing an LLM, consider its tool support, specific capabilities, and associated costs. -### Goose can troubleshoot -If something goes wrong, Goose can help troubleshoot issues by examining logs, analyzing error messages, and suggesting possible resolutions. +### Keep sessions short +LLMs have context windows, which are limits on how much conversation history they can retain. Once exceeded, they may forget earlier parts of the conversation. Monitor your token usage and [start new sessions](/docs/guides/managing-goose-sessions) as needed. -### Leverage Goose to learn -Use Goose to learn new technologies or frameworks by asking it to explain things like code snippets, concepts, or best practices relevant to your project. +### Turn off unnecessary extensions or tool +Turning on too many extensions can degrade performance. Enable only essential [extensions and tools](/docs/guides/tool-permissions) to improve tool selection accuracy, save context window space, and stay within provider tool limits. -### Goose needs context -If Goose doesn't have enough context to start with, it might go in the wrong direction. Tell it to read files that you are referring to or search for objects in code. Even better, ask it to summarize them for you, which will help it set up its own next steps. You can create a [goosehints](/docs/guides/using-goosehints) file to help with this. +### Teach Goose your preferences +Help Goose remember how you like to work by using [`.goosehints`](/docs/guides/using-goosehints/) for permanent project preferences and the [Memory extension](/docs/tutorials/memory-mcp) for things you want Goose to dynamically recall later. Both can help save valuable context window space while keeping your preferences available. -### Use easy search terms -Refer to any objects in files with something that is easy for Goose to search for, such as "the MyExample class". +### Protect sensitive files +Goose is often eager to make changes. You can stop it from changing specific files by creating a [.gooseignore](/docs/guides/using-gooseignore) file. In this file, you can list all the file paths you want it to avoid. -### Teach Goose how you test -Goose *loves* to know how to run tests to get a feedback loop going, just like you do. If you tell it how you test things locally and quickly, it can make use of that when working on your project. +### Version Control +Commit your code changes early and often. This allows you to rollback any unexpected changes. -### Goose can do scripting tasks -You can use Goose for tasks that would require scripting. It can even look at your screen and correct designs, or help you fix bugs. Try asking it to help you in a way you would ask a person. +### Control which extensions Goose can use +Administrators can use an [allowlist](/docs/guides/allowlist) to restrict Goose to approved extensions only. This helps prevent risky installs from unknown MCP servers. -### Goose will make mistakes -Sometimes Goose will go in the wrong direction. Feel free to correct it, or start over again. +### Set up starter templates +You can turn a successful session into a reusable "[recipe](/docs/guides/session-recipes)" to share with others or use again later—no need to start from scratch. -### Goose can run tasks continuously if asked -You can tell Goose to run things for you continuously and it will iterate, try, and retry. - -### Goose can run anywhere -It doesn't have to be in a repo. Just ask Goose! +### Embrace an experimental mindset +You don’t need to get it right the first time. Iterating on prompts and tools is part of the workflow. ### Keep Goose updated -Regularly update Goose to benefit from the latest features, bug fixes, and performance improvements. For the CLI, the best way to keep it updated is by re-running the [Goose installation script][installation]. For Goose Desktop, check the [GitHub Releases page][ui-release] regularly for updates. - -[installation]: https://block.github.io/goose/docs/quickstart/#installation -[ui-release]: https://github.com/block/goose/releases/stable \ No newline at end of file +Regularly [update](/docs/guides/updating-goose) Goose to benefit from the latest features, bug fixes, and performance improvements. \ No newline at end of file diff --git a/documentation/docs/guides/tool-permissions.md b/documentation/docs/guides/tool-permissions.md index 9dda7510..51d2da9c 100644 --- a/documentation/docs/guides/tool-permissions.md +++ b/documentation/docs/guides/tool-permissions.md @@ -42,7 +42,31 @@ Tool permissions work alongside [Goose Permission Modes](/docs/guides/goose-perm ## Configuring Tool Permissions - + + + You can configure tool permissions through either Manual or Smart Approval modes: + + + + 1. Click `...` in the upper right corner + 2. Click `Advanced Settings` + 3. Under `Mode Selection`, choose `Manual Approval` + 4. Click on an extension name + 5. Use the dropdown next to each tool to set its permission level + + + :::tip + In Smart Approval mode, Goose will automatically detect and allow read-only operations while requiring approval for state-changing actions. + ::: + 1. Click `...` in the upper right corner + 2. Click `Advanced Settings` + 3. Under `Mode Selection`, choose `Smart Approval` + 4. Click on an extension name + 5. Use the dropdown next to each tool to set its permission level + + + + 1. Run the configure command: ```sh @@ -102,30 +126,6 @@ Tool permissions work alongside [Goose Permission Modes](/docs/guides/goose-perm └ ``` - - - You can configure tool permissions through either Manual or Smart Approval modes: - - - - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - 3. Under `Mode Selection`, choose `Manual Approval` - 4. Click on an extension name - 5. Use the dropdown next to each tool to set its permission level - - - :::tip - In Smart Approval mode, Goose will automatically detect and allow read-only operations while requiring approval for state-changing actions. - ::: - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - 3. Under `Mode Selection`, choose `Smart Approval` - 4. Click on an extension name - 5. Use the dropdown next to each tool to set its permission level - - - ## Benefits of Permission Management @@ -170,4 +170,4 @@ Documentation Task: ✓ Markdown editing → Always Allow ✗ Code editing → Never Allow ✗ System commands → Never Allow -``` +``` \ No newline at end of file diff --git a/documentation/docs/guides/tool-router.md b/documentation/docs/guides/tool-router.md new file mode 100644 index 00000000..7744b57f --- /dev/null +++ b/documentation/docs/guides/tool-router.md @@ -0,0 +1,86 @@ +# Tool Router (preview) + +## Overview + +Tool Router is a powerful feature that addresses a common challenge in LLM-based development: the difficulty of selecting the right tool when multiple extensions are enabled. Traditional approaches feed an entire list of tools into the context during chat sessions, which not only consumes a significant number of tokens but also reduces the effectiveness of tool calling. + +## The Problem + +When you enable multiple extensions (like Slack), you get access to numerous tools such as: +- Reading threads +- Sending messages +- Creating channels +- And many more + +However, you typically don't need all this functionality at once. Loading every available tool into the context can be inefficient and potentially confusing for the LLM. + +## The Solution: Tool Router + +Tool Router introduces a smarter way to handle tool selection through vector-based indexing. Instead of passing all tools back and forth, it: + +1. Indexes all tools from your enabled extensions +2. Uses vector search to load only the relevant tools into context when needed +3. Ensures that only the functionality you actually need is available + +## Configuration + +To enable this feature, change the Tool Selection Strategy from default to vector. + +#### CLI +To configure Tool Router in the CLI, follow these steps: + +1. Run the configuration command: +```bash +./target/debug/goose configure +``` + +2. This will update your existing config file. Alternatively, you can edit it directly at: +``` +/Users/wendytang/.config/goose/config.yaml +``` + +3. During configuration: + - Select "Goose Settings" + - Choose "Router Tool Selection Strategy" + - Select "Vector Strategy" + +The configuration process will look like this: +``` +┌ goose-configure +│ +◇ What would you like to configure? +│ Goose Settings +│ +◇ What setting would you like to configure? +│ Router Tool Selection Strategy +│ +◇ Which router strategy would you like to use? +│ Vector Strategy +│ +└ Set to Vector Strategy - using vector-based similarity for tool selection +``` + +#### UI +Toggle the settings button on the top right and head to 'Advanced Settings', then 'Tool Selection Strategy' at the botoom. + +## Benefits + +- Reduced token consumption +- More accurate tool selection +- Improved LLM performance +- Better context management +- More efficient use of available tools + +## Notes + +### Model Compatibility + +Tool Router currently only works with Claude models served through Databricks. The embedding functionality uses OpenAI's `text-embedding-3-small` model by default. + +### Feedback & Next Steps + +We'd love to hear your thoughts on this feature! Please reach out in the Goose Discord channel to share your use case and experience. + +Our roadmap includes: +- Expanding Tool Router support to OpenAI models +- Adding customization options for the `k` parameter that controls how many similar tools are returned during vector similarity search diff --git a/documentation/docs/guides/updating-goose.md b/documentation/docs/guides/updating-goose.md index bf073b56..53e0334e 100644 --- a/documentation/docs/guides/updating-goose.md +++ b/documentation/docs/guides/updating-goose.md @@ -11,7 +11,19 @@ import DesktopInstallButtons from '@site/src/components/DesktopInstallButtons'; The Goose CLI and desktop apps are under active and continuous development. To get the newest features and fixes, you should periodically update your Goose client using the following instructions. - + + :::info + To update Goose to the latest stable version, reinstall using the instructions below + ::: +
+ 1. + 2. Unzip the downloaded zip file. + 3. Run the executable file to launch the Goose Desktop application. + 4. Overwrite the existing Goose application with the new version. + 5. Run the executable file to launch the Goose desktop application. +
+
+ You can update Goose by running: ```sh @@ -41,16 +53,4 @@ The Goose CLI and desktop apps are under active and continuous development. To g ``` - - :::info - To update Goose to the latest stable version, reinstall using the instructions below - ::: -
- 1. - 2. Unzip the downloaded zip file. - 3. Run the executable file to launch the Goose Desktop application. - 4. Overwrite the existing Goose application with the new version. - 5. Run the executable file to launch the Goose desktop application. -
-
\ No newline at end of file diff --git a/documentation/docs/guides/using-goosehints.md b/documentation/docs/guides/using-goosehints.md index b55cf21e..89f70e1e 100644 --- a/documentation/docs/guides/using-goosehints.md +++ b/documentation/docs/guides/using-goosehints.md @@ -28,13 +28,7 @@ You can use both global and local hints at the same time. When both exist, Goose ::: - - - - **Global hints file** - Create a `.goosehints` file in `~/.config/goose`. - - **Local hints file** - Create a `.goosehints` file at the root of the directory you'd like it applied to. - - - + #### Global hints file 1. Create a `.goosehints` file in `~/.config/goose`. @@ -51,6 +45,12 @@ You can use both global and local hints at the same time. When both exist, Goose You may have to adjust the screen size to fully see the Save and Cancel buttons. ::: + + + + - **Global hints file** - Create a `.goosehints` file in `~/.config/goose`. + - **Local hints file** - Create a `.goosehints` file at the root of the directory you'd like it applied to. + diff --git a/documentation/docs/quickstart.md b/documentation/docs/quickstart.md index 4a165892..dd545ce3 100644 --- a/documentation/docs/quickstart.md +++ b/documentation/docs/quickstart.md @@ -9,6 +9,7 @@ import { IconDownload } from "@site/src/components/icons/download"; import SupportedEnvironments from '@site/src/components/SupportedEnvironments'; import RateLimits from '@site/src/components/RateLimits'; import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; +import DesktopInstallButtons from '@site/src/components/DesktopInstallButtons'; # Goose in 5 minutes @@ -21,54 +22,72 @@ Goose is an open source AI agent that supercharges your software development by ## Install Goose -You can use Goose via CLI or Desktop application. + + + Choose to install Goose on CLI and/or Desktop: - - -
- Quickstart Video Demo - -
+ + + +
+ 1. Unzip the downloaded zip file. + 2. Run the executable file to launch the Goose Desktop application. +
+
+ + Run the following command to install Goose: - Run the following command to install the latest version of Goose: - - ```sh - curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash - ``` + ```sh + curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash + ``` + +
- -
- Quickstart Video Demo - -
- To install the latest version of Goose, click the **button** below: -
- - - download goose desktop for macOS - -
-
- 1. Unzip the downloaded `Goose.zip` file. - 2. Run the executable file to launch the Goose desktop application. -
+ + + + + Desktop version is currently unavailable for Linux. + + + Run the following command to install the Goose CLI on Linux: + + ```sh + curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash + ``` + + + + + + + + Desktop version is currently unavailable for Windows. + + + There isn't native installation support for Windows, however you can run Goose using WSL (Windows Subsystem for Linux). + + 1. Open [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows) as Administrator and install WSL and the default Ubuntu distribution: + + ```bash + wsl --install + ``` + + 2. Restart your computer if prompted. + + 3. Run the Goose installation script: + ```bash + curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash + ``` + :::tip + If you encounter any issues on download, you might need to install `bzip2` to extract the downloaded file: + + ```bash + sudo apt update && sudo apt install bzip2 -y + ``` + ::: + +
@@ -77,6 +96,9 @@ You can use Goose via CLI or Desktop application. Goose works with [supported LLM providers][providers]. When you install Goose, you'll be prompted to choose your preferred LLM and supply an API key. + + ![Set Up a Provider UI](./assets/guides/set-up-provider-ui.png) + Use the up and down arrow keys to navigate the CLI menu, and press Enter once you've selected a choice. @@ -100,9 +122,6 @@ Goose works with [supported LLM providers][providers]. When you install Goose, y └ Configuration saved successfully ``` - - ![Set Up a Provider UI](./assets/guides/set-up-provider-ui.png) - diff --git a/documentation/docs/troubleshooting.md b/documentation/docs/troubleshooting.md index bfccd71d..fcd582a4 100644 --- a/documentation/docs/troubleshooting.md +++ b/documentation/docs/troubleshooting.md @@ -158,6 +158,13 @@ Welcome to goose! Let's get you set up with a provider. │ gemini-2.0-flash-exp ``` +You may also use the `GOOSE_DISABLE_KEYRING` environment variable, which disables the system keyring for secret storage. Set to any value (e.g., "1", "true", "yes"), to disable. The actual value doesn't matter, only whether the variable is set. + +When the keyring is disabled, secrets are stored here: + +* macOS/Linux: `~/.config/goose/secrets.yaml` +* Windows: `%APPDATA%\Block\goose\config\secrets.yaml` + --- ### Package Runners diff --git a/documentation/docs/tutorials/_template_.md b/documentation/docs/tutorials/_template_.md deleted file mode 100644 index 8a5f8021..00000000 --- a/documentation/docs/tutorials/_template_.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -title: {name} Extension -description: Add {name} MCP Server as a Goose Extension ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; - - - - -This tutorial covers how to add the [{name} MCP Server](/) as a Goose extension to enable file operations, repository management, search functionality, and more. - - -:::tip TLDR - -**Command** -```sh -{command} -``` - -**Environment Variable** -``` -{env_var}: -``` -::: - -## Configuration - -:::info -Note that you'll need [Node.js](https://nodejs.org/) installed on your system to run this command, as it uses `npx`. -::: - -:::info -Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on your system to run this command, as it uses `uvx`. -::: - -:::info -Note that you'll need [JBang](https://www.jbang.dev/download) installed on your system to run this command, as it uses `jbang`. -::: - - - - 1. Run the `configure` command: - ```sh - goose configure - ``` - - 2. Choose to add a `Command-line Extension` - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◆ What type of extension would you like to add? - │ ○ Built-in Extension - // highlight-start - │ ● Command-line Extension (Run a local command or script) - // highlight-end - │ ○ Remote Extension - └ - ``` - - 3. Give your extension a name - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◇ What type of extension would you like to add? - │ Command-line Extension - │ - // highlight-start - ◆ What would you like to call this extension? - │ {name} - // highlight-end - └ - ``` - - 4. Enter the command - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◇ What type of extension would you like to add? - │ Command-line Extension - │ - ◇ What would you like to call this extension? - │ {name} - │ - // highlight-start - ◆ What command should be run? - │ {command} - // highlight-end - └ - ``` - - 5. Enter the number of seconds Goose should wait for actions to complete before timing out. Default is 300s - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◇ What type of extension would you like to add? - │ Command-line Extension - │ - ◇ What would you like to call this extension? - │ {name} - │ - ◇ What command should be run? - │ {command} - │ - // highlight-start - ◆ Please set the timeout for this tool (in secs): - │ 300 - // highlight-end - │ - └ - ``` - - 6. Obtain a [GitHub Personal Access Token](https://github.com/settings/personal-access-tokens) and paste it in. - :::info - When creating your access token, you can specify the repositories and granular permissions you'd like Goose to have access to. - ::: - - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◇ What type of extension would you like to add? - │ Command-line Extension - │ - ◇ What would you like to call this extension? - │ {name} - │ - ◇ What command should be run? - │ {command} - │ - ◇ Please set the timeout for this tool (in secs): - │ 300 - │ - // highlight-start - ◆ Would you like to add environment variables? - │ Yes - │ - ◇ Environment variable name: - │ {env_var} - │ - ◇ Environment variable value: - │ ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪ - │ - ◇ Add another environment variable? - │ No - // highlight-end - └ Added github extension - ``` - - - - 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40hapins%2Ffigma-mcp&id=figma&name=Figma&description=Figma%20design%20tool%20integration&env=FIGMA_ACCESS_TOKEN%3DAccess%20token%20from%20Figma%20user%20settings) - 2. Press `Yes` to confirm the installation - 3. Obtain a [XYZ Access Token](/) and paste it in - 4. Click `Save Configuration` - 5. Scroll to the top and click `Exit` from the upper left corner - - - -## Example Usage - -{describe any environment setup, access controls, and what you want to accomplish.} - -### Goose Prompt - -> _exact prompt_ - - -### Goose Output - -:::note CLI - -{exact output} - -::: \ No newline at end of file diff --git a/documentation/docs/tutorials/_template_.mdx b/documentation/docs/tutorials/_template_.mdx new file mode 100644 index 00000000..d94f749a --- /dev/null +++ b/documentation/docs/tutorials/_template_.mdx @@ -0,0 +1,135 @@ +--- +title: {name} Extension + +escription: Add {name} MCP Server as a Goose Extension +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; +import CLIExtensionInstructions from '@site/src/components/CLIExtensionInstructions'; + + + + + +This tutorial covers how to add the [{name} MCP Server](/) as a Goose extension to enable file operations, repository management, search functionality, and more. + +:::tip TLDR + + + [Launch the installer]({goose_url}) + + + **Command** + ```sh + {command} + ``` + + + **Environment Variable** + ``` + {env_var}: + ``` +::: + +## Configuration + +:::info +Note that you'll need [Node.js](https://nodejs.org/) installed on your system to run this command, as it uses `npx`. +::: + +:::info +Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on your system to run this command, as it uses `uvx`. +::: + +:::info +Note that you'll need [JBang](https://www.jbang.dev/download) installed on your system to run this command, as it uses `jbang`. +::: + + + + 1. [Launch the installer]({goose_url}) + 2. Press Yes to confirm the installation + 3. Obtain a [XYZ Access Token](/) and paste it in + 4. Click Save Configuration + 5. Scroll to the top and click Exit from the upper left corner + + + + + + + + + + When creating your access token, you can specify the repositories and granular permissions you'd like Goose to have access to.{" "} + + Create one here + . + + } + /> + + + + + + When creating your access token, you can specify the repositories and granular permissions you'd like Goose to have access to.{" "} + + Create one here + . + + } + /> + + + + + + +## Example Usage + +{describe any environment setup, access controls, and what you want to accomplish.} + +### Goose Prompt + +> _exact prompt_ + + +### Goose Output + +:::note Desktop + +{exact output} + +::: \ No newline at end of file diff --git a/documentation/docs/tutorials/agentql-mcp.md b/documentation/docs/tutorials/agentql-mcp.md index 9d238173..497d2dba 100644 --- a/documentation/docs/tutorials/agentql-mcp.md +++ b/documentation/docs/tutorials/agentql-mcp.md @@ -12,15 +12,21 @@ This tutorial covers how to add the [AgentQL MCP Server](https://github.com/tiny :::tip TLDR -**Command** -```sh -npx -y agentql-mcp -``` - -**Environment Variable** -``` -AGENTQL_API_KEY: -``` + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=agentql-mcp&id=agentql&name=AgentQL&description=Transform%20unstructured%20web%20content%20into%20structured%20data&env=AGENTQL_API_KEY%3DAgentQL%20API%20Key) + + + **Command** + ```sh + npx -y agentql-mcp + ``` + + + **Environment Variable** + ``` + AGENTQL_API_KEY: + ``` ::: ## Configuration @@ -30,7 +36,14 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ::: - + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=agentql-mcp&id=agentql&name=AgentQL&description=Transform%20unstructured%20web%20content%20into%20structured%20data&env=AGENTQL_API_KEY%3DAgentQL%20API%20Key) + 2. Press `Yes` to confirm the installation + 3. Obtain an [AGENTQL_API_KEY](https://dev.agentql.com/api-keys) and paste it in + 4. Click `Save Configuration` + 5. Scroll to the top and click `Exit` from the upper left corner + + 1. Run the `configure` command: ```sh goose configure @@ -41,7 +54,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -57,7 +70,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -74,7 +87,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -94,7 +107,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -112,13 +125,40 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to └ ``` - 6. Obtain an [AgentQL API Key](https://dev.agentql.com/api-keys) and paste it in. + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ agentql + │ + ◇ What command should be run? + │ npx -y agentql-mcp + │ + ◆ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + │ + └ + ``` + + 7. Obtain an [AgentQL API Key](https://dev.agentql.com/api-keys) and paste it in. ```sh ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -132,6 +172,9 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ◇ Please set the timeout for this tool (in secs): │ 300 │ + ◇ Would you like to add a description? + │ No + │ // highlight-start ◆ Would you like to add environment variables? │ Yes @@ -149,13 +192,6 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ``` - - 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=agentql-mcp&id=agentql&name=AgentQL&description=Transform%20unstructured%20web%20content%20into%20structured%20data&env=AGENTQL_API_KEY%3DAgentQL%20API%20Key) - 2. Press `Yes` to confirm the installation - 3. Obtain an [AGENTQL_API_KEY](https://dev.agentql.com/api-keys) and paste it in - 4. Click `Save Configuration` - 5. Scroll to the top and click `Exit` from the upper left corner - ## Example Usage diff --git a/documentation/docs/tutorials/asana-mcp.md b/documentation/docs/tutorials/asana-mcp.md index 241513e3..538be812 100644 --- a/documentation/docs/tutorials/asana-mcp.md +++ b/documentation/docs/tutorials/asana-mcp.md @@ -12,18 +12,22 @@ import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; This tutorial covers how to add the [Asana MCP Server](https://github.com/roychri/mcp-server-asana) as a Goose extension to enable task automation, project tracking, and team collaboration. - :::tip TLDR - -**Command** -```sh -npx -y @roychri/mcp-server-asana -``` - -**Environment Variable** -``` -ASANA_ACCESS_TOKEN: -``` + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40roychri%2Fmcp-server-asana&id=asana&name=Asana&description=enable%20task%20automation%2C%20project%20tracking%2C%20and%20team%20collaboration&env=ASANA_ACCESS_TOKEN%3DAsana%20Access%20Token) + + + **Command** + ```sh + npx -y @roychri/mcp-server-asana + ``` + + + **Environment Variable** + ``` + ASANA_ACCESS_TOKEN: + ``` ::: ## Configuration @@ -34,7 +38,17 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to - + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40roychri%2Fmcp-server-asana&id=asana&name=Asana&description=enable%20task%20automation%2C%20project%20tracking%2C%20and%20team%20collaboration&env=ASANA_ACCESS_TOKEN%3DAsana%20Access%20Token) + 2. Press `Yes` to confirm the installation + 3. Obtain a [Asana Access Token](https://app.asana.com/0/my-apps) and paste it in + :::info + See [Asana's developer docs](https://developers.asana.com/docs/personal-access-token) if you need detailed instructions on creating an access token. + ::: + 4. Click `Save Configuration` + 5. Scroll to the top and click `Exit` from the upper left corner + + 1. Run the `configure` command: ```sh goose configure @@ -45,7 +59,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -61,7 +75,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -78,7 +92,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -98,7 +112,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -116,8 +130,35 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to │ └ ``` + + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Asana + │ + ◇ What command should be run? + │ npx -y @roychri/mcp-server-asana + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + │ + └ + ``` - 6. Obtain a [Asana Access Token](https://app.asana.com/0/my-apps) and paste it in. + 7. Obtain a [Asana Access Token](https://app.asana.com/0/my-apps) and paste it in. :::info See [Asana's developer docs](https://developers.asana.com/docs/personal-access-token) if you need detailed instructions on creating an access token. ::: @@ -126,7 +167,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -139,6 +180,9 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to │ ◇ Please set the timeout for this tool (in secs): │ 300 + │ + ◇ Would you like to add a description? + │ No │ // highlight-start ◆ Would you like to add environment variables? @@ -157,16 +201,6 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ``` - - 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40roychri%2Fmcp-server-asana&id=asana&name=Asana&description=enable%20task%20automation%2C%20project%20tracking%2C%20and%20team%20collaboration&env=ASANA_ACCESS_TOKEN%3DAsana%20Access%20Token) - 2. Press `Yes` to confirm the installation - 3. Obtain a [Asana Access Token](https://app.asana.com/0/my-apps) and paste it in - :::info - See [Asana's developer docs](https://developers.asana.com/docs/personal-access-token) if you need detailed instructions on creating an access token. - ::: - 4. Click `Save Configuration` - 5. Scroll to the top and click `Exit` from the upper left corner - ## Example Usage diff --git a/documentation/docs/tutorials/blender-mcp.md b/documentation/docs/tutorials/blender-mcp.md index b9f45dad..46a0d7eb 100644 --- a/documentation/docs/tutorials/blender-mcp.md +++ b/documentation/docs/tutorials/blender-mcp.md @@ -7,24 +7,28 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; - + This tutorial covers how to add the [Blender MCP Server](https://github.com/ahujasid/blender-mcp) as a Goose extension to create 3D scenes, control Blender with natural language, generate models, apply materials, and more. - :::tip TLDR - -**Command** -```sh -uvx blender-mcp -``` + + + [Launch the installer](goose://extension?cmd=uvx&arg=blender-mcp&id=blender&name=Blender&description=Blender%203D%20scene%20creation%20integration) + + + **Command** + ```sh + uvx blender-mcp + ``` + + +::: **Requirement** Download [Blender Application](https://www.blender.org/download/) and [Blender MCP Addon file](https://github.com/ahujasid/blender-mcp/blob/main/addon.py) -::: - ## Configuration :::info @@ -46,7 +50,13 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ### Add Blender MCP Server - + + 1. [Launch the installer](goose://extension?cmd=uvx&arg=blender-mcp&id=blender&name=Blender&description=Blender%203D%20scene%20creation%20integration) + 2. Press `Yes` to confirm the installation + 4. Click `Save Configuration` + 5. Scroll to the top and click `Exit` from the upper left corner + + 1. Run the `configure` command: ```sh goose configure @@ -57,7 +67,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -73,7 +83,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -90,7 +100,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -110,7 +120,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -128,14 +138,41 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on │ └ ``` + + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ blender + │ + ◇ What command should be run? + │ uvx blender-mcp + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + │ + └ + ``` - 6. Choose No when asked to add environment variables + 7. Choose No when asked to add environment variables ```sh ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -148,6 +185,9 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on │ ◇ Please set the timeout for this tool (in secs): │ 300 + │ + ◇ Would you like to add a description? + │ No │ // highlight-start ◆ Would you like to add environment variables? @@ -158,12 +198,6 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ``` - - 1. [Launch the installer](goose://extension?cmd=uvx&arg=blender-mcp&id=blender&name=Blender&description=Blender%203D%20scene%20creation%20integration) - 2. Press `Yes` to confirm the installation - 4. Click `Save Configuration` - 5. Scroll to the top and click `Exit` from the upper left corner - ## Example Usage diff --git a/documentation/docs/tutorials/brave-mcp.md b/documentation/docs/tutorials/brave-mcp.md new file mode 100644 index 00000000..59aa2cb3 --- /dev/null +++ b/documentation/docs/tutorials/brave-mcp.md @@ -0,0 +1,289 @@ +--- +title: Brave Search Extension +description: Add Brave Search API as a Goose Extension +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; + + + +This tutorial will get you started with the [Brave Search MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search) as a Goose extension to enable interactive searches for both web and local searches. + +:::tip TLDR + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-brave-search&id=brave-search&name=Brave%20Search&description=Brave%20Search%20API&env=BRAVE_API_KEY%3DYour%20API%20Key) + + + **Command** + ```sh + npx -y @modelcontextprotocol/server-brave-search + ``` + + + **Environment Variable** + ``` + BRAVE_API_KEY: + ``` +::: + +## Configuration + +:::info +Note that you'll need [Node.js](https://nodejs.org/) installed on your system to run this command, as it uses `npx`. +::: + + + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-brave-search&id=brave-search&name=Brave%20Search&description=Brave%20Search%20API&env=BRAVE_API_KEY%3DYour%20API%20Key) + 2. Press `Yes` to confirm the installation + 3. Get your [Brave Search API Key](https://api-dashboard.search.brave.com/app/keys) and paste it in + 4. Click `Save Configuration` + 5. Scroll to the top and click `Exit` from the upper left corner + + + 1. Run the `configure` command: + ```sh + goose configure + ``` + + 2. Choose to add a `Command-line Extension` + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◆ What type of extension would you like to add? + │ ○ Built-in Extension + // highlight-start + │ ● Command-line Extension (Run a local command or script) + // highlight-end + │ ○ Remote Extension + └ + ``` + + 3. Give your extension a name + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + // highlight-start + ◆ What would you like to call this extension? + │ brave-search + // highlight-end + └ + ``` + + 4. Enter the command + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ brave-search + │ + // highlight-start + ◆ What command should be run? + │ npx -y @modelcontextprotocol/server-brave-search + // highlight-end + └ + ``` + + 5. Enter the number of seconds Goose should wait for actions to complete before timing out. Default is 300s + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ brave-search + │ + ◇ What command should be run? + │ npx -y @modelcontextprotocol/server-brave-search + │ + // highlight-start + ◆ Please set the timeout for this tool (in secs): + │ 300 + // highlight-end + │ + └ + ``` + + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ brave-search + │ + ◇ What command should be run? + │ npx -y @modelcontextprotocol/server-brave-search + │ + ◆ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + │ + └ + ``` + + 7. Obtain a [Brave Search API Key](https://api-dashboard.search.brave.com/app/keys) and paste it in. + :::info + Sign up for a [Brave Search API account](https://brave.com/search/api/), choose a plan (the free tier gives you 2000 queries per month), and generate your API key from [the developer dashboard](https://api-dashboard.search.brave.com/app/keys). + ::: + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ brave-search + │ + ◇ What command should be run? + │ npx -y @modelcontextprotocol/server-brave-search + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No + │ + // highlight-start + ◆ Would you like to add environment variables? + │ Yes + │ + ◇ Environment variable name: + │ BRAVE_API_KEY + │ + ◇ Environment variable value: + │ ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪ + │ + ◇ Add another environment variable? + │ No + // highlight-end + └ Added brave-search extension + ``` + + + + +## Example Usage + +The Brave Search MCP server gives you access to the Brave Search API, providing both web and local search capabilities: + +- **Web Search**: General queries, news, articles, with pagination and freshness controls +- **Local Search**: Find businesses, restaurants, and services with detailed information (requires a Pro API key) +- **Flexible Filtering**: Control result types, safety levels, and content freshness +- **Smart Fallbacks**: Local search automatically falls back to web when no results are found + + +#### Goose Prompt + +``` +( O)> Organize a Italian dinner party with Ian, Rizel, Ace, and Ebony. Find 3 or 4 YouTube cooking channels (with URLs) that focus on Italian cuisine for beginners, and then find one recipe (with URL) for each person to make: one salad, two entrees, and one dessert. +``` + +#### Goose Output + +``` + +I'll help you organize the Italian dinner party and search for appropriate cooking resources and recipes. Let me break this down into parts. + +First, let's search for some beginner-friendly Italian cooking YouTube channels: + +Brave Web Search +query: best youtube channels italian cooking for beginners authenti... + +Let me search for some specific recipes: + +Brave Web Search +query: authentic italian salad recipes panzanella caprese insalata + +Brave Web Search +query: authentic italian main course pasta recipes carbonara osso b... + +Brave Web Search +query: authentic italian dessert recipes tiramisu panna cotta + + +Based on my research, I'll organize the dinner party plan with recommended YouTube channels and specific recipes for each person. Here's the plan: + +Recommended Italian Cooking YouTube Channels for Beginners + +1. Vincenzo's Plate (https://www.youtube.com/channel/UCcsSowAamCLJv-xeF9geXoA) + - Authentic Italian recipes with clear instructions + - Run by an Italian native who explains techniques well for beginners + +2. Italia Squisita (https://www.youtube.com/c/ItaliaSquisita) + - Features traditional Italian recipes + - Often shows multiple ways to make classic dishes + +3. Giallo Zafferano (https://www.youtube.com/user/GialloZafferano) + - Italy's most popular cooking channel + - Comprehensive collection of authentic Italian recipes + + +Recipe Assignments + +1. Rizel - Salad Course + - Classic Panzanella (Tuscan Bread and Tomato Salad) + - Recipe: https://www.themediterraneandish.com/tomato-panzanella-salad-recipe/ + - Perfect summer salad with crusty bread, tomatoes, and fresh basil + +2. Ian - First Entrée + - Authentic Spaghetti Carbonara + - Recipe: https://anitalianinmykitchen.com/spaghetti-carbonara/ + - Classic Roman pasta dish with eggs, pecorino, and guanciale + +3. Ace - Second Entrée + - Osso Buco alla Milanese + - Recipe: https://www.pastagrammar.com/post/osso-buco-authentic-italian-veal-recipe + - Traditional Milanese braised veal shanks with gremolata + +4. Ebony - Dessert + - Classic Vanilla Panna Cotta + - Recipe: https://www.recipesfromitaly.com/panna-cotta-recipe/ + - Elegant, creamy dessert that can be made ahead + + +Tips for Success + +- Watch the YouTube channels beforehand to familiarize yourself with Italian cooking techniques +- Make sure to read through the entire recipe before starting +- The Panna Cotta should be made at least 4 hours ahead (or the night before) to allow proper setting time +- The Panzanella can be assembled just before serving for the best texture +- The Osso Buco can be made slightly ahead and reheated, as it often tastes better the next day +- The Carbonara should be made just before serving and served immediately while hot + +``` diff --git a/documentation/docs/tutorials/browserbase-mcp.md b/documentation/docs/tutorials/browserbase-mcp.md new file mode 100644 index 00000000..ad98a97e --- /dev/null +++ b/documentation/docs/tutorials/browserbase-mcp.md @@ -0,0 +1,246 @@ +--- +title: Browserbase Extension +description: Add Browserbase MCP Server as a Goose Extension for Web Automation +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This tutorial covers how to add the Browserbase MCP Server as a Goose extension for browser automation, enabling programmatic control over navigation, page interactions, and content capture. + +:::tip TLDR + + + + [Launch the installer](goose://extension?cmd=npx&arg=@browserbasehq/mcp&id=browserbase&name=Browserbase&description=Automate%20web%20browsing%20and%20data%20extraction&env=BROWSERBASE_PROJECT_ID%3DBrowserbase%20Project%20ID&env=BROWSERBASE_API_KEY%3DBrowserbase%20API%20Key) + + + **Command** + ```sh + npx @browserbasehq/mcp + ``` + + + **Environment Variables** + ``` + BROWSERBASE_PROJECT_ID: + BROWSERBASE_API_KEY: + ``` +::: + +## Configuration + + + + 1. [Launch the installer](goose://extension?cmd=npx&arg=@browserbasehq/mcp&id=browserbase&name=Browserbase&description=Automate%20web%20browsing%20and%20data%20extraction&env=BROWSERBASE_PROJECT_ID%3DBrowserbase%20Project%20ID&env=BROWSERBASE_API_KEY%3DBrowserbase%20API%20Key) + 2. Press `Yes` to confirm the installation + 3. Obtain your [Browserbase Project ID and Browserbase API Key](https://browserbase.io/dashboard) and paste them in + 4. Click `Save Configuration` + 5. Scroll to the top and click `Exit` from the upper left corner + + + 1. Run the `configure` command: + ```sh + goose configure + ``` + + 2. Choose to add a `Command-line Extension` + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◆ What type of extension would you like to add? + │ ○ Built-in Extension + // highlight-start + │ ● Command-line Extension (Run a local command or script) + // highlight-end + │ ○ Remote Extension + └ + ``` + + 3. Give your extension a name + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + // highlight-start + ◆ What would you like to call this extension? + │ browserbase + // highlight-end + └ + ``` + + 4. Enter the command + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ browserbase + │ + // highlight-start + ◆ What command should be run? + │ npx @browserbasehq/mcp + // highlight-end + └ + ``` + + 5. Enter the timeout (default 300s) + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ browserbase + │ + ◇ What command should be run? + │ npx @browserbasehq/mcp + │ + // highlight-start + ◆ Please set the timeout for this tool (in secs): + │ 300 + // highlight-end + └ + ``` + + 6. Add a description (optional) + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ browserbase + │ + ◇ What command should be run? + │ npx @browserbasehq/mcp + │ + ◆ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + └ + ``` + + 7. Add environment variables + ```sh + ┌ goose-configure + │ + // highlight-start + ◆ Would you like to add environment variables? + │ Yes + │ + ◇ Environment variable name: + │ BROWSERBASE_PROJECT_ID + │ + ◇ Environment variable value: + │ ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪ + │ + ◇ Add another environment variable? + │ Yes + │ + ◇ Environment variable name: + │ BROWSERBASE_API_KEY + │ + ◇ Environment variable value: + │ ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪ + │ + ◇ Add another environment variable? + │ No + // highlight-end + └ Added browserbase extension + ``` + + + + +## Example Usage + +Let's use the Browserbase extension to gather information about trending MCP-related repositories on GitHub. + +:::info LLM +Claude 3.5 Sonnet was used for this task. +::: + +### Goose Prompt + +``` +1. Go to https://github.com/trending + +2. Search for "MCP server" or scroll through and find repos with "MCP" or "Model Context Protocol" in the title or description + +3. For the top 3 relevant repositories: + • Click into the repo + • Take a screenshot of the repo page + • Extract: + - Repo name + - Description + - Stars today + - First paragraph of README + • Navigate back + +- Close all tabs + +- Output a Markdown report showing: + • Repo name (link) + • Screenshot + • Description + Stars + • README excerpt +``` + +### Goose Output + +``` +# MCP Repositories Report + +## 1. [FastMCP](https://github.com/jlowin/fastmcp) + +![FastMCP Repository Screenshot] + +**Description**: The fast, Pythonic way to build MCP servers and clients. +**Stars Today**: 10.2k stars + +**README Excerpt**: The Model Context Protocol (MCP) is a new, standardized way to provide context and tools to your LLMs, and FastMCP makes building MCP servers and clients simple and intuitive. Create tools, expose resources, define prompts, and connect components with clean, Pythonic code. + +## 2. [GitHub MCP Server](https://github.com/github/github-mcp-server) + +![GitHub MCP Server Screenshot] + +**Description**: GitHub's official MCP Server that provides seamless integration with GitHub APIs, enabling advanced automation and interaction capabilities for developers and tools. +**Stars Today**: 13.9k stars + +**README Excerpt**: The GitHub MCP Server is a Model Context Protocol (MCP) server that provides seamless integration with GitHub APIs, enabling advanced automation and interaction capabilities for developers and tools. + +## 3. [Playwright MCP](https://github.com/microsoft/playwright-mcp) + +![Playwright MCP Screenshot] + +**Description**: A Model Context Protocol (MCP) server that provides browser automation capabilities using Playwright. +**Stars Today**: 10.2k stars + +**README Excerpt**: A Model Context Protocol (MCP) server that provides browser automation capabilities using Playwright. This server enables LLMs to interact with web pages through structured accessibility snapshots, bypassing the need for screenshots or visually-tuned models. +``` \ No newline at end of file diff --git a/documentation/docs/tutorials/computer-controller-mcp.md b/documentation/docs/tutorials/computer-controller-mcp.md index 83ee9393..2cc9a5ba 100644 --- a/documentation/docs/tutorials/computer-controller-mcp.md +++ b/documentation/docs/tutorials/computer-controller-mcp.md @@ -22,7 +22,12 @@ Let Goose complete its tasks without interruption - avoid using your mouse or ke 1. Ensure extension is enabled: - + + 1. Click `...` in the upper right corner + 2. Click `Advanced Settings` + 3. Under `Extensions`, toggle `Computer Controller` to on. + + 1. Run the `configure` command: ```sh @@ -34,7 +39,7 @@ Let Goose complete its tasks without interruption - avoid using your mouse or ke ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? // highlight-start @@ -50,7 +55,7 @@ Let Goose complete its tasks without interruption - avoid using your mouse or ke ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Built-in Extension @@ -70,7 +75,7 @@ Let Goose complete its tasks without interruption - avoid using your mouse or ke ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Built-in Extension @@ -85,11 +90,6 @@ Let Goose complete its tasks without interruption - avoid using your mouse or ke └ Enabled Computer Controller extension ``` - - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - 3. Under `Extensions`, toggle `Computer Controller` to on. - ## Example Usage @@ -101,7 +101,10 @@ Anthropic's Claude 3.5 Sonnet was used for this task. ::: - + + 1. Open a new session in Goose Desktop + + 1. Open a terminal and start a new Goose session: @@ -110,9 +113,6 @@ Anthropic's Claude 3.5 Sonnet was used for this task. ``` - - 1. Open a new session in Goose Desktop - ### Goose Prompt diff --git a/documentation/docs/tutorials/context7-mcp.mdx b/documentation/docs/tutorials/context7-mcp.mdx new file mode 100644 index 00000000..ec2276d0 --- /dev/null +++ b/documentation/docs/tutorials/context7-mcp.mdx @@ -0,0 +1,170 @@ +--- +title: Context7 Extension + +escription: Add Context7 MCP Server as a Goose Extension +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; +import CLIExtensionInstructions from '@site/src/components/CLIExtensionInstructions'; + + + + + +This tutorial covers how to add the [Context7 MCP Server](https://github.com/upstash/context7) as a Goose extension to pull up-to-date, version-specific code and docs so Goose can vibe code with real context, not hallucinated or outdated answers. + +:::tip TLDR + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40upstash%2Fcontext7-mcp&id=context7&name=Context7&description=Context7%20MCP%20server%20for%20up-to-date%20code%20and%20docs) + + + **Command** + ```sh + npx -y @upstash/context7-mcp + ``` + + +::: + +## Configuration + +:::info +Note that you'll need [Node.js](https://nodejs.org/) installed on your system to run this command, as it uses `npx`. +::: + + + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40upstash%2Fcontext7-mcp&id=context7&name=Context7&description=Context7%20MCP%20server%20for%20up-to-date%20code%20and%20docs) + 2. Press Yes to confirm the installation + 3. Click Save Configuration + 4. Scroll to the top and click Exit from the upper left corner + + + + + + + + +## Example Usage + +In this example, Goose uses Context7 to pull real-time docs, compare tools, and scaffold a modern app using only the latest, official implementation patterns. + + + + 1. Open a new session in Goose Desktop + + + + 1. Open a terminal and start a new Goose session: + + ```sh + goose session + ``` + + + + +### Goose Prompt + +I need help building a modern web application. Could you: + +1. Use Context7 to analyze and compare: + +- Next.js App Router vs Pages Router +- Prisma vs other ORMs +- Different authentication approaches +- State management solutions + +2. For each technology chosen: + +- Show the latest official implementation patterns +- Provide type definitions and interfaces +- Share relevant code snippets from documentation +- Explain best practices and common pitfalls + +3. Help implement: + +- Project structure following official guidelines +- Database schema and type safety +- Authentication flow with proper security +- API routes with error handling +- Performance optimizations + +Please reference specific documentation sections and explain why certain approaches are recommended over others. + +### Goose Output + +:::note Desktop + +1. Technology Analysis +Next.js App Router vs Pages Router +First, let's get the latest Next.js documentation: + + +-------- Resolve-library-id ------- + +Let's get detailed documentation about Next.js, focusing on routing: + + +------ Get-library-docs --------- + +Let me analyze the Next.js documentation and break down the key differences between the App Router and Pages Router: + +------ code structure comparison ----- + +Let's get information about Prisma and other ORMs: + + +------- Resolve-library-id ---------- + +Let's get Prisma's documentation: + + +------ Get-library-docs ---------- + + +Key Decisions and Best Practices: + +1. App Router vs Pages Router: + +Chose App Router for: +Server Components by default (better performance) +Built-in data fetching with streaming +Simpler API routes with Route Handlers +Better TypeScript integration + +2. Prisma as ORM: + +Benefits: +Type-safe database queries +Auto-generated client +Schema migrations +Rich query API +Using Prisma Accelerate for better performance + +3. Authentication: + +NextAuth.js for: +Built-in security features +Multiple authentication providers +Session management +JWT handling + +4. Performance Optimizations: + +Server Components for reduced JavaScript +Route Handlers for API endpoints +Prisma Accelerate for database performance +Type safety across the stack + +Would you like me to explain any part in more detail or help you implement specific features? + +::: \ No newline at end of file diff --git a/documentation/docs/tutorials/developer-mcp.md b/documentation/docs/tutorials/developer-mcp.md index db88fed4..83f2de84 100644 --- a/documentation/docs/tutorials/developer-mcp.md +++ b/documentation/docs/tutorials/developer-mcp.md @@ -23,7 +23,12 @@ The Developer extension is already enabled by default when Goose is installed. 1. Ensure extension is enabled: - + + 1. Click `...` in the upper right corner + 2. Click `Advanced Settings` + 3. Under `Extensions`, toggle `Developer` to on. + + 1. Run the `configure` command: ```sh @@ -44,11 +49,6 @@ The Developer extension is already enabled by default when Goose is installed. └ Extension settings updated successfully ``` - - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - 3. Under `Extensions`, toggle `Developer` to on. - ## Example Usage @@ -61,7 +61,10 @@ Anthropic's Claude 3.5 Sonnet was used for this task. - + + 1. Open a new session in Goose Desktop + + 1. Open a terminal and start a new Goose session: @@ -70,9 +73,6 @@ Anthropic's Claude 3.5 Sonnet was used for this task. ``` - - 1. Open a new session in Goose Desktop - ### Goose Prompt diff --git a/documentation/docs/tutorials/elevenlabs-mcp.md b/documentation/docs/tutorials/elevenlabs-mcp.md new file mode 100644 index 00000000..8d917985 --- /dev/null +++ b/documentation/docs/tutorials/elevenlabs-mcp.md @@ -0,0 +1,255 @@ +--- +title: ElevenLabs Extension +description: Add ElevenLabs MCP Server as a Goose Extension +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; + + + + +This tutorial covers how to add the [ElevenLabs MCP Server](https://github.com/yamadashy/repomix) as a Goose extension to enable AI-powered voice generation, voice cloning, audio editing, and speech-to-text transcription. + +:::tip TLDR + + + [Launch the installer](goose://extension?cmd=uvx&arg=elevenlabs-mcp&id=elevenlabs&name=ElevenLabs&description=ElevenLabs%20voice%20synthesis%20server&env=ELEVENLABS_API_KEY) + + + **Command** + ```sh + uvx elevenlabs-mcp + ``` + + + + **Environment Variable** + ``` + ELEVENLABS_API_KEY: + ``` +::: + +## Configuration + +:::info +Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on your system to run this command, as it uses `uvx`. +::: + + + + 1. [Launch the installer](goose://extension?cmd=uvx&arg=elevenlabs-mcp&id=elevenlabs&name=ElevenLabs&description=ElevenLabs%20voice%20synthesis%20server&env=ELEVENLABS_API_KEY) + 2. Press `Yes` to confirm the installation + 3. Click `Save Configuration` + 4. Scroll to the top and click `Exit` from the upper left corner + + + 1. Run the `configure` command: + ```sh + goose configure + ``` + + 2. Choose to add a `Command-line Extension` + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◆ What type of extension would you like to add? + │ ○ Built-in Extension + // highlight-start + │ ● Command-line Extension (Run a local command or script) + // highlight-end + │ ○ Remote Extension + └ + ``` + + 3. Give your extension a name + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + // highlight-start + ◆ What would you like to call this extension? + │ elevenlabs + // highlight-end + └ + ``` + + 4. Enter the command + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ elevenlabs + │ + // highlight-start + ◆ What command should be run? + │ uvx elevenlabs-mcp + // highlight-end + └ + ``` + + 5. Enter the number of seconds Goose should wait for actions to complete before timing out. Default is 300s + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ elevenlabs + │ + ◇ What command should be run? + │ uvx elevenlabs-mcp + │ + // highlight-start + ◆ Please set the timeout for this tool (in secs): + │ 300 + // highlight-end + │ + └ + ``` + + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ elevenlabs + │ + ◇ What command should be run? + │ uvx elevenlabs-mcp + │ + ◆ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + └ + ``` + + 7. Obtain an [ElevenLabs API Key](https://elevenlabs.io/app/settings/api-keys) and paste it in. + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ elevenlabs + │ + ◇ What command should be run? + │ uvx elevenlabs-mcp + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No + | + // highlight-start + ◆ Would you like to add environment variables? + │ Yes + │ + ◇ Environment variable name: + │ ELEVENLABS_API_KEY + │ + ◇ Environment variable value: + │ ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪ + │ + ◇ Add another environment variable? + │ No + // highlight-end + └ Added elevenlabs extension + ``` + + + + +## Example Usage + +In this example, I’ll show you how to use Goose with the ElevenLabs Extension to create AI-generated voiceovers for a YouTube Short. Goose will take a sample script I provided, generate a narrated version using different AI voices, and seamlessly switch tones mid-script to match the content flow. + +By connecting to the ElevenLabs MCP server, Goose can transform plain text into natural-sounding speech, offering multiple voice styles and character options — all without any manual recording or editing. + +### Goose Prompt + +> Hey Goose, create a script for me for my youtube short video, I want there to be two different voices. The first voice should cut me off and be a human narrator style and then switch to a cassual AI tone after I read the prompt. Here's an example of a YT short script I've done in the past: + +Wait… Within Seconds, Goose performed Security Audits Across Multiple Projects?! 🔥 + +Lets, plug & play to find out how + +Let’s provide Goose with the command it needs to connect to the Filesystem MCP server extension… + +Now lets play +propmt: "Hey Goose, I need to perform a security audit across multiple projects. Let's check for…🔹 Hardcoded Credentials – API keys, passwords, and secrets left in the code.🔹 SQL Injection Risks – Unsafe queries that could expose data.🔹 Insecure Cryptographic Practices – Weak encryption methods that put data at risk.AND🔹 Exposed Config Files – Sensitive information that shouldn't be public.🔹 Outdated Dependencies – Security vulnerabilities in third-party libraries." + +Go Goose, go Goose! + +✅ Goose scanned the entire codebase across 3 different projects, identified security risks, generated a detailed report with fixes and provided me with step by step instructions on how I can test and verify these code fixes! + +If that’s not amazing idk what is … + +🚀 to get started visit block.github.io/goose_ + + +### Goose Output + +:::note Desktop + +I'll create your YouTube script for you using the given script as reference. + +───────────── Text To Speech ───────────── + +───────────── Text To Speech ───────────── + +───────────── Text To Speech ───────────── + +───────────── Text To Speech ───────────── + +───────────── Text To Speech ───────────── + +───────────── Play Audio ──────────────── + +───────────── Play Audio ──────────────── + +The script has been created and read aloud using the specified voices and style. The audio files have been saved to your desktop. + +Press play and hear it for yourself! 🔊 + + + +::: \ No newline at end of file diff --git a/documentation/docs/tutorials/fetch-mcp.md b/documentation/docs/tutorials/fetch-mcp.md index d81ab1ff..cbcdefc8 100644 --- a/documentation/docs/tutorials/fetch-mcp.md +++ b/documentation/docs/tutorials/fetch-mcp.md @@ -9,22 +9,22 @@ import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; -:::warning Known Limitation -The Fetch extension [does not work](https://github.com/block/goose/issues/1184) with Google models (e.g. gemini-2.0-flash) because this extension uses `format: uri` in its JSON schema which Google doesn't support. -::: - This tutorial covers how to add the [Fetch MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch) as a Goose extension to retrieve and process content from the web. - :::tip TLDR - -**Command** -```sh -uvx mcp-server-fetch -``` + + + [Launch the installer](goose://extension?cmd=uvx&arg=mcp-server-fetch&id=fetch&name=Fetch&description=Web%20content%20fetching%20and%20processing%20capabilities) + + + **Command** + ```sh + uvx mcp-server-fetch + ``` + + ::: - ## Configuration :::info @@ -32,7 +32,11 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ::: - + + 1. [Launch the installer](goose://extension?cmd=uvx&arg=mcp-server-fetch&id=fetch&name=Fetch&description=Web%20content%20fetching%20and%20processing%20capabilities) + 2. Press `Yes` to confirm the installation + + 1. Run the `configure` command: ```sh goose configure @@ -43,7 +47,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -59,7 +63,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -76,7 +80,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -96,7 +100,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -114,13 +118,12 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on └ ``` - 6. Choose No when asked to add environment variables - + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. ```sh ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -133,6 +136,36 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on │ ◇ Please set the timeout for this tool (in secs): │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + └ + ``` + + 7. Choose No when asked to add environment variables + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ fetch + │ + ◇ What command should be run? + │ uvx mcp-server-fetch + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No │ // highlight-start ◇ Would you like to add environment variables? @@ -143,10 +176,6 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ``` - - 1. [Launch the installer](goose://extension?cmd=uvx&arg=mcp-server-fetch&id=fetch&name=Fetch&description=Web%20content%20fetching%20and%20processing%20capabilities) - 2. Press `Yes` to confirm the installation - ## Example Usage diff --git a/documentation/docs/tutorials/figma-mcp.md b/documentation/docs/tutorials/figma-mcp.md index 16fd047b..e3d652cc 100644 --- a/documentation/docs/tutorials/figma-mcp.md +++ b/documentation/docs/tutorials/figma-mcp.md @@ -12,18 +12,22 @@ import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; This tutorial covers how to add the [Figma MCP Server](https://github.com/hapins/figma-mcp) as a Goose extension to enable interaction with Figma files, designs, and components. - :::tip TLDR - -**Command** -```sh -npx @hapins/figma-mcp -``` - -**Environment Variable** -``` -FIGMA_ACCESS_TOKEN: -``` + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40hapins%2Ffigma-mcp&id=figma&name=Figma&description=Figma%20design%20tool%20integration&env=FIGMA_ACCESS_TOKEN%3DAccess%20token%20from%20Figma%20user%20settings) + + + **Command** + ```sh + npx -y @hapins/figma-mcp + ``` + + + **Environment Variable** + ``` + FIGMA_ACCESS_TOKEN: + ``` ::: ## Configuration @@ -33,7 +37,14 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ::: - + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40hapins%2Ffigma-mcp&id=figma&name=Figma&description=Figma%20design%20tool%20integration&env=FIGMA_ACCESS_TOKEN%3DAccess%20token%20from%20Figma%20user%20settings) + 2. Press `Yes` to confirm the installation + 3. Obtain a [Figma Access Token](https://www.figma.com/developers/api#access-tokens) and paste it in + 4. Click `Save Configuration` + 5. Scroll to the top and click `Exit` from the upper left corner + + 1. Run the `configure` command: ```sh goose configure @@ -44,7 +55,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -60,7 +71,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -77,7 +88,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -87,7 +98,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to │ // highlight-start ◆ What command should be run? - │ npx @hapins/figma-mcp + │ npx -y @hapins/figma-mcp // highlight-end └ ``` @@ -97,7 +108,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -106,7 +117,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to │ figma │ ◇ What command should be run? - │ npx @hapins/figma-mcp + │ npx -y @hapins/figma-mcp │ // highlight-start ◆ Please set the timeout for this tool (in secs): @@ -116,16 +127,12 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to └ ``` - 6. Obtain a [Figma Access Token](https://www.figma.com/developers/api#access-tokens) and paste it in. - :::info - You can generate an access token from your Figma account settings under the Personal access tokens section. - ::: - + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. ```sh ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -134,12 +141,45 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to │ figma │ ◇ What command should be run? - │ npx @hapins/figma-mcp + │ npx -y @hapins/figma-mcp │ ◇ Please set the timeout for this tool (in secs): │ 300 │ // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + └ + ``` + + 7. Obtain a [Figma Access Token](https://www.figma.com/developers/api#access-tokens) and paste it in. + :::info + You can generate an access token from your Figma account settings under the Personal access tokens section. + ::: + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ figma + │ + ◇ What command should be run? + │ npx -y @hapins/figma-mcp + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No + │ + // highlight-start ◆ Would you like to add environment variables? │ Yes │ @@ -156,13 +196,6 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ``` - - 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40hapins%2Ffigma-mcp&id=figma&name=Figma&description=Figma%20design%20tool%20integration&env=FIGMA_ACCESS_TOKEN%3DAccess%20token%20from%20Figma%20user%20settings) - 2. Press `Yes` to confirm the installation - 3. Obtain a [Figma Access Token](https://www.figma.com/developers/api#access-tokens) and paste it in - 4. Click `Save Configuration` - 5. Scroll to the top and click `Exit` from the upper left corner - ## Example Usage diff --git a/documentation/docs/tutorials/filesystem-mcp.md b/documentation/docs/tutorials/filesystem-mcp.md index 6e50d3ff..689a35c7 100644 --- a/documentation/docs/tutorials/filesystem-mcp.md +++ b/documentation/docs/tutorials/filesystem-mcp.md @@ -12,11 +12,18 @@ import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; This tutorial covers how to add the [Filesystem MCP server](https://github.com/modelcontextprotocol/servers/tree/HEAD/src/filesystem) as a Goose extension, enabling powerful code analysis and file management. With this extension, Goose can analyze project structures, edit and organize files, detect unused dependencies, and generate documentation to improve software maintainability. :::tip TLDR -**Command** -```sh -npx -y @modelcontextprotocol/server-filesystem -``` -You can specify multiple allowed directories by separating them with a space. + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=@modelcontextprotocol/server-filesystem&arg=/path/to/allowed/directory&id=filesystem-mcp&name=Filesystem%20MCP%20Server&description=Filesystem%20MCP%20Server) + + + **Command** + ```sh + npx -y @modelcontextprotocol/server-filesystem + ``` + + + You can specify multiple allowed directories by separating them with a space. ::: ## Configuration @@ -26,7 +33,14 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ::: - + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=@modelcontextprotocol/server-filesystem&arg=/path/to/allowed/directory&id=filesystem-mcp&name=Filesystem%20MCP%20Server&description=Filesystem%20MCP%20Server) + 2. Press `Yes` to confirm the installation + 3. Add additional allowed directories if desired, separated by a space. + 4. Click `Save Configuration` + + + 1. Run the `configure` command: ```sh goose configure @@ -37,7 +51,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -53,7 +67,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -70,7 +84,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -93,7 +107,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -111,12 +125,12 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to └ ``` - 6. Choose No when asked to add environment variables + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. ```sh ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -129,6 +143,35 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to │ ◇ Please set the timeout for this tool (in secs): │ 300 + │ + // highlight-start + ◆ Would you like to add a description? + │ No + // highlight-end + └ + ``` + + 7. Choose No when asked to add environment variables + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ filesystem + │ + ◇ What command should be run? + │ npx -y @modelcontextprotocol/server-filesystem /path/to/allowed/directory + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No │ // highlight-start ◆ Would you like to add environment variables? @@ -138,25 +181,6 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to └ Added filesystem extension ``` - - - - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - 3. Under `Extensions`, click the `Add` link - 4. On the `Add Extension Manually` modal, enter the following: - * **Type**: `Standard IO` - * **ID**: `filesystem-mcp` (_set this to whatever you want_) - * **Name**: `filesystem` (_set this to whatever you want_) - * **Description**: `filesystem MCP Server` (_set this to whatever you want_) - * **Command**: `npx -y @modelcontextprotocol/server-filesystem /path/to/allowed/directory` - 5. Click `Add Extension` button - - :::tip Multiple Directories - You can specify multiple directories by separating them with a space. - ::: - - ## Example Usage diff --git a/documentation/docs/tutorials/github-mcp.md b/documentation/docs/tutorials/github-mcp.md index 3e125152..497ebe15 100644 --- a/documentation/docs/tutorials/github-mcp.md +++ b/documentation/docs/tutorials/github-mcp.md @@ -6,25 +6,31 @@ description: Add GitHub MCP Server as a Goose Extension import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; +import CLIExtensionInstructions from '@site/src/components/CLIExtensionInstructions'; This tutorial covers how to add the [GitHub MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/github) as a Goose extension to enable file operations, repository management, search functionality, and more. :::tip TLDR + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-github&id=github&name=GitHub&description=GitHub%20API&env=GITHUB_PERSONAL_ACCESS_TOKEN%3DGitHub%20Personal%20Access%20Token) + + + **Command** + ```sh + npx -y @modelcontextprotocol/server-github + ``` + + -**Command** -```sh -npx -y @modelcontextprotocol/server-github -``` - -**Environment Variable** -``` -GITHUB_PERSONAL_ACCESS_TOKEN: -``` + **Environment Variable** + ``` + GITHUB_PERSONAL_ACCESS_TOKEN: + ``` ::: - ## Configuration :::info @@ -32,136 +38,37 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ::: - - 1. Run the `configure` command: - ```sh - goose configure - ``` - - 2. Choose to add a `Command-line Extension` - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◆ What type of extension would you like to add? - │ ○ Built-in Extension - // highlight-start - │ ● Command-line Extension (Run a local command or script) - // highlight-end - │ ○ Remote Extension - └ - ``` - - 3. Give your extension a name - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◇ What type of extension would you like to add? - │ Command-line Extension - │ - // highlight-start - ◆ What would you like to call this extension? - │ github - // highlight-end - └ - ``` - - 4. Enter the command - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◇ What type of extension would you like to add? - │ Command-line Extension - │ - ◇ What would you like to call this extension? - │ github - │ - // highlight-start - ◆ What command should be run? - │ npx -y @modelcontextprotocol/server-github - // highlight-end - └ - ``` - - 5. Enter the number of seconds Goose should wait for actions to complete before timing out. Default is 300s - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◇ What type of extension would you like to add? - │ Command-line Extension - │ - ◇ What would you like to call this extension? - │ github - │ - ◇ What command should be run? - │ npx -y @modelcontextprotocol/server-github - │ - // highlight-start - ◆ Please set the timeout for this tool (in secs): - │ 300 - // highlight-end - │ - └ - ``` - - 6. Obtain a [GitHub Personal Access Token](https://github.com/settings/personal-access-tokens) and paste it in. - :::info - When creating your access token, you can specify the repositories and granular permissions you'd like Goose to have access to. - ::: - - ```sh - ┌ goose-configure - │ - ◇ What would you like to configure? - │ Add Extension - │ - ◇ What type of extension would you like to add? - │ Command-line Extension - │ - ◇ What would you like to call this extension? - │ github - │ - ◇ What command should be run? - │ npx -y @modelcontextprotocol/server-github - │ - ◇ Please set the timeout for this tool (in secs): - │ 300 - │ - // highlight-start - ◆ Would you like to add environment variables? - │ Yes - │ - ◇ Environment variable name: - │ GITHUB_PERSONAL_ACCESS_TOKEN - │ - ◇ Environment variable value: - │ ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪ - │ - ◇ Add another environment variable? - │ No - // highlight-end - └ Added github extension - ``` - - - + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-github&id=github&name=GitHub&description=GitHub%20API&env=GITHUB_PERSONAL_ACCESS_TOKEN%3DGitHub%20Personal%20Access%20Token) 2. Press `Yes` to confirm the installation 3. Obtain a [GitHub Personal Access Token](https://github.com/settings/personal-access-tokens) and paste it in 4. Click `Save Configuration` 5. Scroll to the top and click `Exit` from the upper left corner + + + + When creating your access token, you can specify the repositories and granular permissions you'd like Goose to have access to.{" "} + + Create one here + . + + } + /> + + ## Example Usage diff --git a/documentation/docs/tutorials/google-drive-mcp.md b/documentation/docs/tutorials/google-drive-mcp.md index 6b831b74..45715329 100644 --- a/documentation/docs/tutorials/google-drive-mcp.md +++ b/documentation/docs/tutorials/google-drive-mcp.md @@ -12,28 +12,25 @@ import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; This tutorial covers how to add the [Google Drive MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/gdrive) as a Goose extension, allowing you to list, read, and search files in Google Drive. :::tip TLDR - -**Authentication Command** - -In your terminal, run the following: - -```sh -GDRIVE_OAUTH_PATH=/Users//.config/gcp-oauth.keys.json \ -GDRIVE_CREDENTIALS_PATH=/Users//.config/.gdrive-server-credentials.json \ -npx -y @modelcontextprotocol/server-gdrive auth -``` -**Command** -```sh -npx -y @modelcontextprotocol/server-gdrive -``` - -**Environment Variables** -``` -GDRIVE_CREDENTIALS_PATH: ~/.config/.gdrive-server-credentials.json -``` -``` -GDRIVE_OAUTH_PATH: ~/.config/gcp-oauth.keys.json -``` + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-gdrive&id=google-drive&name=Google%20Drive&description=Google%20Drive%20integration&env=GDRIVE_CREDENTIALS_PATH%3DPath%20to%20Google%20Drive%20credentials&env=GDRIVE_OAUTH_PATH%3DPath%20to%20OAuth%20token) + + + **Command** + ```sh + GDRIVE_OAUTH_PATH=/Users//.config/gcp-oauth.keys.json \ + GDRIVE_CREDENTIALS_PATH=/Users//.config/.gdrive-server-credentials.json \ + npx -y @modelcontextprotocol/server-gdrive auth + npx -y @modelcontextprotocol/server-gdrive + ``` + + + **Environment Variable** + ``` + GDRIVE_CREDENTIALS_PATH: ~/.config/.gdrive-server-credentials.json + GDRIVE_OAUTH_PATH: ~/.config/gcp-oauth.keys.json + ``` ::: ## Configuration @@ -86,7 +83,21 @@ You'll need to re-authenticate once a day when using the Google Drive extension. ::: - + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-gdrive&id=google-drive&name=Google%20Drive&description=Google%20Drive%20integration&env=GDRIVE_CREDENTIALS_PATH%3DPath%20to%20Google%20Drive%20credentials&env=GDRIVE_OAUTH_PATH%3DPath%20to%20OAuth%20token) + 2. Press `Yes` to confirm the installation + 3. For `GDRIVE_CREDENTIALS_PATH`, enter the following: + ``` + ~/.config/.gdrive-server-credentials.json + ``` + 4. For `GDRIVE_OAUTH_PATH`, enter the following: + ``` + ~/.config/gcp-oauth.keys.json + ``` + 5. Click `Save Configuration` + 6. Scroll to the top and click `Exit` from the upper left corner + + 1. Run the `configure` command: ```sh goose configure @@ -97,7 +108,7 @@ You'll need to re-authenticate once a day when using the Google Drive extension. ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -113,7 +124,7 @@ You'll need to re-authenticate once a day when using the Google Drive extension. ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -130,7 +141,7 @@ You'll need to re-authenticate once a day when using the Google Drive extension. ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -150,7 +161,7 @@ You'll need to re-authenticate once a day when using the Google Drive extension. ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -168,18 +179,12 @@ You'll need to re-authenticate once a day when using the Google Drive extension. └ ``` - -:::tip -You may need to use absolute paths (rather than relying on shell exapansion for `~`) for the `GDRIVE_CREDENTIALS_PATH` and `GDRIVE_OAUTH_PATH`. -::: - - 6. Add your environment variables - + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. ```sh ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -192,6 +197,36 @@ You may need to use absolute paths (rather than relying on shell exapansion for │ ◇ Please set the timeout for this tool (in secs): │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + └ + ``` + + 7. Add your environment variables + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ google drive + │ + ◇ What command should be run? + │ npx -y @modelcontextprotocol/server-gdrive + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No │ // highlight-start ◆ Would you like to add environment variables? @@ -217,20 +252,6 @@ You may need to use absolute paths (rather than relying on shell exapansion for ``` - - 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-gdrive&id=google-drive&name=Google%20Drive&description=Google%20Drive%20integration&env=GDRIVE_CREDENTIALS_PATH%3DPath%20to%20Google%20Drive%20credentials&env=GDRIVE_OAUTH_PATH%3DPath%20to%20OAuth%20token) - 2. Press `Yes` to confirm the installation - 3. For `GDRIVE_CREDENTIALS_PATH`, enter the following: - ``` - ~/.config/.gdrive-server-credentials.json - ``` - 4. For `GDRIVE_OAUTH_PATH`, enter the following: - ``` - ~/.config/gcp-oauth.keys.json - ``` - 5. Click `Save Configuration` - 6. Scroll to the top and click `Exit` from the upper left corner - ## Example Usage diff --git a/documentation/docs/tutorials/google-maps-mcp.md b/documentation/docs/tutorials/google-maps-mcp.md index 9f1fc4f5..3a7f11b6 100644 --- a/documentation/docs/tutorials/google-maps-mcp.md +++ b/documentation/docs/tutorials/google-maps-mcp.md @@ -10,18 +10,22 @@ import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; This tutorial covers how to add the [Google Maps MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/google-maps) as a Goose extension to enable geocoding, place searching, distance calculations, elevation data retrieval, and directions. - :::tip TLDR - -**Command** -```sh -npx -y @modelcontextprotocol/server-google-maps -``` - -**Environment Variable** -``` -GOOGLE_MAPS_API_KEY: -``` + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-google-maps&id=google-maps&name=Google%20Maps&description=Google%20Maps%20API%20integration&env=GOOGLE_MAPS_API_KEY%3DGoogle%20Maps%20API%20key) + + + **Command** + ```sh + npx -y @modelcontextprotocol/server-google-maps + ``` + + + **Environment Variable** + ``` + GOOGLE_MAPS_API_KEY: + ``` ::: ## Configuration @@ -30,8 +34,15 @@ GOOGLE_MAPS_API_KEY: Note that you'll need [Node.js](https://nodejs.org/) installed on your system to run this command, as it uses `npx`. ::: - - + + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-google-maps&id=google-maps&name=Google%20Maps&description=Google%20Maps%20API%20integration&env=GOOGLE_MAPS_API_KEY%3DGoogle%20Maps%20API%20key) + 2. Press `Yes` to confirm the installation + 3. Obtain a [GOOGLE_MAPS_API_KEY](https://developers.google.com/maps/documentation/javascript/get-api-key) and paste it in + 4. Click `Save Configuration` + 5. Scroll to the top and click `Exit` from the upper left corner + + 1. Run the `configure` command: ```sh goose configure @@ -42,7 +53,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -58,7 +69,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -75,7 +86,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -95,7 +106,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -112,15 +123,41 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to // highlight-end └ ``` - - 6. Obtain a [Google Maps API Key](https://developers.google.com/maps/documentation/javascript/get-api-key) and paste it in. + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Google Maps + │ + ◇ What command should be run? + │ npx -y @modelcontextprotocol/server-google-maps + │ + ◆ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + │ + └ + ``` + + 7. Obtain a [Google Maps API Key](https://developers.google.com/maps/documentation/javascript/get-api-key) and paste it in. ```sh ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -133,6 +170,9 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to │ ◇ Please set the timeout for this tool (in secs): │ 300 + │ + ◇ Would you like to add a description? + │ No │ // highlight-start ◆ Would you like to add environment variables? @@ -151,13 +191,6 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ``` - - 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-google-maps&id=google-maps&name=Google%20Maps&description=Google%20Maps%20API%20integration&env=GOOGLE_MAPS_API_KEY%3DGoogle%20Maps%20API%20key) - 2. Press `Yes` to confirm the installation - 3. Obtain a [GOOGLE_MAPS_API_KEY](https://developers.google.com/maps/documentation/javascript/get-api-key) and paste it in - 4. Click `Save Configuration` - 5. Scroll to the top and click `Exit` from the upper left corner - ## Example Usage diff --git a/documentation/docs/tutorials/jetbrains-mcp.md b/documentation/docs/tutorials/jetbrains-mcp.md index e720b9d2..2dda57e1 100644 --- a/documentation/docs/tutorials/jetbrains-mcp.md +++ b/documentation/docs/tutorials/jetbrains-mcp.md @@ -20,7 +20,12 @@ This tutorial covers how to enable and use the JetBrains MCP Server as a built-i 2. Enable built-in Goose extension: - + + 1. Click `...` in the upper right corner + 2. Click `Advanced Settings` + 3. Under `Extensions`, toggle `Jetbrains` to on. + + 1. Run the `configure` command: ```sh @@ -32,7 +37,7 @@ This tutorial covers how to enable and use the JetBrains MCP Server as a built-i ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? // highlight-start @@ -48,7 +53,7 @@ This tutorial covers how to enable and use the JetBrains MCP Server as a built-i ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Built-in Extension @@ -70,7 +75,7 @@ This tutorial covers how to enable and use the JetBrains MCP Server as a built-i ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Built-in Extension @@ -85,11 +90,30 @@ This tutorial covers how to enable and use the JetBrains MCP Server as a built-i │ └ Enabled jetbrains extension ``` - - - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - 3. Under `Extensions`, toggle `Jetbrains` to on. + + 5. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Built-in Extension + │ + ◇ Which built-in extension would you like to enable? + │ JetBrains + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◆ Would you like to add a description? + │ No + // highlight-end + │ + └ + ``` @@ -103,7 +127,15 @@ Anthropic's Claude 3.5 Sonnet was used for this task. - + + 1. Open [IntelliJ](https://www.jetbrains.com/idea/download) (JetBrains' Java and Kotlin IDE) + 2. Open a new session in Goose Desktop + :::note + You will interact with two separate apps: the Goose Desktop app and the IntelliJ IDE. + ::: + + + 1. Open [IntelliJ](https://www.jetbrains.com/idea/download) (JetBrains' Java and Kotlin IDE) 2. Open a terminal within your IDE and start a new Goose session: @@ -112,14 +144,6 @@ Anthropic's Claude 3.5 Sonnet was used for this task. goose session ``` - - - 1. Open [IntelliJ](https://www.jetbrains.com/idea/download) (JetBrains' Java and Kotlin IDE) - 2. Open a new session in Goose Desktop - :::note - You will interact with two separate apps: the Goose Desktop app and the IntelliJ IDE. - ::: - diff --git a/documentation/docs/tutorials/knowledge-graph-mcp.md b/documentation/docs/tutorials/knowledge-graph-mcp.md index 5ba31457..ced170e1 100644 --- a/documentation/docs/tutorials/knowledge-graph-mcp.md +++ b/documentation/docs/tutorials/knowledge-graph-mcp.md @@ -13,11 +13,17 @@ import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; This tutorial covers how to add the [Knowledge Graph Memory MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) as a Goose extension. This enables Goose to analyze relationships, detect patterns, and gain a deeper understanding of your data. The knowledge graph builds on the [memory extension](/docs/tutorials/memory-mcp) by mapping complex relationships between concepts and providing persistent memory across Goose sessions. :::tip TLDR - -**Command** -```sh -npx -y @modelcontextprotocol/server-memory -``` + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-memory&id=knowledge_graph_memory&name=Knowledge%20Graph%20Memory&description=Graph-based%20memory%20system%20for%20persistent%20knowledge%20storage) + + + **Command** + ```sh + npx -y @modelcontextprotocol/server-memory + ``` + + ::: ## Configuration @@ -27,7 +33,12 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ::: - + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-memory&id=knowledge_graph_memory&name=Knowledge%20Graph%20Memory&description=Graph-based%20memory%20system%20for%20persistent%20knowledge%20storage) + 2. Press `Yes` to confirm the installation + 3. Scroll to the top and click `Exit` from the upper left corner + + 1. Run the `configure` command: ```sh goose configure @@ -38,7 +49,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -54,7 +65,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -71,7 +82,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -91,7 +102,7 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -109,12 +120,39 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to └ ``` - 6. Choose No when asked to add environment variables + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. ```sh ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ knowledge graph memory + │ + ◇ What command should be run? + │ npx -y @modelcontextprotocol/server-memory + │ + ◆ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + │ + └ + ``` + + 7. Choose No when asked to add environment variables + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -127,6 +165,9 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to │ ◇ Please set the timeout for this tool (in secs): │ 300 + │ + ◇ Would you like to add a description? + │ No │ // highlight-start ◆ Would you like to add environment variables? @@ -137,11 +178,6 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to ``` - - 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40modelcontextprotocol%2Fserver-memory&id=knowledge_graph_memory&name=Knowledge%20Graph%20Memory&description=Graph-based%20memory%20system%20for%20persistent%20knowledge%20storage) - 2. Press `Yes` to confirm the installation - 3. Scroll to the top and click `Exit` from the upper left corner - ## Example Usage diff --git a/documentation/docs/tutorials/mbot-mcp.md b/documentation/docs/tutorials/mbot-mcp.md new file mode 100644 index 00000000..1ad9a16c --- /dev/null +++ b/documentation/docs/tutorials/mbot-mcp.md @@ -0,0 +1,256 @@ +--- +title: mbot MCP Extension +description: Control a MakeBlock mbot2 rover through MQTT and MCP as a Goose Extension +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; + + + +This tutorial will get you started with [deemkeen's MQTT MCP server](https://github.com/deemkeen/mbotmcp) for the [MakeBlock mbot2 rover](https://www.makeblock.com/products/buy-mbot2), and outline some code changes we made along the way. + +:::tip TLDR + + + [Launch the installer](goose://extension?cmd=/path/to/java&arg=-jar&arg=/path/to/mbotmcp-0.0.1-SNAPSHOT.jar&name=mbot2&description=mbot2&env=MQTT_SERVER_URI%3Dtcp://1.2.3.4:1883&env=MQTT_USERNAME%3Dyour_username&env=MQTT_PASSWORD%3Dyour_password) + + + **Command** + ```sh + /path/to/java -jar /path/to/mbotmcp-0.0.1-SNAPSHOT.jar + ``` + + + **Environment Variable** + ``` + MQTT_SERVER_URI: tcp://1.2.3.4:1883 + MQTT_PASSWORD: + MQTT_USERNAME: + ``` +::: + +## Configuration + + + + 1. [Launch the installer](goose://extension?cmd=/path/to/java&arg=-jar&arg=/path/to/mbotmcp-0.0.1-SNAPSHOT.jar&name=mbot2&description=mbot2&env=MQTT_SERVER_URI%3Dtcp://1.2.3.4:1883&env=MQTT_USERNAME%3Dyour_username&env=MQTT_PASSWORD%3Dyour_password) + 2. Press `Yes` to confirm the installation + 3. Get your MQTT server URI, and your username/password if your MQTT uses authentication + 4. Click `Save Configuration` + 5. Scroll to the top and click `Exit` from the upper left corner + + + 1. Run the `configure` command: + ```sh + goose configure + ``` + + 1. Choose to add a `Command-line Extension` + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◆ What type of extension would you like to add? + │ ○ Built-in Extension + // highlight-start + │ ● Command-line Extension (Run a local command or script) + // highlight-end + │ ○ Remote Extension + └ + ``` + + 2. Give your extension a name + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + // highlight-start + ◆ What would you like to call this extension? + │ mbot2 + // highlight-end + └ + ``` + + 3. Enter the command + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ mbot2 + │ + // highlight-start + ◆ What command should be run? + │ /path/to/java -jar /path/to/mbotmcp-0.0.1-SNAPSHOT.jar + // highlight-end + └ + ``` + + 4. Enter the number of seconds Goose should wait for actions to complete before timing out. Default is 300s + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ mbot2 + │ + ◇ What command should be run? + │ /path/to/java -jar /path/to/mbotmcp-0.0.1-SNAPSHOT.jar + │ + // highlight-start + ◆ Please set the timeout for this tool (in secs): + │ 300 + // highlight-end + │ + └ + ``` + + 5. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ mbot2 + │ + ◇ What command should be run? + │ /path/to/java -jar /path/to/mbotmcp-0.0.1-SNAPSHOT.jar + │ + ◆ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + │ + └ + ``` + + 6. Add environment variables for MQTT + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ mbot2 + │ + ◇ What command should be run? + │ /path/to/java -jar /path/to/mbotmcp-0.0.1-SNAPSHOT.jar + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No + │ + // highlight-start + ◆ Would you like to add environment variables? + │ Yes + │ + ◇ Environment variable name: + │ MQTT_SERVER_URI + │ + ◇ Environment variable value: + │ tcp://1.2.3.4:1883 + │ + ◇ Add another environment variable? + │ Yes + │ + ◇ Environment variable name: + │ MQTT_USERNAME + │ + ◇ Environment variable value: + │ username + │ + ◇ Add another environment variable? + │ Yes + │ + ◇ Environment variable name: + │ MQTT_PASSWORD + │ + ◇ Environment variable value: + │ password + // highlight-end + │ + └ Added mbot2 extension + ``` + :::info + MQTT_USERNAME and MQTT_PASSWORD are required to exist, but can be empty strings if your MQTT server does not require authentication. + ::: + + + + + +## Example Usage + +The available commands allow you to drive the mbot2 rover around, including: + +- "turn left" or "turn right"" +- drive "forward" or "backward" +- "explore" randomly +- "stop" exploring +- "beep" + +The default distance to travel is 70cm (about 27 inches), and the turn angles are set to 90 degrees. You can change these values in the [Python code on the mbot2](https://github.com/deemkeen/mbotmcp/blob/main/assets/mbot-mqtt.py). The mbot2 has a lot of other capabilities with the proximity sensors, lights, and color detection sensor on the bottom of the unit that you can add to the Python code, and will need to update [the Java code](https://github.com/deemkeen/mbotmcp/blob/main/src/main/java/de/emkeen/mbotmcp/service/BotService.java) to include those commands via MCP. + +#### Goose Interaction + +``` +( O)> Let my mbot2 explore the area + + Okay, let's get the mbot2 rover moving. I will send it on a mission to explore the area. + + > Mbotexplore + +( O)> stop the rover, turn left and move forward + + Alright, I will stop the rover, turn to the left, and then move it forward. + + > Mbotstop + + > Mbotleft + + > Mbotforward + +( O)> let's move backward and beep so I know when it's finished + + Sure, I will move the rover backward and beep when it's done. + + > Mbotbackward + + > Mbotbeep + + Okay, the mbot2 has moved and beeped. What else would you like to do with the rover? + +``` diff --git a/documentation/docs/tutorials/memory-mcp.md b/documentation/docs/tutorials/memory-mcp.md index f16cb49a..b7e6ca07 100644 --- a/documentation/docs/tutorials/memory-mcp.md +++ b/documentation/docs/tutorials/memory-mcp.md @@ -18,7 +18,13 @@ This tutorial covers enabling and using the Memory MCP Server, which is a built- 1. Ensure extension is enabled: - + + 1. Click `...` in the upper right corner + 2. Click `Advanced Settings` + 3. Under `Extensions`, toggle `Memory` to on. + 4. Scroll to the top and click `Exit` from the upper left corner + + 1. Run the `configure` command: ```sh @@ -30,7 +36,7 @@ This tutorial covers enabling and using the Memory MCP Server, which is a built- ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? // highlight-start @@ -46,7 +52,7 @@ This tutorial covers enabling and using the Memory MCP Server, which is a built- ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Built-in Extension @@ -67,7 +73,7 @@ This tutorial covers enabling and using the Memory MCP Server, which is a built- ┌ goose-configure │ ◇ What would you like to configure? - │ Add Extension + │ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Built-in Extension @@ -81,12 +87,29 @@ This tutorial covers enabling and using the Memory MCP Server, which is a built- // highlight-end └ Enabled Memory extension ``` - - - 1. Click `...` in the upper right corner - 2. Click `Advanced Settings` - 3. Under `Extensions`, toggle `Memory` to on. - 4. Scroll to the top and click `Exit` from the upper left corner + + 5. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Built-in Extension + │ + ◇ Which built-in extension would you like to enable? + │ Memory + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◆ Would you like to add a description? + │ No + // highlight-end + └ Enabled Memory extension + ``` @@ -125,7 +148,10 @@ If you frequently work with API standards or other structured knowledge, Goose m ::: - + + 1. Open a new session in Goose Desktop + + 1. Open a terminal and start a new Goose session: @@ -134,9 +160,6 @@ If you frequently work with API standards or other structured knowledge, Goose m ``` - - 1. Open a new session in Goose Desktop - ### Step 1: Teach Goose Your API Standards diff --git a/documentation/docs/tutorials/nostrbook-mcp.md b/documentation/docs/tutorials/nostrbook-mcp.md new file mode 100644 index 00000000..a71f8550 --- /dev/null +++ b/documentation/docs/tutorials/nostrbook-mcp.md @@ -0,0 +1,341 @@ +--- +title: Nostrbook Extension +description: Add Nostrbook MCP Server as a Goose Extension +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + +This tutorial covers how to add the [Nostrbook MCP Server](https://gitlab.com/soapbox-pub/nostrbook) as a Goose extension to provide access to a comprehensive registry of Nostr documentation that helps users understand the decentralized social protocol, its implementation possibilities, and technical specifications. + + +:::tip TLDR + +**Command** +```sh +npx -y xjsr @nostrbook/mcp +``` + +::: + +## Configuration + +:::info +Note that you'll need [Node.js](https://nodejs.org/) installed on your system to run this command, as it uses `npx`. +::: + + + + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=%40nostrbook%2Fmcp&id=nostrbook&name=Nostrbook&description=A%20comprehensive%20registry%20of%20Nostr%20documentation%20with%20structured%20knowledge) + 2. Press `Yes` to confirm the installation + 3. Click `Save Configuration` + 4. Scroll to the top and click `Exit` from the upper left corner + + + 1. Run the `configure` command: + ```sh + goose configure + ``` + + 2. Choose to add a `Command-line Extension` + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension + │ + ◆ What type of extension would you like to add? + │ ○ Built-in Extension + // highlight-start + │ ● Command-line Extension (Run a local command or script) + // highlight-end + │ ○ Remote Extension + └ + ``` + + 3. Give your extension a name + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + // highlight-start + ◆ What would you like to call this extension? + │ Nostrbook + // highlight-end + └ + ``` + + 4. Enter the command + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Nostrbook + │ + // highlight-start + ◆ What command should be run? + │ npx -y xjsr @nostrbook/mcp + // highlight-end + └ + ``` + + 5. Enter the number of seconds Goose should wait for actions to complete before timing out. Default is 300s + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Nostrbook + │ + ◇ What command should be run? + │ npx -y xjsr @nostrbook/mcp + │ + // highlight-start + ◆ Please set the timeout for this tool (in secs): + │ 300 + // highlight-end + │ + └ + ``` + + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Nostrbook + │ + ◇ What command should be run? + │ npx -y xjsr @nostrbook/mcp + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◇ Would you like to add a description? + │ No + // highlight-end + │ + └ + ``` + + 7. Choose No when asked to add environment variables + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Nostrbook + │ + ◇ What command should be run? + │ npx -y xjsr @nostrbook/mcp + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No + │ + // highlight-start + ◆ Would you like to add environment variables? + │ No + │ + // highlight-end + └ Added Nostrbook extension + ``` + + + + +## Example Usage + +### Goose Prompt + +> _Goose, what is the NIP-61 and how can I implement it?._ + + +### Goose Output + +Note that IDs have been changed to generic ones + +:::note CLI + +
+ Tool Calls + + ─── read_nip | nostrbook ────────────────────────── + + nip: 61 + +
+ + + +# NIP-61: Implementing Nutzaps + +NIP-61 defines "Nutzaps," which are P2PK Cashu tokens where the payment itself acts as the receipt. This protocol allows for Bitcoin micropayments through the Nostr ecosystem using Cashu tokens. + +## Core Concept + +Nutzaps enable users to send small Bitcoin payments to each other through Nostr events, with the tokens being locked to the recipient's specified public key. + +## Implementation Guide + +### Prerequisites + +1. Familiarize yourself with [Cashu](https://cashu.space/) and [P2PK token locking](https://github.com/cashubtc/nuts/blob/main/11.md) +2. Understand [NIP-60](https://github.com/nostr-protocol/nips/blob/master/60.md) for wallet functionality +3. Ensure your implementation supports NUT-11 (P2PK) and NUT-12 (DLEQ proofs) + +### Step 1: Set Up Recipient Information (For Receiving Nutzaps) + +Create and publish a `kind:10019` event to inform others how to send you money: + +```json +{ + "kind": 10019, + "tags": [ + ["relay", "wss://your-relay1.com"], + ["relay", "wss://your-relay2.com"], + ["mint", "https://your-trusted-mint.com", "sat"], + ["pubkey", ""] + ] +} +``` + +- The `pubkey` should be a dedicated public key (NOT your Nostr identity key) +- Store the corresponding private key safely in your NIP-60 wallet event +- List only mints you trust and monitor + +### Step 2: Building the Sender Functionality + +To implement nutzapping: + +1. **Query recipient information**: + ```javascript + // Fetch recipient's kind:10019 event + const recipientInfo = await relay.get({ + kinds: [10019], + authors: [recipientPubkey] + }); + ``` + +2. **Extract mint and P2PK information**: + ```javascript + const recipientMints = recipientInfo.tags + .filter(tag => tag[0] === 'mint') + .map(tag => tag[1]); + + const recipientLockKey = recipientInfo.tags + .find(tag => tag[0] === 'pubkey')?.[1]; + ``` + +3. **Mint or swap tokens** at one of the recipient's trusted mints, P2PK-locking them to their specified key + +4. **Publish the nutzap event**: + ```json + { + "kind": 9321, + "content": "Thanks for your post!", + "tags": [ + ["proof", "{\"amount\":1,\"C\":\"02...3f\",\"id\":\"000...\",\"secret\":\"[\\\"P2PK\\\",...]\"}"], + ["u", "https://recipient-specified-mint.com"], + ["e", "", ""], + ["p", ""] + ] + } + ``` + +### Step 3: Building the Recipient Functionality + +To implement nutzap receiving: + +1. **Query for incoming nutzaps**: + ```javascript + // Subscribe to nutzap events + relay.subscribe([ + { + kinds: [9321], + "#p": [myPubkey], + "#u": myTrustedMints, + since: lastCheckTimestamp + } + ]); + ``` + +2. **Validate incoming nutzaps**: + - Verify the token is from a mint you've specified + - Verify the token is locked to your P2PK key + - Verify the DLEQ proof + +3. **Redeem the token**: + - Swap the P2PK token into your wallet + - Create a `kind:7376` event to record redemption: + ```json + { + "kind": 7376, + "content": "", + "tags": [ + ["e", "", "", "redeemed"], + ["p", ""] + ] + } + ``` + +### Important Implementation Details + +1. **P2PK Key Prefixing**: Always prefix the P2PK-lock public key with "02" for compatibility +2. **Token Verification**: Implement local verification of DLEQ proofs +3. **Mint URL Normalization**: Normalize mint URLs to avoid issues with slight URL variations +4. **User Guidance**: Guide users to use only NUT-11 and NUT-12 compatible mints +5. **Publishing Locations**: Publish nutzap events to the recipient's specified relays + +## Testing + +When testing your implementation: +1. Create test nutzaps with small amounts +2. Verify token redemption works correctly +3. Check that redemption records are properly created +4. Test with multiple mints for compatibility + +## Security Considerations + +- Never use the user's main Nostr key for P2PK operations +- Ensure proper encryption of sensitive wallet data +- Validate all proofs before attempting to redeem tokens +- Only interact with explicitly trusted mints + +By following these steps, you can implement a complete nutzap solution that allows users to send and receive Bitcoin micropayments through the Nostr protocol using Cashu tokens. + +::: \ No newline at end of file diff --git a/documentation/docs/tutorials/pdf-mcp.md b/documentation/docs/tutorials/pdf-mcp.md index 8df531c8..9e4fcf7d 100644 --- a/documentation/docs/tutorials/pdf-mcp.md +++ b/documentation/docs/tutorials/pdf-mcp.md @@ -12,13 +12,17 @@ import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; This tutorial covers how to add the [PDF Reader MCP Server](https://github.com/michaelneale/mcp-read-pdf) as a Goose extension, enabling Goose to read and extract text from protected and unprotected PDFs. :::tip TLDR - -**Command** - -```sh -uvx mcp-read-pdf -``` - + + + [Launch the installer](goose://extension?cmd=uvx&arg=mcp-read-pdf&id=pdf_read&name=PDF%20Reader&description=Read%20large%20and%20complex%20PDF%20documents) + + + **Command** + ```sh + uvx mcp-read-pdf + ``` + + ::: ## Configuration @@ -28,7 +32,13 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ::: - + + 1. [Launch the installer](goose://extension?cmd=uvx&arg=mcp-read-pdf&id=pdf_read&name=PDF%20Reader&description=Read%20large%20and%20complex%20PDF%20documents) + 2. Press `Yes` to confirm the installation + 3. Click `Save Configuration` + 4. Scroll to the top and click `Exit` from the upper left corner + + 1. Run the `configure` command: ```sh goose configure @@ -40,7 +50,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? -│ Add Extension +│ Add Extension (Connect to a new extension) │ ◆ What type of extension would you like to add? │ ○ Built-in Extension @@ -57,7 +67,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? -│ Add Extension +│ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -75,7 +85,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? -│ Add Extension +│ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -96,7 +106,7 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on ┌ goose-configure │ ◇ What would you like to configure? -│ Add Extension +│ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -115,13 +125,12 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on └ ``` -6. Choose No when asked to add environment variables - +6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. ```sh ┌ goose-configure │ ◇ What would you like to configure? -│ Add Extension +│ Add Extension (Connect to a new extension) │ ◇ What type of extension would you like to add? │ Command-line Extension @@ -136,6 +145,37 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on │ 300 │ // highlight-start +◇ Would you like to add a description? +│ No +// highlight-end +│ +└ +``` + +7. Choose No when asked to add environment variables + +```sh +┌ goose-configure +│ +◇ What would you like to configure? +│ Add Extension (Connect to a new extension) +│ +◇ What type of extension would you like to add? +│ Command-line Extension +│ +◇ What would you like to call this extension? +│ pdf +│ +◇ What command should be run? +│ uvx mcp-read-pdf +│ +◇ Please set the timeout for this tool (in secs): +│ 300 +│ +◇ Would you like to add a description? +│ No +│ +// highlight-start ◆ Would you like to add environment variables? │ No │ @@ -143,12 +183,6 @@ Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on └ Added pdf extension ``` - - - 1. [Launch the installer](goose://extension?cmd=uvx&arg=mcp-read-pdf&id=pdf_read&name=PDF%20Reader&description=Read%20large%20and%20complex%20PDF%20documents) - 2. Press `Yes` to confirm the installation - 3. Click `Save Configuration` - 4. Scroll to the top and click `Exit` from the upper left corner @@ -264,4 +298,4 @@ Their overall profile suggests they would be able to make immediate contribution **Total Score: 21.5/25** (not including bonus) -::: +::: \ No newline at end of file diff --git a/documentation/docs/tutorials/pieces-mcp.md b/documentation/docs/tutorials/pieces-mcp.md new file mode 100644 index 00000000..72c90f47 --- /dev/null +++ b/documentation/docs/tutorials/pieces-mcp.md @@ -0,0 +1,238 @@ +--- +title: Pieces for Developers Extension +description: Add Pieces for Developers MCP Server as a Goose Extension +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; + + + +This tutorial covers how to add the [Pieces for Developers MCP Server](https://docs.pieces.app/products/mcp/get-started?utm_source=goose&utm_medium=collab&utm_campaign=mcp) as a Goose extension to enable interaction with your Pieces Long-Term Memory. + +## Configuration + +1. Download [PiecesOS](https://pieces.app/) + +2. Enable [Long-Term Memory Context](https://docs.pieces.app/products/quick-guides/ltm-context) in PiecesOS + +3. Locate your MCP Server URL + - In PiecesOS, navigate to Settings > Model Context Protocol (MCP) + - Copy the server URL + +:::tip +The default server URL is shown below. PiecesOS may use a different port if 39300 is already in use on your system: + +``` +http://localhost:39300/model_context_protocol/2024-11-05/sse +``` +::: + +### Add Pieces MCP Server + + + + 1. [Launch the installer](goose://extension?url=http%3A%2F%2Flocalhost%3A39300%2Fmodel_context_protocol%2F2024-11-05%2Fsse&id=pieces&name=Pieces%20for%20Developers&description=Provides%20access%20to%20your%20Pieces%20Long-Term%20Memory.%20You%20need%20to%20have%20Pieces%20installed%20to%20use%20this.) + 2. Press `Yes` to confirm the installation + + + 1. Run the `configure` command: + + ```sh + goose configure + ``` + + 2. Choose to add a `Remote Extension` + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◆ What type of extension would you like to add? + │ ○ Built-in Extension + │ ○ Command-line Extension (Run a local command or script) + // highlight-start + │ ● Remote Extension + // highlight-end + └ + ``` + + 3. Give your extension a name + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Remote Extension + │ + // highlight-start + ◆ What would you like to call this extension? + │ Pieces + // highlight-end + └ + ``` + + 4. Enter the SSE endpoint URI. + + :::info + Use the server URL you copied from PiecesOS settings earlier. + ::: + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Remote Extension + │ + ◇ What would you like to call this extension? + │ Pieces + │ + // highlight-start + ◆ What is the SSE endpoint URI? + │ http://localhost:39300/model_context_protocol/2024-11-05/sse + // highlight-end + └ + ``` + + 5. Enter the number of seconds Goose should wait for actions to complete before timing out. Default is 300s + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Remote Extension + │ + ◇ What would you like to call this extension? + │ Pieces + │ + ◇ What is the SSE endpoint URI? + │ http://localhost:39300/model_context_protocol/2024-11-05/sse + │ + // highlight-start + ◆ Please set the timeout for this tool (in secs): + │ 300 + // highlight-end + └ + ``` + + 6. Add a description if you want, otherwise to leave the description blank select No. + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Remote Extension + │ + ◇ What would you like to call this extension? + │ Pieces + │ + ◇ What is the SSE endpoint URI? + │ http://localhost:39300/model_context_protocol/2024-11-05/sse + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + // highlight-start + ◆ Would you like to add a description? + │ ○ Yes / ● No + // highlight-end + │ + └ + ``` + + 7. No environment variables are needed, so select No. + + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Remote Extension + │ + ◇ What would you like to call this extension? + │ Pieces + │ + ◇ What is the SSE endpoint URI? + │ http://localhost:39300/model_context_protocol/2024-11-05/sse + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No + │ + // highlight-start + ◆ Would you like to add environment variables? + │ ○ Yes / ● No + // highlight-end + └ + ``` + + + + +## Example Usage + +Let's use the Pieces for Developers MCP server to generate a status update for a standup meeting based on your activities on the previous day. + +### Goose Prompt + +``` +I need a status update for what I was using yesterday. Create a report with 5 bullet points based off the most important activities, and give each one a brief description. +``` + +### Goose Output + +``` +Here's a report summarizing your key activities from yesterday, based on the most important interactions and applications you were using: + +1. Code Development with Goose: + + Activity: You worked on integrating SSE MCP servers into the Goose CLI, making enhancements to handle extensions via schema URLs. + Description: This task involved modifying code and testing new integrations to support advanced functionalities. It also included interactions with the Goose community on Discord for feedback and troubleshooting. + +2. Documentation Updates: + + Activity: You updated and managed documentation related to the Goose project and MCP integrations. + Description: This involved preparing documentation for new features, including usage guidelines and technical details, aiding in the improvement of user experience and technical instructions. + +3. Community Interaction on Discord: + + Activity: Engaged with the Block Open Source Community to discuss project developments and potential collaborations. + Description: You actively participated in community discussions, exchanging insights on project enhancements, and exploring collaborative opportunities with other developers and contributors. + +4. Managing and Testing Extensions: + + Activity: You focused on testing and managing various extensions through the Goose platform. + Description: This included configuring and validating different extensions, ensuring their compatibility and functionality, and incorporating feedback from testing. + +5. Content and Integration Planning: + + Activity: Planned integration workflows for MCP and documented their use cases. + Description: You worked on strategies for implementing MCP integrations effectively, involving planning sessions to optimize future project deployments and align them with user requirements and project objectives. + +These activities demonstrate a productive day with a focus on development, collaboration, and content management within your technical community. +``` + + +:::tip +For more examples of prompts you can use with the Pieces for Developers MCP Server, see the [Pieces MCP prompting guide](https://docs.pieces.app/products/mcp/prompting?utm_source=goose&utm_medium=collab&utm_campaign=mcp). +::: \ No newline at end of file diff --git a/documentation/docs/tutorials/playwright-mcp.md b/documentation/docs/tutorials/playwright-mcp.md new file mode 100644 index 00000000..5d5313b1 --- /dev/null +++ b/documentation/docs/tutorials/playwright-mcp.md @@ -0,0 +1,326 @@ +--- +title: Playwright Extension +description: Add Playwright MCP Server as a Goose Extension for Modern Web Testing +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed'; + + + +This tutorial covers how to add the Playwright MCP Server as a Goose extension, to enable cross-browser testing and web automation across Chromium and Webkit. + +:::tip TLDR + + + [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=@playwright/mcp@latest&id=playwright&name=Playwright&description=Modern%20web%20testing%20and%20automation) + + + **Command** + ```sh + npx -y @playwright/mcp@latest + ``` + + +::: + +## Configuration + +:::info +Note that you'll need [Node.js](https://nodejs.org/) installed on your system to run this command, as it uses `npx`. +::: + + + + 1. [Launch the installer](goose://extension?cmd=npx&arg=-y&arg=@playwright/mcp@latest&id=playwright&name=Playwright&description=Modern%20web%20testing%20and%20automation) + 2. Press `Yes` to confirm the installation + 3. Scroll to the top and click `Exit` from the upper left corner + + + 1. Run the `configure` command: + ```sh + goose configure + ``` + + 2. Choose to add a `Command-line Extension` + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◆ What type of extension would you like to add? + │ ○ Built-in Extension + │ ● Command-line Extension (Run a local command or script) + │ ○ Remote Extension + └ + ``` + + 3. Give your extension a name + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◆ What would you like to call this extension? + │ Playwright + └ + ``` + + 4. Enter the command + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Playwright + │ + ◆ What command should be run? + │ npx -y @playwright/mcp@latest + └ + ``` + + 5. Enter the number of seconds Goose should wait for actions to complete before timing out. Default is 300s + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Playwright + │ + ◇ What command should be run? + │ npx -y @playwright/mcp@latest + │ + ◆ Please set the timeout for this tool (in secs): + │ 300 + └ + ``` + + 6. Choose to add a description. If you select "Yes" here, you will be prompted to enter a description for the extension. + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Playwright + │ + ◇ What command should be run? + │ npx -y @playwright/mcp@latest + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◆ Would you like to add a description? + │ No + └ + ``` + + 7. Choose No when asked to add environment variables + ```sh + ┌ goose-configure + │ + ◇ What would you like to configure? + │ Add Extension (Connect to a new extension) + │ + ◇ What type of extension would you like to add? + │ Command-line Extension + │ + ◇ What would you like to call this extension? + │ Playwright + │ + ◇ What command should be run? + │ npx -y @playwright/mcp@latest + │ + ◇ Please set the timeout for this tool (in secs): + │ 300 + │ + ◇ Would you like to add a description? + │ No + │ + ◆ Would you like to add environment variables? + │ No + │ + └ Added Playwright extension + ``` + + + +## Example Usage + +Let's use Goose with the Playwright extension to create a cross-browser testing suite. In this example, we'll demonstrate how Goose can help you: + +1. Test a web application across multiple browsers +2. Generate maintainable test code +3. Capture screenshots for visual comparison + +:::info LLM +Anthropic's Claude 3.5 Sonnet was used for this task. +::: + +### Goose Prompt +``` +Test the random redesign generator app (https://blackgirlbytes.github.io/random-redesign-picker/) +in Chromium and WebKit. For each browser: +1. Navigate to the page +2. Click the "Pick My Combo" button +3. Verify the text changes +4. Take a screenshot +Then generate a Playwright test that works across all browsers. +``` + +### Goose Output +``` +Let's start with Chromium: + +─── browser_navigate | playwright ────────────────────────── +url: https://blackgirlbytes.github.... + +Now let's wait for and click the button: + +─── browser_click | playwright ────────────────────────── +element: button with text "Pick My Combo" +ref: - - goose session --with-extension "{extension.command}" - + {extension.url ? ( + + goose session --with-remote-extension "{extension.url}" + + ) : ( + + goose session --with-extension "{extension.command}" + + )} )} diff --git a/documentation/src/components/server-card.tsx b/documentation/src/components/server-card.tsx index db6754d9..b87022d4 100644 --- a/documentation/src/components/server-card.tsx +++ b/documentation/src/components/server-card.tsx @@ -50,65 +50,104 @@ export function ServerCard({ server }: { server: MCPServer }) {
- {server.is_builtin ? ( + {server.is_builtin && (
- - - Can be enabled in the goose settings page - + + + Can be enabled in the goose settings page +
- ) : ( + )} + + {(!server.is_builtin && server.command !== undefined && server.url === undefined) && ( <> - + + {isCommandVisible && ( + - -

Command

- - - - {isCommandVisible && ( - - - {`goose session --with-extension "${server.command}"`} - - - )} - + + {`goose session --with-extension "${server.command}"`} + +
+ )} +
+ + )} + + {(!server.is_builtin && server.command === undefined && server.url !== undefined) && ( + <> + + + {isCommandVisible && ( + + + {`goose session --with-remote-extension "${server.url}"`} + + + )} + )}
- e.stopPropagation()} - > - - {server.githubStars} on Github - + {server.githubStars !== undefined && ( + e.stopPropagation()} + > + + {server.githubStars} on Github + + )}
{server.is_builtin ? (
('form'); + const [isBuiltin, setIsBuiltin] = useState(false); + const [id, setId] = useState(''); + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [command, setCommand] = useState(''); + const [envVars, setEnvVars] = useState([]); + const [generatedLink, setGeneratedLink] = useState(''); + const [jsonInput, setJsonInput] = useState(''); + const [copied, setCopied] = useState(false); + const [error, setError] = useState(''); + + // Initialize JSON input with sample data + useEffect(() => { + const sampleJson = { + is_builtin: false, + id: "example-extension", + name: "Example Extension", + description: "An example Goose extension", + command: "npx @gooseai/example-extension", + environmentVariables: [ + { + name: "API_KEY", + description: "Your API key", + required: true + } + ] + }; + setJsonInput(JSON.stringify(sampleJson, null, 2)); + }, []); + + // Process URL parameters if present + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.toString()) { + try { + // Check if this is a built-in extension request + if (urlParams.get('cmd') === 'goosed' && urlParams.getAll('arg').includes('mcp')) { + const args = urlParams.getAll('arg'); + const extensionId = args[args.indexOf('mcp') + 1]; + if (!extensionId) { + throw new Error('Missing extension ID in args'); + } + + const server = { + is_builtin: true, + id: extensionId, + environmentVariables: [] + }; + const link = generateDeeplink(server); + handleGeneratedLink(link, true); + return; + } + + // Handle custom extension + const cmd = urlParams.get('cmd'); + if (!cmd) { + throw new Error('Missing required parameter: cmd'); + } + + const args = urlParams.getAll('arg') || []; + const command = [cmd, ...args].join(' '); + const id = urlParams.get('id'); + const name = urlParams.get('name'); + const description = urlParams.get('description'); + + if (!id || !name || !description) { + throw new Error('Missing required parameters. Need: id, name, and description'); + } + + const server = { + is_builtin: false, + id, + name, + description, + command, + environmentVariables: [] + }; + + // Handle environment variables if present + const envVars = urlParams.getAll('env'); + if (envVars.length > 0) { + envVars.forEach(env => { + const [name, description] = env.split('='); + if (name && description) { + server.environmentVariables.push({ + name, + description, + required: true + }); + } + }); + } + + const link = generateDeeplink(server); + handleGeneratedLink(link, true); + } catch (error) { + setError(error.message); + } + } + }, []); + + const handleGeneratedLink = useCallback((link: string, shouldRedirect = false) => { + if (shouldRedirect) { + window.location.href = link; + } else { + setGeneratedLink(link); + setError(''); + } + }, []); + + const generateDeeplink = (server: ServerConfig): string => { + if (server.is_builtin) { + const queryParams = [ + 'cmd=goosed', + 'arg=mcp', + `arg=${encodeURIComponent(server.id)}`, + `description=${encodeURIComponent(server.id)}` + ].join('&'); + return `goose://extension?${queryParams}`; + } + + // Handle the case where the command is a URL + if (server.url) { + const queryParams = [ + `url=${encodeURIComponent(server.url)}`, + `id=${encodeURIComponent(server.id)}`, + `name=${encodeURIComponent(server.name)}`, + `description=${encodeURIComponent(server.description)}`, + ...server.environmentVariables + .filter((env) => env.required) + .map( + (env) => `env=${encodeURIComponent(`${env.name}=${env.description}`)}` + ), + ].join("&"); + + return `goose://extension?${queryParams}`; + } + + const parts = server.command.split(" "); + const baseCmd = parts[0]; + const args = parts.slice(1); + const queryParams = [ + `cmd=${encodeURIComponent(baseCmd)}`, + ...args.map((arg) => `arg=${encodeURIComponent(arg)}`), + `id=${encodeURIComponent(server.id)}`, + `name=${encodeURIComponent(server.name)}`, + `description=${encodeURIComponent(server.description)}`, + ...server.environmentVariables + .filter((env) => env.required) + .map( + (env) => `env=${encodeURIComponent(`${env.name}=${env.description}`)}` + ), + ].join("&"); + + return `goose://extension?${queryParams}`; + }; + + const handleFormSubmit = useCallback((e: React.FormEvent) => { + e.preventDefault(); + const server: ServerConfig = { + is_builtin: isBuiltin, + id, + name, + description, + command, + environmentVariables: envVars + }; + + try { + const link = generateDeeplink(server); + handleGeneratedLink(link); + } catch (error) { + setError(error.message); + } + }, [isBuiltin, id, name, description, command, envVars]); + + const handleJsonSubmit = useCallback(() => { + try { + const server = JSON.parse(jsonInput); + const link = generateDeeplink(server); + handleGeneratedLink(link); + } catch (error) { + setError('Invalid JSON: ' + error.message); + } + }, [jsonInput]); + + const handleAddEnvVar = useCallback(() => { + setEnvVars(prev => [...prev, { name: '', description: '', required: true }]); + }, []); + + const handleRemoveEnvVar = useCallback((index: number) => { + setEnvVars(prev => prev.filter((_, i) => i !== index)); + }, []); + + const handleEnvVarChange = useCallback((index: number, field: 'name' | 'description', value: string) => { + setEnvVars(prev => prev.map((env, i) => + i === index ? { ...env, [field]: value } : env + )); + }, []); + + const handleCopy = useCallback(() => { + navigator.clipboard.writeText(generatedLink) + .then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }) + .catch(err => setError('Failed to copy: ' + err.message)); + }, [generatedLink]); + + return ( + +
+
+

+ Deeplink Generator +

+

+ Generate installation deeplinks for Goose extensions that can be shared with others. +

+
+ +
+
+
+ + +
+
+ + {error && ( +
+ {error} +
+ )} + + {activeTab === 'form' ? ( +
+
+ +
+ +
+ + setId(e.target.value)} + required + className="w-full p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard" + placeholder="Enter extension ID" + /> +
+ + {!isBuiltin && ( + <> +
+ + setName(e.target.value)} + required + className="w-full p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard" + placeholder="Extension name" + /> +
+ +
+ + setDescription(e.target.value)} + required + className="w-full p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard" + placeholder="Brief description" + /> +
+ +
+ + setCommand(e.target.value)} + required + className="w-full p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard" + placeholder="npx @gooseai/example-extension" + /> +
+ +
+ +
+ {envVars.map((env, index) => ( +
+ handleEnvVarChange(index, 'name', e.target.value)} + className="flex-1 p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard" + placeholder="Variable Name" + /> + handleEnvVarChange(index, 'description', e.target.value)} + className="flex-1 p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard" + placeholder="Description" + /> + +
+ ))} + +
+
+ + )} + +
+ +
+
+ ) : ( +
+
+ + -
- -
- -
-

Generated Link

- -
-
- - - \ No newline at end of file diff --git a/documentation/static/install-link-generator/script.js b/documentation/static/install-link-generator/script.js deleted file mode 100644 index ee750049..00000000 --- a/documentation/static/install-link-generator/script.js +++ /dev/null @@ -1,241 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - // Function to show error message - function showError(message) { - const resultDiv = document.getElementById('generatedLink'); - resultDiv.innerHTML = `
${message}
`; - resultDiv.parentElement.style.display = 'block'; - } - - // Function to handle generated link (display or redirect) - function handleGeneratedLink(link, shouldRedirect = false) { - if (shouldRedirect) { - window.location.href = link; - } else { - displayGeneratedLink(link); - } - } - - // Process URL parameters if present - const urlParams = new URLSearchParams(window.location.search); - if (urlParams.toString()) { - try { - // Check if this is a built-in extension request - if (urlParams.get('cmd') === 'goosed' && urlParams.getAll('arg').includes('mcp')) { - const args = urlParams.getAll('arg'); - const extensionId = args[args.indexOf('mcp') + 1]; - if (!extensionId) { - throw new Error('Missing extension ID in args'); - } - - const server = { - is_builtin: true, - id: extensionId - }; - const link = generateInstallLink(server); - handleGeneratedLink(link, true); - return; - } - - // Handle custom extension - const cmd = urlParams.get('cmd'); - if (!cmd) { - throw new Error('Missing required parameter: cmd'); - } - - const args = urlParams.getAll('arg') || []; - const command = [cmd, ...args].join(' '); - const id = urlParams.get('id'); - const name = urlParams.get('name'); - const description = urlParams.get('description'); - - if (!id || !name || !description) { - throw new Error('Missing required parameters. Need: id, name, and description'); - } - - const server = { - is_builtin: false, - id, - name, - description, - command, - environmentVariables: [] - }; - - // Handle environment variables if present - const envVars = urlParams.getAll('env'); - if (envVars.length > 0) { - envVars.forEach(env => { - const [name, description] = env.split('='); - if (name && description) { - server.environmentVariables.push({ - name, - description, - required: true - }); - } - }); - } - - const link = generateInstallLink(server); - handleGeneratedLink(link, true); - } catch (error) { - showError(error.message); - document.querySelector('.container').style.display = 'block'; - } - } else { - // Show the form if no parameters - document.querySelector('.container').style.display = 'block'; - } - - // Tab switching - const tabs = document.querySelectorAll('.tab-btn'); - tabs.forEach(tab => { - tab.addEventListener('click', () => { - tabs.forEach(t => t.classList.remove('active')); - tab.classList.add('active'); - - document.querySelectorAll('.tab-content').forEach(content => { - content.classList.remove('active'); - }); - document.getElementById(tab.dataset.tab).classList.add('active'); - }); - }); - - // Handle built-in checkbox - const isBuiltinCheckbox = document.getElementById('isBuiltin'); - const nonBuiltinFields = document.querySelector('.non-builtin'); - - isBuiltinCheckbox.addEventListener('change', () => { - nonBuiltinFields.style.display = isBuiltinCheckbox.checked ? 'none' : 'block'; - }); - - // Environment variables handling - const envVarsContainer = document.getElementById('envVars'); - const addEnvVarBtn = document.getElementById('addEnvVar'); - - function createEnvVarInputs() { - const envVarDiv = document.createElement('div'); - envVarDiv.className = 'env-var'; - - const nameInput = document.createElement('input'); - nameInput.type = 'text'; - nameInput.placeholder = 'Variable Name'; - nameInput.className = 'env-name'; - - const descInput = document.createElement('input'); - descInput.type = 'text'; - descInput.placeholder = 'Description'; - descInput.className = 'env-desc'; - - const removeBtn = document.createElement('button'); - removeBtn.type = 'button'; - removeBtn.textContent = 'Remove'; - removeBtn.onclick = () => envVarDiv.remove(); - - envVarDiv.appendChild(nameInput); - envVarDiv.appendChild(descInput); - envVarDiv.appendChild(removeBtn); - - return envVarDiv; - } - - addEnvVarBtn.addEventListener('click', () => { - envVarsContainer.appendChild(createEnvVarInputs()); - }); - - // Generate link from form - document.getElementById('installForm').addEventListener('submit', (e) => { - e.preventDefault(); - const formData = new FormData(e.target); - const server = { - is_builtin: formData.get('is_builtin') === 'on', - id: formData.get('id'), - name: formData.get('name'), - description: formData.get('description'), - command: formData.get('command'), - environmentVariables: [] - }; - - // Collect environment variables - document.querySelectorAll('.env-var').forEach(envVar => { - const name = envVar.querySelector('.env-name').value; - const description = envVar.querySelector('.env-desc').value; - if (name && description) { - server.environmentVariables.push({ - name, - description, - required: true - }); - } - }); - - const link = generateInstallLink(server); - handleGeneratedLink(link); - }); - - // Generate link from JSON - document.getElementById('generateFromJson').addEventListener('click', () => { - try { - const jsonInput = document.getElementById('jsonInput').value; - const server = JSON.parse(jsonInput); - const link = generateInstallLink(server); - handleGeneratedLink(link); - } catch (error) { - showError('Invalid JSON: ' + error.message); - } - }); - - // Link generation logic - function generateInstallLink(server) { - if (server.is_builtin) { - const queryParams = [ - 'cmd=goosed', - 'arg=mcp', - `arg=${encodeURIComponent(server.id)}`, - `description=${encodeURIComponent(server.id)}` - ].join('&'); - return `goose://extension?${queryParams}`; - } - - const parts = server.command.split(" "); - const baseCmd = parts[0]; - const args = parts.slice(1); - const queryParams = [ - `cmd=${encodeURIComponent(baseCmd)}`, - ...args.map((arg) => `arg=${encodeURIComponent(arg)}`), - `id=${encodeURIComponent(server.id)}`, - `name=${encodeURIComponent(server.name)}`, - `description=${encodeURIComponent(server.description)}`, - ...server.environmentVariables - .filter((env) => env.required) - .map( - (env) => `env=${encodeURIComponent(`${env.name}=${env.description}`)}` - ), - ].join("&"); - - return `goose://extension?${queryParams}`; - } - - function displayGeneratedLink(link) { - const linkElement = document.getElementById('generatedLink'); - linkElement.textContent = link; - linkElement.parentElement.style.display = 'block'; - } - - // Add sample JSON to the textarea - const sampleJson = { - is_builtin: false, - id: "example-extension", - name: "Example Extension", - description: "An example Goose extension", - command: "npx @gooseai/example-extension", - environmentVariables: [ - { - name: "API_KEY", - description: "Your API key", - required: true - } - ] - }; - document.getElementById('jsonInput').value = JSON.stringify(sampleJson, null, 2); -}); \ No newline at end of file diff --git a/documentation/static/install-link-generator/styles.css b/documentation/static/install-link-generator/styles.css deleted file mode 100644 index b9638c49..00000000 --- a/documentation/static/install-link-generator/styles.css +++ /dev/null @@ -1,258 +0,0 @@ -:root { - --primary: #ffffff; - --primary-dark: #e0e0e0; - --secondary: #333333; - --text: #ffffff; - --background: #000000; - --card-bg: #111111; - --success: #ffffff; - --error: #ff4b4b; - --border-color: rgba(255, 255, 255, 0.1); -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; - transition: all 0.2s ease; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - line-height: 1.5; - padding: 16px; - background: var(--background); - color: var(--text); - min-height: 100vh; -} - -.container { - max-width: 600px; - margin: 0 auto; - background: var(--card-bg); - padding: 1.5rem; - border-radius: 12px; - border: 1px solid var(--border-color); -} - -h1 { - text-align: center; - margin-bottom: 1.5rem; - color: var(--text); - font-size: 1.75rem; - font-weight: 600; - letter-spacing: -0.5px; -} - -.tabs { - display: flex; - gap: 0.5rem; - margin-bottom: 1.5rem; - padding: 0.25rem; - background: rgba(255, 255, 255, 0.05); - border-radius: 8px; -} - -.tab-btn { - flex: 1; - padding: 0.75rem; - border: none; - background: transparent; - color: var(--text); - cursor: pointer; - border-radius: 6px; - font-weight: 500; - font-size: 0.875rem; -} - -.tab-btn.active { - background: var(--secondary); -} - -.tab-btn:hover:not(.active) { - background: rgba(255, 255, 255, 0.05); -} - -.tab-content { - display: none; - animation: fadeIn 0.2s ease; -} - -.tab-content.active { - display: block; -} - -.form-group { - margin-bottom: 1rem; -} - -label { - display: block; - margin-bottom: 0.25rem; - font-weight: 500; - color: var(--text); - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -input[type="text"], -textarea { - width: 100%; - padding: 0.75rem; - background: rgba(255, 255, 255, 0.05); - border: 1px solid var(--border-color); - border-radius: 6px; - color: var(--text); - font-size: 0.875rem; -} - -input[type="text"]:focus, -textarea:focus { - outline: none; - border-color: var(--primary); -} - -input[type="checkbox"] { - appearance: none; - width: 16px; - height: 16px; - border: 2px solid var(--border-color); - border-radius: 4px; - cursor: pointer; - position: relative; -} - -input[type="checkbox"]:checked { - background-color: var(--primary); - border-color: var(--primary); -} - -input[type="checkbox"]:checked::after { - content: "✓"; - position: absolute; - color: var(--background); - font-size: 12px; - left: 2px; - top: -2px; -} - -textarea { - resize: vertical; - min-height: 100px; -} - -button { - background: var(--secondary); - color: white; - padding: 0.75rem 1.5rem; - border: 1px solid var(--border-color); - border-radius: 6px; - cursor: pointer; - font-size: 0.875rem; - font-weight: 500; - letter-spacing: 0.5px; -} - -button:hover { - background: #444444; -} - -button:active { - transform: translateY(1px); -} - -.result { - margin-top: 1.5rem; - padding-top: 1.5rem; - border-top: 1px solid var(--border-color); -} - -#generatedLink { - padding: 1rem; - background: rgba(255, 255, 255, 0.05); - border-radius: 6px; - word-break: break-all; - font-family: 'SF Mono', 'Fira Code', monospace; - font-size: 0.8125rem; - border: 1px solid var(--border-color); -} - -.env-var { - display: flex; - gap: 0.5rem; - margin-bottom: 0.75rem; - align-items: center; -} - -.env-var input { - flex: 1; -} - -.env-var button { - padding: 0.75rem; - min-width: 40px; - background: #333333; -} - -#addEnvVar { - width: 100%; - margin-top: 0.75rem; - background: #333333; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(5px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Scrollbar styling */ -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.05); - border-radius: 3px; -} - -::-webkit-scrollbar-thumb { - background: #444444; - border-radius: 3px; -} - -::-webkit-scrollbar-thumb:hover { - background: #555555; -} - -/* Focus styles */ -*:focus-visible { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -/* Responsive adjustments */ -@media (max-width: 640px) { - .container { - padding: 1rem; - } - - h1 { - font-size: 1.5rem; - } - - .tabs { - flex-direction: column; - } - - button { - width: 100%; - } -} \ No newline at end of file diff --git a/documentation/static/servers.json b/documentation/static/servers.json index 34862674..48ea7bd9 100644 --- a/documentation/static/servers.json +++ b/documentation/static/servers.json @@ -142,5 +142,16 @@ "endorsed": false, "githubStars": 27, "environmentVariables": [] + }, + { + "id": "pieces", + "name": "Pieces", + "description": "Provides access to your Pieces Long-Term Memory. You need to have Pieces installed to use this.", + "url": "http://localhost:39300/model_context_protocol/2024-11-05/sse", + "link": "https://pieces.app?utm_source=goose&utm_medium=collab&utm_campaign=mcp", + "installation_notes": "You need to install Pieces first, and have this running.", + "is_builtin": false, + "endorsed": false, + "environmentVariables": [] } ] diff --git a/documentation/yarn.lock b/documentation/yarn.lock index 8e70607c..3494f6d2 100644 --- a/documentation/yarn.lock +++ b/documentation/yarn.lock @@ -163,7 +163,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2", "@babel/code-frame@^7.8.3": version "7.26.2" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -349,22 +349,15 @@ "@babel/helpers@^7.26.0": version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.0.tgz#53d156098defa8243eab0f32fa17589075a1b808" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz" integrity sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg== dependencies: "@babel/template" "^7.27.0" "@babel/types" "^7.27.0" -"@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.5": - version "7.26.5" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz" - integrity sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw== - dependencies: - "@babel/types" "^7.26.5" - -"@babel/parser@^7.27.0": +"@babel/parser@^7.26.0", "@babel/parser@^7.26.5", "@babel/parser@^7.27.0": version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.0.tgz#3d7d6ee268e41d2600091cbd4e145ffee85a44ec" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz" integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg== dependencies: "@babel/types" "^7.27.0" @@ -1015,7 +1008,7 @@ "@babel/runtime-corejs3@^7.25.9": version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.27.0.tgz#c766df350ec7a2caf3ed64e3659b100954589413" + resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.0.tgz" integrity sha512-UWjX6t+v+0ckwZ50Y5ShZLnlk95pP5MyW/pon9tiYzl3+18pkTHTFNTKr7rQbfRXPkowt2QAn30o1b6oswszew== dependencies: core-js-pure "^3.30.2" @@ -1023,23 +1016,14 @@ "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.25.9", "@babel/runtime@^7.8.4": version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz" integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz" - integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== - dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/parser" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/template@^7.27.0": +"@babel/template@^7.25.9", "@babel/template@^7.27.0": version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz" integrity sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA== dependencies: "@babel/code-frame" "^7.26.2" @@ -1059,17 +1043,9 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.21.3", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.5", "@babel/types@^7.4.4": - version "7.26.5" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz" - integrity sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - -"@babel/types@^7.27.0": +"@babel/types@^7.21.3", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.5", "@babel/types@^7.27.0", "@babel/types@^7.4.4": version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.0.tgz#ef9acb6b06c3173f6632d993ecb6d4ae470b4559" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz" integrity sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg== dependencies: "@babel/helper-string-parser" "^7.25.9" @@ -4199,9 +4175,9 @@ estree-util-to-js@^2.0.0: source-map "^0.7.0" estree-util-value-to-estree@^3.0.1: - version "3.2.1" - resolved "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.2.1.tgz" - integrity sha512-Vt2UOjyPbNQQgT5eJh+K5aATti0OjCIAGc9SgMdOFYbohuifsWclR74l0iZTJwePMgWYdX1hlVS+dedH9XV8kw== + version "3.4.0" + resolved "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.0.tgz" + integrity sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ== dependencies: "@types/estree" "^1.0.0" @@ -4544,7 +4520,7 @@ fs.realpath@^1.0.0: fsevents@~2.3.2: version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: @@ -4964,7 +4940,7 @@ html-tags@^3.3.1: html-url-attributes@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" + resolved "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz" integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== html-void-elements@^3.0.0: @@ -5041,7 +5017,7 @@ http-parser-js@>=0.5.1: http-proxy-middleware@^2.0.3: version "2.0.9" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef" + resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz" integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q== dependencies: "@types/http-proxy" "^1.17.8" @@ -5091,7 +5067,7 @@ ignore@^5.2.0, ignore@^5.2.4: image-size@^1.0.2: version "1.2.1" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.2.1.tgz#ee118aedfe666db1a6ee12bed5821cde3740276d" + resolved "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz" integrity sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw== dependencies: queue "6.0.2" @@ -7512,7 +7488,7 @@ prism-react-renderer@^2.3.0: prismjs@^1.29.0: version "1.30.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9" + resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz" integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw== process-nextick-args@~2.0.0: @@ -7712,7 +7688,7 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: react-markdown@^10.1.0: version "10.1.0" - resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca" + resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz" integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== dependencies: "@types/hast" "^3.0.0" @@ -8696,9 +8672,9 @@ swiper@^11.2.6: integrity sha512-8aXpYKtjy3DjcbzZfz+/OX/GhcU5h+looA6PbAzHMZT6ESSycSp9nAjPCenczgJyslV+rUGse64LMGpWE3PX9Q== tailwind-merge@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-3.0.2.tgz#567eff76de12211e24dd909da0f5ed6f4f422b0c" - integrity sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw== + version "3.2.0" + resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz" + integrity sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA== tailwindcss@^3.4.1: version "3.4.17" diff --git a/recipe.yaml b/recipe.yaml new file mode 100644 index 00000000..30299135 --- /dev/null +++ b/recipe.yaml @@ -0,0 +1,46 @@ +version: 1.0.0 +title: "404Portfolio" +description: "Create personalized, creative 404 pages using public profile data" + +instructions: | + Create an engaging 404 error page that tells a creative story using a user's recent public content from **one** of the following platforms: **GitHub**, **Dev.to**, or **Bluesky**. You do not need to use all three—just the one selected by the user. + + The page should be fully built with **HTML, CSS, and JavaScript**, featuring: + + * Responsive design + * Personal branding elements (e.g., name, handle, avatar) + * Narrative-driven layout that turns the error into an opportunity for discovery + + Use platform-specific methods to fetch recent user content: + + * For **Dev.to**, use the [public Dev.to API](https://developers.forem.com/api) to retrieve recent articles, reactions, and profile information. + * For **GitHub**, use the GitHub REST or GraphQL API to access recent repos, commits, and contributions. + * For **Bluesky**, use public feed endpoints from the AppView API (e.g., `app.bsky.feed.getAuthorFeed`) to pull posts, replies, or likes. + + Incorporate the fetched data into a compelling narrative (e.g., “Looks like this page is missing, but \[username] has been busy!”), and display it using engaging visuals like cards, timelines, or media embeds. + + Wrap the user’s activity into a story — for example: + + “This page may be lost, but @username is building something amazing. Their latest open source journey involves a new repo that’s gaining stars fast…” + “You won’t find what you’re looking for here, but you will find @username’s hot take on async/await in their latest Dev.to post.” + + The result should be a small narrative-driven microsite that dynamically celebrates the user's presence online—even when the destination is missing. + + Ask the user: + + 1. Which platform to use: GitHub, Dev.to, or Bluesky + 2. Their username on that platform + + Then generate the complete code in a folder called 404-story. + + +activities: + - "Build error page from GitHub repos" + - "Generate error page from dev.to blog posts" + - "Create a 404 page featuring Bluesky bio" + +extensions: + - type: builtin + name: developer + - type: builtin + name: computercontroller \ No newline at end of file diff --git a/scripts/bench-postprocess-scripts/generate_leaderboard.py b/scripts/bench-postprocess-scripts/generate_leaderboard.py new file mode 100755 index 00000000..16292c72 --- /dev/null +++ b/scripts/bench-postprocess-scripts/generate_leaderboard.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +# Compatible with Python 3.6+ +""" +Generate a leaderboard CSV from benchmark results, including server error information. + +This script: +1. Looks for model folders in the benchmark directory +2. Finds eval-results/aggregate_metrics.csv in each model folder +3. Extracts key metrics (provider, model_name, eval_suite, eval_name, tool calls, execution time, tokens, score, prompt error, server error) +4. Creates a union of all CSVs with these columns +5. Creates a leaderboard.csv grouping by provider and model_name, averaging numeric columns + +Usage: + python generate_leaderboard.py --benchmark-dir /path/to/benchmark-dir +""" + +import argparse +import pandas as pd +from pathlib import Path +import sys + + +def find_aggregate_metrics_files(benchmark_dir: Path) -> list: + """Find all aggregate_metrics.csv files in model subdirectories.""" + csv_files = [] + + # Look for model directories in the benchmark directory + for model_dir in benchmark_dir.iterdir(): + if model_dir.is_dir(): + # Look for eval-results/aggregate_metrics.csv in each model directory + eval_results_dir = model_dir / "eval-results" + if eval_results_dir.exists() and eval_results_dir.is_dir(): + csv_path = eval_results_dir / "aggregate_metrics.csv" + if csv_path.exists(): + csv_files.append(csv_path) + + return csv_files + + +def process_csv_files(csv_files: list) -> tuple: + """ + Process all CSV files and create two dataframes: + 1. A union of all CSVs with selected columns + 2. A leaderboard grouping by provider and model_name with averaged metrics + """ + selected_columns = [ + 'provider', + 'model_name', + 'eval_suite', + 'eval_name', + 'total_tool_calls_mean', + 'prompt_execution_time_mean', + 'total_tokens_mean', + 'score_mean', + 'prompt_error_mean', + 'server_error_mean' + ] + + all_data = [] + + for csv_file in csv_files: + try: + df = pd.read_csv(csv_file) + + # Check which selected columns are available + missing_columns = [col for col in selected_columns if col not in df.columns] + if missing_columns: + print(f"Warning: {csv_file} is missing columns: {missing_columns}") + + # For missing columns, add them with NaN values + for col in missing_columns: + df[col] = float('nan') + + # Select only the columns we care about + df_subset = df[selected_columns].copy() # Create a copy to avoid SettingWithCopyWarning + + # Add model folder name as additional context + model_folder = csv_file.parent.parent.name + df_subset['model_folder'] = model_folder + + all_data.append(df_subset) + + except Exception as e: + print(f"Error processing {csv_file}: {str(e)}") + + if not all_data: + raise ValueError("No valid CSV files found with required columns") + + # Concatenate all dataframes to create a union + union_df = pd.concat(all_data, ignore_index=True) + + # Create leaderboard by grouping and averaging numerical columns + numeric_columns = [ + 'total_tool_calls_mean', + 'prompt_execution_time_mean', + 'total_tokens_mean', + 'score_mean', + 'prompt_error_mean', + 'server_error_mean' + ] + + # Group by provider and model_name, then calculate averages for numeric columns + leaderboard_df = union_df.groupby(['provider', 'model_name'])[numeric_columns].mean().reset_index() + + # Sort by score_mean in descending order (highest scores first) + leaderboard_df = leaderboard_df.sort_values('score_mean', ascending=False) + + return union_df, leaderboard_df + + +def main(): + parser = argparse.ArgumentParser( + description="Generate a leaderboard CSV from benchmark results, including server error information" + ) + parser.add_argument( + "--benchmark-dir", + type=str, + required=True, + help="Path to the benchmark directory containing model subdirectories" + ) + parser.add_argument( + "--union-output", + type=str, + default="all_metrics.csv", + help="Output filename for the union of all CSVs (default: all_metrics.csv)" + ) + parser.add_argument( + "--leaderboard-output", + type=str, + default="leaderboard.csv", + help="Output filename for the leaderboard (default: leaderboard.csv)" + ) + + args = parser.parse_args() + + benchmark_dir = Path(args.benchmark_dir) + if not benchmark_dir.exists() or not benchmark_dir.is_dir(): + print(f"Error: Benchmark directory {benchmark_dir} does not exist or is not a directory") + sys.exit(1) + + try: + # Find all aggregate_metrics.csv files in model subdirectories + csv_files = find_aggregate_metrics_files(benchmark_dir) + + if not csv_files: + print(f"No aggregate_metrics.csv files found in any model directory under {benchmark_dir}") + sys.exit(1) + + print(f"Found {len(csv_files)} aggregate_metrics.csv files in model directories") + + # Process and create the union and leaderboard dataframes + union_df, leaderboard_df = process_csv_files(csv_files) + + # Save the union CSV to the benchmark directory + union_output_path = benchmark_dir / args.union_output + union_df.to_csv(union_output_path, index=False) + print(f"Union CSV with all metrics saved to: {union_output_path}") + + # Save the leaderboard CSV to the benchmark directory + leaderboard_output_path = benchmark_dir / args.leaderboard_output + leaderboard_df.to_csv(leaderboard_output_path, index=False) + print(f"Leaderboard CSV with averaged metrics saved to: {leaderboard_output_path}") + + # Print a summary of the leaderboard + print("\nLeaderboard Summary:") + pd.set_option('display.max_columns', None) # Show all columns + print(leaderboard_df.to_string(index=False)) + + # Highlight models with server errors + if 'server_error_mean' in leaderboard_df.columns: + models_with_errors = leaderboard_df[leaderboard_df['server_error_mean'] > 0] + if not models_with_errors.empty: + print("\nWARNING - Models with server errors detected:") + for _, row in models_with_errors.iterrows(): + print(f" * {row['provider']} {row['model_name']} - {row['server_error_mean']*100:.1f}% of evaluations had server errors") + print("\nThese models may need to be re-run to get accurate results.") + + except Exception as e: + print(f"Error: {str(e)}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/scripts/bench-postprocess-scripts/llm-judges/blog_summary_prompt.txt b/scripts/bench-postprocess-scripts/llm-judges/blog_summary_prompt.txt new file mode 100644 index 00000000..befbb3a2 --- /dev/null +++ b/scripts/bench-postprocess-scripts/llm-judges/blog_summary_prompt.txt @@ -0,0 +1,8 @@ +You are evaluating a response to a summarization task and will give a score of 0, 1, or 2. The instructions were: + +'What are the top 5 most counterintuitive insights from this blog post? https://huyenchip.com/2025/01/07/agents.html' + +Does the response below appropriately answer the query (ignore formatting)? +0 = does not provide any insights at all +1 = provides some insights, but not all 5 +2 = provides all 5 insights diff --git a/scripts/bench-postprocess-scripts/llm-judges/calculate_final_scores_vibes.py b/scripts/bench-postprocess-scripts/llm-judges/calculate_final_scores_vibes.py new file mode 100755 index 00000000..261fbe52 --- /dev/null +++ b/scripts/bench-postprocess-scripts/llm-judges/calculate_final_scores_vibes.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +""" +Calculate final score for vibes evaluations. +This script combines the LLM judge score with other metrics to produce a final score. +""" + +import json +import sys +from pathlib import Path + + +def get_metric_value(metrics, metric_name): + """Extract a metric value from the metrics array.""" + for metric in metrics: + if metric[0] == metric_name: + value = metric[1] + if "Float" in value: + return float(value["Float"]) + elif "Integer" in value: + return float(value["Integer"]) + elif "Boolean" in value: + return 1.0 if value["Boolean"] else 0.0 + return None + + +def calculate_score(eval_name, metrics): + """Calculate the final score based on the evaluation type.""" + llm_judge_score = get_metric_value(metrics, "llm_judge_score") + used_fetch_tool = get_metric_value(metrics, "used_fetch_tool") + valid_markdown_format = get_metric_value(metrics, "valid_markdown_format") + + if llm_judge_score is None: + raise ValueError("llm_judge_score not found in metrics") + + # Convert boolean metrics to 0/1 if needed + used_fetch_tool = 1.0 if used_fetch_tool else 0.0 + valid_markdown_format = 1.0 if valid_markdown_format else 0.0 + + if eval_name == "blog_summary": + # max score is 4.0 as llm_judge_score is between [0,2] and used_fetch_tool/valid_markedown_format have values [0,1] + score = (llm_judge_score + used_fetch_tool + valid_markdown_format) / 4.0 + elif eval_name == "restaurant_research": + score = (llm_judge_score + valid_markdown_format + used_fetch_tool) / 4.0 + else: + raise ValueError(f"Unknown evaluation type: {eval_name}") + + return score + + +def main(): + if len(sys.argv) != 2: + print("Usage: calculate_final_score.py ") + sys.exit(1) + + eval_name = sys.argv[1] + + # Load eval results from current directory + eval_results_path = Path("eval-results.json") + if not eval_results_path.exists(): + print(f"Error: eval-results.json not found in current directory") + sys.exit(1) + + with open(eval_results_path, 'r') as f: + eval_results = json.load(f) + + try: + # Calculate the final score + score = calculate_score(eval_name, eval_results["metrics"]) + + # Add the score metric + eval_results["metrics"].append([ + "score", + {"Float": score} + ]) + + # Save updated results + with open(eval_results_path, 'w') as f: + json.dump(eval_results, f, indent=2) + + print(f"Successfully added final score: {score}") + + except Exception as e: + print(f"Error calculating final score: {str(e)}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/scripts/bench-postprocess-scripts/llm-judges/llm_judge.py b/scripts/bench-postprocess-scripts/llm-judges/llm_judge.py new file mode 100755 index 00000000..2b22dc24 --- /dev/null +++ b/scripts/bench-postprocess-scripts/llm-judges/llm_judge.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +""" +LLM Judge post-processing script for Goose benchmarks. + +This script evaluates benchmark results using OpenAI's API as a judge. +It reads the eval-results.json file and a specified output file, then uses +OpenAI to score the output based on a provided rubric. + +Usage: + python llm_judge.py [--rubric-max-score N] [--prompt-file PATH] + +Arguments: + output_file: Name of the file containing the output to evaluate (e.g., blog_summary_output.txt) + --rubric-max-score: Maximum score for the rubric (default: 2) + --prompt-file: Path to custom evaluation prompt file +""" + +import argparse +import json +import os +import sys +import time +from collections import Counter +from pathlib import Path +from typing import Dict, Any + +try: + from openai import OpenAI +except ImportError: + print("Error: openai package not found. Please install it with: pip install openai") + sys.exit(1) + + +def evaluate_with_openai(prompt: str, text: str, rubric_max_score: int = 2) -> float: + """Evaluate response using OpenAI's API. + + Args: + prompt: System prompt for evaluation + text: Text to evaluate + rubric_max_score: Maximum score for the rubric (default: 2.0) + + Returns: + float: Evaluation score (0 to rubric_max_score) + + Raises: + ValueError: If OPENAI_API_KEY environment variable is not set + """ + print("Starting OpenAI evaluation...") + api_key = os.getenv("OPENAI_API_KEY") + if not api_key: + print("No OpenAI API key found!") + raise ValueError("OPENAI_API_KEY environment variable is not set, but is needed to run this evaluation.") + + try: + client = OpenAI(api_key=api_key) + + # Append output instructions to system prompt + output_instructions = f""" +Output Instructions: +Return your evaluation as a JSON object in the following format: +{{ + "reasoning": "Your brief reasoning for the score", + "score": +}} + +IMPORTANT: +- Do not use any markdown formatting (no ```json blocks) +- Do not include any additional text before or after the JSON +- Return only the raw JSON object +- The score must be an integer between 0 and {rubric_max_score}""" + + input_prompt = f"{prompt} {output_instructions}\nResponse to evaluate: {text}" + + # Run the chat completion 3 times and collect scores + scores = [] + for i in range(3): + max_retries = 5 + retry_count = 0 + + while retry_count < max_retries: + try: + response = client.chat.completions.create( + model="gpt-4o", + messages=[ + {"role": "user", "content": input_prompt} + ], + temperature=0.9 + ) + + # Extract and parse JSON from response + response_text = response.choices[0].message.content.strip() + try: + evaluation = json.loads(response_text) + score = float(evaluation.get("score", 0.0)) + score = max(0.0, min(score, rubric_max_score)) + scores.append(score) + print(f"Run {i+1} score: {score}") + break # Successfully parsed, exit retry loop + except (json.JSONDecodeError, ValueError) as e: + retry_count += 1 + print(f"Error parsing OpenAI response as JSON (attempt {retry_count}/{max_retries}): {str(e)}") + print(f"Response text: {response_text}") + if retry_count == max_retries: + raise ValueError(f"Failed to parse OpenAI evaluation response after {max_retries} attempts: {str(e)}") + print("Retrying...") + time.sleep(1) # Wait 1 second before retrying + continue + except Exception as e: + # For other exceptions (API errors, etc.), raise immediately + print(f"API error: {str(e)}") + raise + + # Count occurrences of each score + score_counts = Counter(scores) + + # If there's no single most common score (all scores are different), run one more time + if len(scores) == 3 and max(score_counts.values()) == 1: + print("No majority score found. Running tie-breaker...") + max_retries = 5 + retry_count = 0 + + while retry_count < max_retries: + try: + response = client.chat.completions.create( + model="gpt-4o", + messages=[ + {"role": "user", "content": input_prompt} + ], + temperature=0.9 + ) + + response_text = response.choices[0].message.content.strip() + try: + evaluation = json.loads(response_text) + score = float(evaluation.get("score", 0.0)) + score = max(0.0, min(score, rubric_max_score)) + scores.append(score) + print(f"Tie-breaker score: {score}") + score_counts = Counter(scores) + break # Successfully parsed, exit retry loop + except (json.JSONDecodeError, ValueError) as e: + retry_count += 1 + print(f"Error parsing tie-breaker response as JSON (attempt {retry_count}/{max_retries}): {str(e)}") + print(f"Response text: {response_text}") + if retry_count == max_retries: + raise ValueError(f"Failed to parse tie-breaker response after {max_retries} attempts: {str(e)}") + print("Retrying tie-breaker...") + time.sleep(1) # Wait 1 second before retrying + continue + except Exception as e: + # For other exceptions (API errors, etc.), raise immediately + print(f"API error in tie-breaker: {str(e)}") + raise + + # Get the most common score + most_common_score = score_counts.most_common(1)[0][0] + print(f"Most common score: {most_common_score} (occurred {score_counts[most_common_score]} times)") + return most_common_score + + except Exception as e: + if "OPENAI_API_KEY" in str(e): + raise # Re-raise API key errors + print(f"Error evaluating with OpenAI: {str(e)}") + raise ValueError(f"OpenAI evaluation failed: {str(e)}") + + +def load_eval_results(working_dir: Path) -> Dict[str, Any]: + """Load the eval-results.json file from the working directory.""" + eval_results_path = working_dir / "eval-results.json" + if not eval_results_path.exists(): + raise FileNotFoundError(f"eval-results.json not found in {working_dir}") + + with open(eval_results_path, 'r') as f: + return json.load(f) + + +def load_output_file(working_dir: Path, output_file: str) -> str: + """Load the output file to evaluate from the working directory.""" + output_path = working_dir / output_file + if not output_path.exists(): + raise FileNotFoundError(f"Output file not found: {output_path}") + + with open(output_path, 'r') as f: + return f.read().strip() + + +def load_evaluation_prompt(working_dir: Path) -> str: + """Load the evaluation prompt from a file or use a default. + + This function looks for a prompt.txt file in the working directory. + If not found, it returns a default evaluation prompt. + """ + prompt_file = working_dir / "prompt.txt" + if prompt_file.exists(): + with open(prompt_file, 'r') as f: + return f.read().strip() + + # Default evaluation prompt + return """You are an expert evaluator assessing the quality of AI responses. +Evaluate the response based on the following criteria: +- Accuracy and correctness +- Completeness of the answer +- Clarity and coherence +- Helpfulness to the user + +Score the response on a scale from 0 to 2: +0 = Poor response (incorrect, incomplete, or unhelpful) +1 = Acceptable response (partially correct but with issues) +2 = Excellent response (correct, complete, and helpful)""" + + +def main(): + parser = argparse.ArgumentParser(description="LLM Judge post-processing script for Goose benchmarks") + parser.add_argument("output_file", type=str, help="Name of the output file to evaluate (e.g., blog_summary_output.txt)") + parser.add_argument("--rubric-max-score", type=int, default=2, help="Maximum score for the rubric (default: 2)") + parser.add_argument("--prompt-file", type=str, help="Path to custom evaluation prompt file") + + args = parser.parse_args() + + # Use current working directory + working_dir = Path.cwd() + + try: + # Load eval results + eval_results = load_eval_results(working_dir) + + # Load the output file to evaluate + response_text = load_output_file(working_dir, args.output_file) + + # Load evaluation prompt + if args.prompt_file: + with open(args.prompt_file, 'r') as f: + evaluation_prompt = f.read().strip() + else: + evaluation_prompt = load_evaluation_prompt(working_dir) + + # Evaluate with OpenAI + score = evaluate_with_openai(evaluation_prompt, response_text, args.rubric_max_score) + + # Update eval results with the score + eval_results["metrics"].append([ + "llm_judge_score", + {"Float": score} + ]) + + # Save updated results + eval_results_path = working_dir / "eval-results.json" + with open(eval_results_path, 'w') as f: + json.dump(eval_results, f, indent=2) + + print(f"Successfully updated eval-results.json with LLM judge score: {score}") + + except Exception as e: + print(f"Error: {str(e)}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/scripts/bench-postprocess-scripts/llm-judges/restaurant_research_prompt.txt b/scripts/bench-postprocess-scripts/llm-judges/restaurant_research_prompt.txt new file mode 100644 index 00000000..8e28c495 --- /dev/null +++ b/scripts/bench-postprocess-scripts/llm-judges/restaurant_research_prompt.txt @@ -0,0 +1,15 @@ +You are evaluating an AI assistant's response to a restaurant research task. The instructions were: + +'Search the internet for and provide a current, detailed list of the best Sichuanese restaurants specifically in the East Village neighborhood of NYC. Format your response in Markdown using bullet points (either - or *) for each restaurant. For each restaurant include: +- Restaurant name and what they're known for +- Signature dishes +- Atmosphere/setting +- Any relevant details about reservations or dining experience +- What distinguishes them from others + +Present the information in order of significance or quality. Focus specifically on Sichuanese establishments, not general Chinese restaurants. If you encounter a page you cannot access, try another one. Do not ask me for confirmation just conduct the searches yourself until you find the needed information. Remember to use your tools if applicable.' + +Give a score of 0, 1, or 2: +0 = does not provide any restaurants at all +1 = provides some restaurants, but not all are Sichuanese or in the East Village NYC +2 = provides all Sichuanese restaurants in the East Village, probably including Mala project and Szechuan Mountain House, or Uluh. Use your memory/knowledge of the East Village NYC restaurants to double check non-East Village restaurants. diff --git a/scripts/bench-postprocess-scripts/llm-judges/run_vibes_judge.sh b/scripts/bench-postprocess-scripts/llm-judges/run_vibes_judge.sh new file mode 100755 index 00000000..cf91b602 --- /dev/null +++ b/scripts/bench-postprocess-scripts/llm-judges/run_vibes_judge.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Wrapper script for LLM judge post-processing and final score calculation +# This script is called by the benchmark runner with the eval results file as an argument + +# Get the directory where this script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Get the eval results file path from the first argument +EVAL_RESULTS_FILE="$1" + +# Extract the working directory from the eval results file path +WORKING_DIR="$(dirname "$EVAL_RESULTS_FILE")" + +# Change to the working directory +cd "$WORKING_DIR" + +# Determine the evaluation name from the eval-results.json +EVAL_NAME=$(python3 -c "import json; print(json.load(open('eval-results.json'))['name'])") + +# Set the output file name and prompt file based on the evaluation +if [ "$EVAL_NAME" = "blog_summary" ]; then + OUTPUT_FILE="blog_summary_output.txt" + PROMPT_FILE="$SCRIPT_DIR/blog_summary_prompt.txt" +elif [ "$EVAL_NAME" = "restaurant_research" ]; then + OUTPUT_FILE="restaurant_research_output.txt" + PROMPT_FILE="$SCRIPT_DIR/restaurant_research_prompt.txt" +else + echo "Error: Unknown evaluation name: $EVAL_NAME" + exit 1 +fi + +# Run the LLM judge script with the appropriate arguments +python3 "$SCRIPT_DIR/llm_judge.py" "$OUTPUT_FILE" --prompt-file "$PROMPT_FILE" + +# Check if LLM judge succeeded +if [ $? -ne 0 ]; then + echo "Error: LLM judge failed" + exit 1 +fi + +# Calculate the final score +python3 "$SCRIPT_DIR/calculate_final_scores_vibes.py" "$EVAL_NAME" + +# Check if score calculation succeeded +if [ $? -ne 0 ]; then + echo "Error: Final score calculation failed" + exit 1 +fi + +echo "Successfully completed post-processing for $EVAL_NAME" diff --git a/scripts/bench-postprocess-scripts/prepare_aggregate_metrics.py b/scripts/bench-postprocess-scripts/prepare_aggregate_metrics.py new file mode 100755 index 00000000..39917291 --- /dev/null +++ b/scripts/bench-postprocess-scripts/prepare_aggregate_metrics.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python3 +# Compatible with Python 3.6+ +""" +Prepare aggregate_metrics.csv files from individual eval-results.json files with error detection. + +This script: +1. Looks for model folders in the benchmark directory +2. For each model folder, finds all eval-results.json files in subfolders +3. Checks session files for server errors +4. Extracts metrics from these files and combines them +5. Creates an eval-results directory in each model folder +6. Saves a aggregate_metrics.csv file with aggregated metrics + +Usage: + python prepare_aggregate_metrics.py --benchmark-dir /path/to/benchmark-dir +""" + +import argparse +import json +import pandas as pd +from pathlib import Path +import sys + +def extract_provider_model(model_dir): + """Extract provider and model name from directory name.""" + dir_name = model_dir.name + parts = dir_name.split('-') + + if len(parts) > 1: + model_name = parts[-1] # Last part is the model name + provider = '-'.join(parts[:-1]) # Everything else is the provider + else: + model_name = dir_name + provider = "unknown" + + return provider, model_name + +def find_eval_results_files(model_dir): + """Find all eval-results.json files in a model directory.""" + return list(model_dir.glob("**/eval-results.json")) + +def find_session_files(model_dir): + """Find all session jsonl files in a model directory.""" + return list(model_dir.glob("**/*.jsonl")) + +def check_for_errors_in_session(session_file): + """Check if a session file contains server errors.""" + try: + error_found = False + error_messages = [] + + with open(session_file, 'r') as f: + for line in f: + try: + message_obj = json.loads(line.strip()) + # Check for error messages in the content + if 'content' in message_obj and isinstance(message_obj['content'], list): + for content_item in message_obj['content']: + if isinstance(content_item, dict) and 'text' in content_item: + text = content_item['text'] + if 'Server error' in text or 'error_code' in text or 'TEMPORARILY_UNAVAILABLE' in text: + error_found = True + error_messages.append(text) + except json.JSONDecodeError: + continue + + return error_found, error_messages + except Exception as e: + print(f"Error checking session file {session_file}: {str(e)}") + return False, [] + +def extract_metrics_from_eval_file(eval_file, provider, model_name, session_files): + """Extract metrics from an eval-results.json file.""" + try: + with open(eval_file, 'r') as f: + data = json.load(f) + + # Extract directory structure to determine eval suite and name + path_parts = eval_file.parts + run_index = -1 + for i, part in enumerate(path_parts): + if part.startswith("run-"): + run_index = i + break + + if run_index == -1 or run_index + 2 >= len(path_parts): + print(f"Warning: Could not determine eval suite and name from {eval_file}") + return None + + run_number = path_parts[run_index].split('-')[1] # Extract "0" from "run-0" + eval_suite = path_parts[run_index + 1] # Directory after run-N + eval_name = path_parts[run_index + 2] # Directory after eval_suite + + # Create a row with basic identification + row = { + 'provider': provider, + 'model_name': model_name, + 'eval_suite': eval_suite, + 'eval_name': eval_name, + 'run': run_number + } + + # Check for server errors in session files for this evaluation + eval_dir = eval_file.parent + related_session_files = [sf for sf in session_files if eval_dir in sf.parents] + + server_error_found = False + for session_file in related_session_files: + error_found, _ = check_for_errors_in_session(session_file) + if error_found: + server_error_found = True + break + + # Add server error flag + row['server_error'] = 1 if server_error_found else 0 + + # Extract all metrics (flatten the JSON structure) + if isinstance(data, dict): + metrics = {} + + # Extract top-level metrics + for key, value in data.items(): + if isinstance(value, (int, float)) and not isinstance(value, bool): + metrics[key] = value + + # Look for nested metrics structure (list of [name, value] pairs) + if 'metrics' in data and isinstance(data['metrics'], list): + for metric_item in data['metrics']: + if isinstance(metric_item, list) and len(metric_item) == 2: + metric_name = metric_item[0] + metric_value = metric_item[1] + + # Handle different value formats + if isinstance(metric_value, dict): + if 'Integer' in metric_value: + metrics[metric_name] = int(metric_value['Integer']) + elif 'Float' in metric_value: + metrics[metric_name] = float(metric_value['Float']) + elif 'Bool' in metric_value: + metrics[metric_name] = 1 if metric_value['Bool'] else 0 + # Skip string values for aggregation + elif isinstance(metric_value, (int, float)) and not isinstance(metric_value, bool): + metrics[metric_name] = metric_value + elif isinstance(metric_value, bool): + metrics[metric_name] = 1 if metric_value else 0 + + # Look for metrics in other common locations + for metric_location in ['metrics', 'result', 'evaluation']: + if metric_location in data and isinstance(data[metric_location], dict): + for key, value in data[metric_location].items(): + if isinstance(value, (int, float)) and not isinstance(value, bool): + metrics[key] = value + elif isinstance(value, bool): + metrics[key] = 1 if value else 0 + + # Add all metrics to the row + row.update(metrics) + + # Ensure a score is present (if not, add a placeholder) + if 'score' not in row: + # Try to use existing fields to calculate a score + if server_error_found: + row['score'] = 0 # Failed runs get a zero score + else: + # Set a default based on presence of "success" fields + for key in row: + if 'success' in key.lower() and isinstance(row[key], (int, float)): + row['score'] = row[key] + break + else: + # No success field found, mark as NaN + row['score'] = float('nan') + + return row + else: + print(f"Warning: Unexpected format in {eval_file}") + return None + + except Exception as e: + print(f"Error processing {eval_file}: {str(e)}") + return None + +def process_model_directory(model_dir): + """Process a model directory to create aggregate_metrics.csv.""" + provider, model_name = extract_provider_model(model_dir) + + # Find all eval results files + eval_files = find_eval_results_files(model_dir) + if not eval_files: + print(f"No eval-results.json files found in {model_dir}") + return False + + # Find all session files for error checking + session_files = find_session_files(model_dir) + + # Extract metrics from each eval file + rows = [] + for eval_file in eval_files: + row = extract_metrics_from_eval_file(eval_file, provider, model_name, session_files) + if row is not None: + rows.append(row) + + if not rows: + print(f"No valid metrics extracted from {model_dir}") + return False + + # Create a dataframe from all rows + combined_df = pd.DataFrame(rows) + + # Calculate aggregates for numeric columns, grouped by eval_suite, eval_name + numeric_cols = combined_df.select_dtypes(include=['number']).columns.tolist() + # Exclude the run column from aggregation + if 'run' in numeric_cols: + numeric_cols.remove('run') + + # Group by provider, model_name, eval_suite, eval_name and calculate mean for numeric columns + group_by_cols = ['provider', 'model_name', 'eval_suite', 'eval_name'] + agg_dict = {col: 'mean' for col in numeric_cols} + + # Only perform aggregation if we have numeric columns + if numeric_cols: + aggregate_df = combined_df.groupby(group_by_cols).agg(agg_dict).reset_index() + + # Rename columns to add _mean suffix for the averaged metrics + for col in numeric_cols: + aggregate_df = aggregate_df.rename(columns={col: f"{col}_mean"}) + else: + print(f"Warning: No numeric metrics found in {model_dir}") + # Create a minimal dataframe with just the grouping columns + aggregate_df = combined_df[group_by_cols].drop_duplicates() + + # Make sure we have prompt_execution_time_mean and prompt_error_mean columns + # These are expected by the generate_leaderboard.py script + if 'prompt_execution_time_mean' not in aggregate_df.columns: + aggregate_df['prompt_execution_time_mean'] = float('nan') + + if 'prompt_error_mean' not in aggregate_df.columns: + aggregate_df['prompt_error_mean'] = float('nan') + + # Add server_error_mean column if not present + if 'server_error_mean' not in aggregate_df.columns: + aggregate_df['server_error_mean'] = 0.0 + + # Create eval-results directory + eval_results_dir = model_dir / "eval-results" + eval_results_dir.mkdir(exist_ok=True) + + # Save to CSV + csv_path = eval_results_dir / "aggregate_metrics.csv" + aggregate_df.to_csv(csv_path, index=False) + + # Count number of evaluations that had server errors + if 'server_error_mean' in aggregate_df.columns: + error_count = len(aggregate_df[aggregate_df['server_error_mean'] > 0]) + total_count = len(aggregate_df) + print(f"Saved aggregate metrics to {csv_path} with {len(aggregate_df)} rows " + + f"({error_count}/{total_count} evals had server errors)") + else: + print(f"Saved aggregate metrics to {csv_path} with {len(aggregate_df)} rows") + + return True + +def main(): + parser = argparse.ArgumentParser( + description="Prepare aggregate_metrics.csv files from eval-results.json files with error detection" + ) + parser.add_argument( + "--benchmark-dir", + type=str, + required=True, + help="Path to the benchmark directory containing model subdirectories" + ) + + args = parser.parse_args() + + # Convert path to Path object and validate it exists + benchmark_dir = Path(args.benchmark_dir) + if not benchmark_dir.exists() or not benchmark_dir.is_dir(): + print(f"Error: Benchmark directory {benchmark_dir} does not exist or is not a directory") + sys.exit(1) + + success_count = 0 + + # Process each model directory + for model_dir in benchmark_dir.iterdir(): + if model_dir.is_dir() and not model_dir.name.startswith('.'): + if process_model_directory(model_dir): + success_count += 1 + + if success_count == 0: + print("No aggregate_metrics.csv files were created") + sys.exit(1) + + print(f"Successfully created aggregate_metrics.csv files for {success_count} model directories") + print("You can now run generate_leaderboard.py to create the final leaderboard.") + print("Note: The server_error_mean column indicates the average rate of server errors across evaluations.") + +if __name__ == "__main__": + main() + diff --git a/ui-v2/.gitignore b/ui-v2/.gitignore new file mode 100644 index 00000000..70288e57 --- /dev/null +++ b/ui-v2/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* + +node_modules +dist +dist-ssr +dist-electron +*.local + +# Editor directories and files +.vscode +.idea +.DS_Store + +# Build output +.vite/ +out/ + +# Generated JavaScript files in source +electron/**/*.js +!electron/**/*.config.js + +# Playwright +test-results/ +playwright-report/ +playwright/.cache/ diff --git a/ui-v2/.npmrc b/ui-v2/.npmrc new file mode 100644 index 00000000..214c29d1 --- /dev/null +++ b/ui-v2/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ diff --git a/ui-v2/.nvmrc b/ui-v2/.nvmrc new file mode 100644 index 00000000..5502a04f --- /dev/null +++ b/ui-v2/.nvmrc @@ -0,0 +1 @@ +v23 diff --git a/ui-v2/.prettierrc b/ui-v2/.prettierrc new file mode 100644 index 00000000..b4efcf20 --- /dev/null +++ b/ui-v2/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "endOfLine": "auto" +} diff --git a/ui-v2/.stylelintignore b/ui-v2/.stylelintignore new file mode 100644 index 00000000..716c6cc8 --- /dev/null +++ b/ui-v2/.stylelintignore @@ -0,0 +1,4 @@ +node_modules +dist +.vite +coverage \ No newline at end of file diff --git a/ui-v2/.stylelintrc.json b/ui-v2/.stylelintrc.json new file mode 100644 index 00000000..90d8df78 --- /dev/null +++ b/ui-v2/.stylelintrc.json @@ -0,0 +1,14 @@ +{ + "extends": ["stylelint-config-standard"], + "rules": { + "at-rule-no-unknown": [ + true, + { + "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen", "theme", "custom-variant"] + } + ], + "at-rule-no-deprecated": null, + "custom-property-pattern": null, + "no-descending-specificity": null + } +} diff --git a/ui-v2/README.md b/ui-v2/README.md new file mode 100644 index 00000000..0d0287c3 --- /dev/null +++ b/ui-v2/README.md @@ -0,0 +1,156 @@ +# codename goose ui v2 + +Your on-machine AI agent, automating tasks seamlessly. + +## Development + +### Getting Started + +```bash +# Install dependencies +npm install + +# Start electron development server +npm start +``` + +### Building and Packaging + +```bash +# Build electron application +npm run build + +# Package electron app +npm run package + +# Create distributable +npm run make +``` + +### Quality and Testing + +```bash +# Run tests +npm test + +# Run tests with UI +npm run test:ui + +# Generate test coverage +npm run test:coverage + +# End-to-End Testing +npm run test:e2e # Run all e2e tests headlessly +npm run test:e2e:ui # Run e2e tests with UI mode + +# Type checking +npm run typecheck # Check all TypeScript files +npm run tsc:electron # Check electron TypeScript files + +# Linting +npm run lint # Run all linting +npm run lint:fix # Fix linting issues +npm run lint:style # Check CSS +npm run lint:style:fix # Fix CSS issues + +# Code formatting +npm run prettier # Check formatting +npm run prettier:fix # Fix formatting +npm run format # Fix all formatting (prettier + style) + +# Run all checks (types, lint, format) +npm run check-all +``` + +## Project Structure + +``` +├── electron/ # Electron main process files +│ ├── main.ts # Main process entry +│ └── preload.ts # Preload script +├── src/ +│ ├── components/ # React components +│ ├── services/ # Application services +│ │ └── platform/ # Platform services +│ ├── test/ # Test setup and configurations +│ │ ├── e2e/ # End-to-end test files +│ │ ├── setup.ts +│ │ └── types.d.ts +│ └── App.tsx # Main application component +├── index.html # HTML template +├── playwright.config.ts # Playwright e2e test configuration +├── vite.config.ts # Base Vite configuration +├── vite.main.config.ts # Vite config for electron main +├── vite.preload.config.ts # Vite config for preload script +├── vite.renderer.config.ts # Vite config for electron renderer +├── tsconfig.json # Base TypeScript configuration +├── tsconfig.electron.json # TypeScript config for electron +├── tsconfig.node.json # TypeScript config for Node.js +└── forge.config.ts # Electron Forge configuration +``` + +## Architecture + +The application is built as an Electron desktop application with a modern React frontend. Here's a detailed breakdown of the key architectural components: + +### Platform Services + +The application uses a service-based architecture to handle platform-specific functionality: + +```typescript +// Platform Service Interface +export interface IPlatformService { + copyToClipboard(text: string): Promise; + // Additional platform-specific operations +} +``` + +### Electron Integration + +The architecture includes several key Electron-specific components: + +1. **Preload Script**: Safely exposes Electron APIs to the renderer process + +```typescript +// Type definitions for Electron APIs +declare global { + interface Window { + electronAPI: { + copyToClipboard: (text: string) => Promise; + // Other API methods + }; + } +} +``` + +2. **IPC Communication**: Typed handlers for main process communication + +```typescript +// Electron platform service implementation +export class PlatformService implements IPlatformService { + async copyToClipboard(text: string): Promise { + return window.electronAPI.copyToClipboard(text); + } +} +``` + +### Build System + +The project uses a multi-configuration build system: + +1. **Main Process**: Built using `vite.main.config.ts` + - Handles core Electron functionality + - Manages window creation and system integration + +2. **Renderer Process**: Built using `vite.renderer.config.ts` + - React application + - UI components and application logic + +3. **Preload Scripts**: Built using `vite.preload.config.ts` + - Secure bridge between main and renderer processes + - Exposes limited API surface to frontend + +The build process is managed by Electron Forge, which handles: +- Development environment setup +- Application packaging +- Distribution creation for various platforms \ No newline at end of file diff --git a/ui-v2/components.json b/ui-v2/components.json new file mode 100644 index 00000000..59522241 --- /dev/null +++ b/ui-v2/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://localhost:3000/r/registry.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/styles/main.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/ui-v2/electron/main.ts b/ui-v2/electron/main.ts new file mode 100644 index 00000000..d9a1b963 --- /dev/null +++ b/ui-v2/electron/main.ts @@ -0,0 +1,157 @@ +import * as fs from 'fs/promises'; +import * as path from 'path'; + +import { app, BrowserWindow, ipcMain, clipboard, dialog, session } from 'electron'; +import type { + IpcMainInvokeEvent, + OnHeadersReceivedListenerDetails, + HeadersReceivedResponse, +} from 'electron'; + +const isDevelopment = !app.isPackaged; +const isHeadless = process.env.HEADLESS === 'true'; + +// Enable sandbox before app is ready +app.enableSandbox(); + +if (isHeadless) { + app.disableHardwareAcceleration(); +} + +async function createWindow() { + // Handle different preload paths for dev and prod + const preloadPath = isDevelopment + ? path.join(app.getAppPath(), '.vite/build/preload/preload.js') + : path.join(__dirname, 'preload.js'); + + // Create the browser window with headless options when needed + const mainWindow = new BrowserWindow({ + titleBarStyle: process.platform === 'darwin' ? 'hidden' : 'default', + ...(process.platform === 'darwin' ? { trafficLightPosition: { x: 16, y: 10 } } : {}), + frame: false, + width: 1200, + height: 800, + minWidth: 800, + ...(isHeadless + ? { + show: false, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + webSecurity: true, + preload: preloadPath, + sandbox: true, + offscreen: true, + }, + } + : { + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + webSecurity: true, + preload: preloadPath, + sandbox: true, + }, + }), + }); + + // Set up CSP + session.defaultSession.webRequest.onHeadersReceived( + ( + details: OnHeadersReceivedListenerDetails, + callback: (response: HeadersReceivedResponse) => void + ) => { + callback({ + responseHeaders: { + ...details.responseHeaders, + 'Content-Security-Policy': [ + isDevelopment + ? ` + default-src 'self'; + script-src 'self' 'unsafe-inline'; + style-src 'self' 'unsafe-inline'; + connect-src 'self' ws://localhost:3001 http://localhost:3001; + img-src 'self' data: https:; + font-src 'self' data: https://cash-f.squarecdn.com; + ` + .replace(/\s+/g, ' ') + .trim() + : ` + default-src 'self'; + script-src 'self'; + style-src 'self' 'unsafe-inline'; + img-src 'self' data: https:; + font-src 'self' data: https://cash-f.squarecdn.com; + ` + .replace(/\s+/g, ' ') + .trim(), + ], + }, + }); + } + ); + + // Set up IPC handlers + ipcMain.handle('clipboard-copy', async (_: IpcMainInvokeEvent, text: string) => { + clipboard.writeText(text); + }); + + ipcMain.handle('clipboard-read', async () => { + return clipboard.readText(); + }); + + interface SaveFileParams { + content: string; + fileName: string; + } + + interface SaveFileResult { + success: boolean; + path?: string; + } + + ipcMain.handle( + 'save-file', + async ( + _: IpcMainInvokeEvent, + { content, fileName }: SaveFileParams + ): Promise => { + const { filePath } = await dialog.showSaveDialog({ + defaultPath: fileName, + }); + + if (filePath) { + await fs.writeFile(filePath, content, 'utf8'); + return { success: true, path: filePath }; + } + return { success: false }; + } + ); + + // Load the app + if (isDevelopment) { + mainWindow.loadURL('http://localhost:3001/'); + if (!isHeadless) { + mainWindow.webContents.openDevTools(); + } + } else { + // In production, load from the asar archive + mainWindow.loadFile(path.join(__dirname, 'renderer/index.html')); + } +} + +app.whenReady().then(() => { + createWindow().catch(console.error); + + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow().catch(console.error); + } + }); +}); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); diff --git a/ui-v2/electron/preload.ts b/ui-v2/electron/preload.ts new file mode 100644 index 00000000..845179a2 --- /dev/null +++ b/ui-v2/electron/preload.ts @@ -0,0 +1,12 @@ +import { contextBridge, ipcRenderer } from 'electron'; + +// Define the API interface +interface ElectronAPI { + copyToClipboard(text: string): Promise; +} + +// Expose protected methods that allow the renderer process to use +// the ipcRenderer without exposing the entire object +contextBridge.exposeInMainWorld('electronAPI', { + copyToClipboard: (text: string) => ipcRenderer.invoke('clipboard-copy', text), +} as ElectronAPI); diff --git a/ui-v2/eslint.config.cjs b/ui-v2/eslint.config.cjs new file mode 100644 index 00000000..56456936 --- /dev/null +++ b/ui-v2/eslint.config.cjs @@ -0,0 +1,143 @@ +const eslint = require('@eslint/js'); +const typescript = require('@typescript-eslint/eslint-plugin'); +const typescriptParser = require('@typescript-eslint/parser'); +const importPlugin = require('eslint-plugin-import'); +const prettier = require('eslint-plugin-prettier'); +const react = require('eslint-plugin-react'); +const reactHooks = require('eslint-plugin-react-hooks'); + +module.exports = [ + { + ignores: ['**/node_modules/**', '**/dist/**', '**/out/**', '**/coverage/**', '**/.vite/**'], + }, + // Configuration for Node.js files + { + files: ['**/*.{js,cjs,ts,mts}'], + languageOptions: { + globals: { + module: 'readonly', + require: 'readonly', + process: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + }, + }, + }, + // Base configuration for all files + { + files: ['**/*.{js,jsx,ts,tsx}'], + plugins: { + '@typescript-eslint': typescript, + react, + 'react-hooks': reactHooks, + import: importPlugin, + prettier, + }, + languageOptions: { + parser: typescriptParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + }, + settings: { + react: { + version: 'detect', + }, + }, + rules: { + ...eslint.configs.recommended.rules, + ...typescript.configs.recommended.rules, + ...react.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'prettier/prettier': 'error', + 'react/react-in-jsx-scope': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'error', + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + 'import/order': [ + 'error', + { + groups: ['builtin', 'external', 'internal', ['parent', 'sibling']], + pathGroups: [ + { + pattern: 'react', + group: 'external', + position: 'before', + }, + ], + pathGroupsExcludedImportTypes: ['react'], + 'newlines-between': 'always', + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + }, + ], + }, + }, + // Browser-specific configuration + { + files: ['src/**/*.{ts,tsx}'], + languageOptions: { + globals: { + window: 'readonly', + document: 'readonly', + navigator: 'readonly', + console: 'readonly', + setTimeout: 'readonly', + setInterval: 'readonly', + clearTimeout: 'readonly', + clearInterval: 'readonly', + requestAnimationFrame: 'readonly', + localStorage: 'readonly', + HTMLDivElement: 'readonly', + HTMLTextAreaElement: 'readonly', + HTMLFormElement: 'readonly', + HTMLInputElement: 'readonly', + MutationObserver: 'readonly', + IntersectionObserver: 'readonly', + Blob: 'readonly', + SVGSVGElement: 'readonly', + }, + }, + }, + // Electron main process configuration + { + files: ['electron/**/*.ts'], + languageOptions: { + globals: { + __dirname: 'readonly', + process: 'readonly', + console: 'readonly', + }, + }, + }, + // UI components (shadcn/ui) - more relaxed rules + { + files: ['src/components/ui/**/*.{ts,tsx}'], + rules: { + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'react/prop-types': 'off', + }, + }, + // Test configuration + { + files: ['**/*.test.{ts,tsx}', 'src/test/**/*.{ts,tsx}'], + languageOptions: { + globals: { + console: 'readonly', + jest: 'readonly', + }, + }, + rules: { + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +]; diff --git a/ui-v2/forge.config.ts b/ui-v2/forge.config.ts new file mode 100644 index 00000000..2351f9a0 --- /dev/null +++ b/ui-v2/forge.config.ts @@ -0,0 +1,50 @@ +import { VitePlugin } from '@electron-forge/plugin-vite'; +import type { ForgeConfig } from '@electron-forge/shared-types'; + +const config: ForgeConfig = { + packagerConfig: { + asar: true, + }, + rebuildConfig: {}, + makers: [ + { + name: '@electron-forge/maker-squirrel', + config: {}, + }, + { + name: '@electron-forge/maker-zip', + config: {}, + platforms: ['darwin'], + }, + { + name: '@electron-forge/maker-deb', + config: {}, + }, + { + name: '@electron-forge/maker-rpm', + config: {}, + }, + ], + plugins: [ + new VitePlugin({ + build: [ + { + entry: 'electron/main.ts', + config: 'vite.main.config.ts', + }, + { + entry: 'electron/preload.ts', + config: 'vite.preload.config.ts', + }, + ], + renderer: [ + { + name: 'main_window', + config: 'vite.renderer.config.ts', + }, + ], + }), + ], +}; + +export default config; diff --git a/ui-v2/index.html b/ui-v2/index.html new file mode 100644 index 00000000..b32cc48a --- /dev/null +++ b/ui-v2/index.html @@ -0,0 +1,27 @@ + + + + + + + Goose v2 + + + +
+ + + diff --git a/ui-v2/lib/utils.ts b/ui-v2/lib/utils.ts new file mode 100644 index 00000000..a7c26636 --- /dev/null +++ b/ui-v2/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); +} diff --git a/ui-v2/package-lock.json b/ui-v2/package-lock.json new file mode 100644 index 00000000..005112e7 --- /dev/null +++ b/ui-v2/package-lock.json @@ -0,0 +1,16111 @@ +{ + "name": "goose-v2", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "goose-v2", + "version": "1.0.0", + "dependencies": { + "@radix-ui/react-tooltip": "^1.2.7", + "@tanstack/react-router": "^1.120.5", + "@tanstack/react-router-devtools": "^1.120.11", + "@tanstack/router": "^0.0.1-beta.53", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^12.12.1", + "lucide-react": "^0.511.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "recharts": "^2.15.3" + }, + "devDependencies": { + "@electron-forge/cli": "^7.8.1", + "@electron-forge/maker-deb": "^7.8.1", + "@electron-forge/maker-rpm": "^7.8.1", + "@electron-forge/maker-squirrel": "^7.8.1", + "@electron-forge/maker-zip": "^7.8.1", + "@electron-forge/plugin-vite": "^7.8.1", + "@electron-forge/shared-types": "^7.8.1", + "@playwright/test": "^1.52.0", + "@tailwindcss/postcss": "^4.1.7", + "@tailwindcss/typography": "^0.5.16", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/eslint": "^9.6.1", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", + "@typescript-eslint/eslint-plugin": "^8.32.1", + "@typescript-eslint/parser": "^8.32.1", + "@vitejs/plugin-react": "^4.4.1", + "@vitest/coverage-v8": "^3.1.3", + "@vitest/ui": "^3.1.3", + "autoprefixer": "^10.4.21", + "concurrently": "^9.1.2", + "electron": "^36.2.1", + "eslint": "^9.27.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.4.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "husky": "^9.1.7", + "jsdom": "^26.1.0", + "lint-staged": "^16.0.0", + "postcss": "^8.5.3", + "prettier": "^3.5.3", + "stylelint": "^16.19.1", + "stylelint-config-standard": "^38.0.0", + "tailwind-merge": "^3.3.0", + "tailwindcss": "^4.1.7", + "tailwindcss-animate": "^1.0.7", + "ts-node": "^10.9.2", + "tw-animate-css": "^1.3.0", + "typescript": "^5.8.3", + "vite": "^6.3.5", + "vitest": "^3.1.3" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz", + "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz", + "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz", + "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@electron-forge/cli": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/cli/-/cli-7.8.1.tgz", + "integrity": "sha512-QI3EShutfq9Y+2TWWrPjm4JZM3eSAKzoQvRZdVhAfVpUbyJ8K23VqJShg3kGKlPf9BXHAGvE+8LyH5s2yDr1qA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.electron-forge-cli?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "MIT", + "dependencies": { + "@electron-forge/core": "7.8.1", + "@electron-forge/core-utils": "7.8.1", + "@electron-forge/shared-types": "7.8.1", + "@electron/get": "^3.0.0", + "chalk": "^4.0.0", + "commander": "^11.1.0", + "debug": "^4.3.1", + "fs-extra": "^10.0.0", + "listr2": "^7.0.2", + "log-symbols": "^4.0.0", + "semver": "^7.2.1" + }, + "bin": { + "electron-forge": "dist/electron-forge.js", + "electron-forge-vscode-nix": "script/vscode.sh", + "electron-forge-vscode-win": "script/vscode.cmd" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/core": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/core/-/core-7.8.1.tgz", + "integrity": "sha512-jkh0QPW5p0zmruu1E8+2XNufc4UMxy13WLJcm7hn9jbaXKLkMbKuEvhrN1tH/9uGp1mhr/t8sC4N67gP+gS87w==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.electron-forge-core?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "MIT", + "dependencies": { + "@electron-forge/core-utils": "7.8.1", + "@electron-forge/maker-base": "7.8.1", + "@electron-forge/plugin-base": "7.8.1", + "@electron-forge/publisher-base": "7.8.1", + "@electron-forge/shared-types": "7.8.1", + "@electron-forge/template-base": "7.8.1", + "@electron-forge/template-vite": "7.8.1", + "@electron-forge/template-vite-typescript": "7.8.1", + "@electron-forge/template-webpack": "7.8.1", + "@electron-forge/template-webpack-typescript": "7.8.1", + "@electron-forge/tracer": "7.8.1", + "@electron/get": "^3.0.0", + "@electron/packager": "^18.3.5", + "@electron/rebuild": "^3.7.0", + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.3.1", + "fast-glob": "^3.2.7", + "filenamify": "^4.1.0", + "find-up": "^5.0.0", + "fs-extra": "^10.0.0", + "global-dirs": "^3.0.0", + "got": "^11.8.5", + "interpret": "^3.1.1", + "jiti": "^2.4.2", + "listr2": "^7.0.2", + "lodash": "^4.17.20", + "log-symbols": "^4.0.0", + "node-fetch": "^2.6.7", + "rechoir": "^0.8.0", + "semver": "^7.2.1", + "source-map-support": "^0.5.13", + "sudo-prompt": "^9.1.1", + "username": "^5.1.0" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/core-utils": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/core-utils/-/core-utils-7.8.1.tgz", + "integrity": "sha512-mRoPLDNZgmjyOURE/K0D3Op53XGFmFRgfIvFC7c9S/BqsRpovVblrqI4XxPRdNmH9dvhd8On9gGz+XIYAKD3aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "7.8.1", + "@electron/rebuild": "^3.7.0", + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.3.1", + "find-up": "^5.0.0", + "fs-extra": "^10.0.0", + "log-symbols": "^4.0.0", + "semver": "^7.2.1" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/maker-base": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-base/-/maker-base-7.8.1.tgz", + "integrity": "sha512-GUZqschGuEBzSzE0bMeDip65IDds48DZXzldlRwQ+85SYVA6RMU2AwDDqx3YiYsvP2OuxKruuqIJZtOF5ps4FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "7.8.1", + "fs-extra": "^10.0.0", + "which": "^2.0.2" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/maker-deb": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-deb/-/maker-deb-7.8.1.tgz", + "integrity": "sha512-tjjeesQtCP5Xht1X7gl4+K9bwoETPmQfBkOVAY/FZIxPj40uQh/hOUtLX2tYENNGNVZ1ryDYRs8TuPi+I41Vfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/maker-base": "7.8.1", + "@electron-forge/shared-types": "7.8.1" + }, + "engines": { + "node": ">= 16.4.0" + }, + "optionalDependencies": { + "electron-installer-debian": "^3.2.0" + } + }, + "node_modules/@electron-forge/maker-rpm": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-rpm/-/maker-rpm-7.8.1.tgz", + "integrity": "sha512-TF6wylft3BHkw9zdHcxmjEPBZYgTIc0jE31skFnMEQ/aExbNRiNaCZvsXy+7ptTWZxhxUKRc9KHhLFRMCmOK8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/maker-base": "7.8.1", + "@electron-forge/shared-types": "7.8.1" + }, + "engines": { + "node": ">= 16.4.0" + }, + "optionalDependencies": { + "electron-installer-redhat": "^3.2.0" + } + }, + "node_modules/@electron-forge/maker-squirrel": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-squirrel/-/maker-squirrel-7.8.1.tgz", + "integrity": "sha512-qT1PMvT7ALF0ONOkxlA0oc0PiFuKCAKgoMPoxYo9gGOqFvnAb+TBcnLxflQ4ashE/ZkrHpykr4LcDJxqythQTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/maker-base": "7.8.1", + "@electron-forge/shared-types": "7.8.1", + "fs-extra": "^10.0.0" + }, + "engines": { + "node": ">= 16.4.0" + }, + "optionalDependencies": { + "electron-winstaller": "^5.3.0" + } + }, + "node_modules/@electron-forge/maker-zip": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-zip/-/maker-zip-7.8.1.tgz", + "integrity": "sha512-unIxEoV1lnK4BLVqCy3L2y897fTyg8nKY1WT4rrpv0MUKnQG4qmigDfST5zZNNHHaulEn/ElAic2GEiP7d6bhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/maker-base": "7.8.1", + "@electron-forge/shared-types": "7.8.1", + "cross-zip": "^4.0.0", + "fs-extra": "^10.0.0", + "got": "^11.8.5" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/plugin-base": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/plugin-base/-/plugin-base-7.8.1.tgz", + "integrity": "sha512-iCZC2d7CbsZ9l6j5d+KPIiyQx0U1QBfWAbKnnQhWCSizjcrZ7A9V4sMFZeTO6+PVm48b/r9GFPm+slpgZtYQLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "7.8.1" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/plugin-vite": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/plugin-vite/-/plugin-vite-7.8.1.tgz", + "integrity": "sha512-NjaN25rO/kRaJn7xBvn9wlXroPcDYBAacrKRkSbOZIODlHgBda/pw46lk8lmSG5LTw7V0llvM4e01g9CP/8dEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/core-utils": "7.8.1", + "@electron-forge/plugin-base": "7.8.1", + "@electron-forge/shared-types": "7.8.1", + "@electron-forge/web-multi-logger": "7.8.1", + "chalk": "^4.0.0", + "debug": "^4.3.1", + "fs-extra": "^10.0.0", + "listr2": "^7.0.2" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/publisher-base": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/publisher-base/-/publisher-base-7.8.1.tgz", + "integrity": "sha512-z2C+C4pcFxyCXIFwXGDcxhU8qtVUPZa3sPL6tH5RuMxJi77768chLw2quDWk2/dfupcSELXcOMYCs7aLysCzeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "7.8.1" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/shared-types": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/shared-types/-/shared-types-7.8.1.tgz", + "integrity": "sha512-guLyGjIISKQQRWHX+ugmcjIOjn2q/BEzCo3ioJXFowxiFwmZw/oCZ2KlPig/t6dMqgUrHTH5W/F0WKu0EY4M+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/tracer": "7.8.1", + "@electron/packager": "^18.3.5", + "@electron/rebuild": "^3.7.0", + "listr2": "^7.0.2" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/template-base": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/template-base/-/template-base-7.8.1.tgz", + "integrity": "sha512-k8jEUr0zWFWb16ZGho+Es2OFeKkcbTgbC6mcH4eNyF/sumh/4XZMcwRtX1i7EiZAYiL9sVxyI6KVwGu254g+0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/core-utils": "7.8.1", + "@electron-forge/shared-types": "7.8.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "fs-extra": "^10.0.0", + "username": "^5.1.0" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/template-vite": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/template-vite/-/template-vite-7.8.1.tgz", + "integrity": "sha512-qzSlJaBYYqQAbBdLk4DqAE3HCNz4yXbpkb+VC74ddL4JGwPdPU57DjCthr6YetKJ2FsOVy9ipovA8HX5UbXpAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "7.8.1", + "@electron-forge/template-base": "7.8.1", + "fs-extra": "^10.0.0" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/template-vite-typescript": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.8.1.tgz", + "integrity": "sha512-CccQhwUjZcc6svzuOi3BtbDal591DzyX2J5GPa6mwVutDP8EMtqJL1VyOHdcWO/7XjI6GNAD0fiXySOJiUAECA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "7.8.1", + "@electron-forge/template-base": "7.8.1", + "fs-extra": "^10.0.0" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/template-webpack": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack/-/template-webpack-7.8.1.tgz", + "integrity": "sha512-DA77o9kTCHrq+W211pyNP49DyAt0d1mzMp2gisyNz7a+iKvlv2DsMAeRieLoCQ44akb/z8ZsL0YLteSjKLy4AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "7.8.1", + "@electron-forge/template-base": "7.8.1", + "fs-extra": "^10.0.0" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/template-webpack-typescript": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.8.1.tgz", + "integrity": "sha512-h922E+6zWwym1RT6WKD79BLTc4H8YxEMJ7wPWkBX59kw/exsTB/KFdiJq6r82ON5jSJ+Q8sDGqSmDWdyCfo+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "7.8.1", + "@electron-forge/template-base": "7.8.1", + "fs-extra": "^10.0.0" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/tracer": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/tracer/-/tracer-7.8.1.tgz", + "integrity": "sha512-r2i7aHVp2fylGQSPDw3aTcdNfVX9cpL1iL2MKHrCRNwgrfR+nryGYg434T745GGm1rNQIv5Egdkh5G9xf00oWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chrome-trace-event": "^1.0.3" + }, + "engines": { + "node": ">= 14.17.5" + } + }, + "node_modules/@electron-forge/web-multi-logger": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@electron-forge/web-multi-logger/-/web-multi-logger-7.8.1.tgz", + "integrity": "sha512-Z8oU39sbrVDvyk0yILBqL0CFIysVlxkM5m4RWyeo+GLoc/t4LYAhGLSquFTOD1t20nzqZzgzG8M56zIgYuyX1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "express": "^4.17.1", + "express-ws": "^5.0.2", + "xterm": "^4.9.0", + "xterm-addon-fit": "^0.5.0", + "xterm-addon-search": "^0.8.0" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@electron/get/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@electron/get/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@electron/node-gyp": { + "version": "10.2.0-electron.1", + "resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", + "integrity": "sha512-CrYo6TntjpoMO1SHjl5Pa/JoUsECNqNdB7Kx49WLQpWzPw53eEITJ2Hs9fh/ryUYDn4pxZz11StaBYBrLFJdqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^8.1.0", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.2.1", + "nopt": "^6.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/@electron/node-gyp/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/node-gyp/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/node-gyp/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/packager": { + "version": "18.3.6", + "resolved": "https://registry.npmjs.org/@electron/packager/-/packager-18.3.6.tgz", + "integrity": "sha512-1eXHB5t+SQKvUiDpWGpvr90ZSSbXj+isrh3YbjCTjKT4bE4SQrKSBfukEAaBvp67+GXHFtCHjQgN9qSTFIge+Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@electron/asar": "^3.2.13", + "@electron/get": "^3.0.0", + "@electron/notarize": "^2.1.0", + "@electron/osx-sign": "^1.0.5", + "@electron/universal": "^2.0.1", + "@electron/windows-sign": "^1.0.0", + "debug": "^4.0.1", + "extract-zip": "^2.0.0", + "filenamify": "^4.1.0", + "fs-extra": "^11.1.0", + "galactus": "^1.0.0", + "get-package-info": "^1.0.0", + "junk": "^3.1.0", + "parse-author": "^2.0.0", + "plist": "^3.0.0", + "resedit": "^2.0.0", + "resolve": "^1.1.6", + "semver": "^7.1.3", + "yargs-parser": "^21.1.1" + }, + "bin": { + "electron-packager": "bin/electron-packager.js" + }, + "engines": { + "node": ">= 16.13.0" + }, + "funding": { + "url": "https://github.com/electron/packager?sponsor=1" + } + }, + "node_modules/@electron/packager/node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/rebuild": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.7.2.tgz", + "integrity": "sha512-19/KbIR/DAxbsCkiaGMXIdPnMCJLkcf8AvGnduJtWBs/CBwiAjY1apCqOLVxrXg+rtXFCngbXhBanWjxLUt1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/node-gyp": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "fs-extra": "^10.0.0", + "got": "^11.7.0", + "node-abi": "^3.45.0", + "node-api-version": "^0.2.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/js": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.0.tgz", + "integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.0.tgz", + "integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/fs-minipass/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3" + } + }, + "node_modules/@keyv/serialize/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz", + "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@playwright/test": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", + "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz", + "integrity": "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", + "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", + "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", + "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", + "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.7.tgz", + "integrity": "sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.7" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.7.tgz", + "integrity": "sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.7", + "@tailwindcss/oxide-darwin-arm64": "4.1.7", + "@tailwindcss/oxide-darwin-x64": "4.1.7", + "@tailwindcss/oxide-freebsd-x64": "4.1.7", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.7", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.7", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.7", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.7", + "@tailwindcss/oxide-linux-x64-musl": "4.1.7", + "@tailwindcss/oxide-wasm32-wasi": "4.1.7", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.7", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.7" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.7.tgz", + "integrity": "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.7.tgz", + "integrity": "sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.7.tgz", + "integrity": "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.7.tgz", + "integrity": "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.7.tgz", + "integrity": "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.7.tgz", + "integrity": "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.7.tgz", + "integrity": "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.7.tgz", + "integrity": "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.7.tgz", + "integrity": "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.7.tgz", + "integrity": "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.9", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.7.tgz", + "integrity": "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.7.tgz", + "integrity": "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.7.tgz", + "integrity": "sha512-88g3qmNZn7jDgrrcp3ZXEQfp9CVox7xjP1HN2TFKI03CltPVd/c61ydn5qJJL8FYunn0OqBaW5HNUga0kmPVvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.7", + "@tailwindcss/oxide": "4.1.7", + "postcss": "^8.4.41", + "tailwindcss": "4.1.7" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", + "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tanstack/history": { + "version": "1.115.0", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.115.0.tgz", + "integrity": "sha512-K7JJNrRVvyjAVnbXOH2XLRhFXDkeP54Kt2P4FR1Kl2KDGlIbkua5VqZQD2rot3qaDrpufyUa63nuLai1kOLTsQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-router": { + "version": "1.120.11", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.120.11.tgz", + "integrity": "sha512-VpG8gT+kibsdF9yQIOMfnCGe1pmUlrAG/fOoTm0gru1OEkJ2Tzc80codqiocRHQ9ULmlB4H/Zx56EZyQyF3ELw==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.115.0", + "@tanstack/react-store": "^0.7.0", + "@tanstack/router-core": "1.120.10", + "jsesc": "^3.1.0", + "tiny-invariant": "^1.3.3", + "tiny-warning": "^1.0.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0" + } + }, + "node_modules/@tanstack/react-router-devtools": { + "version": "1.120.11", + "resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.120.11.tgz", + "integrity": "sha512-bk34Kn7SubkUq3TbVN6wfALvOZ63ou/dzPqhijZAwHKXpatE90BwB/Y8mLhcoH+64iXtpf/ZP2lqqsrxLXz0pw==", + "license": "MIT", + "dependencies": { + "@tanstack/router-devtools-core": "^1.120.10", + "solid-js": "^1.9.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-router": "^1.120.11", + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.7.0.tgz", + "integrity": "sha512-S/Rq17HaGOk+tQHV/yrePMnG1xbsKZIl/VsNWnNXt4XW+tTY8dTlvpJH2ZQ3GRALsusG5K6Q3unAGJ2pd9W/Ng==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.7.0", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/router": { + "version": "0.0.1-beta.53", + "resolved": "https://registry.npmjs.org/@tanstack/router/-/router-0.0.1-beta.53.tgz", + "integrity": "sha512-nX1avh939ufOCoXhJ5wihX/ft6sn50ARrj4FAhRYn50KKzo9fXsYSmPyVgqVdW0hJ4n9XXKIMkwwpzEhLQmqrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "@tanstack/store": "0.0.1-beta.52", + "tiny-invariant": "^1.3.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-core": { + "version": "1.120.10", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.120.10.tgz", + "integrity": "sha512-AmEJAYt+6w/790zTnfddVhnK1QJCnd96H4xg1aD65Oohc8+OTQBxgWky/wzqwhHRdkdsBgRT7iWac9x5Y8UrQA==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.115.0", + "@tanstack/store": "^0.7.0", + "tiny-invariant": "^1.3.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-devtools-core": { + "version": "1.120.10", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.120.10.tgz", + "integrity": "sha512-fysPrKH7dL/G/guHm0HN+ceFEBZnbKaU9R8KZHo/Qzue7WxQV+g4or2EWnbBJ8/aF+C/WYgxR1ATFqfZEjHSfg==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/router-core": "^1.120.10", + "csstype": "^3.0.10", + "solid-js": ">=1.9.5", + "tiny-invariant": "^1.3.3" + }, + "peerDependenciesMeta": { + "csstype": { + "optional": true + } + } + }, + "node_modules/@tanstack/router/node_modules/@tanstack/store": { + "version": "0.0.1-beta.52", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.0.1-beta.52.tgz", + "integrity": "sha512-3/5XqeG09sCJnakHpq8qgXjdVpm97aITbi1wfbjLGXdVsss62zpJAvc4vL8ccHf+J/OmoS4dJTNuEZ0alywZtg==", + "license": "MIT", + "dependencies": { + "immer": "^9.0.15" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/store": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.0.tgz", + "integrity": "sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.15.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", + "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", + "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", + "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", + "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/type-utils": "8.32.1", + "@typescript-eslint/utils": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", + "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", + "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", + "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/utils": "8.32.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", + "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", + "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", + "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", + "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.32.1", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", + "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.10", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.4.tgz", + "integrity": "sha512-G4p6OtioySL+hPV7Y6JHlhpsODbJzt1ndwHAFkyk6vVjpK03PFsKnauZIzcd0PrK4zAbc5lc+jeZ+eNGiMA+iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.1.4", + "vitest": "3.1.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz", + "integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz", + "integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz", + "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz", + "integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz", + "integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz", + "integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.1.4.tgz", + "integrity": "sha512-CFc2Bpb3sz4Sdt53kdNGq+qZKLftBwX4qZLC03CBUc0N1LJrOoL0ZeK0oq/708mtnpwccL0BZCY9d1WuiBSr7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.4", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.1", + "tinyglobby": "^0.2.13", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "3.1.4" + } + }, + "node_modules/@vitest/utils": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.4", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/author-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", + "integrity": "sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacheable": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.9.0.tgz", + "integrity": "sha512-8D5htMCxPDUULux9gFzv30f04Xo3wCnik0oOxKoRTPIBoqA7HtOcJ87uBhQTs3jCfZZTrUBGsYIZOgE0ZRgMAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.8.2", + "keyv": "^5.3.3" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz", + "integrity": "sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.0.3" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", + "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-zip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cross-zip/-/cross-zip-4.0.1.tgz", + "integrity": "sha512-n63i0lZ0rvQ6FXiGQ+/JFCKAUyPFhLQYJIqKaa+tSJtfKeULF/IDNDAbdnSIxgS4NTuw2b0+lj8LzfITuq+ZxQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.10" + } + }, + "node_modules/css-functions-list": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", + "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12 || >=16" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz", + "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.1.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron": { + "version": "36.2.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-36.2.1.tgz", + "integrity": "sha512-mm1Y+Ms46xcOTA69h8hpqfX392HfV4lga9aEkYkd/Syx1JBStvcACOIouCgGrnZpxNZPVS1jM8NTcMkNjuK6BQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^22.7.7", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-installer-common": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.4.tgz", + "integrity": "sha512-8gMNPXfAqUE5CfXg8RL0vXpLE9HAaPkgLXVoHE3BMUzogMWenf4LmwQ27BdCUrEhkjrKl+igs2IHJibclR3z3Q==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@electron/asar": "^3.2.5", + "@malept/cross-spawn-promise": "^1.0.0", + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "glob": "^7.1.4", + "lodash": "^4.17.15", + "parse-author": "^2.0.0", + "semver": "^7.1.1", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "url": "https://github.com/electron-userland/electron-installer-common?sponsor=1" + }, + "optionalDependencies": { + "@types/fs-extra": "^9.0.1" + } + }, + "node_modules/electron-installer-common/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/electron-installer-common/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-debian": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/electron-installer-debian/-/electron-installer-debian-3.2.0.tgz", + "integrity": "sha512-58ZrlJ1HQY80VucsEIG9tQ//HrTlG6sfofA3nRGr6TmkX661uJyu4cMPPh6kXW+aHdq/7+q25KyQhDrXvRL7jw==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin", + "linux" + ], + "dependencies": { + "@malept/cross-spawn-promise": "^1.0.0", + "debug": "^4.1.1", + "electron-installer-common": "^0.10.2", + "fs-extra": "^9.0.0", + "get-folder-size": "^2.0.1", + "lodash": "^4.17.4", + "word-wrap": "^1.2.3", + "yargs": "^16.0.2" + }, + "bin": { + "electron-installer-debian": "src/cli.js" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-installer-debian/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/electron-installer-debian/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/electron-installer-debian/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/electron-installer-debian/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-debian/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-debian/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-debian/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-debian/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/electron-installer-debian/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-debian/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-redhat": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/electron-installer-redhat/-/electron-installer-redhat-3.4.0.tgz", + "integrity": "sha512-gEISr3U32Sgtj+fjxUAlSDo3wyGGq6OBx7rF5UdpIgbnpUvMN4W5uYb0ThpnAZ42VEJh/3aODQXHbFS4f5J3Iw==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin", + "linux" + ], + "dependencies": { + "@malept/cross-spawn-promise": "^1.0.0", + "debug": "^4.1.1", + "electron-installer-common": "^0.10.2", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "word-wrap": "^1.2.3", + "yargs": "^16.0.2" + }, + "bin": { + "electron-installer-redhat": "src/cli.js" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-installer-redhat/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/electron-installer-redhat/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/electron-installer-redhat/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/electron-installer-redhat/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-redhat/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-redhat/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-redhat/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-redhat/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/electron-installer-redhat/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-redhat/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.155", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", + "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", + "dev": true, + "license": "ISC" + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optional": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/electron/node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/electron/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/electron/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", + "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz", + "integrity": "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-ws": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz", + "integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "ws": "^7.4.6" + }, + "engines": { + "node": ">=4.5.0" + }, + "peerDependencies": { + "express": "^4.0.0 || ^5.0.0-alpha.1" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/flora-colossus": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-2.0.0.tgz", + "integrity": "sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "fs-extra": "^10.1.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.12.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.12.1.tgz", + "integrity": "sha512-PFw4/GCREHI2suK/NlPSUxd+x6Rkp80uQsfCRFSOQNrm5pZif7eGtmG1VaD/UF1fW9tRBy5AaS77StatB3OJDg==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.12.1", + "motion-utils": "^12.12.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/galactus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/galactus/-/galactus-1.0.0.tgz", + "integrity": "sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "flora-colossus": "^2.0.0", + "fs-extra": "^10.1.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/gar": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/gar/-/gar-1.0.4.tgz", + "integrity": "sha512-w4n9cPWyP7aHxKxYHFQMegj7WIAsL/YX/C4Bs5Rr8s1H9M1rNtRWRsw+ovYMkXDQ5S4ZbYHsHAPmevPjPgw44w==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-folder-size": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/get-folder-size/-/get-folder-size-2.0.1.tgz", + "integrity": "sha512-+CEb+GDCM7tkOS2wdMKTn9vU7DgnKUTuDlehkNJKNSovdCOVxs14OfKCk4cvSaR3za4gj+OBdl9opPN9xrJ0zA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "gar": "^1.0.4", + "tiny-each-async": "2.0.3" + }, + "bin": { + "get-folder-size": "bin/get-folder-size" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-info": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz", + "integrity": "sha512-SCbprXGAPdIhKAXiG+Mk6yeoFH61JlYunqdFQFHDtLjJlDjFf6x07dsS8acO+xWt52jpdVo49AlVDnUVK1sDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.1.1", + "debug": "^2.2.0", + "lodash.get": "^4.0.0", + "read-pkg-up": "^2.0.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/get-package-info/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/get-package-info/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true, + "license": "MIT" + }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookified": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.9.0.tgz", + "integrity": "sha512-2yEEGqphImtKIe1NXWEhu6yD3hlFR4Mxk4Mtp3XEyScpSt4pQ4ymmXA1zzxZpj99QkFK+nN0nzjeb2+RUi/6CQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/known-css-properties": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz", + "integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.0.0.tgz", + "integrity": "sha512-sUCprePs6/rbx4vKC60Hez6X10HPkpDJaGcy3D1NdwR7g1RcNkWL8q9mJMreOqmHBTs+1sNFp+wOiX9fr+hoOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "lilconfig": "^3.1.3", + "listr2": "^8.3.3", + "micromatch": "^4.0.8", + "nano-spawn": "^1.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.18" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/lint-staged/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/lint-staged/node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lint-staged/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/listr2": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-7.0.2.tgz", + "integrity": "sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^5.0.1", + "rfdc": "^1.3.0", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", + "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^5.0.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^5.0.0", + "strip-ansi": "^7.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.511.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz", + "integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/motion-dom": { + "version": "12.12.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.12.1.tgz", + "integrity": "sha512-GXq/uUbZBEiFFE+K1Z/sxdPdadMdfJ/jmBALDfIuHGi0NmtealLOfH9FqT+6aNPgVx8ilq0DtYmyQlo6Uj9LKQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.12.1" + } + }, + "node_modules/motion-utils": { + "version": "12.12.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz", + "integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nano-spawn": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", + "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-author": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", + "integrity": "sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "author-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pe-library": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-1.0.1.tgz", + "integrity": "sha512-nh39Mo1eGWmZS7y+mK/dQIqg7S1lp38DpRxkyoHf0ZcUs/HDc+yyTjuOtTvSMZHmfSLuSQaX945u05Y2Q6UWZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14", + "npm": ">=7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/proc-log": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", + "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/recharts": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.3.tgz", + "integrity": "sha512-EdOPzTwcFSuqtvkDoaM5ws/Km1+WTAO2eizL7rqiG0V2UVhTnz0m7J2i0CjVPUCdEkZImaWvXLbZDS2H5t6GFQ==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-2.0.3.tgz", + "integrity": "sha512-oTeemxwoMuxxTYxXUwjkrOPfngTQehlv0/HoYFNkB4uzsP1Un1A9nI8JQKGOFkxpqkC7qkMs0lUsGrvUlbLNUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^1.0.1" + }, + "engines": { + "node": ">=14", + "npm": ">=7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", + "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.0", + "@rollup/rollup-android-arm64": "4.41.0", + "@rollup/rollup-darwin-arm64": "4.41.0", + "@rollup/rollup-darwin-x64": "4.41.0", + "@rollup/rollup-freebsd-arm64": "4.41.0", + "@rollup/rollup-freebsd-x64": "4.41.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.0", + "@rollup/rollup-linux-arm-musleabihf": "4.41.0", + "@rollup/rollup-linux-arm64-gnu": "4.41.0", + "@rollup/rollup-linux-arm64-musl": "4.41.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-musl": "4.41.0", + "@rollup/rollup-linux-s390x-gnu": "4.41.0", + "@rollup/rollup-linux-x64-gnu": "4.41.0", + "@rollup/rollup-linux-x64-musl": "4.41.0", + "@rollup/rollup-win32-arm64-msvc": "4.41.0", + "@rollup/rollup-win32-ia32-msvc": "4.41.0", + "@rollup/rollup-win32-x64-msvc": "4.41.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/seroval": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.2.tgz", + "integrity": "sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/solid-js": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.7.tgz", + "integrity": "sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.3.0", + "seroval-plugins": "~1.3.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-outer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/stylelint": { + "version": "16.19.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.19.1.tgz", + "integrity": "sha512-C1SlPZNMKl+d/C867ZdCRthrS+6KuZ3AoGW113RZCOL0M8xOGpgx7G70wq7lFvqvm4dcfdGFVLB/mNaLFChRKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2", + "@csstools/selector-specificity": "^5.0.0", + "@dual-bundle/import-meta-resolve": "^4.1.0", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.3.7", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^10.0.8", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^7.0.3", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.36.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.3", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "supports-hyperlinks": "^3.2.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz", + "integrity": "sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.16.0" + } + }, + "node_modules/stylelint-config-standard": { + "version": "38.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-38.0.0.tgz", + "integrity": "sha512-uj3JIX+dpFseqd/DJx8Gy3PcRAJhlEZ2IrlFOc4LUxBX/PNMEQ198x7LCOE2Q5oT9Vw8nyc4CIL78xSqPr6iag==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "stylelint-config-recommended": "^16.0.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.18.0" + } + }, + "node_modules/stylelint/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stylelint/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.0.tgz", + "integrity": "sha512-Et/ex6smi3wOOB+n5mek+Grf7P2AxZR5ueqRUvAAn4qkyatXi3cUC1cuQXVkX0VlzBVsN4BkWJFmY/fYiRTdww==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.9" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.9.tgz", + "integrity": "sha512-DUqiKkTlAfhtl7g78IuwqYM+YqvT+as0mY+EVk6mfimy19U79pJCzDZQsnqk3Ou/T6hFXWLGbwbADzD/c8Tydg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^1.9.0", + "flatted": "^3.3.3", + "hookified": "^1.8.2" + } + }, + "node_modules/stylelint/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT" + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.6.tgz", + "integrity": "sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/table/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwind-merge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz", + "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.7.tgz", + "integrity": "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/tiny-each-async": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tiny-each-async/-/tiny-each-async-2.0.3.tgz", + "integrity": "sha512-5ROII7nElnAirvFn8g7H7MtpfV1daMcyfTGQwsn/x2VtyV+VPiO5CjReCJtWLvoKTDEDmZocf3cNPraiMnBXLA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-repeated/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.0.tgz", + "integrity": "sha512-jrJ0XenzS9KVuDThJDvnhalbl4IYiMQ/XvpA0a2FL8KmlK+6CSMviO7ROY/I7z1NnUs5NnDhlM6fXmF40xPxzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/username": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/username/-/username-5.1.0.tgz", + "integrity": "sha512-PCKbdWw85JsYMvmCv5GH3kXmM66rCd9m1hBEDutPNv94b/pqCMT4NtcKyeWYvLFiE8b+ha1Jdl8XAaUdPn5QTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^1.0.0", + "mem": "^4.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz", + "integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz", + "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.1.4", + "@vitest/mocker": "3.1.4", + "@vitest/pretty-format": "^3.1.4", + "@vitest/runner": "3.1.4", + "@vitest/snapshot": "3.1.4", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.13", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.1.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.1.4", + "@vitest/ui": "3.1.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xterm": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-4.19.0.tgz", + "integrity": "sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ==", + "deprecated": "This package is now deprecated. Move to @xterm/xterm instead.", + "dev": true, + "license": "MIT" + }, + "node_modules/xterm-addon-fit": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz", + "integrity": "sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ==", + "deprecated": "This package is now deprecated. Move to @xterm/addon-fit instead.", + "dev": true, + "license": "MIT", + "peerDependencies": { + "xterm": "^4.0.0" + } + }, + "node_modules/xterm-addon-search": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/xterm-addon-search/-/xterm-addon-search-0.8.2.tgz", + "integrity": "sha512-I1863mjn8P6uVrqm/X+btalVsqjAKLhnhpbP7SavAOpEkI1jJhbHU2UTp7NjeRtcKTks6UWk/ycgds5snDSejg==", + "deprecated": "This package is now deprecated. Move to @xterm/addon-search instead.", + "dev": true, + "license": "MIT", + "peerDependencies": { + "xterm": "^4.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/ui-v2/package.json b/ui-v2/package.json new file mode 100644 index 00000000..3a721621 --- /dev/null +++ b/ui-v2/package.json @@ -0,0 +1,108 @@ +{ + "name": "goose-v2", + "private": true, + "version": "1.0.0", + "description": "Goose v2", + "main": ".vite/build/main.js", + "scripts": { + "start": "electron-forge start", + "build": "electron-forge make", + "package": "electron-forge package", + "make": "electron-forge make", + "test": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", + "test:e2e": "playwright test --project=electron", + "test:e2e:ui": "playwright test --ui --project=electron --workers=1", + "test:e2e:electron": "playwright test --project=electron --headed", + "test:e2e:electron:headless": "HEADLESS=true playwright test --project=electron", + "test:e2e:electron:ui": "playwright test --ui --project=electron", + "tsc": "tsc --noEmit", + "tsc:web": "tsc --project tsconfig.json --noEmit", + "tsc:electron": "tsc --project tsconfig.electron.json --noEmit", + "typecheck": "npm run tsc:web && npm run tsc:electron", + "lint": "eslint . && npm run lint:style", + "lint:fix": "eslint . --fix && npm run lint:style:fix", + "lint:style": "stylelint src/**/*.css", + "lint:style:fix": "stylelint src/**/*.css --fix", + "prettier": "prettier --check \"src/**/*.{ts,tsx,js,jsx,css}\" \"electron/**/*.{ts,tsx,js,jsx,css}\"", + "prettier:fix": "prettier --write \"src/**/*.{ts,tsx,js,jsx,css}\" \"electron/**/*.{ts,tsx,js,jsx,css}\"", + "format": "npm run prettier:fix && npm run lint:style:fix", + "check-all": "npm run typecheck && npm run lint && npm run prettier", + "prepare": "cd .. && npx husky" + }, + "dependencies": { + "@radix-ui/react-tooltip": "^1.2.7", + "@tanstack/react-router": "^1.120.5", + "@tanstack/react-router-devtools": "^1.120.11", + "@tanstack/router": "^0.0.1-beta.53", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^12.12.1", + "lucide-react": "^0.511.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "recharts": "^2.15.3" + }, + "devDependencies": { + "@electron-forge/cli": "^7.8.1", + "@electron-forge/maker-deb": "^7.8.1", + "@electron-forge/maker-rpm": "^7.8.1", + "@electron-forge/maker-squirrel": "^7.8.1", + "@electron-forge/maker-zip": "^7.8.1", + "@electron-forge/plugin-vite": "^7.8.1", + "@electron-forge/shared-types": "^7.8.1", + "@playwright/test": "^1.52.0", + "@tailwindcss/postcss": "^4.1.7", + "@tailwindcss/typography": "^0.5.16", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/eslint": "^9.6.1", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", + "@typescript-eslint/eslint-plugin": "^8.32.1", + "@typescript-eslint/parser": "^8.32.1", + "@vitejs/plugin-react": "^4.4.1", + "@vitest/coverage-v8": "^3.1.3", + "@vitest/ui": "^3.1.3", + "autoprefixer": "^10.4.21", + "concurrently": "^9.1.2", + "electron": "^36.2.1", + "eslint": "^9.27.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.4.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "husky": "^9.1.7", + "jsdom": "^26.1.0", + "lint-staged": "^16.0.0", + "postcss": "^8.5.3", + "prettier": "^3.5.3", + "stylelint": "^16.19.1", + "stylelint-config-standard": "^38.0.0", + "tailwind-merge": "^3.3.0", + "tailwindcss": "^4.1.7", + "tailwindcss-animate": "^1.0.7", + "ts-node": "^10.9.2", + "tw-animate-css": "^1.3.0", + "typescript": "^5.8.3", + "vite": "^6.3.5", + "vitest": "^3.1.3" + }, + "lint-staged": { + "src/**/*.{ts,tsx}": [ + "eslint --fix --max-warnings 0 --no-warn-ignored", + "prettier --write", + "bash -c 'tsc --pretty --noEmit --project tsconfig.json'" + ], + "src/**/*.{css,json}": [ + "prettier --write", + "stylelint --fix" + ] + }, + "config": { + "forge": "./forge.config.ts" + } +} diff --git a/ui-v2/playwright.config.ts b/ui-v2/playwright.config.ts new file mode 100644 index 00000000..ce73727b --- /dev/null +++ b/ui-v2/playwright.config.ts @@ -0,0 +1,28 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './src/test/e2e', + workers: 1, + use: { + trace: 'on-first-retry', + // Use headless mode in CI, non-headless locally unless specified + headless: process.env.CI === 'true' || process.env.HEADLESS === 'true', + // Add longer timeouts for CI + navigationTimeout: 30000, + actionTimeout: 15000, + }, + projects: [ + { + name: 'electron', + testMatch: ['**/electron/*.spec.ts'], + use: { + ...devices['Desktop Chrome'], + }, + }, + ], + timeout: 60000, // Increase overall timeout + expect: { + timeout: 15000, // Increase expect timeout + }, + reporter: [['html'], ['list']], +}); diff --git a/ui-v2/postcss.config.js b/ui-v2/postcss.config.js new file mode 100644 index 00000000..fc395682 --- /dev/null +++ b/ui-v2/postcss.config.js @@ -0,0 +1,9 @@ +/* eslint-env node */ + +/** @type {import('postcss').Config} */ +module.exports = { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +}; diff --git a/ui-v2/src/assets/backgrounds/clock-bg.png b/ui-v2/src/assets/backgrounds/clock-bg.png new file mode 100644 index 00000000..176c01a7 Binary files /dev/null and b/ui-v2/src/assets/backgrounds/clock-bg.png differ diff --git a/ui-v2/src/assets/backgrounds/wave-bg.png b/ui-v2/src/assets/backgrounds/wave-bg.png new file mode 100644 index 00000000..176c01a7 Binary files /dev/null and b/ui-v2/src/assets/backgrounds/wave-bg.png differ diff --git a/ui-v2/src/assets/fonts/CashSansMono-Light.woff2 b/ui-v2/src/assets/fonts/CashSansMono-Light.woff2 new file mode 100644 index 00000000..d0fd7f6d Binary files /dev/null and b/ui-v2/src/assets/fonts/CashSansMono-Light.woff2 differ diff --git a/ui-v2/src/assets/fonts/CashSansMono-Regular.woff2 b/ui-v2/src/assets/fonts/CashSansMono-Regular.woff2 new file mode 100644 index 00000000..79ca6db7 Binary files /dev/null and b/ui-v2/src/assets/fonts/CashSansMono-Regular.woff2 differ diff --git a/ui-v2/src/assets/logo.svg b/ui-v2/src/assets/logo.svg new file mode 100644 index 00000000..04aa42d4 --- /dev/null +++ b/ui-v2/src/assets/logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/ui-v2/src/components/BrandCard.tsx b/ui-v2/src/components/BrandCard.tsx new file mode 100644 index 00000000..a01a5cec --- /dev/null +++ b/ui-v2/src/components/BrandCard.tsx @@ -0,0 +1,139 @@ +import { ReactElement } from 'react'; + +interface BrandCardProps { + date?: Date; + className?: string; +} + +// Array of congratulatory messages for past days +const pastDayMessages = [ + { title: 'Great work!', message: 'You accomplished so much' }, + { title: 'Well done!', message: 'Another successful day' }, + { title: 'Fantastic job!', message: 'Making progress every day' }, + { title: 'Nice one!', message: 'Another day in the books' }, + { title: 'Awesome work!', message: 'Keep up the momentum' }, +]; + +export default function BrandCard({ date, className = '' }: BrandCardProps): ReactElement { + const isToday = date ? new Date().toDateString() === date.toDateString() : true; + + // Get a consistent message for each date + const getPastDayMessage = (date: Date) => { + // Use the date's day as an index to select a message + const index = date.getDate() % pastDayMessages.length; + return pastDayMessages[index]; + }; + + // Get message for past days + const pastMessage = date ? getPastDayMessage(date) : pastDayMessages[0]; + + return ( +
+ {/* Content */} +
+ {/* Logo */} +
+ + + + + + + + + + + + + + + +
+
+ + {/* Text content - bottom */} +
+ {isToday ? ( + <> + {/* Today's content */} +

+ Good morning +

+ +

+ You've got 3 major updates this morning +

+ + ) : ( + <> + {/* Past/Future date content */} +

+ {pastMessage?.title || 'Hello'} +

+ +

+ {pastMessage?.message || 'Great work'} +

+ + )} +
+
+ ); +} diff --git a/ui-v2/src/components/DarkModeToggle.tsx b/ui-v2/src/components/DarkModeToggle.tsx new file mode 100644 index 00000000..186fe733 --- /dev/null +++ b/ui-v2/src/components/DarkModeToggle.tsx @@ -0,0 +1,63 @@ +import { useEffect, useState, ReactElement } from 'react'; + +import { Moon, Sun } from 'lucide-react'; + +export function DarkModeToggle(): ReactElement { + const [isDark, setIsDark] = useState(false); + + useEffect(() => { + // Initialize from localStorage or system preference + const savedTheme = localStorage.getItem('theme'); + const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + const shouldBeDark = savedTheme === 'dark' || (!savedTheme && systemPrefersDark); + setIsDark(shouldBeDark); + + if (shouldBeDark) { + document.documentElement.classList.add('dark'); + } + }, []); + + const toggleDarkMode = () => { + const newIsDark = !isDark; + setIsDark(newIsDark); + + if (newIsDark) { + document.documentElement.classList.add('dark'); + localStorage.setItem('theme', 'dark'); + } else { + document.documentElement.classList.remove('dark'); + localStorage.setItem('theme', 'light'); + } + }; + + return ( + + ); +} diff --git a/ui-v2/src/components/DateDisplay.tsx b/ui-v2/src/components/DateDisplay.tsx new file mode 100644 index 00000000..decb405f --- /dev/null +++ b/ui-v2/src/components/DateDisplay.tsx @@ -0,0 +1,60 @@ +import { useEffect, useState, ReactElement } from 'react'; + +import { useTimeline } from '../contexts/TimelineContext'; + +export function DateDisplay(): ReactElement { + const { currentDate } = useTimeline(); + const [displayDate, setDisplayDate] = useState(currentDate); + const [isFlipping, setIsFlipping] = useState(false); + + useEffect(() => { + setIsFlipping(true); + const timer = setTimeout(() => { + setDisplayDate(currentDate); + setIsFlipping(false); + }, 50); // Reduced from 100ms to 50ms for faster flip + + return () => clearTimeout(timer); + }, [currentDate]); + + const formatDate = (date: Date) => { + const monthNames = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + + return { + month: monthNames[date.getMonth()], + day: date.getDate(), + weekday: dayNames[date.getDay()], + }; + }; + + const formattedDate = formatDate(displayDate); + + return ( +
+
+ {formattedDate.weekday} {formattedDate.month} {formattedDate.day} +
+
+ ); +} diff --git a/ui-v2/src/components/GooseLogo.tsx b/ui-v2/src/components/GooseLogo.tsx new file mode 100644 index 00000000..2f3c1674 --- /dev/null +++ b/ui-v2/src/components/GooseLogo.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react'; + +import { Goose, Rain } from './icons/Goose'; + +interface GooseLogoProps { + className?: string; + size?: 'default' | 'small'; + hover?: boolean; +} + +const GooseLogo: FC = ({ className = '', size = 'default', hover = true }) => { + const sizes = { + default: { + frame: 'w-16 h-16', + rain: 'w-[275px] h-[275px]', + goose: 'w-16 h-16', + }, + small: { + frame: 'w-8 h-8', + rain: 'w-[150px] h-[150px]', + goose: 'w-8 h-8', + }, + }; + + return ( +
+ {/* Rain with enhanced visibility for testing */} +
+ +
+ +
+ ); +}; + +export default GooseLogo; diff --git a/ui-v2/src/components/Home.tsx b/ui-v2/src/components/Home.tsx new file mode 100644 index 00000000..fd5e19c1 --- /dev/null +++ b/ui-v2/src/components/Home.tsx @@ -0,0 +1,12 @@ +import { ReactElement } from 'react'; + +import GooseLogo from '../components/GooseLogo'; + +export default function Home(): ReactElement { + return ( +
+ +

Goose v2

+
+ ); +} diff --git a/ui-v2/src/components/SuspenseLoader.tsx b/ui-v2/src/components/SuspenseLoader.tsx new file mode 100644 index 00000000..ab07b355 --- /dev/null +++ b/ui-v2/src/components/SuspenseLoader.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const SuspenseLoader: React.FC = (): React.ReactElement => { + return
Loading...
; +}; + +export default SuspenseLoader; diff --git a/ui-v2/src/components/Timeline.tsx b/ui-v2/src/components/Timeline.tsx new file mode 100644 index 00000000..96ceb0b9 --- /dev/null +++ b/ui-v2/src/components/Timeline.tsx @@ -0,0 +1,536 @@ +import { useRef, useMemo, useEffect, ReactElement } from 'react'; + +import { + ChartLineIcon, + ChartBarIcon, + PieChartIcon, + ListIcon, + StarIcon, + TrendingUpIcon, +} from './icons'; +import { useTimeline } from '../contexts/TimelineContext'; +import ChartTile from './tiles/ChartTile.tsx'; +import ClockTile from './tiles/ClockTile.tsx'; +import HighlightTile from './tiles/HighlightTile.tsx'; +import ListTile from './tiles/ListTile.tsx'; +import PieChartTile from './tiles/PieChartTile.tsx'; +import TimelineDots from './TimelineDots'; + +const generateRandomData = (length: number) => + Array.from({ length }, () => Math.floor(Math.random() * 100)); + +const generateTileData = (date: Date) => { + const isToday = new Date().toDateString() === date.toDateString(); + + return { + left: [ + // Performance metrics + { + type: 'chart' as const, + props: { + title: 'Daily Activity', + value: '487', + trend: '↑ 12%', + data: generateRandomData(7), + icon: , + variant: 'line' as const, + date, + }, + }, + { + type: 'highlight' as const, + props: { + title: 'Achievement', + value: isToday ? 'New Record!' : 'Great Work', + icon: , + subtitle: isToday ? 'Personal best today' : 'Keep it up', + date, + accentColor: '#FFB800', + }, + }, + { + type: 'pie' as const, + props: { + title: 'Task Distribution', + icon: , + segments: [ + { value: 45, color: '#00CAF7', label: 'Completed' }, + { value: 35, color: '#FFB800', label: 'In Progress' }, + { value: 20, color: '#FF4444', label: 'Pending' }, + ], + date, + }, + }, + // Additional metrics + { + type: 'chart' as const, + props: { + title: 'Response Time', + value: '245ms', + trend: '↓ 18%', + data: generateRandomData(7), + icon: , + variant: 'bar' as const, + date, + }, + }, + { + type: 'highlight' as const, + props: { + title: 'User Satisfaction', + value: '98%', + icon: , + subtitle: 'Based on feedback', + date, + accentColor: '#4CAF50', + }, + }, + { + type: 'list' as const, + props: { + title: 'Top Priorities', + icon: , + items: [ + { text: 'Project Alpha', value: '87%', color: '#00CAF7' }, + { text: 'Team Meeting', value: '2:30 PM' }, + { text: 'Review Code', value: '13', color: '#FFB800' }, + { text: 'Deploy Update', value: 'Done', color: '#4CAF50' }, + ], + date, + }, + }, + // System metrics + { + type: 'chart' as const, + props: { + title: 'System Load', + value: '42%', + trend: '↑ 5%', + data: generateRandomData(7), + icon: , + variant: 'line' as const, + date, + }, + }, + { + type: 'pie' as const, + props: { + title: 'Storage Usage', + icon: , + segments: [ + { value: 60, color: '#4CAF50', label: 'Free' }, + { value: 25, color: '#FFB800', label: 'Used' }, + { value: 15, color: '#FF4444', label: 'System' }, + ], + date, + }, + }, + ], + right: [ + // Performance metrics + { + type: 'chart' as const, + props: { + title: 'Performance', + value: '92%', + trend: '↑ 8%', + data: generateRandomData(7), + icon: , + variant: 'bar' as const, + date, + }, + }, + // Clock tile + { + type: 'clock' as const, + props: { + title: 'Current Time', + date, + }, + }, + { + type: 'highlight' as const, + props: { + title: 'Efficiency', + value: '+28%', + icon: , + subtitle: 'Above target', + date, + accentColor: '#4CAF50', + }, + }, + { + type: 'pie' as const, + props: { + title: 'Resource Usage', + icon: , + segments: [ + { value: 55, color: '#4CAF50', label: 'Available' }, + { value: 30, color: '#FFB800', label: 'In Use' }, + { value: 15, color: '#FF4444', label: 'Reserved' }, + ], + date, + }, + }, + // Updates and notifications + { + type: 'list' as const, + props: { + title: 'Recent Updates', + icon: , + items: [ + { text: 'System Update', value: 'Complete', color: '#4CAF50' }, + { text: 'New Features', value: '3', color: '#00CAF7' }, + { text: 'Bug Fixes', value: '7', color: '#FFB800' }, + { text: 'Performance', value: '+15%', color: '#4CAF50' }, + ], + date, + }, + }, + // Additional metrics + { + type: 'chart' as const, + props: { + title: 'User Activity', + value: '1,247', + trend: '↑ 23%', + data: generateRandomData(7), + icon: , + variant: 'line' as const, + date, + }, + }, + { + type: 'highlight' as const, + props: { + title: 'New Users', + value: '+156', + icon: , + subtitle: 'Last 24 hours', + date, + accentColor: '#00CAF7', + }, + }, + // System health + { + type: 'pie' as const, + props: { + title: 'API Health', + icon: , + segments: [ + { value: 75, color: '#4CAF50', label: 'Healthy' }, + { value: 20, color: '#FFB800', label: 'Warning' }, + { value: 5, color: '#FF4444', label: 'Critical' }, + ], + date, + }, + }, + { + type: 'list' as const, + props: { + title: 'System Status', + icon: , + items: [ + { text: 'Main API', value: 'Online', color: '#4CAF50' }, + { text: 'Database', value: '98%', color: '#00CAF7' }, + { text: 'Cache', value: 'Synced', color: '#4CAF50' }, + { text: 'CDN', value: 'Active', color: '#4CAF50' }, + ], + date, + }, + }, + ], + }; +}; + +export default function Timeline(): ReactElement { + const containerRef = useRef(null); + const sectionRefs = useRef<(HTMLDivElement | null)[]>([]); + const { setCurrentDate } = useTimeline(); + + const sections = useMemo(() => { + const result = []; + const today = new Date(); + + for (let i = 0; i <= 29; i++) { + const date = new Date(today); + date.setDate(today.getDate() - i); + + const tileData = generateTileData(date); + + result.push({ + date, + isToday: i === 0, + leftTiles: tileData.left, + rightTiles: tileData.right, + }); + } + + return result; + }, []); + + // Function to center the timeline in a section + const centerTimeline = ( + sectionElement: HTMLDivElement | null, + animate: boolean = true + ): HTMLDivElement | null => { + if (!sectionElement) return sectionElement; + + requestAnimationFrame(() => { + const totalWidth = sectionElement.scrollWidth; + const viewportWidth = sectionElement.clientWidth; + const scrollToX = Math.max(0, (totalWidth - viewportWidth) / 2); + + if (animate) { + sectionElement.scrollTo({ + left: scrollToX, + behavior: 'smooth', + }); + } else { + sectionElement.scrollLeft = scrollToX; + } + }); + + return sectionElement; + }; + + useEffect(() => { + // Capture ref values at the start of the effect + const currentContainer = containerRef.current; + const currentSections = [...sectionRefs.current]; + + // Create the intersection observer + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + const section = entry.target as HTMLDivElement; + + // When section comes into view + if (entry.isIntersecting) { + // Update current date + const sectionIndex = sectionRefs.current.indexOf(section); + if (sectionIndex !== -1 && sections[sectionIndex]) { + const date = sections[sectionIndex].date; + setCurrentDate(date); + } + } + + // When section is fully visible and centered + if (entry.intersectionRatio > 0.8) { + centerTimeline(section, true); + } + }); + }, + { + threshold: [0, 0.8, 1], // Track when section is hidden, mostly visible, and fully visible + rootMargin: '-10% 0px', // Slightly reduced margin for more natural triggering + } + ); + + // Add scroll handler for even faster updates + const handleScroll = () => { + if (!currentContainer) return; + + // Find the section closest to the middle of the viewport + const viewportMiddle = window.innerHeight / 2; + let closestSection: HTMLDivElement | null = null; + let closestDistance = Infinity; + + sectionRefs.current.forEach((section) => { + if (!section) return; + const rect = section.getBoundingClientRect(); + const sectionMiddle = rect.top + rect.height / 2; + const distance = Math.abs(sectionMiddle - viewportMiddle); + + if (distance < closestDistance) { + closestDistance = distance; + closestSection = section; + } + }); + + if (closestSection) { + const sectionIndex = sectionRefs.current.indexOf(closestSection); + if (sectionIndex !== -1 && sections[sectionIndex]) { + const date = sections[sectionIndex].date; + setCurrentDate(date); + } + } + }; + + // Add scroll event listener with throttling + let lastScrollTime = 0; + const throttledScrollHandler = () => { + const now = Date.now(); + if (now - lastScrollTime >= 150) { + // Throttle to ~6-7 times per second + handleScroll(); + lastScrollTime = now; + } + }; + + currentContainer?.addEventListener('scroll', throttledScrollHandler, { passive: true }); + + // Add resize handler + const handleResize = () => { + // Find the currently visible section + const visibleSection = sectionRefs.current.find((section) => { + if (!section) return false; + const rect = section.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + return rect.top >= -viewportHeight / 2 && rect.bottom <= viewportHeight * 1.5; + }); + + if (visibleSection) { + centerTimeline(visibleSection, true); // Animate on resize + } + }; + + // Add resize event listener + window.addEventListener('resize', handleResize); + + // Observe all sections + sectionRefs.current.forEach((section) => { + if (section) { + observer.observe(section); + centerTimeline(section, false); // No animation on initial load + } + }); + + // Cleanup function using captured values + return () => { + window.removeEventListener('resize', handleResize); + currentContainer?.removeEventListener('scroll', throttledScrollHandler); + currentSections.forEach((section) => { + if (section) { + observer.unobserve(section); + } + }); + }; + }, [sections, setCurrentDate]); + + interface TileProps { + [key: string]: unknown; + } + + interface Tile { + type: string; + props: TileProps; + } + + const renderTile = (tile: Tile, index: number): ReactElement | null => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const props = tile.props as any; // Use any for flexibility with different tile prop types + switch (tile.type) { + case 'chart': + return ; + case 'highlight': + return ; + case 'pie': + return ; + case 'list': + return ; + case 'clock': + return ; + default: + return null; + } + }; + + return ( +
+ {sections.map((section, index) => ( +
{ + sectionRefs.current[index] = el; + }} + className="h-screen relative snap-center snap-always overflow-y-hidden overflow-x-scroll snap-x snap-mandatory scrollbar-hide animate-[fadein_300ms_ease-in-out]" + > +
+ {/* Main flex container */} +
+ {/* Left Grid */} +
+
+ {section.leftTiles.map((tile, i) => ( +
+ {renderTile(tile, i)} +
+ ))} +
+
+ + {/* Center Timeline */} +
+ {/* Upper Timeline Dots */} + + + {/* Date Display */} +
+
+ {section.date.toLocaleString('default', { month: 'short' })} +
+
+ {section.date.getDate()} +
+
+ {section.date.toLocaleString('default', { weekday: 'long' })} +
+
+ + {/* Lower Timeline Dots */} + +
+ + {/* Right Grid */} +
+
+ {section.rightTiles.map((tile, i) => ( +
+ {renderTile(tile, i)} +
+ ))} +
+
+
+
+
+ ))} +
+ ); +} diff --git a/ui-v2/src/components/TimelineContext.tsx b/ui-v2/src/components/TimelineContext.tsx new file mode 100644 index 00000000..5d562e45 --- /dev/null +++ b/ui-v2/src/components/TimelineContext.tsx @@ -0,0 +1,31 @@ +import { createContext, useContext, useState, useCallback, ReactElement, ReactNode } from 'react'; + +interface TimelineContextType { + currentDate: Date; + setCurrentDate: (date: Date) => void; + isCurrentDate: (date: Date) => boolean; +} + +const TimelineContext = createContext(undefined); + +export function TimelineProvider({ children }: { children: ReactNode }): ReactElement { + const [currentDate, setCurrentDate] = useState(new Date()); + + const isCurrentDate = useCallback((date: Date): boolean => { + return date.toDateString() === new Date().toDateString(); + }, []); + + return ( + + {children} + + ); +} + +export function useTimeline(): TimelineContextType { + const context = useContext(TimelineContext); + if (context === undefined) { + throw new Error('useTimeline must be used within a TimelineProvider'); + } + return context; +} diff --git a/ui-v2/src/components/TimelineDots.tsx b/ui-v2/src/components/TimelineDots.tsx new file mode 100644 index 00000000..3260431f --- /dev/null +++ b/ui-v2/src/components/TimelineDots.tsx @@ -0,0 +1,106 @@ +import { useMemo, ReactElement } from 'react'; + +interface TimelineDotsProps { + height: number | string; + isUpper?: boolean; + isCurrentDay?: boolean; +} + +interface Dot { + top: string; + size: number; + opacity: number; +} + +export default function TimelineDots({ + height, + isUpper = false, + isCurrentDay = false, +}: TimelineDotsProps): ReactElement { + // Generate random dots with clusters + const dots = useMemo(() => { + const generateDots = () => { + const dots: Dot[] = []; + const numDots = Math.floor(Math.random() * 8) + 8; // 8-15 dots + + // Create 2-3 cluster points + const clusterPoints = Array.from( + { length: Math.floor(Math.random() * 2) + 2 }, + () => Math.random() * 100 + ); + + for (let i = 0; i < numDots; i++) { + // Decide if this dot should be part of a cluster + const isCluster = Math.random() < 0.7; // 70% chance of being in a cluster + + let top; + if (isCluster && clusterPoints.length > 0) { + // Pick a random cluster point and add some variation + const clusterPoint = clusterPoints[Math.floor(Math.random() * clusterPoints.length)]; + if (clusterPoint !== undefined) { + top = clusterPoint + (Math.random() - 0.5) * 15; // ±7.5% variation + } else { + top = Math.random() * 100; + } + } else { + top = Math.random() * 100; + } + + // Ensure dot is within bounds + top = Math.max(5, Math.min(95, top)); + + dots.push({ + top: `${top}%`, + size: Math.random() * 2 + 2, // 2-4px + opacity: Math.random() * 0.5 + 0.2, // 0.2-0.7 opacity + }); + } + return dots; + }; + + return generateDots(); + }, []); // Empty dependency array means this only runs once + + return ( +
+ {/* Main line */} +
+ {/* Top dot for current day */} + {isUpper && isCurrentDay && ( +
+ )} + + {/* Random dots */} + {dots.map((dot, index) => ( +
+ ))} +
+
+ ); +} diff --git a/ui-v2/src/components/ValueCard.tsx b/ui-v2/src/components/ValueCard.tsx new file mode 100644 index 00000000..dfa1d85a --- /dev/null +++ b/ui-v2/src/components/ValueCard.tsx @@ -0,0 +1,138 @@ +import { ReactElement } from 'react'; + +interface BrandCardProps { + date?: Date; + className?: string; +} + +const pastDayMessages = [ + { title: 'Great work!', message: 'You accomplished so much' }, + { title: 'Well done!', message: 'Another successful day' }, + { title: 'Fantastic job!', message: 'Making progress every day' }, + { title: 'Nice one!', message: 'Another day in the books' }, + { title: 'Awesome work!', message: 'Keep up the momentum' }, +]; + +export default function BrandCard({ date, className }: BrandCardProps): ReactElement { + const isToday = date ? new Date().toDateString() === date.toDateString() : true; + + // Get a consistent message for each date + const getPastDayMessage = (date: Date) => { + // Use the date's day as an index to select a message + const index = date.getDate() % pastDayMessages.length; + return pastDayMessages[index]; + }; + + // Get message for past days + const pastMessage = date ? getPastDayMessage(date) : pastDayMessages[0]; + + return ( +
+ {/* Content */} +
+ {/* Logo */} +
+ + + + + + + + + + + + + + + +
+
+ + {/* Text content - bottom */} +
+ {isToday ? ( + <> + {/* Today's content */} +

+ Good morning +

+ +

+ You've got 3 major updates this morning +

+ + ) : ( + <> + {/* Past/Future date content */} +

+ {pastMessage?.title || 'Hello'} +

+ +

+ {pastMessage?.message || 'Great work'} +

+ + )} +
+
+ ); +} diff --git a/ui-v2/src/components/chat/ChatDock.tsx b/ui-v2/src/components/chat/ChatDock.tsx new file mode 100644 index 00000000..e40b530c --- /dev/null +++ b/ui-v2/src/components/chat/ChatDock.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { motion } from 'framer-motion'; + +interface ChatDockProps { + onTileCreatorToggle: () => void; +} + +export const ChatDock: React.FC = ({ onTileCreatorToggle }) => { + return ( + + + + + + + + + + + ); +}; diff --git a/ui-v2/src/components/chat/ChatIcons.tsx b/ui-v2/src/components/chat/ChatIcons.tsx new file mode 100644 index 00000000..601d5142 --- /dev/null +++ b/ui-v2/src/components/chat/ChatIcons.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react'; + +import { motion } from 'framer-motion'; + +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; + +// Define the tool items +const CHAT_TOOLS = [ + { + icon: ( + + + + ), + label: 'Make a Tile', + color: 'bg-[#4F6BFF] hover:bg-[#4F6BFF]/90', + rotation: -3, + }, + { + icon: ( + + + + ), + label: 'Tasks', + color: 'bg-[#E042A5] hover:bg-[#E042A5]/90', + rotation: 2, + }, + { + icon: ( + + + + ), + label: 'Add', + color: 'bg-[#05C168] hover:bg-[#05C168]/90', + rotation: -2, + }, + { + icon: ( + + + + ), + label: 'Issues', + color: 'bg-[#FF9900] hover:bg-[#FF9900]/90', + rotation: 3, + }, +]; + +interface ChatIconsProps { + className?: string; +} + +export const ChatIcons: React.FC = ({ className }) => { + const [hoveredIndex, setHoveredIndex] = useState(null); + + return ( +
+
+
+ {CHAT_TOOLS.map((tool, index) => { + const getX = () => { + if (hoveredIndex === null) return 0; + const spread = 16; + const centerOffset = hoveredIndex * -spread; + return index * spread + centerOffset; + }; + + return ( + setHoveredIndex(index)} + onHoverEnd={() => setHoveredIndex(null)} + > + + + + {tool.icon} + + + {tool.label} + + + ); + })} +
+
+
+ ); +}; diff --git a/ui-v2/src/components/chat/ChatInput.tsx b/ui-v2/src/components/chat/ChatInput.tsx new file mode 100644 index 00000000..a55330d2 --- /dev/null +++ b/ui-v2/src/components/chat/ChatInput.tsx @@ -0,0 +1,135 @@ +import React, { useState, useRef, useEffect } from 'react'; + +import { motion } from 'framer-motion'; + +interface ChatInputProps { + handleSubmit: (event: React.FormEvent) => void; + isLoading?: boolean; + onStop?: () => void; + initialValue?: string; +} + +export const ChatInput: React.FC = ({ + handleSubmit, + isLoading = false, + onStop: _onStop, + initialValue = '', +}) => { + const [input, setInput] = useState(initialValue); + const [key, setKey] = useState(0); // Add a key to force re-render + const textareaRef = useRef(null); + const containerRef = useRef(null); + + const adjustTextareaHeight = () => { + const textarea = textareaRef.current; + if (textarea) { + textarea.style.height = 'auto'; + textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px'; + } + }; + + useEffect(() => { + adjustTextareaHeight(); + }, [input]); + + // Watch for class changes on html element (theme changes) + useEffect(() => { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.attributeName === 'class') { + setKey((prev) => prev + 1); // Force textarea to re-render + } + }); + }); + + const htmlElement = document.documentElement; + observer.observe(htmlElement, { attributes: true }); + + return () => observer.disconnect(); + }, []); + + const handleFormSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim() || isLoading) return; + + handleSubmit(e); + setInput(''); + if (textareaRef.current) { + textareaRef.current.style.height = 'auto'; + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + const form = (e.target as HTMLTextAreaElement).form; + if (form) form.requestSubmit(); + } + }; + + return ( + +
+
+