From 26d7e20c1c83df0dc1ba8cefc624cd892792f45c Mon Sep 17 00:00:00 2001 From: karitham Date: Thu, 1 Oct 2026 23:44:02 +0200 Subject: [PATCH] pkgs/opencode-zellij: slop up a v2-compat opencode-zellij --- modules/dev/home.nix | 12 +- modules/opencode/default.nix | 12 +- pkgs/opencode-zellij/.gitignore | 2 + pkgs/opencode-zellij/package-lock.json | 4879 +++++++++++++++++ pkgs/opencode-zellij/package.json | 20 + pkgs/opencode-zellij/package.nix | 30 + pkgs/opencode-zellij/src/config.ts | 217 + pkgs/opencode-zellij/src/lib/zellij/cli.ts | 223 + pkgs/opencode-zellij/src/lib/zellij/pane.ts | 281 + pkgs/opencode-zellij/src/lib/zellij/parse.ts | 194 + pkgs/opencode-zellij/src/plugin.ts | 149 + pkgs/opencode-zellij/src/pty/manager.ts | 136 + pkgs/opencode-zellij/src/pty/probe.ts | 68 + pkgs/opencode-zellij/src/pty/ring-buffer.ts | 112 + pkgs/opencode-zellij/src/pty/session.ts | 42 + pkgs/opencode-zellij/src/pty/write-data.ts | 39 + pkgs/opencode-zellij/src/tools/format.ts | 97 + pkgs/opencode-zellij/src/tools/kill.ts | 92 + pkgs/opencode-zellij/src/tools/list.ts | 26 + pkgs/opencode-zellij/src/tools/output.ts | 62 + .../opencode-zellij/src/tools/pane-cleanup.ts | 39 + pkgs/opencode-zellij/src/tools/read.ts | 165 + pkgs/opencode-zellij/src/tools/registry.ts | 42 + .../opencode-zellij/src/tools/request-sudo.ts | 167 + pkgs/opencode-zellij/src/tools/spawn.ts | 102 + pkgs/opencode-zellij/src/tools/tool.ts | 5 + pkgs/opencode-zellij/src/tools/write.ts | 74 + pkgs/opencode-zellij/src/utils/debug.ts | 8 + pkgs/opencode-zellij/src/utils/errors.ts | 3 + pkgs/opencode-zellij/src/utils/exit-code.ts | 36 + pkgs/opencode-zellij/src/utils/ids.ts | 5 + pkgs/opencode-zellij/src/utils/logger.ts | 74 + pkgs/opencode-zellij/src/utils/pane-title.ts | 13 + pkgs/opencode-zellij/src/utils/shell-args.ts | 31 + .../src/zellij/completion-notifications.ts | 75 + .../src/zellij/pane-watchdog-runner.ts | 118 + .../src/zellij/pane-watchdog.ts | 225 + .../src/zellij/shutdown-cleanup.ts | 56 + pkgs/opencode-zellij/src/zellij/subscribe.ts | 473 ++ .../src/zellij/tab-title-events.ts | 105 + pkgs/opencode-zellij/src/zellij/tab-title.ts | 393 ++ pkgs/opencode-zellij/tsdown.config.ts | 15 + 42 files changed, 8915 insertions(+), 2 deletions(-) create mode 100644 pkgs/opencode-zellij/.gitignore create mode 100644 pkgs/opencode-zellij/package-lock.json create mode 100644 pkgs/opencode-zellij/package.json create mode 100644 pkgs/opencode-zellij/package.nix create mode 100644 pkgs/opencode-zellij/src/config.ts create mode 100644 pkgs/opencode-zellij/src/lib/zellij/cli.ts create mode 100644 pkgs/opencode-zellij/src/lib/zellij/pane.ts create mode 100644 pkgs/opencode-zellij/src/lib/zellij/parse.ts create mode 100644 pkgs/opencode-zellij/src/plugin.ts create mode 100644 pkgs/opencode-zellij/src/pty/manager.ts create mode 100644 pkgs/opencode-zellij/src/pty/probe.ts create mode 100644 pkgs/opencode-zellij/src/pty/ring-buffer.ts create mode 100644 pkgs/opencode-zellij/src/pty/session.ts create mode 100644 pkgs/opencode-zellij/src/pty/write-data.ts create mode 100644 pkgs/opencode-zellij/src/tools/format.ts create mode 100644 pkgs/opencode-zellij/src/tools/kill.ts create mode 100644 pkgs/opencode-zellij/src/tools/list.ts create mode 100644 pkgs/opencode-zellij/src/tools/output.ts create mode 100644 pkgs/opencode-zellij/src/tools/pane-cleanup.ts create mode 100644 pkgs/opencode-zellij/src/tools/read.ts create mode 100644 pkgs/opencode-zellij/src/tools/registry.ts create mode 100644 pkgs/opencode-zellij/src/tools/request-sudo.ts create mode 100644 pkgs/opencode-zellij/src/tools/spawn.ts create mode 100644 pkgs/opencode-zellij/src/tools/tool.ts create mode 100644 pkgs/opencode-zellij/src/tools/write.ts create mode 100644 pkgs/opencode-zellij/src/utils/debug.ts create mode 100644 pkgs/opencode-zellij/src/utils/errors.ts create mode 100644 pkgs/opencode-zellij/src/utils/exit-code.ts create mode 100644 pkgs/opencode-zellij/src/utils/ids.ts create mode 100644 pkgs/opencode-zellij/src/utils/logger.ts create mode 100644 pkgs/opencode-zellij/src/utils/pane-title.ts create mode 100644 pkgs/opencode-zellij/src/utils/shell-args.ts create mode 100644 pkgs/opencode-zellij/src/zellij/completion-notifications.ts create mode 100644 pkgs/opencode-zellij/src/zellij/pane-watchdog-runner.ts create mode 100644 pkgs/opencode-zellij/src/zellij/pane-watchdog.ts create mode 100644 pkgs/opencode-zellij/src/zellij/shutdown-cleanup.ts create mode 100644 pkgs/opencode-zellij/src/zellij/subscribe.ts create mode 100644 pkgs/opencode-zellij/src/zellij/tab-title-events.ts create mode 100644 pkgs/opencode-zellij/src/zellij/tab-title.ts create mode 100644 pkgs/opencode-zellij/tsdown.config.ts diff --git a/modules/dev/home.nix b/modules/dev/home.nix index 51944ad..39feaed 100644 --- a/modules/dev/home.nix +++ b/modules/dev/home.nix @@ -1,7 +1,13 @@ # Home-manager side of the dev tools. Enabled by default whenever this # module is imported; the NixOS-side `dev.enable` is declared in # ./nixos.nix and set by the class modules (desktop, wsl). -{ lib, ... }: { +{ + lib, + config, + self', + ... +}: +{ options.dev.enable = lib.mkEnableOption "development tools"; config.dev.enable = lib.mkDefault true; @@ -10,6 +16,10 @@ # unless overridden (e.g. dev.opencode.sops.enable = false per host). config.dev.opencode.enable = lib.mkDefault true; + # Vendored OpenCode V2 plugin (see pkgs/opencode-zellij). Override to [ ] on + # hosts that should not load it. + config.dev.opencode.plugins = lib.mkDefault [ "${self'.packages.opencode-zellij}" ]; + imports = [ ./shell ./editor diff --git a/modules/opencode/default.nix b/modules/opencode/default.nix index f5e8aa8..c3f9cbd 100644 --- a/modules/opencode/default.nix +++ b/modules/opencode/default.nix @@ -61,6 +61,15 @@ in type = lib.types.str; default = "opencode-go/glm-5.3-flash"; }; + plugins = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = '' + OpenCode V2 `plugins` entries: npm package names or paths to a local + plugin directory. V2-only; the V1 `opencode` binary ignores this key. + Set to `[ ]` on hosts without the referenced plugin. + ''; + }; sops.enable = lib.mkOption { type = lib.types.bool; default = true; @@ -216,7 +225,8 @@ in command = [ "${self'.packages.browsermcp}/bin/mcp-server-browsermcp" ]; }; }; - }; + } + // lib.optionalAttrs (cfg.plugins != [ ]) { inherit (cfg) plugins; }; }; }; } diff --git a/pkgs/opencode-zellij/.gitignore b/pkgs/opencode-zellij/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/pkgs/opencode-zellij/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/pkgs/opencode-zellij/package-lock.json b/pkgs/opencode-zellij/package-lock.json new file mode 100644 index 0000000..26ecda0 --- /dev/null +++ b/pkgs/opencode-zellij/package-lock.json @@ -0,0 +1,4879 @@ +{ + "name": "opencode-zellij", + "version": "0.1.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "opencode-zellij", + "version": "0.1.1", + "dependencies": { + "@loglayer/transport-log-file-rotation": "^3.2.0", + "@opencode/plugin": "^2.0.21", + "confbox": "^0.2.4", + "loglayer": "^9.3.0", + "serialize-error": "^13.0.1", + "zod": "^4.4.3" + }, + "devDependencies": { + "tsdown": "^0.21.10" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.1057.0.tgz", + "integrity": "sha512-5MliYkp2u0+2arTp5fZIaxl+xmm90LEKv/VeSxhfNQW4t0fvWJrNO429/jchWQenNoDRrOGE59VfbuZUfwFujg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.978.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.1.tgz", + "integrity": "sha512-LbY9aGsEiznDWmUc30Nwv3aIX/+dbwTx8KfS0yOC3NPYMO+O91e6jkT1azf34FwjOndq8/Q+RcVVZz5xnerwdg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.6", + "@aws-sdk/xml-builder": "^3.972.41", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.35.0", + "@smithy/signature-v4": "^5.7.3", + "@smithy/types": "^4.19.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.71.tgz", + "integrity": "sha512-rsxsi8tF3vMGdWdaHQNXceHBdb49442yR2Pe08K+Phoc/yICbptdsHwL25Wf0z5+hjhSS5a+hDJHP7Xul4zvHQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.72.tgz", + "integrity": "sha512-xTKO/FWJPozTIXbozVnVGoNBhaGba8TBcx+KyUjRVeOlXE+dUc7GTR1cLvu0uTdIdmemzaFbqqCshXeZA1fZew==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.74", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.74.tgz", + "integrity": "sha512-u91E/hT8f4d1xy0Jl7VG4nVKJ3lxbrZkoBTeSVoJdWBiSEUMwMS/9+e0H/aJVQV//Lt5wuzP+E69v4aRSsNTmw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.17.tgz", + "integrity": "sha512-ged4KXdBkvIC81bLvNHHuQKdKak/VXhQTR1NWYTTqW0474nlmsxy9O/vlgTIohDDWH3xpBdtVMZRyjb+DnocDA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/credential-provider-env": "^3.972.72", + "@aws-sdk/credential-provider-http": "^3.972.74", + "@aws-sdk/credential-provider-login": "^3.972.79", + "@aws-sdk/credential-provider-process": "^3.972.72", + "@aws-sdk/credential-provider-sso": "^3.973.16", + "@aws-sdk/credential-provider-web-identity": "^3.972.78", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.79", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.79.tgz", + "integrity": "sha512-L+Z85anONJd8MaiuraO4wRxATCdEejBZ3K3eymzWI5JPXa9sOS9CkIm72PBKqXKX+Z9p9NGMX5AIMXm0LEflgw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.84", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.84.tgz", + "integrity": "sha512-oHt854odINVwzwsh+c5x69j0ajm4DbqqqVJ+O1ECsCIZeMDAbzFpXItaqP7UZstJj/ATdTk/KFSH0LaNAgV+kA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.72", + "@aws-sdk/credential-provider-http": "^3.972.74", + "@aws-sdk/credential-provider-ini": "^3.973.17", + "@aws-sdk/credential-provider-process": "^3.972.72", + "@aws-sdk/credential-provider-sso": "^3.973.16", + "@aws-sdk/credential-provider-web-identity": "^3.972.78", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.72.tgz", + "integrity": "sha512-rLIp2xbMjX/k9/od7APpqq1ZgXXnV0pOL1Th3ZsL8Wu0TRtBsDTVS8iPqcfRFcHakFxPvR04OSTv2ka2qOb/2A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.16.tgz", + "integrity": "sha512-IGihaJfFZYacJJr/odqILCoK7W/mvrZ7cuK7ECn3sAu4vLC6u0V8bS7mCGbdugJ8Aum2tnvqmx0F2MRFp2rn9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/token-providers": "3.1138.0", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.78.tgz", + "integrity": "sha512-/y9WvNtlcPBGLR0qc1a+9J/xtYZfVczvLUOuXaVWylzttH7ewsxwHtjmiJSolNrVSDorIxHGHMU61CbonRkmwA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1057.0.tgz", + "integrity": "sha512-rbrEHtz11g0kxsSkYr3fx2HABNNblp4AhB2MgPvJHgYOWfJ2eBviU7Mvoaef0PW8QH6lbZDfJcnM7eKvtvz3sw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.1057.0", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-cognito-identity": "^3.972.38", + "@aws-sdk/credential-provider-env": "^3.972.41", + "@aws-sdk/credential-provider-http": "^3.972.43", + "@aws-sdk/credential-provider-ini": "^3.972.46", + "@aws-sdk/credential-provider-login": "^3.972.45", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/credential-provider-process": "^3.972.41", + "@aws-sdk/credential-provider-sso": "^3.972.45", + "@aws-sdk/credential-provider-web-identity": "^3.972.45", + "@aws-sdk/nested-clients": "^3.997.13", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.5", + "@smithy/credential-provider-imds": "^4.3.6", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.46.tgz", + "integrity": "sha512-oRxtBcka/JGHGs9l9p9IVajGoTP8vTPmoAzdHGy4Qcy9P5vPnDf6nhIeM/COQNY9k/OahImTRaLkHftoXvfcmQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.47", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.47", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.47.tgz", + "integrity": "sha512-Zk08macMvQTHzQJCLJVkOlviVoqwYMrpXv4lmLN7b7sAbiMoOK7Go0NYdR5UeF+MW8LIbRmwrNy9u/5VvX1U5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.6", + "@smithy/signature-v4": "^5.7.3", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1138.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1138.0.tgz", + "integrity": "sha512-GpyAr0DD63YOEmYFM6Df+gJuIgC92MMTiBK4FTKfxii5MJ9ge20epR7LyroulscYlG89J+ZB2ivFDPjvfQhzdw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.6.tgz", + "integrity": "sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.10.tgz", + "integrity": "sha512-ycwH6Zd2GhuSqdXX9ihbCjeGTB6xOJs+O3+Jb8/zDG9978XU80qs75dfkPJRMNKe5MvBZPuNeFpd4JZKPoUF4g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.41.tgz", + "integrity": "sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.3.tgz", + "integrity": "sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0-rc.3", + "@babel/types": "^8.0.0-rc.3", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", + "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.3.tgz", + "integrity": "sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@babel/parser": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.3.tgz", + "integrity": "sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0-rc.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@babel/types": { + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.3.tgz", + "integrity": "sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0-rc.3", + "@babel/helper-validator-identifier": "^8.0.0-rc.3" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@effect/opentelemetry": { + "version": "4.0.0-rc.112", + "resolved": "https://registry.npmjs.org/@effect/opentelemetry/-/opentelemetry-4.0.0-rc.112.tgz", + "integrity": "sha512-OTRv1DxTHUmnakgJ6XVM8wVgF1KgZH4UXnOemwSLUwUjXO+RCikzF8oR/rlVmOGq81KtQzj1URM4M4nchlQOuQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <2.0.0", + "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", + "@opentelemetry/resources": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", + "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", + "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", + "effect": "^4.0.0-rc.112" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/api-logs": { + "optional": true + }, + "@opentelemetry/resources": { + "optional": true + }, + "@opentelemetry/sdk-logs": { + "optional": true + }, + "@opentelemetry/sdk-metrics": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "@opentelemetry/sdk-trace-node": { + "optional": true + }, + "@opentelemetry/sdk-trace-web": { + "optional": true + } + } + }, + "node_modules/@effect/platform-node": { + "version": "4.0.0-rc.112", + "resolved": "https://registry.npmjs.org/@effect/platform-node/-/platform-node-4.0.0-rc.112.tgz", + "integrity": "sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ==", + "license": "MIT", + "dependencies": { + "@effect/platform-node-shared": "^4.0.0-rc.112", + "mime": "^4.1.0", + "undici": "^8.10.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "effect": "^4.0.0-rc.112", + "redis": ">=5.0.0 <7.0.0" + } + }, + "node_modules/@effect/platform-node-shared": { + "version": "4.0.0-rc.112", + "resolved": "https://registry.npmjs.org/@effect/platform-node-shared/-/platform-node-shared-4.0.0-rc.112.tgz", + "integrity": "sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ==", + "license": "MIT", + "dependencies": { + "@types/ws": "^8.18.1", + "ws": "^8.21.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "effect": "^4.0.0-rc.112" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "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==", + "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==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "license": "ISC" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@loglayer/context-manager": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@loglayer/context-manager/-/context-manager-2.3.0.tgz", + "integrity": "sha512-bStCSDKUk88ZHTloOR+eE+Jfnfcs+jfI2hNSrcchorwi2UHS8AYHWMm9o2Ov3tSQ4v64je9JDeTVLgoeL+JHvw==", + "license": "MIT", + "dependencies": { + "@loglayer/shared": "4.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@loglayer/log-level-manager": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@loglayer/log-level-manager/-/log-level-manager-2.3.0.tgz", + "integrity": "sha512-tkiKKa5yBnWqCLUWFZraNAotaHxGlb8yqTUpYS1o5Qv3XeTPoNkI87oDwI8V8m63yvKLPrXsZtNas2pIiFldCQ==", + "license": "MIT", + "dependencies": { + "@loglayer/shared": "4.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@loglayer/plugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@loglayer/plugin/-/plugin-3.3.0.tgz", + "integrity": "sha512-qSEgukxOXrQsUPPUbcrOBGymowBfobTK+vC3+eDRx1dEqlhCl1YxTn8zIJjQKV+nZJyCRTzgPTvEfvceFO+QnQ==", + "license": "MIT", + "dependencies": { + "@loglayer/shared": "4.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@loglayer/shared": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@loglayer/shared/-/shared-4.4.0.tgz", + "integrity": "sha512-9C6zpO9RXjfjddR+Bbk+h5NiLDL2wNlno5jLplzhw+Jaj2gO2Dv53rxrgdRmBP5/DgSHNCZu6FdOq8ankSYdGA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@loglayer/transport": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@loglayer/transport/-/transport-3.3.0.tgz", + "integrity": "sha512-fFh4XaX24FiyZtpzOjBP2PScA3sQk1rfka287FsYXkGMm2/n74BOVj6/D/oLaiI+cJMy7fv3dtyy5sUtNil5UQ==", + "license": "MIT", + "dependencies": { + "@loglayer/shared": "4.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@loglayer/transport-log-file-rotation": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@loglayer/transport-log-file-rotation/-/transport-log-file-rotation-3.3.0.tgz", + "integrity": "sha512-WXmKndbHOXCItlnF7IXPLBQjSq/lvTYZIMeS8s2wI0sHLShTnkEIaCzBTZ7dsjrDmq8Bka57y2knRrMmNpR89w==", + "license": "MIT", + "dependencies": { + "@loglayer/transport": "3.3.0", + "file-stream-rotator": "1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz", + "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz", + "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz", + "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz", + "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz", + "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz", + "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@npmcli/agent": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.2.tgz", + "integrity": "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==", + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/arborist": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-9.4.0.tgz", + "integrity": "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA==", + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/config": { + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-10.8.1.tgz", + "integrity": "sha512-MAYk9IlIGiyC0c9fnjdBSQfIFPZT0g1MfeSiD1UXTq2zJOLX55jS9/sETJHqw/7LN18JjITrhYfgCfapbmZHiQ==", + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-5.0.3.tgz", + "integrity": "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw==", + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.3.tgz", + "integrity": "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg==", + "license": "ISC", + "dependencies": { + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-4.0.0.tgz", + "integrity": "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-5.0.0.tgz", + "integrity": "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@opencode/ai": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-2.0.21.tgz", + "integrity": "sha512-eB4M0O44VoX075Kv+vIVxIpiKSrfjSXpNaeBSjtPgZdhZCjKN5CxjJXtyPbhrDxvmDf0PxLcvjGNKQBRMkZ0fw==", + "license": "MIT", + "dependencies": { + "@aws-sdk/credential-providers": "3.1057.0", + "@opencode/schema": "2.0.21", + "@smithy/eventstream-codec": "4.2.14", + "@smithy/util-utf8": "4.2.2", + "aws4fetch": "1.0.20", + "effect": "4.0.0-rc.112", + "google-auth-library": "10.5.0" + } + }, + "node_modules/@opencode/client": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@opencode/client/-/client-2.0.21.tgz", + "integrity": "sha512-cX9sj9qpaTlkVfdaeLCFeV43+1mcSHVhvgjkVLCYyfQJj0MmBGgx/x9AM/GHVZDEX6T2mUljn0Qh5XhFM9CgrQ==", + "license": "MIT", + "dependencies": { + "@opencode/protocol": "2.0.21", + "@opencode/schema": "2.0.21" + }, + "peerDependencies": { + "effect": "4.0.0-rc.112", + "solid-js": ">=1.9.0" + }, + "peerDependenciesMeta": { + "effect": { + "optional": true + }, + "solid-js": { + "optional": true + } + } + }, + "node_modules/@opencode/plugin": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.21.tgz", + "integrity": "sha512-ZLagbklGyYPg77GvK//ir3LaNXBsZQENU8APCnaT4elsBk7YzoVt8stR6+b4wH7Sfadw76cWSaNiWgDo2QOSHA==", + "license": "MIT", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@opencode/ai": "2.0.21", + "@opencode/client": "2.0.21", + "@opencode/protocol": "2.0.21", + "@opencode/schema": "2.0.21", + "@opencode/util": "2.0.21", + "@standard-schema/spec": "1.1.0", + "effect": "4.0.0-rc.112", + "zod": "4.1.8" + }, + "peerDependencies": { + "@opencode/theme": "2.0.21", + "@opentui/core": ">=0.5.14", + "@opentui/solid": ">=0.5.14", + "solid-js": ">=1.9.0" + }, + "peerDependenciesMeta": { + "@opencode/theme": { + "optional": true + }, + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + }, + "solid-js": { + "optional": true + } + } + }, + "node_modules/@opencode/plugin/node_modules/zod": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", + "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@opencode/protocol": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.21.tgz", + "integrity": "sha512-KvcpCPpIXPMkJaffUFwLxUAZwf6SiNf/8qFW/ZR7LEHFBcnmzyl7hPk5QthAHmnTRoR+dU2U/WMakj06ePRPyg==", + "license": "MIT", + "dependencies": { + "@opencode/schema": "2.0.21", + "effect": "4.0.0-rc.112" + } + }, + "node_modules/@opencode/schema": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.0.21.tgz", + "integrity": "sha512-HMIU9sCNOvbMWQeslxbTtCLfvhMyar40+VHkZq9hNjXTaPWJrGxSzbuHJQhKzeOeDaiP7t1zllUDHTxTA/5SOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "1.1.0", + "effect": "4.0.0-rc.112" + } + }, + "node_modules/@opencode/util": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@opencode/util/-/util-2.0.21.tgz", + "integrity": "sha512-wQaa4Q19zbK+B76Pzf2NV3eE8orwWw7IDlBi/Y4WpUaHwaaF+fCQGQxkByyOeWmSy38IQEX93c8RyLPB+WWjog==", + "license": "MIT", + "dependencies": { + "@effect/opentelemetry": "4.0.0-rc.112", + "@effect/platform-node": "4.0.0-rc.112", + "@effect/platform-node-shared": "4.0.0-rc.112", + "@npmcli/arborist": "9.4.0", + "@npmcli/config": "10.8.1", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/api-logs": "0.219.0", + "@opentelemetry/context-async-hooks": "2.8.0", + "@opentelemetry/exporter-trace-otlp-http": "0.219.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-logs": "0.219.0", + "@opentelemetry/sdk-metrics": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0", + "@opentelemetry/sdk-trace-node": "2.8.0", + "cross-spawn": "7.0.6", + "effect": "4.0.0-rc.112", + "glob": "13.0.5", + "mime-types": "3.0.2", + "minimatch": "10.2.5", + "npm-package-arg": "13.0.2", + "open": "11.0.4", + "pacote": "21.5.1" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.219.0.tgz", + "integrity": "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.8.0.tgz", + "integrity": "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.219.0.tgz", + "integrity": "sha512-9t6SvBXXBEjOBcIzgozvBbd3jWrv3Gt3ngGhl1fhdZ/zRc7oZDVOFEqbi2zlBpW9BXhgDMKv422J0DL/3iQWfw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/otlp-exporter-base": "0.219.0", + "@opentelemetry/otlp-transformer": "0.219.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.219.0.tgz", + "integrity": "sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/otlp-transformer": "0.219.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.219.0.tgz", + "integrity": "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.219.0", + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-logs": "0.219.0", + "@opentelemetry/sdk-metrics": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.219.0.tgz", + "integrity": "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.219.0", + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", + "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", + "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.8.0.tgz", + "integrity": "sha512-nZt9OGufioAc3AfoLTqA9bsAeaMJAictYDdI2VcNQ+PmT+3rfKjAZDZvgPfd8VPX0O5Bw1hdQF6kDK8VSpZiWg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.8.0", + "@opentelemetry/core": "2.8.0", + "@opentelemetry/sdk-trace-base": "2.8.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "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==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@quansync/fs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.1.0.tgz", + "integrity": "sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/@redis/bloom": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-6.3.0.tgz", + "integrity": "sha512-NQ5poYpZr0jv6zazN1cy6JY5kIipKFZ3R9GTL4kOUTX3a9YAfY29U8bsE4lWhVQEM+G4V0BXVC57mubIdDijeA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.3.0" + } + }, + "node_modules/@redis/client": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-6.3.0.tgz", + "integrity": "sha512-fHrjwGCBvAANGi+TuRuwKi83sIOzgHr8eh+Uf48w3fSX7Hhrz174NrWqfJhAC2vnN0d/tfaZpdr6+o6JC3QHwg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/json": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-6.3.0.tgz", + "integrity": "sha512-yt6vRfPBXtW/3qm53cF81TSQpLMjWdW4/2E9WQ2GgbvsKkuCKCFQIcMiKlil1ZMnezK+IQuS795xKU5psbaegw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.3.0" + } + }, + "node_modules/@redis/search": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-6.3.0.tgz", + "integrity": "sha512-RPXAZmKjZZG7Tm7m1w9keFdXzbL91PiSHQBfQRe9xwW40jNsTRbG0FwhIz4QlHPU4gKDAsCy8IQHj4yZl4H9rw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.3.0" + } + }, + "node_modules/@redis/time-series": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-6.3.0.tgz", + "integrity": "sha512-+fJOB8mN1z5WRakCJNh5lVdz1KqvHGSOistsKeitD24QsCtPwyRQXWC0iq6T/i/ubLE/YcguZqPm8F598MKk4A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.3.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sigstore/bundle": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/core": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.1.tgz", + "integrity": "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==", + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.2.tgz", + "integrity": "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw==", + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", + "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", + "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.1.tgz", + "integrity": "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.35.1.tgz", + "integrity": "sha512-i4YPS4B6ts7bjn7UwLnGjiZdprOvHvgGobFZsYK3GIY3E5hIqtj0rReU69BcTpGp+fvtraSNXeG1l+jtJvF55w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.1", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.4.tgz", + "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.19.0.tgz", + "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.5.2.tgz", + "integrity": "sha512-nxu3SgmAw9JXT2CtkU0m/XNLWpP9MsaBx1zAGAypCbYj15tIFlmcYwpF+Oh18le83d+IM9PT7ENdXnE4C+d5mA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.5.2.tgz", + "integrity": "sha512-iq+cW3mAb7vfcxEEpYi3zXKpDtbrIFyanWjQl4zBq4seWD4OSxXDWSfespZxenX6aEaighn+NR3u1nU1DSvs3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", + "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.4.0.tgz", + "integrity": "sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/ast-kit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-3.0.0.tgz", + "integrity": "sha512-8OG92q3R35qjC/4i6BLBMg8IB+fClWu/1PEwg2Z9Rn+BuNaiEgJzpzn+pxWOdHJWDCAwu2JP0wCDTozAM4QirQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "estree-walker": "^3.0.3", + "pathe": "^2.0.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-kit/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/ast-kit/node_modules/@babel/parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.6.tgz", + "integrity": "sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/ast-kit/node_modules/@babel/types": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz", + "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.6", + "@babel/helper-validator-identifier": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/aws4fetch": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.20.tgz", + "integrity": "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "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==", + "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/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bin-links": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.2.tgz", + "integrity": "sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w==", + "license": "ISC", + "dependencies": { + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/birpc": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.2.0.tgz", + "integrity": "sha512-KxgKcZPfrtzJDDALHPguGpGJUrzdgpymyiQQgzFjWreHMOpWrnFNVREr5J48x2DBh8ZVioscrV1SBkDipGiX+Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/cacache": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "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==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cmd-shim": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", + "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dts-resolver": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-2.1.3.tgz", + "integrity": "sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "oxc-resolver": ">=11.0.0" + }, + "peerDependenciesMeta": { + "oxc-resolver": { + "optional": true + } + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/effect": { + "version": "4.0.0-rc.112", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-rc.112.tgz", + "integrity": "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A==", + "license": "MIT", + "dependencies": { + "fast-check": "^4.9.0", + "msgpackr": "^2.0.5" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.1.0.tgz", + "integrity": "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.10.2.tgz", + "integrity": "sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^8.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-stream-rotator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-1.0.0.tgz", + "integrity": "sha512-qg5mQO7o+vhS7NPqkrkfJS8qqhz0d17Tnewmb5sUTUKwYe27LKaDtbTuRAtQWkBn6jROuFPVIDF5DtckzokFTQ==", + "license": "MIT" + }, + "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==", + "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/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.4.tgz", + "integrity": "sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "7.1.3", + "google-logging-utils": "1.1.3", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.5.tgz", + "integrity": "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.2.0.tgz", + "integrity": "sha512-WE9av4wKDZgRjBwgVUabocx8T6/7o3Ca1Fat46FXDhXVAFibzNadedcOXrdgd1Kzmk8tsk/9ZH89Wyf/SqeZ3A==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/hookable": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.2.tgz", + "integrity": "sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "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==", + "license": "BSD-2-Clause" + }, + "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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/import-without-cache": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/import-without-cache/-/import-without-cache-0.3.3.tgz", + "integrity": "sha512-bDxwDdF04gm550DfZHgffvlX+9kUlcz32UD0AeBTmVPFiWkrexF2XVmiuFFbDhiFuP8fQkrkvI2KdSNPYWAXkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ip-address": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "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==", + "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/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", + "license": "MIT" + }, + "node_modules/just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/loglayer": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/loglayer/-/loglayer-9.4.0.tgz", + "integrity": "sha512-abAZY0TkGw1LOA8n0yGJbRi9R0upRWymYBZItWfiktXMTHqdgCpHukONoX/Byfbt1RfRdw/GjT/PwrztePYOcw==", + "license": "MIT", + "dependencies": { + "@loglayer/context-manager": "2.3.0", + "@loglayer/log-level-manager": "2.3.0", + "@loglayer/plugin": "3.3.0", + "@loglayer/shared": "4.4.0", + "@loglayer/transport": "3.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-fetch-happen": { + "version": "15.0.6", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.6.tgz", + "integrity": "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==", + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "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==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.1.0.tgz", + "integrity": "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.4" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz", + "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" + } + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/non-error": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/non-error/-/non-error-0.1.0.tgz", + "integrity": "sha512-TMB1uHiGsHRGv1uYclfhivcnf0/PdFp2pNqRxXjncaAsjYMoisaQJI+SSZCqRq+VliwRTC8tsMQfmrWjDMhkPQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-bundled": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-install-checks": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-package-arg": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", + "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-packlist": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/open": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", + "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.5.1", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.2.1", + "wsl-utils": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.8.tgz", + "integrity": "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "21.5.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.1.tgz", + "integrity": "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==", + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/parse-conflict-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-5.0.1.tgz", + "integrity": "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ==", + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^5.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/proggy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-4.0.0.tgz", + "integrity": "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-call-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pure-rand": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.2.tgz", + "integrity": "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/read-cmd-shim": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz", + "integrity": "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/redis": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-6.3.0.tgz", + "integrity": "sha512-XFQbPie1lGpKeUZ8ySYY43yLnQy/iIQtHNvIr/1XdaABpb96rUSdm3Mfl+98FEVbv9ZJIzINMno5s0Nh8E5LEw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@redis/bloom": "6.3.0", + "@redis/client": "6.3.0", + "@redis/json": "6.3.0", + "@redis/search": "6.3.0", + "@redis/time-series": "6.3.0" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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==", + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "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/rimraf/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==", + "license": "ISC" + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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==", + "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/rolldown": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.127.0", + "@rolldown/pluginutils": "1.0.0-rc.17" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + } + }, + "node_modules/rolldown-plugin-dts": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.23.2.tgz", + "integrity": "sha512-PbSqLawLgZBGcOGT3yqWBGn4cX+wh2nt5FuBGdcMHyOhoukmjbhYAl8NT9sE4U38Cm9tqLOIQeOrvzeayM0DLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "8.0.0-rc.3", + "@babel/helper-validator-identifier": "8.0.0-rc.3", + "@babel/parser": "8.0.0-rc.3", + "@babel/types": "8.0.0-rc.3", + "ast-kit": "^3.0.0-beta.1", + "birpc": "^4.0.0", + "dts-resolver": "^2.1.3", + "get-tsconfig": "^4.13.7", + "obug": "^2.1.1", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@ts-macro/tsc": "^0.3.6", + "@typescript/native-preview": ">=7.0.0-dev.20260325.1", + "rolldown": "^1.0.0-rc.12", + "typescript": "^5.0.0 || ^6.0.0", + "vue-tsc": "~3.2.0" + }, + "peerDependenciesMeta": { + "@ts-macro/tsc": { + "optional": true + }, + "@typescript/native-preview": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "optional": true + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-error": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-13.0.2.tgz", + "integrity": "sha512-G+YKwp9I6IvWMe5RlwqxdmTPIcq/G8HK/sU51gdV1XAJznYkRSTB0ph+zB9AfSxG0fx6xrqYA6i9HeIcHFTXow==", + "license": "MIT", + "dependencies": { + "non-error": "^0.1.0", + "type-fest": "^5.4.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.1.tgz", + "integrity": "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.1", + "@sigstore/tuf": "^4.0.2", + "@sigstore/verify": "^3.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "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==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.24.tgz", + "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", + "license": "CC0-1.0" + }, + "node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "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==", + "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==", + "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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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/treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tsdown": { + "version": "0.21.10", + "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.21.10.tgz", + "integrity": "sha512-3wk73yBhZe/wX7REqSdivNQ84TDs1mJ+IlnzrrEREP70xlJ/AEIzqaI04l/TzMKVIdkTdC3CPaADn2Lk/0SkdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.2.0", + "cac": "^7.0.0", + "defu": "^6.1.7", + "empathic": "^2.0.0", + "hookable": "^6.1.1", + "import-without-cache": "^0.3.3", + "obug": "^2.1.1", + "picomatch": "^4.0.4", + "rolldown": "1.0.0-rc.17", + "rolldown-plugin-dts": "^0.23.2", + "semver": "^7.7.4", + "tinyexec": "^1.1.1", + "tinyglobby": "^0.2.16", + "tree-kill": "^1.2.2", + "unconfig-core": "^7.5.0", + "unrun": "^0.2.37" + }, + "bin": { + "tsdown": "dist/run.mjs" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@arethetypeswrong/core": "^0.18.1", + "@tsdown/css": "0.21.10", + "@tsdown/exe": "0.21.10", + "@vitejs/devtools": "*", + "publint": "^0.3.0", + "typescript": "^5.0.0 || ^6.0.0", + "unplugin-unused": "^0.5.0" + }, + "peerDependenciesMeta": { + "@arethetypeswrong/core": { + "optional": true + }, + "@tsdown/css": { + "optional": true + }, + "@tsdown/exe": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "publint": { + "optional": true + }, + "typescript": { + "optional": true + }, + "unplugin-unused": { + "optional": true + } + } + }, + "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/tuf-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "license": "MIT", + "dependencies": { + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/type-fest": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.10.0.tgz", + "integrity": "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unconfig-core": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.5.0.tgz", + "integrity": "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^1.0.0", + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/unrun": { + "version": "0.2.39", + "resolved": "https://registry.npmjs.org/unrun/-/unrun-0.2.39.tgz", + "integrity": "sha512-h9FxYVpztY/wwq+bauLOh6Y3CWu2IVeRLq5lxzneBiIU9Tn86OGp9xiQrGhnYspAmg5dzdY0Cc8+Y70kuTARCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "rolldown": "1.0.0-rc.17" + }, + "bin": { + "unrun": "dist/cli.mjs" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/Gugustinette" + }, + "peerDependencies": { + "synckit": "^0.11.11" + }, + "peerDependenciesMeta": { + "synckit": { + "optional": true + } + } + }, + "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==", + "license": "MIT" + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.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==", + "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==", + "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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?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==", + "license": "MIT" + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/write-file-atomic": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", + "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", + "license": "ISC", + "dependencies": { + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "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/wsl-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.1.tgz", + "integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/pkgs/opencode-zellij/package.json b/pkgs/opencode-zellij/package.json new file mode 100644 index 0000000..9f6540e --- /dev/null +++ b/pkgs/opencode-zellij/package.json @@ -0,0 +1,20 @@ +{ + "name": "opencode-zellij", + "version": "0.1.1", + "private": true, + "type": "module", + "scripts": { + "build": "tsdown" + }, + "dependencies": { + "@loglayer/transport-log-file-rotation": "^3.2.0", + "@opencode/plugin": "^2.0.21", + "confbox": "^0.2.4", + "loglayer": "^9.3.0", + "serialize-error": "^13.0.1", + "zod": "^4.4.3" + }, + "devDependencies": { + "tsdown": "^0.21.10" + } +} diff --git a/pkgs/opencode-zellij/package.nix b/pkgs/opencode-zellij/package.nix new file mode 100644 index 0000000..32bb4b7 --- /dev/null +++ b/pkgs/opencode-zellij/package.nix @@ -0,0 +1,30 @@ +# OpenCode V2 port of opencode-zellij (upstream: maou-shonen/opencode-zellij). +# +# Source is vendored here (runtime only; tests and dev tooling are omitted) and +# built hermetically with tsdown. The output is a plugin *directory*: OpenCode +# V2 loads a local plugin directory through an index file inside it, so only the +# two build outputs are installed. +{ lib, buildNpmPackage }: +buildNpmPackage { + pname = "opencode-zellij"; + version = "0.1.1"; + + src = ./.; + + npmDepsHash = "sha256-9O7ucZAmQyC993xa44Rl5t5vFFePtyvZwqXfULPSJdU="; + + # Two self-contained files (node builtins only), not the node package layout. + installPhase = '' + runHook preInstall + mkdir -p $out + install -m644 dist/index.mjs $out/index.mjs + install -m644 dist/pane-watchdog-runner.mjs $out/pane-watchdog-runner.mjs + runHook postInstall + ''; + + meta = { + description = "OpenCode V2 plugin: run long-lived commands in visible Zellij panes"; + homepage = "https://github.com/maou-shonen/opencode-zellij"; + license = lib.licenses.mit; + }; +} diff --git a/pkgs/opencode-zellij/src/config.ts b/pkgs/opencode-zellij/src/config.ts new file mode 100644 index 0000000..48505dd --- /dev/null +++ b/pkgs/opencode-zellij/src/config.ts @@ -0,0 +1,217 @@ +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { homedir } from 'node:os' +import { join } from 'node:path' +import process from 'node:process' +import { parseJSON, parseJSONC } from 'confbox' +import { z } from 'zod' + +const sudoPaneSchema = z.enum(['allow', 'deny', 'hide']) +const sudoPaneModeSchema = z.enum(['floating', 'fullscreen']) +const sudoPaneFloatingSizeSchema = z.object({ + width: z.string().default('80%'), + height: z.string().default('60%'), + pinned: z.boolean().default(true), +}).strict() + +export interface TabTitleConfig { + enabled: boolean + emojiIdle: string + emojiRunning: string + emojiNeedsInput: string + debounceMs: number +} + +export interface SudoPaneFloatingSize { + width: string + height: string + pinned: boolean +} + +export interface PtyConfig { + enabled: boolean + cleanupExitedPaneOnRead: boolean + sudoPane: SudoPaneMode + sudoPaneMode: SudoPaneModeKind + sudoPaneFloatingSize: SudoPaneFloatingSize +} + +export type SudoPaneMode = z.infer +export type SudoPaneModeKind = z.infer + +export interface ZellijPluginConfig { + tabTitle: TabTitleConfig + pty: PtyConfig +} + +export interface LoadConfigInput { + directory?: string | undefined + worktree?: string | undefined +} + +export interface LoadConfigResult { + config: ZellijPluginConfig + sources: { + user?: string | undefined + project?: string | undefined + } + warnings: string[] +} + +const configFilenames = [ + 'opencode-zellij.config.jsonc', + 'opencode-zellij.config.json', +] as const + +const tabTitleLayerSchema = z.object({ + enabled: z.boolean().optional().describe('Enable dynamic Zellij tab title updates.'), + emojiIdle: z.string().optional().describe('Prefix used when OpenCode is idle.'), + emojiRunning: z.string().optional().describe('Prefix used while OpenCode is running work.'), + emojiNeedsInput: z.string().optional().describe('Prefix used when OpenCode is waiting for human input.'), + debounceMs: z.number().finite().min(0).optional().describe('Debounce time for tab title updates in milliseconds.'), +}).strict() + +const ptyLayerSchema = z.object({ + enabled: z.boolean().optional().describe('Enable Zellij-backed PTY tools.'), + cleanupExitedPaneOnRead: z.boolean().optional().describe('Remove exited PTY panes after they are read.'), + sudoPane: sudoPaneSchema.optional().describe('Controls whether the sudo pane tool is available, denied, or hidden.'), + sudoPaneMode: sudoPaneModeSchema.optional().describe('How the sudo request pane is presented to the user. `floating` (default) opens a large, pinned floating pane without stealing focus; `fullscreen` opens a non-floating pane that Zellij focuses automatically.'), + sudoPaneFloatingSize: sudoPaneFloatingSizeSchema.optional().describe('Size of the floating sudo pane. `width`/`height` accept a percent string (e.g. `80%`) or a bare integer in cells. `pinned` keeps the pane on top of other floating panes.'), +}).strict() + +export const sidecarConfigSchema = z.object({ + $schema: z.string().optional().describe('JSON Schema URI for editor completion.'), + tabTitle: tabTitleLayerSchema.optional(), + pty: ptyLayerSchema.optional(), +}).strict() + +export const defaultConfig: ZellijPluginConfig = { + tabTitle: { + enabled: true, + emojiIdle: '🟢', + emojiRunning: '⚡', + emojiNeedsInput: '💬', + debounceMs: 300, + }, + pty: { + enabled: true, + cleanupExitedPaneOnRead: true, + sudoPane: 'allow', + sudoPaneMode: 'floating', + sudoPaneFloatingSize: { width: '80%', height: '60%', pinned: true }, + }, +} + +type ConfigLayer = Pick, 'tabTitle' | 'pty'> + +const DEPRECATED_TAB_TITLE_KEYS = ['emojiBranch'] as const + +function stripDeprecatedTabTitleFields(value: unknown): unknown { + if (!value || typeof value !== 'object' || Array.isArray(value)) + return value + const result = { ...(value as Record) } + for (const key of DEPRECATED_TAB_TITLE_KEYS) + delete result[key] + return result +} + +function validConfigLayer(value: unknown): ConfigLayer | undefined { + if (!value || typeof value !== 'object' || Array.isArray(value)) + return undefined + const cloned = { ...(value as Record) } + cloned.tabTitle = stripDeprecatedTabTitleFields(cloned.tabTitle) + const result = sidecarConfigSchema.safeParse(cloned) + if (!result.success) + return undefined + + return { + tabTitle: result.data.tabTitle, + pty: result.data.pty, + } +} + +function mergeConfig(user?: ConfigLayer | undefined, project?: ConfigLayer | undefined): ZellijPluginConfig { + return { + tabTitle: { + enabled: project?.tabTitle?.enabled ?? user?.tabTitle?.enabled ?? defaultConfig.tabTitle.enabled, + emojiIdle: project?.tabTitle?.emojiIdle ?? user?.tabTitle?.emojiIdle ?? defaultConfig.tabTitle.emojiIdle, + emojiRunning: project?.tabTitle?.emojiRunning ?? user?.tabTitle?.emojiRunning ?? defaultConfig.tabTitle.emojiRunning, + emojiNeedsInput: project?.tabTitle?.emojiNeedsInput ?? user?.tabTitle?.emojiNeedsInput ?? defaultConfig.tabTitle.emojiNeedsInput, + debounceMs: project?.tabTitle?.debounceMs ?? user?.tabTitle?.debounceMs ?? defaultConfig.tabTitle.debounceMs, + }, + pty: { + enabled: project?.pty?.enabled ?? user?.pty?.enabled ?? defaultConfig.pty.enabled, + cleanupExitedPaneOnRead: project?.pty?.cleanupExitedPaneOnRead ?? user?.pty?.cleanupExitedPaneOnRead ?? defaultConfig.pty.cleanupExitedPaneOnRead, + sudoPane: project?.pty?.sudoPane ?? user?.pty?.sudoPane ?? defaultConfig.pty.sudoPane, + sudoPaneMode: project?.pty?.sudoPaneMode ?? user?.pty?.sudoPaneMode ?? defaultConfig.pty.sudoPaneMode, + sudoPaneFloatingSize: project?.pty?.sudoPaneFloatingSize ?? user?.pty?.sudoPaneFloatingSize ?? defaultConfig.pty.sudoPaneFloatingSize, + }, + } +} + +async function loadConfigLayer(directory: string, warnings: string[]): Promise<{ layer?: ConfigLayer | undefined, source?: string | undefined }> { + const configFile = detectConfigFile(directory) + if (!configFile) + return {} + + try { + const text = await readFile(configFile, 'utf8') + const parsed = configFile.endsWith('.jsonc') ? parseJSONC(text) : parseJSON(text) + const layer = validConfigLayer(parsed) + if (!layer) { + warnings.push(`Ignoring invalid config shape in ${configFile}.`) + return { source: configFile } + } + return { layer, source: configFile } + } + catch (cause) { + warnings.push(`Ignoring unreadable or invalid config file ${configFile}: ${cause instanceof Error ? cause.message : String(cause)}`) + return {} + } +} + +function detectConfigFile(directory: string): string | undefined { + return configFilenames + .map(filename => join(directory, filename)) + .find(path => existsSync(path)) +} + +export function userConfigDir(): string { + return process.env.XDG_CONFIG_HOME ? join(process.env.XDG_CONFIG_HOME, 'opencode') : join(homedir(), '.config', 'opencode') +} + +export function projectConfigDirs(input: LoadConfigInput): string[] { + const dirs: string[] = [] + if (input.worktree) + dirs.push(join(input.worktree, '.opencode')) + if (input.directory && input.directory !== input.worktree) + dirs.push(join(input.directory, '.opencode')) + return dirs +} + +export async function loadConfig(input: LoadConfigInput): Promise { + const warnings: string[] = [] + const sources: LoadConfigResult['sources'] = {} + + const userResult = await loadConfigLayer(userConfigDir(), warnings) + const userLayer = userResult.layer + if (userResult.source && userLayer) + sources.user = userResult.source + + let projectLayer: ConfigLayer | undefined + for (const projectDir of projectConfigDirs(input)) { + const projectResult = await loadConfigLayer(projectDir, warnings) + if (!projectResult.source) + continue + projectLayer = projectResult.layer + if (projectLayer) + sources.project = projectResult.source + break + } + + return { + config: mergeConfig(userLayer, projectLayer), + sources, + warnings, + } +} diff --git a/pkgs/opencode-zellij/src/lib/zellij/cli.ts b/pkgs/opencode-zellij/src/lib/zellij/cli.ts new file mode 100644 index 0000000..0f13031 --- /dev/null +++ b/pkgs/opencode-zellij/src/lib/zellij/cli.ts @@ -0,0 +1,223 @@ +import type { CommandInput } from '../../utils/shell-args.js' +import { execFile, spawnSync } from 'node:child_process' +import process from 'node:process' +import { promisify } from 'node:util' +import { buildCommandArgv } from '../../utils/shell-args.js' +import { parsePaneId } from './pane.js' +import { parseActiveTabName, parseCurrentPaneTabId, parsePaneExists, parseTabName } from './parse.js' + +const execFileAsync = promisify(execFile) + +export type NewPaneOptions = CommandInput & { + cwd?: string | undefined + title?: string | undefined + floating?: boolean | undefined + floatingWidth?: string | undefined + floatingHeight?: string | undefined + floatingPinned?: boolean | undefined + closeOnExit?: boolean | undefined + exitCodeToken?: string | undefined +} + +export interface ZellijRunOptions { + timeoutMs?: number | undefined +} + +export interface ZellijRunResult { + stdout: string + stderr: string +} + +export interface ZellijRunner { + run: (actionArgs: string[], options?: ZellijRunOptions) => Promise +} + +export interface ZellijClient { + newPane: (options: NewPaneOptions) => Promise + writeChars: (paneId: string, data: string) => Promise + sendCtrlC: (paneId: string) => Promise + closePane: (paneId: string) => Promise + closePaneSync: (paneId: string) => void + focusPane: (paneId: string) => Promise + dumpScreen: (paneId: string) => Promise + paneExists: (paneId: string) => Promise + currentPaneTabId: () => Promise + renameTab: (title: string) => Promise + renameTabById: (tabId: number, title: string) => Promise + currentTabTitle: () => Promise +} + +export interface CreateZellijClientOptions { + runner?: ZellijRunner | undefined +} + +export function zellijCommandArgs(actionArgs: string[]): string[] { + const sessionName = process.env.ZELLIJ_SESSION_NAME?.trim() + if (sessionName) + return ['--session', sessionName, ...actionArgs] + return actionArgs +} + +export function zellijActionArgs(action: string, args: string[] = []): string[] { + return ['action', action, ...args] +} + +export function buildNewPaneActionArgs(options: NewPaneOptions): string[] { + const args = ['action', 'new-pane'] + // `--near-current-pane` only applies to layout-bound (non-floating) panes. + // Floating panes use their own positioning (`--x`/`--y`/centered defaults) + // and anchoring via `--width`/`--height`/`--pinned`. + if (process.env.ZELLIJ && !options.floating) + args.push('--near-current-pane') + + if (options.title) + args.push('--name', options.title) + if (options.cwd) + args.push('--cwd', options.cwd) + if (options.floating) { + args.push('--floating') + if (options.floatingWidth) + args.push('--width', options.floatingWidth) + if (options.floatingHeight) + args.push('--height', options.floatingHeight) + if (options.floatingPinned) + args.push('--pinned', 'true') + } + if (options.closeOnExit) + args.push('--close-on-exit') + + args.push('--', ...buildCommandArgv(options, { exitCodeToken: options.exitCodeToken })) + return args +} + +export function buildRenameTabActionArgs(title: string, options: { tabId?: number } = {}): string[] { + if (options.tabId !== undefined) + return ['action', 'rename-tab', '--tab-id', String(options.tabId), title] + return ['action', 'rename-tab', title] +} + +export function ensureZellijTarget(): void { + if (process.env.ZELLIJ || process.env.ZELLIJ_SESSION_NAME) + return + throw new Error('Zellij context not found. Run OpenCode inside Zellij or set ZELLIJ_SESSION_NAME to an existing session.') +} + +async function defaultRunZellij(actionArgs: string[], options: ZellijRunOptions = {}): Promise { + ensureZellijTarget() + try { + const result = await execFileAsync('zellij', zellijCommandArgs(actionArgs), { + encoding: 'utf8', + timeout: options.timeoutMs ?? 10_000, + maxBuffer: 20 * 1024 * 1024, + }) + + return { + stdout: result.stdout ?? '', + stderr: result.stderr ?? '', + } + } + catch (cause) { + const error = cause as { message?: string, stdout?: string, stderr?: string } + const stderr = error.stderr?.trim() + const stdout = error.stdout?.trim() + const detail = stderr || stdout || error.message || 'unknown error' + throw new Error(`zellij ${actionArgs.join(' ')} failed: ${detail}`) + } +} + +const defaultRunner: ZellijRunner = { run: defaultRunZellij } + +export function createZellijClient(options: CreateZellijClientOptions = {}): ZellijClient { + const runner = options.runner ?? defaultRunner + + async function resolveCurrentPaneTabId(): Promise { + const paneId = process.env.ZELLIJ_PANE_ID + if (!paneId) + return undefined + + const result = await runner.run(zellijActionArgs('list-panes', ['--json']), { timeoutMs: 5_000 }) + return parseCurrentPaneTabId(result.stdout, paneId) + } + + return { + newPane: async (paneOptions) => { + const result = await runner.run(buildNewPaneActionArgs(paneOptions)) + return parsePaneId(result.stdout) + }, + + writeChars: async (paneId, data) => { + await runner.run(zellijActionArgs('write-chars', ['--pane-id', paneId, data])) + }, + + sendCtrlC: async (paneId) => { + await runner.run(zellijActionArgs('send-keys', ['--pane-id', paneId, 'Ctrl c'])) + }, + + closePane: async (paneId) => { + await runner.run(zellijActionArgs('close-pane', ['--pane-id', paneId])) + }, + + closePaneSync: (paneId) => { + ensureZellijTarget() + spawnSync('zellij', zellijCommandArgs(zellijActionArgs('close-pane', ['--pane-id', paneId])), { + encoding: 'utf8', + stdio: 'ignore', + timeout: 2_000, + }) + }, + + focusPane: async (paneId) => { + await runner.run(zellijActionArgs('focus-pane-id', [paneId])) + }, + + dumpScreen: async (paneId) => { + const result = await runner.run( + zellijActionArgs('dump-screen', ['--pane-id', paneId, '--full']), + { timeoutMs: 10_000 }, + ) + return result.stdout + }, + + paneExists: async (paneId) => { + const result = await runner.run(zellijActionArgs('list-panes', ['--json']), { timeoutMs: 5_000 }) + return parsePaneExists(result.stdout, paneId) + }, + + currentPaneTabId: resolveCurrentPaneTabId, + + renameTab: async (title) => { + const tabId = await resolveCurrentPaneTabId() + if (tabId === undefined && process.env.ZELLIJ) + throw new Error(`Could not resolve Zellij tab id for pane ${process.env.ZELLIJ_PANE_ID ?? ''}`) + await runner.run( + tabId === undefined + ? buildRenameTabActionArgs(title) + : buildRenameTabActionArgs(title, { tabId }), + ) + }, + + renameTabById: async (tabId, title) => { + await runner.run(buildRenameTabActionArgs(title, { tabId })) + }, + + currentTabTitle: async () => { + const paneId = process.env.ZELLIJ_PANE_ID + if (!paneId) { + if (!process.env.ZELLIJ_SESSION_NAME?.trim()) + return undefined + + const result = await runner.run(zellijActionArgs('list-tabs', ['--json']), { timeoutMs: 5_000 }) + return parseActiveTabName(result.stdout) + } + + const tabId = await resolveCurrentPaneTabId() + if (tabId === undefined) + return undefined + + const result = await runner.run(zellijActionArgs('list-tabs', ['--json']), { timeoutMs: 5_000 }) + return parseTabName(result.stdout, tabId) + }, + } +} + +export const zellij: ZellijClient = createZellijClient() diff --git a/pkgs/opencode-zellij/src/lib/zellij/pane.ts b/pkgs/opencode-zellij/src/lib/zellij/pane.ts new file mode 100644 index 0000000..505336a --- /dev/null +++ b/pkgs/opencode-zellij/src/lib/zellij/pane.ts @@ -0,0 +1,281 @@ +import { execFile } from 'node:child_process' +import process from 'node:process' +import { promisify } from 'node:util' +import { zellij, zellijCommandArgs } from './cli.js' + +const execFileAsync = promisify(execFile) + +export interface ZellijPaneInfo { + id?: number | string | undefined + pane_id?: number | string | undefined + tab_id?: number | string | undefined + is_plugin?: boolean | undefined +} + +// Coerce a Zellij pane/tab id field (number or numeric string) into a +// plain integer. Returns undefined when the value is missing or not +// integer-shaped so callers can use `??` to fall back across the +// alternate field names (`id` / `pane_id`, `tab_id` / `tabId`). +export function coercePaneId(value: number | string | undefined): number | undefined { + if (value === undefined) + return undefined + const parsed = Number(value) + return Number.isInteger(parsed) ? parsed : undefined +} + +export interface PaneExistsWithRetryOptions { + targetPaneId: string + attempts?: number | undefined + intervalMs?: number | undefined +} + +export interface PaneExistsWithRetryResult { + alive: boolean + rawListPanes: string +} + +// Match a Zellij terminal pane id (`terminal_`) or a bare numeric id +// (``), anchored to a whole line. The `m` flag lets the id appear on +// any line of a multi-line payload so a future Zellij change that adds +// a leading debug prefix doesn't immediately break this. Crucially we +// no longer use `\b…\d+\b` with a global match: that would happily pick +// the FIRST number in stdout (e.g. a PID in a debug banner) and map the +// session to the wrong pane, which would then turn the next +// `close-pane` into a silent close-wrong-pane. +const paneIdPattern = /^(?:terminal_)?(\d+)\s*$/m + +export function normalizePaneId(rawPaneId: string): string { + const trimmed = rawPaneId.trim() + if (/^terminal_\d+$/.test(trimmed)) + return trimmed + if (/^\d+$/.test(trimmed)) + return `terminal_${trimmed}` + throw new Error(`Invalid Zellij terminal pane id: ${rawPaneId}`) +} + +export function parsePaneId(output: string): string { + const match = output.match(paneIdPattern) + if (!match?.[1]) { + throw new Error(`Unable to parse Zellij pane id from output: ${output.trim() || ''}`) + } + return normalizePaneId(match[1]) +} + +export async function runZellij(actionArgs: string[], timeoutMs = 5_000): Promise { + const result = await execFileAsync('zellij', zellijCommandArgs(actionArgs), { + encoding: 'utf8', + timeout: timeoutMs, + }) + return result.stdout ?? '' +} + +export async function listPanes(): Promise { + const output = await runZellij(['action', 'list-panes', '--json']) + try { + const parsed = JSON.parse(output) + return Array.isArray(parsed) ? parsed as ZellijPaneInfo[] : [] + } + catch { + return [] + } +} + +export async function dumpScreen(paneId: string): Promise { + return await runZellij(['action', 'dump-screen', '--pane-id', paneId, '--full'], 10_000) +} + +export async function currentPaneTabId(): Promise { + const paneId = process.env.ZELLIJ_PANE_ID + if (!paneId) + return undefined + + const parsedPaneId = Number(paneId) + if (!Number.isInteger(parsedPaneId)) + return undefined + + const panes = await listPanes() + const pane = panes.find(p => + !p.is_plugin + && (coercePaneId(p.id) === parsedPaneId || coercePaneId(p.pane_id) === parsedPaneId), + ) + return coercePaneId(pane?.tab_id) +} + +// Rename a specific tab by its numeric id. No-op when tabId is undefined so +// callers can pass through `await currentPaneTabId()` results without a +// guard. Throws inside an attached Zellij session whose current pane id +// can't be resolved, matching the underlying CLI's expectations. +export async function renameTabById(tabId: number | undefined, title: string): Promise { + if (tabId === undefined) { + if (process.env.ZELLIJ) + throw new Error(`Could not resolve Zellij tab id for pane ${process.env.ZELLIJ_PANE_ID ?? ''}`) + return + } + await zellij.renameTabById(tabId, title) +} + +// Probe `zellij action list-panes --json` up to `attempts` times before +// declaring the pane gone. Returns the raw stdout from the last attempt +// so the caller can include it in the failure dump and disambiguate +// "Zellij settled and the pane is really gone" from "RPC returned +// transient empty payload". +export async function paneExistsWithRetry({ + targetPaneId, + attempts = 3, + intervalMs = 200, +}: PaneExistsWithRetryOptions): Promise { + let lastOutput = '' + for (let i = 0; i < attempts; i++) { + let output = '' + try { + output = await runZellij(['action', 'list-panes', '--json']) + } + catch (error) { + lastOutput = `` + if (i < attempts - 1) + await new Promise(r => setTimeout(r, intervalMs)) + continue + } + lastOutput = output + + let panes: ZellijPaneInfo[] = [] + try { + const parsed = JSON.parse(output) + panes = Array.isArray(parsed) ? parsed : [] + } + catch { + panes = [] + } + + const alive = panes.some((p) => { + const id = normalizePaneIdOrUndefined(p.id) + const paneIdAlt = normalizePaneIdOrUndefined(p.pane_id) + return id === targetPaneId || paneIdAlt === targetPaneId + }) + if (alive) + return { alive: true, rawListPanes: output } + if (i < attempts - 1) + await new Promise(r => setTimeout(r, intervalMs)) + } + return { alive: false, rawListPanes: lastOutput } +} + +// --------------------------------------------------------------------------- +// Spawned pane identity verification +// --------------------------------------------------------------------------- + +export interface SpawnedTerminalPaneIdentity { + normalizedPaneId: string + numericPaneId: number +} + +export interface VerifySpawnedTerminalPaneIdentityOptions { + spawnOutput: string + currentPaneId?: string | undefined + paneIdsBeforeSpawn?: ReadonlySet | undefined +} + +export function verifySpawnedTerminalPaneIdentity({ + spawnOutput, + currentPaneId, + paneIdsBeforeSpawn, +}: VerifySpawnedTerminalPaneIdentityOptions): SpawnedTerminalPaneIdentity { + const trimmedCurrentPaneId = currentPaneId?.trim() || undefined + + let normalizedPaneId: string + try { + normalizedPaneId = parsePaneId(spawnOutput) + } + catch (error) { + throw createSpawnedPaneIdentityError({ + spawnOutput, + currentPaneId: trimmedCurrentPaneId, + reason: error instanceof Error + ? error.message + : 'Unable to parse Zellij pane id from new-pane output', + }) + } + + const numericPaneId = terminalPaneNumber(normalizedPaneId) + if (numericPaneId === undefined) { + throw createSpawnedPaneIdentityError({ + spawnOutput, + currentPaneId: trimmedCurrentPaneId, + parsedPaneId: normalizedPaneId, + reason: 'Parsed pane id was not a terminal pane id', + }) + } + + const currentPaneNumeric = terminalPaneNumber(trimmedCurrentPaneId) + if (currentPaneNumeric !== undefined && numericPaneId === currentPaneNumeric) { + throw createSpawnedPaneIdentityError({ + spawnOutput, + currentPaneId: trimmedCurrentPaneId, + parsedPaneId: normalizedPaneId, + reason: 'Parsed pane id matches the current/outer pane id', + }) + } + + if (paneIdsBeforeSpawn?.has(numericPaneId)) { + throw createSpawnedPaneIdentityError({ + spawnOutput, + currentPaneId: trimmedCurrentPaneId, + parsedPaneId: normalizedPaneId, + reason: 'Parsed pane id already existed before spawn, so the new terminal pane is not safely identifiable', + }) + } + + return { + normalizedPaneId, + numericPaneId, + } +} + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- + +function normalizePaneIdOrUndefined(value: number | string | undefined): string | undefined { + if (value === undefined) + return undefined + const normalized = String(value).trim() + if (!normalized) + return undefined + if (/^terminal_\d+$/.test(normalized)) + return normalized + if (/^\d+$/.test(normalized)) + return `terminal_${normalized}` + return normalized +} + +function terminalPaneNumber(rawPaneId: string | undefined): number | undefined { + if (!rawPaneId) + return undefined + + const trimmed = rawPaneId.trim() + if (/^terminal_\d+$/.test(trimmed)) + return Number(trimmed.slice('terminal_'.length)) + if (/^\d+$/.test(trimmed)) + return Number(trimmed) + return undefined +} + +function createSpawnedPaneIdentityError({ + spawnOutput, + currentPaneId, + parsedPaneId, + reason, +}: { + spawnOutput: string + currentPaneId?: string | undefined + parsedPaneId?: string | undefined + reason: string +}): Error { + return new Error( + 'Spawn output could not be safely identified as a new terminal pane.\n' + + ` reason: ${reason}\n` + + ` raw new-pane output: ${JSON.stringify(spawnOutput)}\n` + + ` current pane id: ${JSON.stringify(currentPaneId ?? '')}\n` + + ` parsed pane id: ${JSON.stringify(parsedPaneId ?? '')}`, + ) +} diff --git a/pkgs/opencode-zellij/src/lib/zellij/parse.ts b/pkgs/opencode-zellij/src/lib/zellij/parse.ts new file mode 100644 index 0000000..62a549f --- /dev/null +++ b/pkgs/opencode-zellij/src/lib/zellij/parse.ts @@ -0,0 +1,194 @@ +import { debug } from '../../utils/debug.js' +import { errorMessage } from '../../utils/errors.js' +import { normalizePaneId } from './pane.js' + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- + +function numericProperty(object: Record, keys: string[]): number | undefined { + for (const key of keys) { + const value = object[key] + if (typeof value === 'number' && Number.isFinite(value)) + return value + if (typeof value === 'string') { + const parsed = Number(value) + if (Number.isInteger(parsed)) + return parsed + } + } + return undefined +} + +function stringProperty(object: Record, keys: string[]): string | undefined { + for (const key of keys) { + const value = object[key] + if (typeof value === 'string') + return value + } + return undefined +} + +// --------------------------------------------------------------------------- +// Pane → tab resolution +// --------------------------------------------------------------------------- + +function paneMatches(object: Record, paneId: number): boolean { + const candidate = numericProperty(object, ['id', 'pane_id', 'paneId']) + return candidate === paneId && object.is_plugin !== true +} + +function findPaneRecord(value: unknown, paneId: number): Record | undefined { + if (Array.isArray(value)) { + for (const item of value) { + const found = findPaneRecord(item, paneId) + if (found !== undefined) + return found + } + return undefined + } + + if (typeof value !== 'object' || value === null) + return undefined + + const object = value as Record + if (paneMatches(object, paneId)) + return object + + for (const nested of Object.values(object)) { + const found = findPaneRecord(nested, paneId) + if (found !== undefined) + return found + } + return undefined +} + +export function parseCurrentPaneTabId(listPanesJson: string, paneId: string | undefined): number | undefined { + if (!paneId) + return undefined + const parsedPaneId = Number(paneId) + if (!Number.isInteger(parsedPaneId)) + return undefined + + try { + const pane = findPaneRecord(JSON.parse(listPanesJson), parsedPaneId) + return pane ? numericProperty(pane, ['tab_id', 'tabId']) : undefined + } + catch (error) { + debug('parseCurrentPaneTabId failed', errorMessage(error)) + return undefined + } +} + +export function parsePaneExists(listPanesJson: string, paneId: string | undefined): boolean | undefined { + if (!paneId) + return undefined + let parsedPaneId: number + try { + parsedPaneId = Number(normalizePaneId(paneId).slice('terminal_'.length)) + } + catch { + return undefined + } + + try { + return findPaneRecord(JSON.parse(listPanesJson), parsedPaneId) !== undefined + } + catch (error) { + debug('parsePaneExists failed', errorMessage(error)) + return undefined + } +} + +// --------------------------------------------------------------------------- +// Tab name resolution +// --------------------------------------------------------------------------- + +function tabNameProperty(object: Record, tabId: number | undefined): string | undefined { + if (tabId === undefined) + return undefined + const foundTabId = numericProperty(object, ['tab_id', 'tabId']) + if (foundTabId !== tabId) + return undefined + const name = stringProperty(object, ['name', 'title']) + return typeof name === 'string' ? name : undefined +} + +function findTabName(value: unknown, tabId: number | undefined): string | undefined { + if (Array.isArray(value)) { + for (const item of value) { + const found = findTabName(item, tabId) + if (found !== undefined) + return found + } + return undefined + } + + if (typeof value !== 'object' || value === null) + return undefined + + const object = value as Record + const name = tabNameProperty(object, tabId) + if (name !== undefined) + return name + + for (const nested of Object.values(object)) { + const found = findTabName(nested, tabId) + if (found !== undefined) + return found + } + return undefined +} + +function activeTabNameProperty(object: Record): string | undefined { + if (object.active !== true || object.is_plugin === true) + return undefined + const name = stringProperty(object, ['name', 'title']) + return typeof name === 'string' ? name : undefined +} + +function findActiveTabName(value: unknown): string | undefined { + if (Array.isArray(value)) { + for (const item of value) { + const found = findActiveTabName(item) + if (found !== undefined) + return found + } + return undefined + } + + if (typeof value !== 'object' || value === null) + return undefined + + const object = value as Record + const name = activeTabNameProperty(object) + if (name !== undefined) + return name + + for (const nested of Object.values(object)) { + const found = findActiveTabName(nested) + if (found !== undefined) + return found + } + return undefined +} + +export function parseTabName(listTabsJson: string, tabId: number | undefined): string | undefined { + try { + return findTabName(JSON.parse(listTabsJson), tabId) + } + catch (error) { + debug('parseTabName failed', errorMessage(error)) + return undefined + } +} + +export function parseActiveTabName(listTabsJson: string): string | undefined { + try { + return findActiveTabName(JSON.parse(listTabsJson)) + } + catch (error) { + debug('parseActiveTabName failed', errorMessage(error)) + return undefined + } +} diff --git a/pkgs/opencode-zellij/src/plugin.ts b/pkgs/opencode-zellij/src/plugin.ts new file mode 100644 index 0000000..4268672 --- /dev/null +++ b/pkgs/opencode-zellij/src/plugin.ts @@ -0,0 +1,149 @@ +import type { Context, Plugin as PluginDefinition } from '@opencode/plugin/promise/plugin' +import type { CompletionNotificationContext, CompletionNotificationManager } from './zellij/completion-notifications.js' +import type { OpenCodeEventLike } from './zellij/tab-title-events.js' +import { define } from '@opencode/plugin/promise/plugin' +import { loadConfig } from './config.js' +import { sessionManager } from './pty/manager.js' +import { buildToolTable } from './tools/registry.js' +import { debug } from './utils/debug.js' +import { errorMessage } from './utils/errors.js' +import { SessionCompletionNotificationManager } from './zellij/completion-notifications.js' +import { cleanupStaleWatchdogRegistries, unregisterPaneFromWatchdog } from './zellij/pane-watchdog.js' +import { registerShutdownCleanup } from './zellij/shutdown-cleanup.js' +import { subscriberManager } from './zellij/subscribe.js' +import { normalizeTabTitleEvent } from './zellij/tab-title-events.js' +import { TabTitleActivityModel, TabTitleActor, TabTitleManager } from './zellij/tab-title.js' + +const PLUGIN_ID = 'opencode-zellij' + +async function cleanupStep(stepName: string, sessionId: string, step: () => void | Promise): Promise { + try { + await step() + } + catch (error) { + debug(`session.deleted cleanup failed: ${stepName} for ${sessionId}`, errorMessage(error)) + } +} + +async function cleanupDeletedSession(sessionId: string): Promise { + await cleanupStep('close pane', sessionId, () => subscriberManager.closeSessionPane(sessionId)) + await cleanupStep('forget subscriber', sessionId, () => subscriberManager.forget(sessionId)) + await cleanupStep('unregister watchdog', sessionId, () => unregisterPaneFromWatchdog(sessionId)) + await cleanupStep('remove session', sessionId, () => sessionManager.remove(sessionId)) +} + +export interface ZellijPtyPluginDependencies { + /** Test seam: build the completion notification manager. */ + createCompletionNotifications?: ((context: CompletionNotificationContext) => CompletionNotificationManager | undefined) | undefined +} + +/** + * Builds the V2 plugin definition. Exported as a factory so tests can supply + * dependencies; the module default is the production instance. + */ +export function createZellijPtyPlugin(dependencies: ZellijPtyPluginDependencies = {}): PluginDefinition { + return define({ + id: PLUGIN_ID, + async setup(ctx: Context) { + const directory = ctx.location.directory + const worktree = ctx.location.project.canonical + + const { config, warnings } = await loadConfig({ directory, worktree }) + for (const warning of warnings) + debug(warning) + + cleanupStaleWatchdogRegistries() + registerShutdownCleanup() + + const activityModel = config.tabTitle.enabled + ? new TabTitleActivityModel({ worktreeDirectory: worktree }) + : undefined + const actor = activityModel + ? new TabTitleActor({ activity: activityModel }) + : undefined + const tabTitleManager = config.tabTitle.enabled && actor + ? new TabTitleManager({ + actor, + debounceMs: config.tabTitle.debounceMs, + emojis: { + idle: config.tabTitle.emojiIdle, + running: config.tabTitle.emojiRunning, + needsInput: config.tabTitle.emojiNeedsInput, + }, + }) + : undefined + + const notificationContext: CompletionNotificationContext = { + prompt: (sessionID, text) => ctx.session.prompt({ sessionID, text }), + } + const completionNotifications = dependencies.createCompletionNotifications?.(notificationContext) + ?? new SessionCompletionNotificationManager(notificationContext) + subscriberManager.setLifecycleHooks({ + onSessionTerminal: event => void completionNotifications.handleSessionTerminal(event) + .catch(error => debug('completion notification lifecycle hook failed', errorMessage(error))), + }) + + // Best-effort initial render; no-op when not inside a real Zellij pane. + tabTitleManager?.renderImmediate() + .catch(error => debug('initial tab title render failed', errorMessage(error))) + + const tools = buildToolTable({ + directory, + enabled: config.pty.enabled, + cleanupExitedPaneOnRead: config.pty.cleanupExitedPaneOnRead, + sudoPane: config.pty.sudoPane, + sudoPaneMode: config.pty.sudoPaneMode, + sudoPaneFloatingSize: config.pty.sudoPaneFloatingSize, + }) + await ctx.tool.transform((editor) => { + for (const tool of tools) + editor.add(tool) + }) + + const controller = new AbortController() + void (async () => { + for await (const event of ctx.event.subscribe({ signal: controller.signal })) { + await handleEvent(event as OpenCodeEventLike) + } + })().catch((error) => { + if (!controller.signal.aborted) + debug('event subscription failed', errorMessage(error)) + }) + + async function handleEvent(event: OpenCodeEventLike): Promise { + const normalized = normalizeTabTitleEvent(event) + + if (actor && tabTitleManager) { + for (const tabEvent of normalized) + actor.handleEvent(tabEvent) + tabTitleManager.scheduleUpdate() + } + + if (event.type === 'location.shutdown') + dispose() + + for (const tabEvent of normalized) { + if (tabEvent.type !== 'session-deleted') + continue + // The event carries the OpenCode session id; panes are keyed by + // their own plugin session id, so map through the OpenCode id. + const panes = sessionManager.listByOpenCodeSession(tabEvent.sessionID) + await Promise.all(panes.map(session => cleanupDeletedSession(session.id))) + } + } + + function dispose(): void { + completionNotifications.dispose() + subscriberManager.setLifecycleHooks(undefined) + } + + return () => { + controller.abort() + dispose() + void tabTitleManager?.destroy() + } + }, + }) +} + +export default createZellijPtyPlugin() diff --git a/pkgs/opencode-zellij/src/pty/manager.ts b/pkgs/opencode-zellij/src/pty/manager.ts new file mode 100644 index 0000000..d3212c3 --- /dev/null +++ b/pkgs/opencode-zellij/src/pty/manager.ts @@ -0,0 +1,136 @@ +import type { CreateSessionInput, PtySession, SessionStatus, SessionTerminalReason, SessionTombstone } from './session.js' +import { createSessionId } from '../utils/ids.js' + +const tombstoneTailLimit = 200 + +export interface MarkTerminalInput { + reason: SessionTerminalReason + tail?: string[] | undefined + exitCode?: number | undefined +} + +export interface MarkTerminalResult { + session: PtySession + created: boolean +} + +export class SessionManager { + private readonly sessions = new Map() + + create(input: CreateSessionInput): PtySession { + const now = new Date().toISOString() + const session: PtySession = { + id: createSessionId(), + openCodeSessionId: input.openCodeSessionId ?? null, + paneId: input.paneId, + title: input.title, + command: input.command, + args: input.args ?? [], + cwd: input.cwd, + status: 'running', + lineCount: 0, + createdAt: now, + updatedAt: now, + allowAgentInput: input.allowAgentInput, + humanInputOnly: input.humanInputOnly, + exitCode: null, + exitedAt: null, + exitCodeToken: input.exitCodeToken ?? null, + tombstone: null, + } + this.sessions.set(session.id, session) + return session + } + + get(id: string): PtySession { + const session = this.sessions.get(id) + if (!session) + throw new Error(`Unknown zellij PTY session: ${id}`) + return session + } + + find(id: string): PtySession | undefined { + return this.sessions.get(id) + } + + list(): PtySession[] { + return Array.from(this.sessions.values()).sort((a, b) => a.createdAt.localeCompare(b.createdAt)) + } + + updateLineCount(id: string, lineCount: number): PtySession { + const session = this.get(id) + session.lineCount = lineCount + session.updatedAt = new Date().toISOString() + return session + } + + updateStatus(id: string, status: SessionStatus): PtySession { + const session = this.get(id) + if (session.status === 'terminal' && status !== 'terminal') + return session + session.status = status + session.updatedAt = new Date().toISOString() + return session + } + + markExited(id: string, exitCode: number): PtySession { + return this.markTerminal(id, { reason: 'exit_marker', exitCode }).session + } + + markTerminal(id: string, input: MarkTerminalInput): MarkTerminalResult { + const session = this.get(id) + const now = new Date().toISOString() + const created = session.status !== 'terminal' || !session.tombstone + + if (created) { + const tombstone: SessionTombstone = { + reason: input.reason, + terminalAt: now, + tail: (input.tail ?? []).slice(-tombstoneTailLimit), + paneClosedAt: null, + } + + session.status = 'terminal' + session.tombstone = tombstone + session.updatedAt = now + } + + if (input.exitCode !== undefined && session.exitCode === null) { + session.exitCode = input.exitCode + session.exitedAt = now + session.updatedAt = now + } + + if (session.tombstone) { + if (input.tail?.length && session.tombstone.tail.length === 0) + session.tombstone.tail = input.tail.slice(-tombstoneTailLimit) + if (!session.tombstone.reason) + session.tombstone.reason = input.reason + } + + return { session, created } + } + + markTerminalPaneClosed(id: string): PtySession { + const session = this.get(id) + const now = new Date().toISOString() + if (!session.tombstone) + return session + if (!session.tombstone.paneClosedAt) { + session.tombstone.paneClosedAt = now + session.updatedAt = now + } + return session + } + + listByOpenCodeSession(openCodeSessionId: string): PtySession[] { + return this.list().filter(session => session.openCodeSessionId === openCodeSessionId) + } + + remove(id: string): void { + if (!this.sessions.delete(id)) + throw new Error(`Unknown zellij PTY session: ${id}`) + } +} + +export const sessionManager = new SessionManager() diff --git a/pkgs/opencode-zellij/src/pty/probe.ts b/pkgs/opencode-zellij/src/pty/probe.ts new file mode 100644 index 0000000..b4c631b --- /dev/null +++ b/pkgs/opencode-zellij/src/pty/probe.ts @@ -0,0 +1,68 @@ +import { setTimeout as delay } from 'node:timers/promises' + +export type Probe + = | { type: 'sleep', seconds?: number | undefined } + | { type: 'http', url: string, expectStatus?: number | undefined, timeoutSeconds?: number | undefined } + | { type: 'output', grep: string, ignoreCase?: boolean | undefined, timeoutSeconds?: number | undefined } + +export interface ProbeResult { + type: Probe['type'] + ok: boolean + message: string +} + +export type OutputProbeReader = (grep: string, ignoreCase: boolean | undefined) => boolean + +const defaultSleepSeconds = 1 +const defaultProbeTimeoutSeconds = 20 +const pollIntervalMs = 250 + +export async function runProbe(probe: Probe | undefined, outputReader: OutputProbeReader): Promise { + const effectiveProbe = probe ?? { type: 'sleep', seconds: defaultSleepSeconds } + + if (effectiveProbe.type === 'sleep') { + const seconds = effectiveProbe.seconds ?? defaultSleepSeconds + await delay(seconds * 1_000) + return result(effectiveProbe.type, true, `Slept for ${seconds}s.`) + } + + if (effectiveProbe.type === 'output') { + const timeoutSeconds = effectiveProbe.timeoutSeconds ?? defaultProbeTimeoutSeconds + const deadline = Date.now() + timeoutSeconds * 1_000 + while (Date.now() <= deadline) { + if (outputReader(effectiveProbe.grep, effectiveProbe.ignoreCase)) { + return result(effectiveProbe.type, true, `Observed output matching /${effectiveProbe.grep}/.`) + } + await delay(pollIntervalMs) + } + return result(effectiveProbe.type, false, `Timed out after ${timeoutSeconds}s waiting for output matching /${effectiveProbe.grep}/.`) + } + + const timeoutSeconds = effectiveProbe.timeoutSeconds ?? defaultProbeTimeoutSeconds + const deadline = Date.now() + timeoutSeconds * 1_000 + const expectStatus = effectiveProbe.expectStatus + let lastError = 'no response' + + while (Date.now() <= deadline) { + try { + const remainingMs = Math.max(1, deadline - Date.now()) + const response = await fetch(effectiveProbe.url, { signal: AbortSignal.timeout(Math.min(remainingMs, 3_000)) }) + const ok = expectStatus === undefined ? response.status >= 200 && response.status < 400 : response.status === expectStatus + if (ok) { + const expected = expectStatus === undefined ? '2xx/3xx' : String(expectStatus) + return result(effectiveProbe.type, true, `HTTP probe ${effectiveProbe.url} returned expected status ${expected}.`) + } + lastError = `HTTP ${response.status}` + } + catch (error) { + lastError = error instanceof Error ? error.message : String(error) + } + await delay(pollIntervalMs) + } + + return result(effectiveProbe.type, false, `Timed out after ${timeoutSeconds}s probing ${effectiveProbe.url}: ${lastError}.`) +} + +function result(type: Probe['type'], ok: boolean, message: string): ProbeResult { + return { type, ok, message } +} diff --git a/pkgs/opencode-zellij/src/pty/ring-buffer.ts b/pkgs/opencode-zellij/src/pty/ring-buffer.ts new file mode 100644 index 0000000..891aefb --- /dev/null +++ b/pkgs/opencode-zellij/src/pty/ring-buffer.ts @@ -0,0 +1,112 @@ +export interface ReadLinesInput { + offset?: number | undefined + limit?: number | undefined + grep?: string | undefined + ignoreCase?: boolean | undefined +} + +export interface ReadLinesResult { + offset: number + returned: number + lineCount: number + lines: string[] +} + +const escapeCharacter = String.fromCharCode(27) +const ansiPattern = new RegExp(`${escapeCharacter}\\[[0-9;?]*[a-z]`, 'gi') + +function normalizeLines(input: string | string[]): string[] { + const lines = Array.isArray(input) ? input : input.replace(/\r\n/g, '\n').split('\n') + if (lines.at(-1) === '') + return lines.slice(0, -1) + return lines +} + +function stripAnsi(line: string): string { + return line.replace(ansiPattern, '') +} + +function overlapSize(existing: string[], incoming: string[]): number { + const max = Math.min(existing.length, incoming.length) + for (let size = max; size > 0; size -= 1) { + const existingStart = existing.length - size + let matches = true + for (let index = 0; index < size; index += 1) { + if (existing[existingStart + index] !== incoming[index]) { + matches = false + break + } + } + if (matches) + return size + } + return 0 +} + +export class RingBuffer { + private readonly maxLines: number + private lines: string[] = [] + private totalAppended = 0 + + constructor(maxLines = 50_000) { + this.maxLines = Math.max(1, maxLines) + } + + get lineCount(): number { + return this.totalAppended + } + + get startOffset(): number { + return Math.max(0, this.totalAppended - this.lines.length) + } + + append(input: string | string[]): number { + const incoming = normalizeLines(input) + if (incoming.length === 0) + return 0 + this.lines.push(...incoming) + this.totalAppended += incoming.length + this.trim() + return incoming.length + } + + appendSnapshot(input: string | string[]): number { + const incoming = normalizeLines(input) + if (incoming.length === 0) + return 0 + const overlap = overlapSize(this.lines, incoming) + return this.append(incoming.slice(overlap)) + } + + read(input: ReadLinesInput = {}): ReadLinesResult { + const limit = Math.max(1, Math.min(input.limit ?? 200, 5_000)) + const firstReadableOffset = this.startOffset + const defaultOffset = Math.max(firstReadableOffset, this.lineCount - limit) + const requestedOffset = input.offset ?? defaultOffset + const offset = Math.max(firstReadableOffset, Math.min(requestedOffset, this.lineCount)) + const relativeOffset = offset - firstReadableOffset + const unfiltered = this.lines.slice(relativeOffset, relativeOffset + limit) + const pattern = input.grep ? new RegExp(input.grep, input.ignoreCase ? 'i' : '') : undefined + const lines = unfiltered + .map(stripAnsi) + .filter(line => (pattern ? pattern.test(line) : true)) + + return { + offset, + returned: lines.length, + lineCount: this.lineCount, + lines, + } + } + + clear(): void { + this.lines = [] + this.totalAppended = 0 + } + + private trim(): void { + if (this.lines.length <= this.maxLines) + return + this.lines = this.lines.slice(this.lines.length - this.maxLines) + } +} diff --git a/pkgs/opencode-zellij/src/pty/session.ts b/pkgs/opencode-zellij/src/pty/session.ts new file mode 100644 index 0000000..974fffa --- /dev/null +++ b/pkgs/opencode-zellij/src/pty/session.ts @@ -0,0 +1,42 @@ +export type SessionStatus = 'running' | 'exited' | 'killed' | 'unknown' | 'terminal' + +export type SessionTerminalReason = 'pane_closed' | 'exit_marker' | 'read_cleanup' | 'subscriber_exit' | 'subscriber_error' | 'session_deleted' + +export interface SessionTombstone { + reason: SessionTerminalReason + terminalAt: string + tail: string[] + paneClosedAt: string | null +} + +export interface PtySession { + id: string + openCodeSessionId: string | null + paneId: string + title: string + command: string + args: string[] + cwd: string + status: SessionStatus + lineCount: number + createdAt: string + updatedAt: string + allowAgentInput: boolean + humanInputOnly: boolean + exitCode: number | null + exitedAt: string | null + exitCodeToken: string | null + tombstone?: SessionTombstone | null +} + +export interface CreateSessionInput { + openCodeSessionId?: string | undefined + paneId: string + title: string + command: string + args?: string[] | undefined + cwd: string + allowAgentInput: boolean + humanInputOnly: boolean + exitCodeToken?: string | undefined +} diff --git a/pkgs/opencode-zellij/src/pty/write-data.ts b/pkgs/opencode-zellij/src/pty/write-data.ts new file mode 100644 index 0000000..613487c --- /dev/null +++ b/pkgs/opencode-zellij/src/pty/write-data.ts @@ -0,0 +1,39 @@ +import { Buffer } from 'node:buffer' +import process from 'node:process' + +const defaultMaxWriteBytes = 64 * 1024 +const defaultChunkBytes = 8 * 1024 + +export function maxWriteBytes(): number { + const configured = Number(process.env.ZELLIJ_PTY_MAX_WRITE_BYTES ?? defaultMaxWriteBytes) + return Number.isFinite(configured) && configured > 0 ? configured : defaultMaxWriteBytes +} + +export function assertWriteSizeAllowed(data: string): void { + const bytes = Buffer.byteLength(data, 'utf8') + const maxBytes = maxWriteBytes() + if (bytes > maxBytes) { + throw new Error(`Write payload is too large: ${bytes} bytes exceeds ${maxBytes} bytes. Split the input into smaller writes.`) + } +} + +export function chunkWriteData(data: string, maxChunkBytes = defaultChunkBytes): string[] { + const chunks: string[] = [] + let current = '' + let currentBytes = 0 + + for (const character of data) { + const characterBytes = Buffer.byteLength(character, 'utf8') + if (current && currentBytes + characterBytes > maxChunkBytes) { + chunks.push(current) + current = '' + currentBytes = 0 + } + current += character + currentBytes += characterBytes + } + + if (current) + chunks.push(current) + return chunks +} diff --git a/pkgs/opencode-zellij/src/tools/format.ts b/pkgs/opencode-zellij/src/tools/format.ts new file mode 100644 index 0000000..10650f9 --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/format.ts @@ -0,0 +1,97 @@ +import type { PtySession, SessionTombstone } from '../pty/session.js' + +export interface PublicSessionOptions { + /** + * Include `tombstone` in the response. Only `read` and `list` need it, because agents + * observe completed/cleaned-up sessions through those tools. `spawn` / `write` / + * `request_sudo` should omit it. + */ + includeTombstone?: boolean | undefined + /** + * `true` for agent-writable panes (the default; matches `zellij_pty_spawn`). + * `false` for human-input-only sudo panes (`zellij_pty_request_sudo`). The + * non-default value is surfaced to the agent via `humanInputOnly` so it can + * tell apart "I cannot write here" sessions. + */ + agentWritable?: boolean | undefined +} + +export interface PublicTombstone { + reason: SessionTombstone['reason'] + terminalAt: string + tailLines: number + paneClosedAt: string | null +} + +export function publicSession(session: PtySession, options: PublicSessionOptions = {}): Record { + const status = session.status === 'terminal' ? 'exited' : session.status + const summary: Record = { + id: session.id, + paneId: session.paneId, + title: session.title, + command: session.command, + status, + } + + if (options.agentWritable === false) + summary.humanInputOnly = true + + if (options.includeTombstone) { + summary.tombstone = session.tombstone + ? { + reason: session.tombstone.reason, + terminalAt: session.tombstone.terminalAt, + tailLines: session.tombstone.tail.length, + paneClosedAt: session.tombstone.paneClosedAt, + } + : null + } + + return summary +} + +export function jsonResponse(value: unknown): string { + return JSON.stringify(value, null, 2) +} + +/** + * Lean summary of a session that already reached its terminal state. Returned + * by `spawn` and `list` so agents can spot dead panes that still need to be + * closed via `zellij_pty_read` (which triggers cleanup) or `zellij_pty_kill`. + */ +export interface PublicCompletedPane { + id: string + paneId: string + status: 'exited' | 'killed' | 'unknown' + exitCode: number | null + reason: SessionTombstone['reason'] | null +} + +export interface CompletedPanesSummary { + completedPaneIds: string[] + completedPanes: PublicCompletedPane[] +} + +export function publicCompletedPane(session: PtySession): PublicCompletedPane { + const status = session.status === 'terminal' ? 'exited' : session.status + const safeStatus: PublicCompletedPane['status'] = status === 'exited' || status === 'killed' || status === 'unknown' + ? status + : 'unknown' + return { + id: session.id, + paneId: session.paneId, + status: safeStatus, + exitCode: session.exitCode, + reason: session.tombstone?.reason ?? null, + } +} + +export function completedPanesFromSessions(sessions: PtySession[]): CompletedPanesSummary { + const completedPanes = sessions + .filter(session => session.status === 'terminal') + .map(publicCompletedPane) + return { + completedPaneIds: completedPanes.map(pane => pane.id), + completedPanes, + } +} diff --git a/pkgs/opencode-zellij/src/tools/kill.ts b/pkgs/opencode-zellij/src/tools/kill.ts new file mode 100644 index 0000000..e8b2007 --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/kill.ts @@ -0,0 +1,92 @@ +import type { ZellijToolInfo } from './tool.js' +import { setTimeout as delay } from 'node:timers/promises' +import { z } from 'zod' +import { zellij } from '../lib/zellij/cli.js' +import { sessionManager } from '../pty/manager.js' +import { unregisterPaneFromWatchdog } from '../zellij/pane-watchdog.js' +import { subscriberManager } from '../zellij/subscribe.js' +import { jsonResponse, publicSession } from './format.js' +import { readOutputSnapshot } from './output.js' +import { closePaneOrVerifyGone } from './pane-cleanup.js' + +export interface KillToolDependencies { + zellij?: Pick | undefined +} + +export interface KillToolResult { + killed: boolean + cleanedUp: boolean + id?: string | undefined + paneId?: string | undefined + session?: ReturnType | undefined + output?: ReturnType | undefined + warnings: string[] +} + +export async function executeZellijPtyKill(args: { id: string }, dependencies: KillToolDependencies = {}): Promise { + const zellijApi = dependencies.zellij ?? zellij + const session = sessionManager.get(args.id) + const warnings: string[] = [] + const output = subscriberManager.has(session.id) ? readOutputSnapshot(session.id) : undefined + + try { + await zellijApi.sendCtrlC(session.paneId) + await delay(500) + } + catch (error) { + warnings.push(`Ctrl-C failed or pane was already gone: ${error instanceof Error ? error.message : String(error)}`) + } + + const closeResult = await closePaneOrVerifyGone({ + paneId: session.paneId, + closePane: () => zellijApi.closePane(session.paneId), + paneExists: zellijApi.paneExists, + }) + + if (!closeResult.cleanupReady) { + warnings.push(`close-pane failed: ${closeResult.closeErrorMessage ?? 'unknown error'}`) + const updated = sessionManager.updateStatus(session.id, 'unknown') + return { + killed: false, + cleanedUp: false, + session: publicSession(updated), + output, + warnings, + } + } + + return finalizeKilledSession(session.id, session.paneId, output, warnings) +} + +export const zellijPtyKillInput = z.object({ + id: z.string().describe('zellij-pty session id.'), +}) + +export const zellijPtyKillTool: ZellijToolInfo = { + name: 'zellij_pty_kill', + description: 'Terminate a known Zellij PTY session by sending Ctrl-C, then closing its pane.', + input: zellijPtyKillInput, + async execute(args) { + return { content: jsonResponse(await executeZellijPtyKill(args)) } + }, +} + +async function finalizeKilledSession( + sessionId: string, + paneId: string, + output: ReturnType | undefined, + warnings: string[], +): Promise { + subscriberManager.stop(sessionId) + subscriberManager.forget(sessionId) + unregisterPaneFromWatchdog(sessionId) + sessionManager.remove(sessionId) + return { + killed: true, + cleanedUp: true, + id: sessionId, + paneId, + output, + warnings, + } +} diff --git a/pkgs/opencode-zellij/src/tools/list.ts b/pkgs/opencode-zellij/src/tools/list.ts new file mode 100644 index 0000000..beecff4 --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/list.ts @@ -0,0 +1,26 @@ +import type { ZellijToolInfo } from './tool.js' +import { z } from 'zod' +import { sessionManager } from '../pty/manager.js' +import { subscriberManager } from '../zellij/subscribe.js' +import { completedPanesFromSessions, jsonResponse, publicSession } from './format.js' + +export const zellijPtyListInput = z.object({}) + +export const zellijPtyListTool: ZellijToolInfo = { + name: 'zellij_pty_list', + description: 'List known Zellij pane-backed PTY sessions created by this plugin process for the current OpenCode session.', + input: zellijPtyListInput, + async execute(_args, context) { + const sessionList = sessionManager.listByOpenCodeSession(context.sessionID) + const sessions = sessionList.map(session => ({ + ...publicSession(session, { includeTombstone: true }), + subscriber: subscriberManager.status(session.id), + })) + return { + content: jsonResponse({ + sessions, + ...completedPanesFromSessions(sessionList), + }), + } + }, +} diff --git a/pkgs/opencode-zellij/src/tools/output.ts b/pkgs/opencode-zellij/src/tools/output.ts new file mode 100644 index 0000000..3dc2cd5 --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/output.ts @@ -0,0 +1,62 @@ +import { sessionManager } from '../pty/manager.js' +import { errorMessage } from '../utils/errors.js' +import { subscriberManager } from '../zellij/subscribe.js' + +export interface OutputSnapshot { + text: string + lineCount: number + truncated: boolean + /** + * Number of matched lines included in `text`. Only present when the snapshot + * was produced with a `grep` filter. + */ + matched?: number +} + +export function emptyOutputSnapshot(lineCount = 0): OutputSnapshot { + return { text: '', lineCount, truncated: false } +} + +export interface OutputOptions { + maxLines?: number | undefined + grep?: string | undefined + ignoreCase?: boolean | undefined +} + +export function validateGrep(grep: string | undefined): string | null { + if (!grep) + return null + try { + new RegExp(grep).test('') + return null + } + catch (error) { + return errorMessage(error) + } +} + +export function readOutputSnapshot(sessionId: string, options: OutputOptions = {}): OutputSnapshot { + const grepError = validateGrep(options.grep) + if (grepError) + throw new Error(`Invalid grep regex: ${grepError}`) + + const buffered = subscriberManager.read(sessionId, { + limit: options.maxLines, + grep: options.grep, + ignoreCase: options.ignoreCase, + }) + sessionManager.updateLineCount(sessionId, buffered.lineCount) + + const snapshot: OutputSnapshot = { + text: buffered.lines.join('\n'), + lineCount: buffered.lineCount, + truncated: buffered.offset > 0, + } + if (options.grep !== undefined) + snapshot.matched = buffered.returned + return snapshot +} + +export function outputMatches(sessionId: string, grep: string, ignoreCase?: boolean | undefined): boolean { + return (readOutputSnapshot(sessionId, { maxLines: 5_000, grep, ignoreCase }).matched ?? 0) > 0 +} diff --git a/pkgs/opencode-zellij/src/tools/pane-cleanup.ts b/pkgs/opencode-zellij/src/tools/pane-cleanup.ts new file mode 100644 index 0000000..fcec5d4 --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/pane-cleanup.ts @@ -0,0 +1,39 @@ +export type PaneExistsFn = (paneId: string) => Promise | boolean | undefined + +export interface ClosePaneOrVerifyGoneInput { + paneId: string + closePane: () => Promise + paneExists?: PaneExistsFn | undefined +} + +export interface ClosePaneOrVerifyGoneResult { + cleanupReady: boolean + alreadyGone: boolean + closeErrorMessage?: string | undefined +} + +export async function closePaneOrVerifyGone(input: ClosePaneOrVerifyGoneInput): Promise { + try { + await input.closePane() + return { cleanupReady: true, alreadyGone: false } + } + catch (error) { + const closeErrorMessage = error instanceof Error ? error.message : String(error) + const paneGone = await isPaneGone(input.paneId, input.paneExists) + if (paneGone) + return { cleanupReady: true, alreadyGone: true, closeErrorMessage } + return { cleanupReady: false, alreadyGone: false, closeErrorMessage } + } +} + +async function isPaneGone(paneId: string, paneExists?: PaneExistsFn | undefined): Promise { + if (!paneExists) + return false + + try { + return (await paneExists(paneId)) === false + } + catch { + return false + } +} diff --git a/pkgs/opencode-zellij/src/tools/read.ts b/pkgs/opencode-zellij/src/tools/read.ts new file mode 100644 index 0000000..ab50ff7 --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/read.ts @@ -0,0 +1,165 @@ +import type { PaneExistsFn } from './pane-cleanup.js' +import type { ZellijToolInfo } from './tool.js' +import { z } from 'zod' +import { zellij } from '../lib/zellij/cli.js' +import { sessionManager } from '../pty/manager.js' +import { subscriberManager } from '../zellij/subscribe.js' +import { jsonResponse, publicSession } from './format.js' +import { readOutputSnapshot, validateGrep } from './output.js' +import { closePaneOrVerifyGone } from './pane-cleanup.js' + +export interface ReadToolArgs { + id: string + maxLines?: number | undefined + grep?: string | undefined + ignoreCase?: boolean | undefined + cleanupExitedPaneOnRead?: boolean | undefined +} + +export interface ReadCleanupResult { + requested: boolean + performed: boolean + alreadyClosed: boolean + warning?: string | undefined +} + +export interface ReadToolResult { + session: ReturnType + output: ReturnType + subscriberActive: boolean + subscriberLastExitedAt: string | null + subscriberErrors: string[] + warnings: string[] + cleanup: ReadCleanupResult +} + +export interface ReadToolDependencies { + sessionManager?: Pick | undefined + subscriberManager?: Pick | undefined + publicSession?: typeof publicSession | undefined + readOutputSnapshot?: typeof readOutputSnapshot | undefined + validateGrep?: typeof validateGrep | undefined + paneExists?: PaneExistsFn | undefined + defaultCleanupExitedPaneOnRead?: boolean | undefined +} + +export async function executeZellijPtyRead(args: ReadToolArgs, dependencies: ReadToolDependencies = {}): Promise { + const sessionManagerApi = dependencies.sessionManager ?? sessionManager + const subscriberManagerApi = dependencies.subscriberManager ?? subscriberManager + const publicSessionApi = dependencies.publicSession ?? publicSession + const readOutputSnapshotApi = dependencies.readOutputSnapshot ?? readOutputSnapshot + const validateGrepApi = dependencies.validateGrep ?? validateGrep + const paneExistsApi = dependencies.paneExists ?? (paneId => zellij.paneExists(paneId)) + + const session = sessionManagerApi.get(args.id) + const grepError = validateGrepApi(args.grep) + if (grepError) { + return { + session: publicSessionApi(session, { includeTombstone: true }), + output: { text: '', lineCount: session.lineCount, truncated: false }, + subscriberActive: false, + subscriberLastExitedAt: null, + subscriberErrors: [], + warnings: [`Invalid grep regex: ${grepError}`], + cleanup: { requested: false, performed: false, alreadyClosed: false }, + } + } + + const subscriberStatus = subscriberManagerApi.status(session.id) + if (!subscriberStatus.hasBuffer || (!subscriberStatus.active && (session.status === 'running' || session.status === 'unknown'))) + await subscriberManagerApi.start(session) + + const statusAfterStart = subscriberManagerApi.status(session.id) + const warnings: string[] = [] + if (session.humanInputOnly) + warnings.push('This pane is human-input-only: agent writes are forbidden, but rendered output is visible to the agent.') + + if (!statusAfterStart.active && session.status === 'running') { + warnings.push('Subscriber is inactive; returned output may be stale.') + sessionManagerApi.updateStatus(session.id, 'unknown') + } + + const output = readOutputSnapshotApi(session.id, { maxLines: args.maxLines, grep: args.grep, ignoreCase: args.ignoreCase }) + const cleanup = await cleanupExitedPaneOnRead(session.id, session.status, args.cleanupExitedPaneOnRead ?? dependencies.defaultCleanupExitedPaneOnRead ?? true, { + sessionManager: sessionManagerApi, + subscriberManager: subscriberManagerApi, + paneExists: paneExistsApi, + }) + if (cleanup.warning) + warnings.push(cleanup.warning) + + return { + session: publicSessionApi(session, { includeTombstone: true }), + output, + subscriberActive: statusAfterStart.active, + subscriberLastExitedAt: statusAfterStart.lastExitedAt, + subscriberErrors: subscriberManagerApi.stderr(session.id), + warnings, + cleanup, + } +} + +async function cleanupExitedPaneOnRead( + sessionId: string, + status: string, + enabled: boolean, + dependencies: { + sessionManager: Pick + subscriberManager: Pick + paneExists: PaneExistsFn + }, +): Promise { + const requested = enabled && isCompletedSession(status) + if (!requested) + return { requested: false, performed: false, alreadyClosed: false } + + const session = dependencies.sessionManager.get(sessionId) + if (session.tombstone?.paneClosedAt) + return { requested: true, performed: false, alreadyClosed: true } + + const closeResult = await closePaneOrVerifyGone({ + paneId: session.paneId, + closePane: () => dependencies.subscriberManager.closeSessionPane(sessionId, { throwOnFailure: true }), + paneExists: dependencies.paneExists, + }) + + if (closeResult.cleanupReady) { + dependencies.sessionManager.markTerminalPaneClosed(sessionId) + return { requested: true, performed: true, alreadyClosed: closeResult.alreadyGone } + } + return { + requested: true, + performed: false, + alreadyClosed: false, + warning: `Completed pane cleanup failed: ${closeResult.closeErrorMessage ?? 'unknown error'}`, + } +} + +export const zellijPtyReadInput = z.object({ + id: z.string().describe('zellij-pty session id.'), + maxLines: z.number().int().positive().max(5_000).optional().describe('Maximum recent output lines to return. Defaults to 200.'), + grep: z.string().optional().describe('Regex used to filter returned lines.'), + ignoreCase: z.boolean().optional().describe('Use case-insensitive regex matching.'), + cleanupExitedPaneOnRead: z.boolean().optional().describe('Close completed panes after returning the final output. Defaults to true.'), +}) + +export function createZellijPtyReadTool(options: { defaultCleanupExitedPaneOnRead?: boolean | undefined } = {}): ZellijToolInfo { + return { + name: 'zellij_pty_read', + description: 'Read recent rendered output from a Zellij PTY session. Supports regex grep filtering.', + input: zellijPtyReadInput, + async execute(args) { + return { + content: jsonResponse(await executeZellijPtyRead(args, { + defaultCleanupExitedPaneOnRead: options.defaultCleanupExitedPaneOnRead, + })), + } + }, + } +} + +export const zellijPtyReadTool = createZellijPtyReadTool() + +function isCompletedSession(status: string): boolean { + return status === 'terminal' || status === 'exited' || status === 'killed' +} diff --git a/pkgs/opencode-zellij/src/tools/registry.ts b/pkgs/opencode-zellij/src/tools/registry.ts new file mode 100644 index 0000000..56ac416 --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/registry.ts @@ -0,0 +1,42 @@ +import type { SudoPaneFloatingSize, SudoPaneMode, SudoPaneModeKind } from '../config.js' +import type { ZellijToolInfo } from './tool.js' +import { zellijPtyKillTool } from './kill.js' +import { zellijPtyListTool } from './list.js' +import { createZellijPtyReadTool } from './read.js' +import { createRequestSudoTool } from './request-sudo.js' +import { createZellijPtySpawnTool } from './spawn.js' +import { zellijPtyWriteTool } from './write.js' + +export interface ToolTableOptions { + directory: string + enabled: boolean + cleanupExitedPaneOnRead: boolean + sudoPane: SudoPaneMode + sudoPaneMode: SudoPaneModeKind + sudoPaneFloatingSize: SudoPaneFloatingSize +} + +/** Builds the V2 tool definitions for this location. */ +export function buildToolTable(options: ToolTableOptions): ZellijToolInfo[] { + if (!options.enabled) + return [] + + const tools: ZellijToolInfo[] = [ + createZellijPtySpawnTool({ directory: options.directory }), + zellijPtyListTool, + zellijPtyWriteTool, + createZellijPtyReadTool({ defaultCleanupExitedPaneOnRead: options.cleanupExitedPaneOnRead }), + zellijPtyKillTool, + ] + + if (options.sudoPane !== 'hide') { + tools.push(createRequestSudoTool({ + mode: options.sudoPaneMode, + floatingSize: options.sudoPaneFloatingSize, + sudoAllowed: options.sudoPane === 'allow', + directory: options.directory, + })) + } + + return tools +} diff --git a/pkgs/opencode-zellij/src/tools/request-sudo.ts b/pkgs/opencode-zellij/src/tools/request-sudo.ts new file mode 100644 index 0000000..aed2174 --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/request-sudo.ts @@ -0,0 +1,167 @@ +import type { ZellijToolInfo } from './tool.js' +import process from 'node:process' +import { z } from 'zod' +import { zellij } from '../lib/zellij/cli.js' +import { sessionManager } from '../pty/manager.js' +import { createExitCodeToken } from '../utils/exit-code.js' +import { createOpenCodePaneTitle } from '../utils/pane-title.js' +import { registerPaneForWatchdog } from '../zellij/pane-watchdog.js' +import { subscriberManager } from '../zellij/subscribe.js' +import { jsonResponse, publicSession } from './format.js' +import { readOutputSnapshot } from './output.js' + +export function shellQuote(value: string): string { + return `'${value.replaceAll('\'', `'"'"'`)}'` +} + +export function buildReviewScript(summary: string, scripts: Array<{ command: string, description: string }>): string { + const lines = [ + 'set +e', + 'printf \'%s\\n\' \'=== OpenCode sudo request ===\'', + `printf '%s\\n' ${shellQuote(summary)}`, + 'printf \'\\n%s\\n\' \'Commands to review:\'', + ] + + scripts.forEach((script, index) => { + const number = index + 1 + lines.push(`printf '\\n[%s/%s] %s\\n' ${shellQuote(String(number))} ${shellQuote(String(scripts.length))} ${shellQuote(script.description)}`) + lines.push(`printf ' $ %s\\n' ${shellQuote(script.command)}`) + }) + + // 3s countdown before the confirmation prompt. We use carriage return + // (`\r`) to overwrite the same line so the number ticks down (3 → 2 → 1) + // instead of stacking three separate lines. This gives the user time to + // read the summary and focus the floating pane before they decide. + lines.push( + 'printf \'\\n%s\\n\' \'This pane is human-input-only. The agent cannot type here.\'', + 'printf \'Waiting 3s to prevent accidental confirmation: 3\\r\'', + 'sleep 1', + 'printf \'Waiting 3s to prevent accidental confirmation: 2\\r\'', + 'sleep 1', + 'printf \'Waiting 3s to prevent accidental confirmation: 1\\r\'', + 'sleep 1', + 'while true; do', + ' printf \'\\n%s\' \'[y/n]: \'', + ' read -r answer', + // [y/n] convention with strict input: only `y`/`Y` approves, only + // `n`/`N` cancels. Anything else (empty Enter, stray key, gibberish) + // loops back to the prompt with feedback. This forces the user to make + // an explicit, deliberate choice — empty Enter from a fast double-tap + // or an accidental keypress must not silently cancel the request. + ' case "$answer" in', + ' [Yy]) break ;;', + ' [Nn]) printf \'%s\\n\' \'Cancelled by user.\'; exit 130 ;;', + ' "") printf \'%s\\n\' \'Empty input. Please type y or n explicitly.\' ;;', + ' *) printf \'%s\\n\' "Please type y or n (got: $answer)." ;;', + ' esac', + 'done', + 'status=0', + ) + + scripts.forEach((script, index) => { + const number = index + 1 + lines.push(`printf '\\n[%s/%s] %s\\n' ${shellQuote(String(number))} ${shellQuote(String(scripts.length))} ${shellQuote(script.description)}`) + lines.push(`printf '$ %s\\n' ${shellQuote(script.command)}`) + lines.push(`bash -lc ${shellQuote(script.command)}`) + lines.push('code=$?') + lines.push('if [ $code -ne 0 ]; then status=$code; printf \'Command failed with exit code %s\\n\' "$code"; break; fi') + }) + + lines.push('exit $status') + return lines.join('\n') +} + +export interface RequestSudoToolOptions { + mode: 'floating' | 'fullscreen' + floatingSize: { width: string, height: string, pinned: boolean } + /** + * Carried by the tool definition rather than a module-level flag so the + * `deny` behavior travels with the tool the plugin registered. + */ + sudoAllowed: boolean + /** Working directory for the review pane; defaults to the plugin location. */ + directory: string +} + +export const DEFAULT_REQUEST_SUDO_TOOL_OPTIONS: RequestSudoToolOptions = { + mode: 'floating', + floatingSize: { width: '80%', height: '60%', pinned: true }, + sudoAllowed: true, + directory: process.cwd(), +} + +export function buildPaneTitle(summary: string): string { + const trimmed = summary.trim().replace(/\s+/g, ' ') + const limited = trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed + return `⚠ sudo: ${limited}` +} + +export const requestSudoInput = z.object({ + summary: z.string().min(1).describe('TL;DR of why privileged or human-reviewed execution is needed.'), + scripts: z + .array( + z.object({ + command: z.string().min(1).describe('Command or script to run after the user explicitly approves in the pane.'), + description: z.string().min(1).describe('Why this command is needed and what it is expected to change.'), + }), + ) + .min(1) + .describe('Commands shown to the user for review before execution.'), +}) + +export function createRequestSudoTool(options: RequestSudoToolOptions = DEFAULT_REQUEST_SUDO_TOOL_OPTIONS): ZellijToolInfo { + const isFloating = options.mode === 'floating' + return { + name: 'zellij_pty_request_sudo', + description: 'Open a human-reviewed, human-input-only Zellij pane for sudo or other privileged commands.', + input: requestSudoInput, + async execute(args, context) { + if (!options.sudoAllowed) + throw new Error('sudo pane is disabled by zellij-pty config.') + + const cwd = options.directory + const exitCodeToken = createExitCodeToken() + + const command = buildReviewScript(args.summary, args.scripts) + const title = createOpenCodePaneTitle(buildPaneTitle(args.summary)) + const paneId = await zellij.newPane({ + command: 'bash', + args: ['-lc', command], + cwd, + title, + floating: isFloating, + floatingWidth: isFloating ? options.floatingSize.width : undefined, + floatingHeight: isFloating ? options.floatingSize.height : undefined, + floatingPinned: isFloating ? options.floatingSize.pinned : undefined, + // The sudo request is single-shot: when bash exits (YES ran the + // commands, or the user rejected), Zellij closes the pane so the + // user isn't left looking at a dead terminal with stale output. + closeOnExit: true, + exitCodeToken, + }) + + const session = sessionManager.create({ + openCodeSessionId: context.sessionID, + paneId, + title, + command: 'zellij_pty_request_sudo', + args: [], + cwd, + allowAgentInput: false, + humanInputOnly: true, + exitCodeToken, + }) + registerPaneForWatchdog(session) + await subscriberManager.start(session) + + return { + content: jsonResponse({ + session: publicSession(session, { agentWritable: false }), + output: readOutputSnapshot(session.id), + }), + } + }, + } +} + +export const requestSudoTool = createRequestSudoTool() diff --git a/pkgs/opencode-zellij/src/tools/spawn.ts b/pkgs/opencode-zellij/src/tools/spawn.ts new file mode 100644 index 0000000..82c470f --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/spawn.ts @@ -0,0 +1,102 @@ +import type { Probe } from '../pty/probe.js' +import type { ZellijToolInfo } from './tool.js' +import process from 'node:process' +import { z } from 'zod' +import { zellij } from '../lib/zellij/cli.js' +import { sessionManager } from '../pty/manager.js' +import { runProbe } from '../pty/probe.js' +import { createExitCodeToken } from '../utils/exit-code.js' +import { createOpenCodePaneTitle } from '../utils/pane-title.js' +import { registerPaneForWatchdog } from '../zellij/pane-watchdog.js' +import { subscriberManager } from '../zellij/subscribe.js' +import { completedPanesFromSessions, jsonResponse, publicSession } from './format.js' +import { outputMatches, readOutputSnapshot, validateGrep } from './output.js' + +export const zellijPtySpawnInput = z.object({ + command: z.string().describe('Command to run. Without args, it is executed through bash -lc.'), + args: z.array(z.string()).optional().describe('Optional argv. When provided, command is executed directly without shell parsing.'), + cwd: z.string().optional().describe('Working directory for the new pane.'), + title: z.string().optional().describe('Pane title/name.'), + probe: z.discriminatedUnion('type', [ + z.object({ + type: z.literal('sleep'), + seconds: z.number().positive().max(300).optional().describe('Seconds to wait before returning initial output. Defaults to 1.'), + }), + z.object({ + type: z.literal('http'), + url: z.string().url().describe('HTTP URL to poll until it returns the expected status.'), + expectStatus: z.number().int().min(100).max(599).optional().describe('Expected HTTP status. Defaults to any 2xx/3xx response.'), + timeoutSeconds: z.number().positive().max(300).optional().describe('How long to poll before returning a failed probe result. Defaults to 20.'), + }), + z.object({ + type: z.literal('output'), + grep: z.string().describe('Regex to search for in observed pane output.'), + ignoreCase: z.boolean().optional().describe('Use case-insensitive regex matching.'), + timeoutSeconds: z.number().positive().max(300).optional().describe('How long to wait for matching output. Defaults to 20.'), + }), + ]).optional().describe('Optional readiness probe. Defaults to a short sleep before returning output.'), + maxLines: z.number().int().positive().max(5_000).optional().describe('Maximum recent output lines to return. Defaults to 200.'), +}) + +export type ZellijPtySpawnArgs = z.infer + +export interface SpawnContext { + sessionID: string +} + +export async function executeZellijPtySpawn(args: ZellijPtySpawnArgs, context: SpawnContext, directory: string): Promise { + const cwd = args.cwd ?? directory + const exitCodeToken = createExitCodeToken() + const grepError = args.probe?.type === 'output' ? validateGrep(args.probe.grep) : null + if (grepError) + throw new Error(`Invalid probe.grep regex: ${grepError}`) + const title = createOpenCodePaneTitle(args.title ?? args.command) + + const paneId = await zellij.newPane({ + command: args.command, + args: args.args, + cwd, + title, + floating: false, + exitCodeToken, + }) + + const session = sessionManager.create({ + openCodeSessionId: context.sessionID, + paneId, + title, + command: args.command, + args: args.args, + cwd, + allowAgentInput: true, + humanInputOnly: false, + exitCodeToken, + }) + registerPaneForWatchdog(session) + await subscriberManager.start(session) + const probe = await runProbe(args.probe as Probe | undefined, (grep, ignoreCase) => outputMatches(session.id, grep, ignoreCase)) + const output = readOutputSnapshot(session.id, { maxLines: args.maxLines }) + const completedPanes = completedPanesFromSessions( + sessionManager.listByOpenCodeSession(context.sessionID), + ) + + return { + session: publicSession(session), + output, + probe, + ...completedPanes, + } +} + +export function createZellijPtySpawnTool(options: { directory: string }): ZellijToolInfo { + return { + name: 'zellij_pty_spawn', + description: 'Create a visible Zellij pane and run a command in it.', + input: zellijPtySpawnInput, + async execute(args, context) { + return { content: jsonResponse(await executeZellijPtySpawn(args, context, options.directory)) } + }, + } +} + +export const zellijPtySpawnTool = createZellijPtySpawnTool({ directory: process.cwd() }) diff --git a/pkgs/opencode-zellij/src/tools/tool.ts b/pkgs/opencode-zellij/src/tools/tool.ts new file mode 100644 index 0000000..036e59e --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/tool.ts @@ -0,0 +1,5 @@ +// Single import point for the V2 tool definition type, which the package +// exposes at a subpath rather than from its root entry. +import type { Info } from '@opencode/plugin/promise/tool' + +export type ZellijToolInfo = Info diff --git a/pkgs/opencode-zellij/src/tools/write.ts b/pkgs/opencode-zellij/src/tools/write.ts new file mode 100644 index 0000000..36c6132 --- /dev/null +++ b/pkgs/opencode-zellij/src/tools/write.ts @@ -0,0 +1,74 @@ +import type { ZellijToolInfo } from './tool.js' +import { setTimeout as delay } from 'node:timers/promises' +import { z } from 'zod' +import { zellij } from '../lib/zellij/cli.js' +import { sessionManager } from '../pty/manager.js' +import { assertWriteSizeAllowed, chunkWriteData } from '../pty/write-data.js' +import { errorMessage } from '../utils/errors.js' +import { jsonResponse, publicSession } from './format.js' +import { emptyOutputSnapshot, readOutputSnapshot } from './output.js' + +export const zellijPtyWriteInput = z.object({ + id: z.string().describe('zellij-pty session id returned by zellij_pty_spawn or zellij_pty_request_sudo.'), + data: z.string().describe('Text to write. Use \u0003 to send Ctrl-C.'), + maxLines: z.number().int().positive().max(5_000).optional().describe('Maximum recent output lines to return. Defaults to 200.'), + interruptAfterSeconds: z.number().positive().max(300).optional().describe('Blindly send Ctrl-C after this many seconds if the pane is still running; keeps the pane alive.'), +}) + +export const zellijPtyWriteTool: ZellijToolInfo = { + name: 'zellij_pty_write', + description: 'Write stdin to a Zellij PTY session. Throws on human-input-only sessions; agent must not bypass this guard (e.g. via `zellij action write-chars`).', + input: zellijPtyWriteInput, + async execute(args) { + const session = sessionManager.get(args.id) + if (session.humanInputOnly || !session.allowAgentInput) { + // Throw rather than return a warning so the call is unambiguously + // rejected. The agent can still type into a human-input-only pane by + // shelling out to `zellij action write-chars` — that path is a + // conscious bypass, not an accidental "oh the write silently failed". + throw new Error( + `zellij_pty_write refused: session ${session.id} (${session.command}) is human-input-only. ` + + `The user owns decisions for this pane. Do not type into it.`, + ) + } + + if (args.data === '\u0003' || args.data === '\x03') { + await zellij.sendCtrlC(session.paneId) + } + else { + assertWriteSizeAllowed(args.data) + for (const chunk of chunkWriteData(args.data)) { + await zellij.writeChars(session.paneId, chunk) + } + } + + session.updatedAt = new Date().toISOString() + if (args.interruptAfterSeconds) { + await delay(args.interruptAfterSeconds * 1_000) + if (sessionManager.find(session.id)?.status === 'running') { + await zellij.sendCtrlC(session.paneId) + await delay(500) + } + } + else { + await delay(1_000) + } + + const warnings: string[] = [] + let output = emptyOutputSnapshot(session.lineCount) + try { + output = readOutputSnapshot(session.id, { maxLines: args.maxLines }) + } + catch (error) { + warnings.push(`Session output was unavailable before the write response completed: ${errorMessage(error)}`) + } + + return { + content: jsonResponse({ + session: publicSession(session), + output, + warnings, + }), + } + }, +} diff --git a/pkgs/opencode-zellij/src/utils/debug.ts b/pkgs/opencode-zellij/src/utils/debug.ts new file mode 100644 index 0000000..8da42d1 --- /dev/null +++ b/pkgs/opencode-zellij/src/utils/debug.ts @@ -0,0 +1,8 @@ +import process from 'node:process' + +export function debug(message: string, ...details: unknown[]): void { + if (!process.env.ZELLIJ_PTY_DEBUG) + return + + console.warn(`[opencode-zellij] ${message}`, ...details) +} diff --git a/pkgs/opencode-zellij/src/utils/errors.ts b/pkgs/opencode-zellij/src/utils/errors.ts new file mode 100644 index 0000000..3911546 --- /dev/null +++ b/pkgs/opencode-zellij/src/utils/errors.ts @@ -0,0 +1,3 @@ +export function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error) +} diff --git a/pkgs/opencode-zellij/src/utils/exit-code.ts b/pkgs/opencode-zellij/src/utils/exit-code.ts new file mode 100644 index 0000000..59d1333 --- /dev/null +++ b/pkgs/opencode-zellij/src/utils/exit-code.ts @@ -0,0 +1,36 @@ +import { randomUUID } from 'node:crypto' + +export interface ExitCodeMarker { + token: string + exitCode: number +} + +const markerPattern = /^\[zellij-pty:([a-f0-9]+)\] exit-code=(\d+)$/ +const escapeCharacter = String.fromCharCode(27) +const ansiPattern = new RegExp(`${escapeCharacter}\\[[0-9;?]*[a-z]`, 'gi') + +export function createExitCodeToken(): string { + return randomUUID().replaceAll('-', '') +} + +export function parseExitCodeMarker(line: string): ExitCodeMarker | null { + const match = line.replace(ansiPattern, '').replace(/\r?\n/g, '').trim().match(markerPattern) + if (!match?.[1] || !match[2]) + return null + return { + token: match[1], + exitCode: Number(match[2]), + } +} + +export function parseExitCodeMarkerLines(lines: string[], maxWindowLines = 8): ExitCodeMarker | null { + for (let start = 0; start < lines.length; start += 1) { + for (let size = 1; size <= maxWindowLines && start + size <= lines.length; size += 1) { + const marker = parseExitCodeMarker(lines.slice(start, start + size).join('\n')) + if (marker) + return marker + } + } + + return null +} diff --git a/pkgs/opencode-zellij/src/utils/ids.ts b/pkgs/opencode-zellij/src/utils/ids.ts new file mode 100644 index 0000000..1315b63 --- /dev/null +++ b/pkgs/opencode-zellij/src/utils/ids.ts @@ -0,0 +1,5 @@ +import { randomUUID } from 'node:crypto' + +export function createSessionId(): string { + return `zpty_${randomUUID().replaceAll('-', '').slice(0, 10)}` +} diff --git a/pkgs/opencode-zellij/src/utils/logger.ts b/pkgs/opencode-zellij/src/utils/logger.ts new file mode 100644 index 0000000..85f01f7 --- /dev/null +++ b/pkgs/opencode-zellij/src/utils/logger.ts @@ -0,0 +1,74 @@ +import { mkdirSync } from 'node:fs' +import { homedir } from 'node:os' +import { dirname, join } from 'node:path' +import process from 'node:process' +import { LogFileRotationTransport } from '@loglayer/transport-log-file-rotation' +import { LogLayer } from 'loglayer' +import { serializeError } from 'serialize-error' + +// Shared debug logger for the whole plugin. Submodules should NOT construct +// their own LogLayer; instead call `getChildLogger(name)` to get a child +// with a `[name]` prefix that inherits this root's transport. +// +// Logs are written under ~/.cache/opencode-zellij/ (overridable via +// OPENCODE_ZELLIJ_DEBUG_LOG or XDG_CACHE_HOME). They are rotated by +// LogFileRotationTransport — see `buildRootLogger` for the size-based +// rotation policy. Each entry is a JSON object with level, message, +// timestamp, and any structured metadata. +// +// If the transport fails to initialize (e.g. cache dir unwritable), the +// root logger is silently null and `getChildLogger` returns null too; +// callers should tolerate a null logger. +const defaultDebugLogPath = `${process.env.XDG_CACHE_HOME?.trim() || `${homedir()}/.cache`}/opencode-zellij/debug.log` + +function buildRootLogger(): LogLayer | null { + const filename = process.env.OPENCODE_ZELLIJ_DEBUG_LOG?.trim() || defaultDebugLogPath + if (!filename) + return null + + try { + mkdirSync(dirname(filename), { recursive: true }) + } + catch { + return null + } + + try { + // The filename must contain `%DATE%` and the date format must be set + // to a value file-stream-rotator accepts. Without `%DATE%` it auto- + // appends one and logs to stderr; without an explicit `dateFormat` + // it falls back to 'YMD' and logs a warning to stderr. Both are + // unwanted at plugin startup. + // + // `auditFile` is colocated with the rotated log files: file-stream- + // rotator otherwise writes its retention audit to the process CWD, + // which would drop `audit.json` into whichever directory the plugin + // is launched from (e.g. the repo root during local development). + const transport = new LogFileRotationTransport({ + filename: filename.includes('%DATE%') ? filename : `${filename}-%DATE%`, + dateFormat: 'YMD', + size: '1M', + maxLogs: '7d', + frequency: 'daily', + compressOnRotate: true, + auditFile: join(dirname(filename), 'audit.json'), + }) + return new LogLayer({ + errorSerializer: serializeError, + transport, + }) + } + catch { + return null + } +} + +const rootLogger = buildRootLogger() + +/** + * Returns a child logger that prefixes every message with `[name]`, or + * null if the root logger failed to initialize. + */ +export function getChildLogger(name: string): LogLayer | null { + return rootLogger?.withPrefix(`[${name}]`) ?? null +} diff --git a/pkgs/opencode-zellij/src/utils/pane-title.ts b/pkgs/opencode-zellij/src/utils/pane-title.ts new file mode 100644 index 0000000..9800326 --- /dev/null +++ b/pkgs/opencode-zellij/src/utils/pane-title.ts @@ -0,0 +1,13 @@ +import { randomUUID } from 'node:crypto' + +const generatedInstanceId = randomUUID().replaceAll('-', '').slice(0, 8) +const existingOpenCodePrefixPattern = /^oc:[a-z0-9]{4,16}:/i + +export function createOpenCodePaneTitle(title: string, instanceId = generatedInstanceId): string { + const trimmedTitle = title.trim() || 'opencode' + if (existingOpenCodePrefixPattern.test(trimmedTitle)) + return trimmedTitle + + const safeInstanceId = instanceId.replace(/[^a-z0-9]/gi, '').slice(0, 8) || generatedInstanceId + return `oc:${safeInstanceId}:${trimmedTitle}` +} diff --git a/pkgs/opencode-zellij/src/utils/shell-args.ts b/pkgs/opencode-zellij/src/utils/shell-args.ts new file mode 100644 index 0000000..c87af36 --- /dev/null +++ b/pkgs/opencode-zellij/src/utils/shell-args.ts @@ -0,0 +1,31 @@ +export interface CommandInput { + command: string + args?: string[] | undefined +} + +export interface BuildCommandArgvOptions { + exitCodeToken?: string | undefined +} + +const directCommandExitWrapper = 'token="$1"; shift; set +e; "$@"; code=$?; printf "\\n[zellij-pty:%s] exit-code=%s\\n" "$token" "$code"; exit "$code"' +const shellCommandExitWrapper = 'token="$1"; command="$2"; set +e; bash -lc "$command"; code=$?; printf "\\n[zellij-pty:%s] exit-code=%s\\n" "$token" "$code"; exit "$code"' + +export function buildCommandArgv(input: CommandInput, options: BuildCommandArgvOptions = {}): string[] { + const command = input.command.trim() + if (!command) + throw new Error('command is required') + + if (options.exitCodeToken) { + if (input.args && input.args.length > 0) { + return ['bash', '-lc', directCommandExitWrapper, 'zellij-pty', options.exitCodeToken, command, ...input.args] + } + + return ['bash', '-lc', shellCommandExitWrapper, 'zellij-pty', options.exitCodeToken, command] + } + + if (input.args && input.args.length > 0) { + return [command, ...input.args] + } + + return ['bash', '-lc', command] +} diff --git a/pkgs/opencode-zellij/src/zellij/completion-notifications.ts b/pkgs/opencode-zellij/src/zellij/completion-notifications.ts new file mode 100644 index 0000000..30043f7 --- /dev/null +++ b/pkgs/opencode-zellij/src/zellij/completion-notifications.ts @@ -0,0 +1,75 @@ +import type { PtySession, SessionTerminalReason } from '../pty/session.js' +import { errorMessage } from '../utils/errors.js' +import { getChildLogger } from '../utils/logger.js' + +const logger = getChildLogger('completion-notifications') + +export interface SubscriberTerminalEvent { + sessionId: string + reason: SessionTerminalReason + session: PtySession +} + +/** Delivers a synthetic prompt to the OpenCode session that owns a pane. */ +export type CompletionPrompt = (sessionID: string, text: string) => Promise + +export interface CompletionNotificationContext { + prompt: CompletionPrompt +} + +export interface CompletionNotificationManager { + handleSessionTerminal: (event: SubscriberTerminalEvent) => Promise + dispose: () => void +} + +function formatExitCode(exitCode: number | null): string { + return exitCode === null ? '?' : String(exitCode) +} + +// Short, system-notification style. Contains the plugin name, pane id, and +// exit code so the agent can immediately call zellij_pty_read on the right +// pane without digging through session metadata. +export function buildCompletionPromptText(event: SubscriberTerminalEvent): string { + const { paneId, exitCode } = event.session + return `[zellij_pty] pane ${paneId} exit=${formatExitCode(exitCode)} — call zellij_pty_read to read, then zellij_pty_kill to close.` +} + +export class SessionCompletionNotificationManager implements CompletionNotificationManager { + private readonly seen = new Set() + + constructor( + private readonly context: CompletionNotificationContext, + ) { } + + dispose(): void { + this.seen.clear() + } + + async handleSessionTerminal(event: SubscriberTerminalEvent): Promise { + logger?.withMetadata({ + session: event.sessionId, + reason: event.reason, + paneId: event.session.paneId, + openCodeSessionId: event.session.openCodeSessionId ?? 'null', + }).info('handleSessionTerminal') + if (this.seen.has(event.sessionId)) + return + this.seen.add(event.sessionId) + + const sessionID = event.session.openCodeSessionId + if (!sessionID) { + logger?.withMetadata({ session: event.sessionId }).info('skipped: no openCodeSessionId') + return + } + + try { + await this.context.prompt(sessionID, buildCompletionPromptText(event)) + logger?.withMetadata({ sessionID }).info('prompt ok') + } + catch (error) { + // A prompt can legitimately fail (aborted message, shut-down session); + // the pane is already terminal, so log and move on. + logger?.withMetadata({ sessionID, error: errorMessage(error) }).warn('prompt failed') + } + } +} diff --git a/pkgs/opencode-zellij/src/zellij/pane-watchdog-runner.ts b/pkgs/opencode-zellij/src/zellij/pane-watchdog-runner.ts new file mode 100644 index 0000000..19bf178 --- /dev/null +++ b/pkgs/opencode-zellij/src/zellij/pane-watchdog-runner.ts @@ -0,0 +1,118 @@ +import { spawnSync } from 'node:child_process' +import { appendFileSync, existsSync, readFileSync, rmSync } from 'node:fs' +import process from 'node:process' + +const registryPath = process.argv[2] +const pollIntervalMs = 1_000 + +function writeWatchdogDebug(message: string, error: unknown): void { + if (!process.env.ZELLIJ_PTY_DEBUG) + return + + try { + appendFileSync(`${registryPath}.log`, `${new Date().toISOString()} ${message}: ${error instanceof Error ? error.message : String(error)}\n`) + } + catch (loggingError) { + void loggingError + // The watchdog has no stderr; if file logging fails, there is nowhere else to report diagnostics. + } +} + +interface WatchdogPane { + paneId: string +} + +interface WatchdogRegistry { + ownerPid: number + ownerStartTime?: string | null | undefined + zellijSessionName?: string | null | undefined + panes?: WatchdogPane[] | undefined +} + +if (!registryPath) + process.exit(1) + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +function linuxProcessStartTime(pid: number): string | null { + try { + const stat = readFileSync(`/proc/${pid}/stat`, 'utf8') + const fieldsAfterCommand = stat.slice(stat.lastIndexOf(')') + 2).trim().split(/\s+/) + return fieldsAfterCommand[19] || null + } + catch (error) { + // Missing /proc data is expected when the owner has exited or on non-Linux systems. + writeWatchdogDebug('linuxProcessStartTime failed', error) + return null + } +} + +function readRegistry(): WatchdogRegistry | null { + try { + if (!existsSync(registryPath!)) + return null + return JSON.parse(readFileSync(registryPath!, 'utf8')) as WatchdogRegistry + } + catch (error) { + // A missing or corrupt registry cannot be used safely; let the watchdog exit. + writeWatchdogDebug('readRegistry failed', error) + return null + } +} + +function ownerAlive(registry: WatchdogRegistry): boolean { + try { + process.kill(registry.ownerPid, 0) + } + catch (error) { + // process.kill(pid, 0) throws when the owner is gone or inaccessible. + writeWatchdogDebug('ownerAlive kill check failed', error) + return false + } + + return !registry.ownerStartTime || linuxProcessStartTime(registry.ownerPid) === registry.ownerStartTime +} + +function closePane(registry: WatchdogRegistry, paneId: string): void { + const args: string[] = [] + if (registry.zellijSessionName) + args.push('--session', registry.zellijSessionName) + args.push('action', 'close-pane', '--pane-id', paneId) + spawnSync('zellij', args, { stdio: 'ignore', timeout: 2_000 }) +} + +async function main(): Promise { + for (;;) { + const registry = readRegistry() + if (!registry) + return + + if (!ownerAlive(registry)) { + const finalRegistry = readRegistry() || registry + for (const pane of finalRegistry.panes || []) { + closePane(finalRegistry, pane.paneId) + } + rmSync(registryPath!, { force: true }) + return + } + + await sleep(pollIntervalMs) + } +} + +function writeFatalError(error: unknown): void { + try { + appendFileSync(`${registryPath}.log`, `${new Date().toISOString()} ${error instanceof Error ? error.stack || error.message : String(error)}\n`) + } + catch (loggingError) { + void loggingError + // The watchdog has no stderr; if file logging also fails, exiting is the only safe fallback. + } +} + +main().catch((error: unknown) => { + writeFatalError(error) + process.exit(1) +}) diff --git a/pkgs/opencode-zellij/src/zellij/pane-watchdog.ts b/pkgs/opencode-zellij/src/zellij/pane-watchdog.ts new file mode 100644 index 0000000..aa64661 --- /dev/null +++ b/pkgs/opencode-zellij/src/zellij/pane-watchdog.ts @@ -0,0 +1,225 @@ +import type { PtySession } from '../pty/session.js' +import { spawn } from 'node:child_process' +import { randomUUID } from 'node:crypto' +import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import { debug } from '../utils/debug.js' +import { errorMessage } from '../utils/errors.js' + +export interface WatchdogPane { + sessionId: string + paneId: string + title: string + openCodeSessionId: string | null + createdAt: string +} + +export interface WatchdogRegistry { + version: 1 + instanceId: string + ownerPid: number + ownerStartTime: string | null + zellijSessionName: string | null + panes: WatchdogPane[] +} + +const instanceId = randomUUID() +let watchdogStarted = false +let watchdogChild: ReturnType | null = null + +function registryDirectory(): string { + const base = process.env.XDG_RUNTIME_DIR || tmpdir() + return path.join(base, `opencode-zellij-${process.getuid?.() ?? 'user'}`) +} + +export function watchdogRegistryPath(): string { + return path.join(registryDirectory(), `panes-${process.pid}-${instanceId}.json`) +} + +export function parseLinuxProcessStartTime(stat: string): string | null { + const fieldsAfterCommand = stat.slice(stat.lastIndexOf(')') + 2).trim().split(/\s+/) + return fieldsAfterCommand[19] ?? null +} + +function linuxProcessStartTime(pid: number): string | null { + try { + return parseLinuxProcessStartTime(readFileSync(`/proc/${pid}/stat`, 'utf8')) + } + catch (error) { + // Missing /proc data is expected when the owner has exited or on non-Linux systems. + debug('linuxProcessStartTime failed', errorMessage(error)) + return null + } +} + +function emptyRegistry(): WatchdogRegistry { + return { + version: 1, + instanceId, + ownerPid: process.pid, + ownerStartTime: linuxProcessStartTime(process.pid), + zellijSessionName: process.env.ZELLIJ_SESSION_NAME?.trim() || null, + panes: [], + } +} + +function readRegistry(): WatchdogRegistry { + const file = watchdogRegistryPath() + if (!existsSync(file)) + return emptyRegistry() + + try { + const parsed = JSON.parse(readFileSync(file, 'utf8')) as WatchdogRegistry + if (parsed.version !== 1 || parsed.instanceId !== instanceId || parsed.ownerPid !== process.pid || !Array.isArray(parsed.panes)) + return emptyRegistry() + return parsed + } + catch (error) { + // The current instance registry is corrupt or unreadable; start from an empty registry. + debug('readRegistry failed', errorMessage(error)) + return emptyRegistry() + } +} + +function writeRegistry(registry: WatchdogRegistry): void { + const directory = registryDirectory() + mkdirSync(directory, { recursive: true, mode: 0o700 }) + const file = watchdogRegistryPath() + const tempFile = `${file}.tmp-${process.pid}` + writeFileSync(tempFile, JSON.stringify(registry, null, 2), { mode: 0o600 }) + renameSync(tempFile, file) +} + +function ensureWatchdog(): void { + if (watchdogStarted && watchdogChild) + return + watchdogStarted = true + + const child = spawn(process.execPath, [watchdogRunnerPath(), watchdogRegistryPath()], { + detached: true, + stdio: 'ignore', + env: process.env, + }) + watchdogChild = child + child.unref() + child.on('error', () => { + // Child failed early or was killed; allow watchdog to be restarted on next pane registration. + watchdogStarted = false + if (watchdogChild === child) + watchdogChild = null + }) + child.on('exit', () => { + watchdogStarted = false + if (watchdogChild === child) + watchdogChild = null + if (existsSync(watchdogRegistryPath())) + ensureWatchdog() + }) +} + +function watchdogRunnerPath(): string { + return fileURLToPath(new URL('./pane-watchdog-runner.mjs', import.meta.url)) +} + +export function cleanupStaleWatchdogRegistries(): void { + const directory = registryDirectory() + if (!existsSync(directory)) + return + + for (const fileName of readdirSync(directory)) { + if (!fileName.startsWith('panes-') || !fileName.endsWith('.json')) + continue + + const file = path.join(directory, fileName) + try { + const registry = JSON.parse(readFileSync(file, 'utf8')) as WatchdogRegistry + if (registry.version !== 1 || ownerStillMatches(registry)) + continue + closeRegistryPanes(registry) + rmSync(file, { force: true }) + } + catch (error) { + // Corrupt stale registries cannot be used safely and would otherwise fail every startup. + debug('cleanupStaleWatchdogRegistries failed', errorMessage(error)) + rmSync(file, { force: true }) + } + } +} + +function ownerStillMatches(registry: WatchdogRegistry): boolean { + try { + process.kill(registry.ownerPid, 0) + } + catch (error) { + // process.kill(pid, 0) throws when the owner is gone or inaccessible. + debug('ownerStillMatches kill check failed', errorMessage(error)) + return false + } + + return !registry.ownerStartTime || linuxProcessStartTime(registry.ownerPid) === registry.ownerStartTime +} + +function closeRegistryPanes(registry: WatchdogRegistry): void { + for (const pane of registry.panes) { + const args = [] + if (registry.zellijSessionName) + args.push('--session', registry.zellijSessionName) + args.push('action', 'close-pane', '--pane-id', pane.paneId) + spawn('zellij', args, { detached: true, stdio: 'ignore', env: process.env }).unref() + } +} + +export function upsertWatchdogPane(registry: WatchdogRegistry, session: PtySession): WatchdogRegistry { + return { + ...registry, + panes: [ + ...registry.panes.filter(pane => pane.sessionId !== session.id && pane.paneId !== session.paneId), + { + sessionId: session.id, + paneId: session.paneId, + title: session.title, + openCodeSessionId: session.openCodeSessionId, + createdAt: session.createdAt, + }, + ], + } +} + +export function removeWatchdogPane(registry: WatchdogRegistry, sessionId: string): WatchdogRegistry { + return { + ...registry, + panes: registry.panes.filter(pane => pane.sessionId !== sessionId), + } +} + +export function registerPaneForWatchdog(session: PtySession): void { + writeRegistry(upsertWatchdogPane(readRegistry(), session)) + ensureWatchdog() +} + +export function unregisterPaneFromWatchdog(sessionId: string): void { + const registry = readRegistry() + const updated = removeWatchdogPane(registry, sessionId) + if (updated.panes.length === registry.panes.length) + return + if (updated.panes.length === 0) { + removeWatchdogRegistry() + return + } + writeRegistry(updated) +} + +export function removeWatchdogRegistry(): void { + try { + rmSync(watchdogRegistryPath(), { force: true }) + } + catch (error) { + // Watchdog registry cleanup is best effort. + debug('removeWatchdogRegistry failed', errorMessage(error)) + } + if (!watchdogChild) + watchdogStarted = false +} diff --git a/pkgs/opencode-zellij/src/zellij/shutdown-cleanup.ts b/pkgs/opencode-zellij/src/zellij/shutdown-cleanup.ts new file mode 100644 index 0000000..10258b3 --- /dev/null +++ b/pkgs/opencode-zellij/src/zellij/shutdown-cleanup.ts @@ -0,0 +1,56 @@ +import type { SessionManager } from '../pty/manager.js' +import type { SubscriberManager } from './subscribe.js' +import process from 'node:process' +import { zellij } from '../lib/zellij/cli.js' +import { sessionManager } from '../pty/manager.js' +import { debug } from '../utils/debug.js' +import { errorMessage } from '../utils/errors.js' +import { subscriberManager } from './subscribe.js' + +let registered = false +let cleanedUp = false + +export function cleanupPanesOnShutdown( + sessions: SessionManager = sessionManager, + subscribers: SubscriberManager = subscriberManager, +): void { + if (cleanedUp) + return + cleanedUp = true + + for (const session of sessions.list()) { + try { + zellij.closePaneSync(session.paneId) + } + catch (error) { + // Shutdown cleanup is only a fast best-effort path; the watchdog registry remains as fallback. + debug('cleanupPanesOnShutdown closePane failed', errorMessage(error)) + } + + subscribers.forget(session.id) + try { + sessions.remove(session.id) + } + catch (error) { + // Another cleanup path may have already removed it. + debug('cleanupPanesOnShutdown sessions.remove failed', errorMessage(error)) + } + } +} + +export function registerShutdownCleanup(): void { + if (registered) + return + registered = true + + process.once('exit', () => cleanupPanesOnShutdown()) + process.once('SIGINT', () => exitAfterCleanup('SIGINT', 130)) + process.once('SIGTERM', () => exitAfterCleanup('SIGTERM', 143)) + process.once('SIGHUP', () => exitAfterCleanup('SIGHUP', 129)) +} + +function exitAfterCleanup(signal: NodeJS.Signals, code: number): void { + cleanupPanesOnShutdown() + process.removeAllListeners(signal) + process.exit(code) +} diff --git a/pkgs/opencode-zellij/src/zellij/subscribe.ts b/pkgs/opencode-zellij/src/zellij/subscribe.ts new file mode 100644 index 0000000..be990d6 --- /dev/null +++ b/pkgs/opencode-zellij/src/zellij/subscribe.ts @@ -0,0 +1,473 @@ +import type { ChildProcessWithoutNullStreams } from 'node:child_process' +import type { SessionManager } from '../pty/manager.js' +import type { ReadLinesInput, ReadLinesResult } from '../pty/ring-buffer.js' +import type { PtySession, SessionTerminalReason } from '../pty/session.js' +import { spawn } from 'node:child_process' +import process from 'node:process' +import { ensureZellijTarget, zellij, zellijCommandArgs } from '../lib/zellij/cli.js' +import { sessionManager } from '../pty/manager.js' +import { RingBuffer } from '../pty/ring-buffer.js' +import { debug } from '../utils/debug.js' +import { errorMessage } from '../utils/errors.js' +import { parseExitCodeMarkerLines } from '../utils/exit-code.js' +import { unregisterPaneFromWatchdog } from './pane-watchdog.js' + +interface SubscriberState { + child: ChildProcessWithoutNullStreams | null + buffer: RingBuffer + stderr: string[] + stdoutRemainder: string + startedAt: string + lastExitedAt: string | null +} + +export interface SubscriberTerminalEvent { + sessionId: string + reason: SessionTerminalReason + session: PtySession +} + +export interface SubscriberLifecycleHooks { + onSessionTerminal?: (event: SubscriberTerminalEvent) => void | Promise +} + +export interface SubscriberManagerDependencies { + spawn?: typeof spawn | undefined + dumpScreen?: typeof zellij.dumpScreen | undefined + paneExists?: typeof zellij.paneExists | undefined + closePane?: typeof zellij.closePane | undefined + lifecycleHooks?: SubscriberLifecycleHooks | undefined + terminalTailLines?: number | undefined +} + +type JsonObject = Record + +export interface SubscriberStatus { + hasBuffer: boolean + active: boolean + lastExitedAt: string | null + terminal: boolean +} + +const maxStderrLines = 200 + +function splitLines(input: string): string[] { + const lines = input.replace(/\r\n/g, '\n').split('\n') + if (lines.at(-1) === '') + return lines.slice(0, -1) + return lines +} + +function textFromCell(cell: unknown): string { + if (typeof cell === 'string') + return cell + if (!cell || typeof cell !== 'object') + return '' + const object = cell as JsonObject + const value = object.text ?? object.character ?? object.ch ?? object.content + return typeof value === 'string' ? value : '' +} + +function linesFromRows(rows: unknown[]): string[] { + return rows.map((row) => { + if (typeof row === 'string') + return row + if (Array.isArray(row)) + return row.map(textFromCell).join('') + return textFromCell(row) + }) +} + +function eventPaneId(event: JsonObject): string | undefined { + const paneId = event.pane_id ?? event.paneId + return typeof paneId === 'string' ? paneId : undefined +} + +function eventType(event: JsonObject): string | undefined { + const type = event.event ?? event.type + return typeof type === 'string' ? type : undefined +} + +function extractRenderedLines(event: JsonObject): string[] { + for (const key of ['viewport', 'scrollback', 'lines'] as const) { + const value = event[key] + if (Array.isArray(value)) + return linesFromRows(value) + } + + for (const key of ['text', 'output', 'content'] as const) { + const value = event[key] + if (typeof value === 'string') + return splitLines(value) + } + + return [] +} + +export class SubscriberManager { + private readonly subscribers = new Map() + // Per-session start promises to prevent concurrent spawn races + private readonly startingSessions = new Map>() + private readonly spawnProcess: typeof spawn + private readonly dumpScreen: typeof zellij.dumpScreen + private readonly paneExists: typeof zellij.paneExists + private readonly closePane: typeof zellij.closePane + private lifecycleHooks: SubscriberLifecycleHooks | undefined + private readonly terminalTailLines: number + + constructor( + private readonly sessions: SessionManager, + private readonly maxBufferLines = Number(process.env.PTY_MAX_BUFFER_LINES ?? 50_000), + dependencies: SubscriberManagerDependencies = {}, + ) { + this.spawnProcess = dependencies.spawn ?? spawn + this.dumpScreen = dependencies.dumpScreen ?? (paneId => zellij.dumpScreen(paneId)) + this.paneExists = dependencies.paneExists ?? (paneId => zellij.paneExists(paneId)) + this.closePane = dependencies.closePane ?? (paneId => zellij.closePane(paneId)) + this.lifecycleHooks = dependencies.lifecycleHooks + this.terminalTailLines = dependencies.terminalTailLines ?? 200 + } + + setLifecycleHooks(hooks: SubscriberLifecycleHooks | undefined): void { + this.lifecycleHooks = hooks + } + + async start(session: PtySession): Promise { + const currentSession = this.sessions.find(session.id) ?? session + if (currentSession.status === 'terminal') + return + const existing = this.subscribers.get(session.id) + if (existing?.child) + return + + // Prevent concurrent start races for the same session + const inProgress = this.startingSessions.get(session.id) + if (inProgress) + return inProgress + + ensureZellijTarget() + + const startPromise = this.doStart(session) + this.startingSessions.set(session.id, startPromise) + try { + await startPromise + } + finally { + this.startingSessions.delete(session.id) + } + } + + private async doStart(session: PtySession): Promise { + const existing = this.subscribers.get(session.id) + + const state: SubscriberState + = existing + ?? { + child: null, + buffer: new RingBuffer(this.maxBufferLines), + stderr: [], + stdoutRemainder: '', + startedAt: new Date().toISOString(), + lastExitedAt: null, + } + + if (!existing) { + this.subscribers.set(session.id, state) + } + + // Spawn the subscribe child WITHOUT `--scrollback`. + // + // Bare `--scrollback` on zellij 0.44.x asks for the full scrollback in + // the initial event; observed behaviour for panes with non-trivial + // scrollback is a burst-then-stall delivery (~70 KB out, then nothing + // for many seconds before the rest arrives). That child stays alive for + // the duration, so any caller awaiting `subscriberManager.start()` — + // i.e. every `zellij_pty_read` / `zellij_pty_spawn` — blocks with the + // pane and the surrounding Zellij session appearing frozen. + // + // The initial event's `scrollback` field was never read into the + // buffer anyway: `extractRenderedLines` prefers `viewport`, and the + // canonical scrollback snapshot is captured separately via + // `dump-screen --full` a few lines below (which is fast, ~20 ms, even + // at 5k+ lines). See 1eb6cb3 for the original "subscribe first, then + // dump-screen" ordering this preserves. + const child = this.spawnProcess('zellij', zellijCommandArgs(['subscribe', '--pane-id', session.paneId, '--format', 'json', '--ansi']), { + stdio: ['pipe', 'pipe', 'pipe'], + }) + child.stdin.end() + // Only assign child if state is still the same (no concurrent restart) + const currentState = this.subscribers.get(session.id) + if (currentState !== state) { + child.kill('SIGTERM') + return + } + state.child = child + state.lastExitedAt = null + + child.stdout.setEncoding('utf8') + child.stdout.on('data', (chunk: string) => this.handleStdout(session.id, child, chunk)) + child.stderr.setEncoding('utf8') + child.stderr.on('data', (chunk: string) => this.handleStderr(session.id, child, chunk)) + child.on('exit', () => this.handleSubscriberExit(session.id, child)) + child.on('error', error => this.handleSubscriberError(session.id, child, error)) + + if (!existing) { + try { + const snapshot = await this.dumpScreen(session.paneId) + if (this.subscribers.get(session.id) !== state || state.child !== child) + return + state.buffer.appendSnapshot(snapshot) + this.sessions.updateLineCount(session.id, state.buffer.lineCount) + } + catch (error) { + // dump-screen may race with pane creation; subscribe will still collect future output. + debug('dumpScreen failed', errorMessage(error)) + } + } + } + + read(sessionId: string, input: ReadLinesInput): ReadLinesResult { + const state = this.subscribers.get(sessionId) + if (!state) + throw new Error(`No subscriber buffer exists for session: ${sessionId}`) + return state.buffer.read(input) + } + + has(sessionId: string): boolean { + return this.subscribers.has(sessionId) + } + + status(sessionId: string): SubscriberStatus { + const state = this.subscribers.get(sessionId) + return { + hasBuffer: Boolean(state), + active: Boolean(state?.child), + lastExitedAt: state?.lastExitedAt ?? null, + terminal: this.sessions.find(sessionId)?.status === 'terminal', + } + } + + stderr(sessionId: string): string[] { + return this.subscribers.get(sessionId)?.stderr ?? [] + } + + stop(sessionId: string): void { + const state = this.subscribers.get(sessionId) + if (!state) + return + state.child?.kill('SIGTERM') + state.child = null + state.lastExitedAt = new Date().toISOString() + } + + forget(sessionId: string): void { + this.stop(sessionId) + this.subscribers.delete(sessionId) + } + + stopAll(): void { + for (const sessionId of this.subscribers.keys()) { + this.forget(sessionId) + } + } + + async closeSessionPane(sessionId: string, options: { throwOnFailure?: boolean } = {}): Promise { + const session = this.sessions.get(sessionId) + this.stop(sessionId) + try { + await this.closePane(session.paneId) + } + catch (error) { + // Pane may already be closed by the user or command exit. + debug('closePane failed', errorMessage(error)) + if (options.throwOnFailure) + throw error + } + } + + private handleStdout(sessionId: string, child: ChildProcessWithoutNullStreams, chunk: string): void { + const state = this.subscribers.get(sessionId) + if (!state || state.child !== child) + return + + const parts = `${state.stdoutRemainder}${chunk}`.split('\n') + state.stdoutRemainder = parts.pop() ?? '' + for (const part of parts) { + this.handleJsonLine(sessionId, child, part) + } + } + + private handleJsonLine(sessionId: string, child: ChildProcessWithoutNullStreams, line: string): void { + const state = this.subscribers.get(sessionId) + if (!state || state.child !== child) + return + const trimmed = line.trim() + if (!trimmed) + return + + let event: JsonObject + try { + const parsed: unknown = JSON.parse(trimmed) + if (!parsed || typeof parsed !== 'object') + return + event = parsed as JsonObject + } + catch (error) { + state.buffer.append(trimmed) + this.sessions.updateLineCount(sessionId, state.buffer.lineCount) + debug('JSON parse of subscriber event failed, treating as raw text', errorMessage(error)) + return + } + + let session: PtySession + try { + session = this.sessions.get(sessionId) + } + catch (error) { + this.forget(sessionId) + debug('session lookup by id failed', errorMessage(error)) + return + } + const paneId = eventPaneId(event) + if (paneId && paneId !== session.paneId) + return + + const type = eventType(event) + if (type === 'pane_closed' || type === 'PaneClosed') { + this.markSessionTerminal(sessionId, 'pane_closed') + unregisterPaneFromWatchdog(sessionId) + return + } + + const lines = extractRenderedLines(event) + if (lines.length === 0) + return + state.buffer.appendSnapshot(lines) + this.captureExitCode(sessionId, lines) + this.sessions.updateLineCount(sessionId, state.buffer.lineCount) + } + + private captureExitCode(sessionId: string, lines: string[]): void { + const session = this.sessions.get(sessionId) + if (!session.exitCodeToken) + return + + const marker = parseExitCodeMarkerLines(lines) + if (!marker || marker.token !== session.exitCodeToken) + return + + this.markSessionTerminal(sessionId, 'exit_marker', { exitCode: marker.exitCode }) + } + + private handleStderr(sessionId: string, child: ChildProcessWithoutNullStreams, chunk: string): void { + const state = this.subscribers.get(sessionId) + if (!state || state.child !== child) + return + this.appendStderr(state, ...splitLines(chunk)) + } + + private handleSubscriberExit(sessionId: string, child: ChildProcessWithoutNullStreams): void { + const state = this.subscribers.get(sessionId) + if (!state) + return + if (state.child !== child) + return + state.child = null + state.lastExitedAt = new Date().toISOString() + if (this.sessions.find(sessionId)?.status !== 'terminal') + this.appendStderr(state, `[zellij-pty] subscriber exited at ${state.lastExitedAt}; last buffered output is retained.`) + void this.reconcileSubscriberTermination(sessionId, state, 'subscriber_exit') + } + + private handleSubscriberError(sessionId: string, child: ChildProcessWithoutNullStreams, error: Error): void { + const state = this.subscribers.get(sessionId) + if (state?.child === child) { + this.appendStderr(state, error.message) + state.child = null + state.lastExitedAt = new Date().toISOString() + const session = this.sessions.find(sessionId) + if (session?.status !== 'terminal') + this.sessions.updateStatus(sessionId, 'unknown') + void this.reconcileSubscriberTermination(sessionId, state, 'subscriber_error') + } + } + + private appendStderr(state: SubscriberState, ...lines: string[]): void { + state.stderr.push(...lines) + if (state.stderr.length > maxStderrLines) + state.stderr = state.stderr.slice(state.stderr.length - maxStderrLines) + } + + private async reconcileSubscriberTermination( + sessionId: string, + state: SubscriberState, + reason: Extract, + ): Promise { + const session = this.sessions.find(sessionId) + if (!session || session.status === 'terminal' || this.subscribers.get(sessionId) !== state || state.child) + return + + let paneExists: boolean | undefined + try { + paneExists = await this.paneExists(session.paneId) + } + catch (error) { + const latestState = this.subscribers.get(sessionId) + const latestSession = this.sessions.find(sessionId) + if (!latestState || latestState !== state || latestState.child || !latestSession || latestSession.status === 'terminal') + return + this.appendStderr(latestState, `[zellij-pty] ${reason} reconciliation could not verify pane ${latestSession.paneId}: ${errorMessage(error)}`) + return + } + + const latestState = this.subscribers.get(sessionId) + const latestSession = this.sessions.find(sessionId) + if (!latestState || latestState !== state || latestState.child || !latestSession || latestSession.status === 'terminal') + return + + if (paneExists === false) { + this.markSessionTerminal(sessionId, reason) + unregisterPaneFromWatchdog(sessionId) + return + } + + if (paneExists === undefined) { + this.appendStderr(latestState, `[zellij-pty] ${reason} reconciliation could not confirm whether pane ${latestSession.paneId} still exists; leaving session non-terminal.`) + } + } + + private markSessionTerminal(sessionId: string, reason: SessionTerminalReason, input: { exitCode?: number | undefined } = {}): void { + const state = this.subscribers.get(sessionId) + if (!state) + return + + const tail = state.buffer.read({ limit: this.terminalTailLines }).lines + const result = this.sessions.markTerminal(sessionId, { + reason, + tail, + exitCode: input.exitCode, + }) + + if (result.created) { + try { + const maybePromise = this.lifecycleHooks?.onSessionTerminal?.({ + sessionId, + reason, + session: result.session, + }) + if (maybePromise && typeof maybePromise.then === 'function') { + void maybePromise.catch((error) => { + debug('onSessionTerminal hook failed', errorMessage(error)) + }) + } + } + catch (error) { + debug('onSessionTerminal hook failed', errorMessage(error)) + } + } + + this.stop(sessionId) + } +} + +export const subscriberManager = new SubscriberManager(sessionManager) diff --git a/pkgs/opencode-zellij/src/zellij/tab-title-events.ts b/pkgs/opencode-zellij/src/zellij/tab-title-events.ts new file mode 100644 index 0000000..763c96e --- /dev/null +++ b/pkgs/opencode-zellij/src/zellij/tab-title-events.ts @@ -0,0 +1,105 @@ +/** + * Normalizes OpenCode V2 events into the small internal vocabulary the tab + * title activity model consumes. This is the only module that knows both + * shapes, so V2 payload changes stop here. + * + * V2 payloads live under `event.data` (V1 used `event.properties`). + */ +export type TabTitleEvent + = | { type: 'session-created', sessionID: string, directory: string | undefined, parentID: string | undefined } + | { type: 'session-running', sessionID: string } + | { type: 'session-idle', sessionID: string } + | { type: 'input-asked', sessionID: string, id: string } + | { type: 'input-resolved', sessionID: string, id: string } + | { type: 'session-deleted', sessionID: string } + +/** Structural subset of `V2Event`; avoids importing the generated client type. */ +export interface OpenCodeEventLike { + type: string + data?: unknown + location?: { directory?: string } | undefined +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null +} + +function asRecord(value: unknown): Record | undefined { + return isRecord(value) ? value : undefined +} + +function stringField(object: Record | undefined, key: string): string | undefined { + const value = object?.[key] + return typeof value === 'string' ? value : undefined +} + +function sessionIDOf(event: OpenCodeEventLike): string | undefined { + return stringField(asRecord(event.data), 'sessionID') +} + +export function normalizeTabTitleEvent(event: OpenCodeEventLike): TabTitleEvent[] { + const data = asRecord(event.data) + + switch (event.type) { + case 'session.created': { + const sessionID = stringField(data, 'sessionID') + if (!sessionID) + return [] + const directory = stringField(asRecord(data?.location), 'directory') + ?? stringField(asRecord(event.location), 'directory') + return [{ type: 'session-created', sessionID, directory, parentID: stringField(data, 'parentID') }] + } + + case 'session.status': { + const sessionID = stringField(data, 'sessionID') + const status = stringField(asRecord(data?.status), 'type') + if (!sessionID) + return [] + if (status === 'idle') + return [{ type: 'session-idle', sessionID }] + if (status === 'busy' || status === 'retry') + return [{ type: 'session-running', sessionID }] + return [] + } + + case 'session.idle': + case 'session.execution.failed': { + const sessionID = sessionIDOf(event) + return sessionID ? [{ type: 'session-idle', sessionID }] : [] + } + + case 'session.deleted': { + const sessionID = sessionIDOf(event) + return sessionID ? [{ type: 'session-deleted', sessionID }] : [] + } + + case 'permission.asked': { + const sessionID = stringField(data, 'sessionID') + const id = stringField(data, 'id') + return sessionID && id ? [{ type: 'input-asked', sessionID, id }] : [] + } + + case 'permission.replied': { + const sessionID = stringField(data, 'sessionID') + const id = stringField(data, 'requestID') + return sessionID && id ? [{ type: 'input-resolved', sessionID, id }] : [] + } + + case 'form.created': { + const form = asRecord(asRecord(data?.form)) + const sessionID = stringField(form, 'sessionID') + const id = stringField(form, 'id') + return sessionID && id ? [{ type: 'input-asked', sessionID, id }] : [] + } + + case 'form.replied': + case 'form.cancelled': { + const sessionID = stringField(data, 'sessionID') + const id = stringField(data, 'id') + return sessionID && id ? [{ type: 'input-resolved', sessionID, id }] : [] + } + + default: + return [] + } +} diff --git a/pkgs/opencode-zellij/src/zellij/tab-title.ts b/pkgs/opencode-zellij/src/zellij/tab-title.ts new file mode 100644 index 0000000..4bbec0c --- /dev/null +++ b/pkgs/opencode-zellij/src/zellij/tab-title.ts @@ -0,0 +1,393 @@ +import type { TabTitleEvent } from './tab-title-events.js' +import process from 'node:process' +import { createZellijClient } from '../lib/zellij/cli.js' +import { debug } from '../utils/debug.js' +import { errorMessage } from '../utils/errors.js' + +export interface TabTitleCli { + renameTab: (title: string) => Promise + currentTabTitle: () => Promise +} + +export type TabTitleStatus = 'idle' | 'running' | 'needs-input' + +export interface TabTitleEmojis { + idle: string + running: string + needsInput: string +} + +export const defaultTabTitleEmojis: TabTitleEmojis = { + idle: '🟢', + running: '⚡', + needsInput: '💬', +} + +export interface TitleContext { + status: TabTitleStatus + emojis: TabTitleEmojis +} + +export function formatTabTitle(context: TitleContext): string { + return context.emojis[context.status === 'needs-input' ? 'needsInput' : context.status] +} + +export function sanitizeTitle(title: string, maxLength = 90): string { + let cleaned = title + .replace(/[\p{Cc}\p{Cf}\p{Co}\p{Cn}]/gu, ' ') + .replace(/\s+/g, ' ') + .trim() + + const chars = Array.from(cleaned) + if (chars.length > maxLength) { + cleaned = `${chars.slice(0, maxLength - 1).join('')}…` + } + + return cleaned +} + +interface SessionRecord { + directory: string | undefined + parentID: string | undefined +} + +export class TabTitleActivityModel { + status: 'idle' | 'running' | 'needs-input' = 'idle' + private worktreeDirectory: string + private sessions = new Map() + private scopedSessions = new Set() + private runningSessions = new Set() + private pendingInputs = new Map() + + constructor(options: { worktreeDirectory: string }) { + this.worktreeDirectory = options.worktreeDirectory + } + + getSession(sessionID: string): SessionRecord | undefined { + return this.scopedSessions.has(sessionID) ? this.sessions.get(sessionID) : undefined + } + + hasPendingInput(sessionID: string, requestID: string): boolean { + return this.pendingInputs.has(`${sessionID}:${requestID}`) + } + + handleEvent(event: TabTitleEvent): void { + switch (event.type) { + case 'session-created': + this.storeSession(event.sessionID, event.directory, event.parentID) + break + case 'session-running': + if (this.scopedSessions.has(event.sessionID)) { + this.runningSessions.add(event.sessionID) + this.updateStatus() + } + break + case 'session-idle': + if (this.runningSessions.delete(event.sessionID)) + this.updateStatus() + break + case 'input-asked': + if (this.scopedSessions.has(event.sessionID)) { + this.pendingInputs.set(`${event.sessionID}:${event.id}`, event.sessionID) + this.runningSessions.add(event.sessionID) + this.updateStatus() + } + break + case 'input-resolved': + this.pendingInputs.delete(`${event.sessionID}:${event.id}`) + this.updateStatus() + break + case 'session-deleted': + this.removeSessionAndDescendants(event.sessionID) + this.updateStatus() + break + } + } + + private storeSession(id: string, directory: string | undefined, parentID: string | undefined): void { + this.sessions.set(id, { directory, parentID }) + + const isDirectlyScoped = directory === this.worktreeDirectory + const isDescendantScoped = parentID ? this.scopedSessions.has(parentID) : false + const isKnown = this.scopedSessions.has(id) + + if (isKnown || isDirectlyScoped || isDescendantScoped) + this.scopedSessions.add(id) + } + + private removeSessionAndDescendants(rootID: string): void { + const toRemove = new Set() + toRemove.add(rootID) + + let changed = true + while (changed) { + changed = false + for (const [id, session] of this.sessions) { + if (!toRemove.has(id) && session.parentID && toRemove.has(session.parentID)) { + toRemove.add(id) + changed = true + } + } + } + + for (const id of toRemove) { + this.sessions.delete(id) + this.scopedSessions.delete(id) + this.runningSessions.delete(id) + } + + for (const [key, sessionID] of [...this.pendingInputs.entries()]) { + if (toRemove.has(sessionID)) + this.pendingInputs.delete(key) + } + } + + private updateStatus(): void { + if (this.pendingInputs.size > 0) + this.status = 'needs-input' + else if (this.runningSessions.size > 0) + this.status = 'running' + else + this.status = 'idle' + } +} + +export class TabTitleActor { + private activity: TabTitleActivityModel + private emojis: TabTitleEmojis + + constructor(options: { + activity: TabTitleActivityModel + emojis?: Partial | undefined + }) { + this.activity = options.activity + this.emojis = { ...defaultTabTitleEmojis, ...options.emojis } + } + + get context() { + return { + status: this.activity.status, + } + } + + get title(): string { + return formatTabTitle({ + ...this.context, + emojis: this.emojis, + }) + } + + handleEvent(event: TabTitleEvent): void { + this.activity.handleEvent(event) + } +} + +export interface TabTitleManagerOptions { + cli?: TabTitleCli + emojis?: Partial | undefined + debounceMs?: number + retryInitialMs?: number + retryMaxMs?: number + actor: TabTitleActor +} + +export class TabTitleManager { + private lastDesiredStatus: TabTitleStatus | undefined + private lastSyncedTitle: string | undefined + private syncGeneration = 0 + private debounceTimer: ReturnType | undefined + private retryTimer: ReturnType | undefined + private retryAttempt = 0 + private syncInFlight = false + private syncPromise: Promise | undefined + private readonly debounceMs: number + private readonly retryInitialMs: number + private readonly retryMaxMs: number + private readonly cli: TabTitleCli + private readonly emojis: TabTitleEmojis + private readonly enabled: boolean + private destroyed = false + private destroyPromise: Promise | undefined + private readonly actor: TabTitleActor + + constructor(options: TabTitleManagerOptions) { + this.cli = options.cli ?? createZellijClient() + this.emojis = { ...defaultTabTitleEmojis, ...options.emojis } + this.debounceMs = options.debounceMs ?? 300 + this.retryInitialMs = options.retryInitialMs ?? 250 + this.retryMaxMs = options.retryMaxMs ?? 5_000 + this.enabled = Boolean(process.env.ZELLIJ || process.env.ZELLIJ_SESSION_NAME) + this.actor = options.actor + } + + private buildTitle(): string { + return sanitizeTitle(formatTabTitle({ + ...this.actor.context, + emojis: this.emojis, + })) + } + + /** + * Compose a full tab title by appending or replacing the status emoji + * at the end of the current title. + */ + private composeTitle(current: string | undefined, newEmoji: string, allEmojis: string[]): string { + if (!current) + return newEmoji + // Whole title is exactly a known status emoji (e.g. from a previous version) + if (allEmojis.includes(current)) + return newEmoji + // Ends with " emoji" → replace the trailing emoji + for (const emoji of allEmojis) { + if (current.endsWith(` ${emoji}`)) + return `${current.slice(0, current.length - emoji.length - 1)} ${newEmoji}` + } + // No known emoji at the end → append + return `${current} ${newEmoji}` + } + + private get statusEmojis(): string[] { + return [this.emojis.idle, this.emojis.running, this.emojis.needsInput] + } + + getCurrentTitle(): string { + return this.buildTitle() + } + + private async readCurrentTitle(): Promise { + try { + return await this.cli.currentTabTitle() + } + catch (error) { + debug('Failed to read current tab title', errorMessage(error)) + return undefined + } + } + + async renderImmediate(): Promise { + if (!this.enabled || this.destroyed) + return + this.lastDesiredStatus = undefined + this.clearDebounceTimer() + await this.syncDesiredTitle() + } + + scheduleUpdate(): void { + if (!this.enabled || this.destroyed) + return + const status = this.actor.context.status + if (status === this.lastDesiredStatus && this.lastSyncedTitle !== undefined) + return + this.lastDesiredStatus = status + + this.clearRetryTimer() + this.clearDebounceTimer() + this.debounceTimer = setTimeout(() => { + this.debounceTimer = undefined + this.syncDesiredTitle() + .catch(error => debug('debounced tab title sync failed', errorMessage(error))) + }, this.debounceMs) + this.unrefTimer(this.debounceTimer) + } + + private async syncDesiredTitle(): Promise { + if (!this.enabled || this.destroyed) + return + const generation = this.syncGeneration + if (this.destroyed || generation !== this.syncGeneration) + return + if (this.syncInFlight) + return this.syncPromise + + this.syncInFlight = true + this.syncPromise = this.runTitleSync(generation) + return this.syncPromise + } + + private async runTitleSync(generation: number): Promise { + try { + while (generation === this.syncGeneration) { + const currentTitle = await this.readCurrentTitle() + // Reading current title failed; skip this sync to avoid destructive overwrite + if (currentTitle === undefined) + break + + const newEmoji = this.buildTitle() + const composed = sanitizeTitle(this.composeTitle(currentTitle, newEmoji, this.statusEmojis)) + + if (composed === this.lastSyncedTitle) + break + + try { + await this.cli.renameTab(composed) + if (generation !== this.syncGeneration || this.destroyed) + return + this.lastSyncedTitle = composed + this.retryAttempt = 0 + this.clearRetryTimer() + } + catch (cause) { + debug('Failed to rename Zellij tab.', cause) + if (generation !== this.syncGeneration || this.destroyed) + break + this.scheduleRetry() + break + } + } + } + finally { + this.syncInFlight = false + this.syncPromise = undefined + } + } + + private scheduleRetry(): void { + if (!this.enabled || this.destroyed || this.retryTimer) + return + + const delay = Math.min(this.retryMaxMs, this.retryInitialMs * 2 ** this.retryAttempt) + this.retryAttempt += 1 + this.retryTimer = setTimeout(() => { + this.retryTimer = undefined + this.syncDesiredTitle() + .catch(error => debug('retry tab title sync failed', errorMessage(error))) + }, delay) + this.unrefTimer(this.retryTimer) + } + + private clearRetryTimer(): void { + if (this.retryTimer) + clearTimeout(this.retryTimer) + this.retryTimer = undefined + } + + private unrefTimer(timer: ReturnType): void { + if (typeof timer === 'object' && timer && 'unref' in timer && typeof timer.unref === 'function') + timer.unref() + } + + private clearDebounceTimer(): void { + if (this.debounceTimer) + clearTimeout(this.debounceTimer) + this.debounceTimer = undefined + } + + // No-op. Previously this method captured the tab title and restored it + // on destroy, but in practice the opencode plugin lifecycle never + // dispatches a reliable "session ended" event in any mode (TUI + // worker shutdown, `opencode run` headless, or `opencode run --interactive` + // all exit without firing `server.instance.disposed` / `global.disposed`). + // `process.once('exit')` only supports sync callbacks so it cannot drive + // the async Zellij rename, and SIGKILL cannot be intercepted at all. + // The remaining work is to clear timers so a straggler sync cannot race + // the process exit; the tab title we set is simply left in place. + destroy(): Promise { + if (this.destroyed) + return this.destroyPromise ?? Promise.resolve() + this.destroyed = true + this.syncGeneration += 1 + this.clearDebounceTimer() + this.clearRetryTimer() + return Promise.resolve() + } +} diff --git a/pkgs/opencode-zellij/tsdown.config.ts b/pkgs/opencode-zellij/tsdown.config.ts new file mode 100644 index 0000000..a9dbb8b --- /dev/null +++ b/pkgs/opencode-zellij/tsdown.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsdown' + +export default defineConfig({ + entry: { 'index': 'src/plugin.ts', 'pane-watchdog-runner': 'src/zellij/pane-watchdog-runner.ts' }, + format: ['esm'], + dts: false, + clean: true, + sourcemap: false, + minify: true, + codeSplitting: false, + target: 'node20', + // OpenCode V2 loads a plugin directory without installing dependencies for + // it, so bundle every runtime dependency into the two output files. + noExternal: [/.*/], +}) -- 2.51.2