From 0d4ce154b07b7b415bd1f96b38b0b6de77b7b71a Mon Sep 17 00:00:00 2001 From: Matt Norris Date: Tue, 12 May 2026 16:47:40 -0400 Subject: [PATCH 1/3] feat(essentials-sync): add agent to sync packages --- pyproject.toml | 1 + tools/typescript/essentials-sync/.env.example | 8 + tools/typescript/essentials-sync/.gitignore | 7 + tools/typescript/essentials-sync/.nvmrc | 1 + .../essentials-sync/.python-version | 1 + .../essentials-sync/.secretlintrc.json | 7 + tools/typescript/essentials-sync/README.md | 138 + .../essentials-sync/package-lock.json | 5020 +++++++++++++++++ tools/typescript/essentials-sync/package.json | 35 + tools/typescript/essentials-sync/src/agent.ts | 364 ++ tools/typescript/essentials-sync/src/cli.ts | 368 ++ .../essentials-sync/src/extract-plan.ts | 179 + .../essentials-sync/src/jargon-list.ts | 80 + .../essentials-sync/src/model-resolver.ts | 157 + .../typescript/essentials-sync/src/prompts.ts | 303 + .../essentials-sync/src/reviewer.ts | 134 + .../essentials-sync/src/scanners/index.ts | 94 + .../essentials-sync/src/scanners/jargon.ts | 87 + .../essentials-sync/src/scanners/pii.ts | 110 + .../src/scanners/secretlint.ts | 93 + .../src/scanners/trufflehog.ts | 93 + tools/typescript/essentials-sync/src/sync.ts | 120 + tools/typescript/essentials-sync/src/types.ts | 76 + .../tests/extract-plan.test.ts | 182 + .../tests/fixtures/clean-package/README.md | 3 + .../tests/fixtures/clean-package/src/main.py | 5 + .../tests/fixtures/dirty-package/README.md | 3 + .../fixtures/dirty-package/src/client.py | 8 + .../essentials-sync/tests/scanners.test.ts | 40 + .../typescript/essentials-sync/tsconfig.json | 21 + uv.lock | 24 + 31 files changed, 7762 insertions(+) create mode 100644 tools/typescript/essentials-sync/.env.example create mode 100644 tools/typescript/essentials-sync/.gitignore create mode 100644 tools/typescript/essentials-sync/.nvmrc create mode 100644 tools/typescript/essentials-sync/.python-version create mode 100644 tools/typescript/essentials-sync/.secretlintrc.json create mode 100644 tools/typescript/essentials-sync/README.md create mode 100644 tools/typescript/essentials-sync/package-lock.json create mode 100644 tools/typescript/essentials-sync/package.json create mode 100644 tools/typescript/essentials-sync/src/agent.ts create mode 100644 tools/typescript/essentials-sync/src/cli.ts create mode 100644 tools/typescript/essentials-sync/src/extract-plan.ts create mode 100644 tools/typescript/essentials-sync/src/jargon-list.ts create mode 100644 tools/typescript/essentials-sync/src/model-resolver.ts create mode 100644 tools/typescript/essentials-sync/src/prompts.ts create mode 100644 tools/typescript/essentials-sync/src/reviewer.ts create mode 100644 tools/typescript/essentials-sync/src/scanners/index.ts create mode 100644 tools/typescript/essentials-sync/src/scanners/jargon.ts create mode 100644 tools/typescript/essentials-sync/src/scanners/pii.ts create mode 100644 tools/typescript/essentials-sync/src/scanners/secretlint.ts create mode 100644 tools/typescript/essentials-sync/src/scanners/trufflehog.ts create mode 100644 tools/typescript/essentials-sync/src/sync.ts create mode 100644 tools/typescript/essentials-sync/src/types.ts create mode 100644 tools/typescript/essentials-sync/tests/extract-plan.test.ts create mode 100644 tools/typescript/essentials-sync/tests/fixtures/clean-package/README.md create mode 100644 tools/typescript/essentials-sync/tests/fixtures/clean-package/src/main.py create mode 100644 tools/typescript/essentials-sync/tests/fixtures/dirty-package/README.md create mode 100644 tools/typescript/essentials-sync/tests/fixtures/dirty-package/src/client.py create mode 100644 tools/typescript/essentials-sync/tests/scanners.test.ts create mode 100644 tools/typescript/essentials-sync/tsconfig.json diff --git a/pyproject.toml b/pyproject.toml index 0a491e6..1ef9410 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ members = [ "tools/python/pulumi-utils", "packages/python/ess-auth", "packages/python/ess-browser", + "packages/python/ess-service-now-incident", "packages/python/langsmith-client", "packages/python/langsmith-network", "packages/python/azure-ai", diff --git a/tools/typescript/essentials-sync/.env.example b/tools/typescript/essentials-sync/.env.example new file mode 100644 index 0000000..b3bd851 --- /dev/null +++ b/tools/typescript/essentials-sync/.env.example @@ -0,0 +1,8 @@ +# Generate at: https://cursor.com/dashboard/integrations -> API Keys +# (Team service accounts: https://cursor.com/dashboard/team-settings -> Service Accounts; Enterprise only) +CURSOR_API_KEY= + +# Optional: override the default model sentinels (opus / codex). +# Examples: CURSOR_MODEL=claude-opus-4.5, CURSOR_REVIEW_MODEL=gpt-5.1-codex +CURSOR_MODEL= +CURSOR_REVIEW_MODEL= diff --git a/tools/typescript/essentials-sync/.gitignore b/tools/typescript/essentials-sync/.gitignore new file mode 100644 index 0000000..d8421de --- /dev/null +++ b/tools/typescript/essentials-sync/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +.env +.env.* +!.env.example +*.log +.DS_Store diff --git a/tools/typescript/essentials-sync/.nvmrc b/tools/typescript/essentials-sync/.nvmrc new file mode 100644 index 0000000..e222811 --- /dev/null +++ b/tools/typescript/essentials-sync/.nvmrc @@ -0,0 +1 @@ +22.19.0 diff --git a/tools/typescript/essentials-sync/.python-version b/tools/typescript/essentials-sync/.python-version new file mode 100644 index 0000000..2d4715b --- /dev/null +++ b/tools/typescript/essentials-sync/.python-version @@ -0,0 +1 @@ +3.11.11 diff --git a/tools/typescript/essentials-sync/.secretlintrc.json b/tools/typescript/essentials-sync/.secretlintrc.json new file mode 100644 index 0000000..7a1a5df --- /dev/null +++ b/tools/typescript/essentials-sync/.secretlintrc.json @@ -0,0 +1,7 @@ +{ + "rules": [ + { + "id": "@secretlint/secretlint-rule-preset-recommend" + } + ] +} diff --git a/tools/typescript/essentials-sync/README.md b/tools/typescript/essentials-sync/README.md new file mode 100644 index 0000000..e5cbffb --- /dev/null +++ b/tools/typescript/essentials-sync/README.md @@ -0,0 +1,138 @@ +# essentials-sync + +Extract a jargon-free `ess-*` shared package out of a team- or account-specific tool in your private repo, then sync it to the open-source `essentials` repo. Single command. Deterministic scanners and an adversarial LLM reviewer between you and either tree. + +## Example + +```bash +essentials-sync \ + --source ~/code/honeycomb/tools/python/service-now-incident \ + --target-repo ~/code/essentials +``` + +That invocation produces three artifacts across two repos: + +``` +honeycomb/tools/python/service-now-incident/ (rewritten as a thin wrapper, Cisco defaults) +honeycomb/packages/python/ess-service-now-incident/ (NEW: generic core, no defaults) +essentials/packages/python/ess-service-now-incident/ (NEW: copy of the generic core) +``` + +The wrapper depends on the extracted package via `[tool.uv.sources] ess-service-now-incident = { workspace = true }` -- the same shape as `sales-ai-langsmith-hosting` / `langsmith-hosting`. The extracted and synced packages are scanned and reviewed; the wrapper is intentionally left alone so it can keep the company-specific defaults that justify its existence. + +## How it works + +Two phases run sequentially in a single invocation: + +**Phase A (extract, writes to the source repo).** A primary model (highest available Opus by default) reads the original tool, splits the reusable logic into `packages/python/ess-/`, and rewrites `tools/python//` as a wrapper that imports the new package and supplies the company defaults. The extracted package is then scanned by TruffleHog, secretlint, the jargon regex, and the PII regex. If any critical findings remain, the agent is asked to revise -- up to `--max-revisions` iterations. + +**Phase B (sync, writes to the target repo).** Once Phase A is clean, a fresh primary agent copies the extracted package into `essentials/packages/python/ess-/`. The result is scanned again, then an adversarial reviewer running on a different model family (highest available Codex by default) does a paranoid pass. Findings flow back into the scan-and-revise loop. + +Critical findings block; non-blocking reviewer findings are surfaced as warnings so you can address them by hand. On success, both working trees are left dirty for you to review with `git diff` and commit yourself. + +For sources that are already generic and don't need an extract step, pass `--no-extract` and the tool runs Phase B only against `--source`. + +## Installation + +```bash +cd essentials/tools/typescript/essentials-sync +nvm use # picks up local .nvmrc (Node 22.19.0) +pyenv shell # picks up local .python-version (3.11.11) +npm install +npm run build +npm link # optional: puts `essentials-sync` on $PATH +brew install trufflehog +``` + +This package pins its own Node and Python versions (`.nvmrc` + `.python-version`) that diverge from the parent `essentials/` repo. The reason: `@cursor/sdk` pulls in a `sqlite3` build that requires `distutils` (removed in Python 3.12) when it can't find a prebuilt binary. See "Install troubleshooting" below for the full story. + +Required environment variable: + +```bash +export CURSOR_API_KEY=cursor_... # cursor.com/dashboard/integrations -> API Keys +``` + +Or copy `.env.example` to `.env` and edit it; the CLI auto-loads `.env` from the current working directory and from the package root (`essentials-sync/.env`). Pre-existing process env vars take precedence over `.env` values. + +Optional environment variables: + +- `CURSOR_MODEL` -- default primary model spec (overrides the `opus` sentinel). +- `CURSOR_REVIEW_MODEL` -- default reviewer model spec (overrides the `codex` sentinel). + +### Install troubleshooting + +The local `.nvmrc` (Node 22.19.0) and `.python-version` (3.11.11) handle this for anyone using `nvm` + `pyenv`. If you don't use those: + +`@cursor/sdk` transitively depends on `sqlite3@5.1.7`, which can fall back to compiling a native addon at install time. That fallback path bundles `node-gyp@8`, which still imports `distutils` -- removed in Python 3.12 per [PEP 632](https://peps.python.org/pep-0632/). If `npm install` dies with `ModuleNotFoundError: No module named 'distutils'`, point npm at a Python that still has it (3.11 or earlier): + +```bash +# one-time, for this install +npm_config_python=/path/to/python3.11 npm install + +# or persist via .npmrc using env-var substitution (npm 8.6+) +echo 'python = ${HOME}/.pyenv/versions/3.11.11/bin/python3' >> .npmrc +``` + +Why a per-package pin: the parent `essentials/` repo pins Python 3.12 globally, which is what the rest of the workspace needs. This package overrides the Python pin only inside its own directory; `pyenv` honors the deepest `.python-version` walking up the tree. + +## Customizing the jargon wordlist + +Drop a `.essentials-sync-jargon.json` file at the root of the original source tool (or, in `--no-extract` mode, the source package) to extend the built-in wordlist. Either a flat array or `{ "terms": [...] }` is accepted: + +```json +{ + "terms": [ + "internal-codename", + "*.internal.example.com" + ] +} +``` + +Entries that start with `*.` are treated as hostname suffixes; everything else as case-insensitive word-boundary matches. Per-org employee-ID patterns belong here too -- the bundled PII scanner only flags emails, phone numbers, and SSNs. + +## Usage + +``` +essentials-sync [options] +``` + +| Option | Required | Description | +| --- | --- | --- | +| `--source ` | yes | Absolute path to the source tool directory. In default (extract) mode this must be a `tools/python//` directory inside the source repo. | +| `--target-repo ` | yes | Absolute path to the target repo root. Must be a git working tree. | +| `--target-path ` | no | Path relative to `--target-repo` where the synced package lands. Default: `packages/python/`. Required when `--no-extract` is set. | +| `--source-repo ` | no | Source repo root. Default: the git root discovered by walking up from `--source`. | +| `--package-name ` | no | Override for the extracted `ess-*` package name. Must start with `ess-` and be kebab-case. Default: `ess-`. | +| `--no-extract` | no | Skip Phase A. Assume `--source` is already a generic package and only run the sync to essentials. | +| `--model ` | no | Primary model. Accepts a concrete ID, a family sentinel (`opus`, `codex`, `claude`, `gpt`, `gemini`, `composer`), or `auto`. Default: `opus`. | +| `--review-model ` | no | Adversarial reviewer model. Same shape as `--model`. Default: `codex`. | +| `--max-revisions ` | no | Maximum scan-and-revise iterations *per phase*. Default: `3`. | +| `--dry-run` | no | Write to a temp directory instead of `--target-path`. | +| `--no-source-scan` | no | Skip the informational source pre-scan. | +| `--no-adversarial-review` | no | Skip the LLM reviewer; run deterministic scanners only. | +| `--list-models` | no | Print the available model IDs for your `CURSOR_API_KEY` and exit. | + +The `opus` sentinel resolves to the highest-version Opus model on your Cursor account; `codex` to the highest-version Codex; etc. Fallback chains kick in if a family is unavailable (primary: `opus` -> any `claude` -> `auto`; reviewer: `codex` -> any `gpt` -> any non-primary -> `auto`). The resolver prefers the canonical version of each family (e.g. `gpt-5.3-codex` over `gpt-5.3-codex-spark`) and always logs the pair it chose so you can see what actually ran. + +### Scan coverage + +In extract mode (the default), scanners only run against the **extracted package**, not the wrapper. That is deliberate: the wrapper exists to hold the company-specific defaults that the open-source library cannot ship with. In `--no-extract` mode, scanners run against the sync target (the essentials copy) as before. + +## Exit codes + +- `0` -- both phases clean; review and commit yourself. +- `1` -- could not start (bad args, missing API key, agent startup error). +- `2` -- a phase still has critical findings after `--max-revisions`. Both working trees are left dirty for inspection. +- `3` -- primary agent ran but failed mid-execution. Use the logged `agent` and `run` IDs to investigate via `Agent.getRun(...)` or the Cursor dashboard. + +## Running tests + +```bash +npm test +``` + +Tests use [Vitest](https://vitest.dev) and exercise the jargon and PII scanners against the `clean-package` and `dirty-package` fixtures under `tests/fixtures/`, plus the extract-plan name/path derivation logic. + +## License + +[Apache 2.0](https://choosealicense.com/licenses/apache-2.0/). diff --git a/tools/typescript/essentials-sync/package-lock.json b/tools/typescript/essentials-sync/package-lock.json new file mode 100644 index 0000000..49681e4 --- /dev/null +++ b/tools/typescript/essentials-sync/package-lock.json @@ -0,0 +1,5020 @@ +{ + "name": "essentials-sync", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "essentials-sync", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@cursor/sdk": "latest", + "@secretlint/secretlint-rule-preset-recommend": "^9.0.0", + "chalk": "^5.3.0", + "commander": "^12.1.0", + "execa": "^9.5.1", + "secretlint": "^9.0.0" + }, + "bin": { + "essentials-sync": "dist/cli.js" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "tsx": "^4.19.0", + "typescript": "^5.6.0", + "vitest": "^2.1.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", + "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@connectrpc/connect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.7.0.tgz", + "integrity": "sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^1.10.0" + } + }, + "node_modules/@connectrpc/connect-node": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-1.7.0.tgz", + "integrity": "sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A==", + "license": "Apache-2.0", + "dependencies": { + "undici": "^5.28.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^1.10.0", + "@connectrpc/connect": "1.7.0" + } + }, + "node_modules/@cursor/sdk": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk/-/sdk-1.0.13.tgz", + "integrity": "sha512-w6MWkgOTL6yb6GV/4Odx7QcamQgqhzX/CzcMBkqiiOPTPuEWItWrgA0qdivchm5YJXTt+LZkFSEQ/Ti44hVbfg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@bufbuild/protobuf": "1.10.0", + "@connectrpc/connect": "^1.6.1", + "@connectrpc/connect-node": "^1.6.1", + "@statsig/js-client": "3.31.0", + "sqlite3": "^5.1.7", + "zod": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@cursor/sdk-darwin-arm64": "1.0.13", + "@cursor/sdk-darwin-x64": "1.0.13", + "@cursor/sdk-linux-arm64": "1.0.13", + "@cursor/sdk-linux-x64": "1.0.13", + "@cursor/sdk-win32-x64": "1.0.13" + } + }, + "node_modules/@cursor/sdk-darwin-arm64": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-arm64/-/sdk-darwin-arm64-1.0.13.tgz", + "integrity": "sha512-zHRTNtVRHw4KSAEFmtO0Av7jv9D60DrB+pygVNWGyKtRR44fcwtRHuLAJmO4HThxQw7MMvUJuAaNmCQxzHtPDQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cursor/sdk-darwin-x64": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-x64/-/sdk-darwin-x64-1.0.13.tgz", + "integrity": "sha512-7XsIkMKp6h/4W9zBx02Py1euJLAJVxlkwmm9GSoUjc+3hfFvHY/R/WTbX2TFgF4g1vOAq/HM7GmXBXq+e4M4+w==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cursor/sdk-linux-arm64": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-arm64/-/sdk-linux-arm64-1.0.13.tgz", + "integrity": "sha512-bDgfPPgc84gUn3k+Iiq5OLZozzM0UYZdKbQ821pbZy1OPWTFaSkjXsoAB6xqf9wALWyW1eQxOC4RprPBLoy+yA==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cursor/sdk-linux-x64": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-x64/-/sdk-linux-x64-1.0.13.tgz", + "integrity": "sha512-BTccnB5hVqK8Y0778oql6gbk7kIIlzQrBqt5QNLJpwBidjjde/mlvAajVB9hB3a29jelOwm0gJjMsLfqTkEPdw==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cursor/sdk-win32-x64": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-win32-x64/-/sdk-win32-x64-1.0.13.tgz", + "integrity": "sha512-GxWlwj4G513EfGmvPVBa4y+vNn9B5Cj+npu8fVcJ0P+U9sruhgo4pvqGbWxkn5EIKbpGoraLq9QB4nFeoT1uRQ==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@secretlint/config-creator": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-9.3.4.tgz", + "integrity": "sha512-GRMYfHJ+rewwB26CC3USVObqSQ/mDLXzXcUMJw/wJisPr3HDZmdsYlcsNnaAcGN+EZmvqSDkgSibQm1hyZpzbg==", + "license": "MIT", + "dependencies": { + "@secretlint/types": "^9.3.4" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/@secretlint/config-loader": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-9.3.4.tgz", + "integrity": "sha512-sy+yWDWh4cbAbpQYLiO39DjwNGEK1EUhTqNamLLBo163BdJP10FIWhqpe8mtGQBSBXRtxr8Hg/gc3Xe4meIoww==", + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^9.3.4", + "@secretlint/resolver": "^9.3.4", + "@secretlint/types": "^9.3.4", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/@secretlint/core": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-9.3.4.tgz", + "integrity": "sha512-ErIVHI6CJd191qdNKuMkH3bZQo9mWJsrSg++bQx64o0WFuG5nPvkYrDK0p/lebf+iQuOnzvl5HrZU6GU9a6o+Q==", + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^9.3.4", + "@secretlint/types": "^9.3.4", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-9.3.4.tgz", + "integrity": "sha512-ARpoBOKz6WP3ocLITCFkR1/Lj636ugpBknylhlpc45r5aLdvmyvWAJqodlw5zmUCfgD6JXeAMf3Hi60aAiuqWQ==", + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^9.3.4", + "@secretlint/types": "^9.3.4", + "@textlint/linter-formatter": "^14.7.2", + "@textlint/module-interop": "^14.7.2", + "@textlint/types": "^14.7.2", + "chalk": "^4.1.2", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "terminal-link": "^2.1.1" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/node": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-9.3.4.tgz", + "integrity": "sha512-S0u8i+CnPmyAKtuccgot9L5cmw6DqJc0F+b3hhVIALd8kkeLt3RIXOOej15tU7N0V1ISph90Gz92V72ovsprgQ==", + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^9.3.4", + "@secretlint/core": "^9.3.4", + "@secretlint/formatter": "^9.3.4", + "@secretlint/profiler": "^9.3.4", + "@secretlint/source-creator": "^9.3.4", + "@secretlint/types": "^9.3.4", + "debug": "^4.4.1", + "p-map": "^4.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-9.3.4.tgz", + "integrity": "sha512-99WmaHd4dClNIm5BFsG++E6frNIZ3qVwg6s804Ql/M19pDmtZOoVCl4/UuzWpwNniBqLIgn9rHQZ/iGlIW3wyw==", + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-9.3.4.tgz", + "integrity": "sha512-L1lIrcjzqcspPzZttmOvMmOFDpJTYFyRBONg94TZBWrpv4x0w5G2SYR+K7EE1SbYQAiPxw1amoXT1YRP8cZF2A==", + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-9.3.4.tgz", + "integrity": "sha512-RvzrLNN2A0B2bYQgRSRjh2dkdaIDuhXjj4SO5bElK1iBtJNiD6VBTxSSY1P3hXYaBeva7MEF+q1PZ3cCL8XYOA==", + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-9.3.4.tgz", + "integrity": "sha512-I9ZA1gm9HJNaAhZiQdInY9VM04VTAGDV4bappVbEJzMUDnK/LTbYqfQ88RPqgCGCqa6ee8c0/j5Bn7ypweouIw==", + "license": "MIT", + "dependencies": { + "@secretlint/types": "^9.3.4", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-9.3.4.tgz", + "integrity": "sha512-z9rdKHNeL4xa48+367RQJVw1d7/Js9HIQ+gTs/angzteM9osfgs59ad3iwVRhCGYbeUoUUDe2yxJG2ylYLaH3Q==", + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@statsig/client-core": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@statsig/client-core/-/client-core-3.31.0.tgz", + "integrity": "sha512-SuxQD6TmVszPG7FoMKwTk/uyBuVFk7XnxI3T/E0uyb7PL7GNjONtfsoh+NqBBVUJVse0CUeSFfgJPoZy1ZOslQ==", + "license": "ISC" + }, + "node_modules/@statsig/js-client": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@statsig/js-client/-/js-client-3.31.0.tgz", + "integrity": "sha512-LFa5E0LjT6sTfZv3sNGoyRLSZ1078+agdgOA+Vm1ecjG+KbSOfBLTW7hMwimrJ29slRwbYDzbtKaPJo/R37N2g==", + "license": "ISC", + "dependencies": { + "@statsig/client-core": "3.31.0" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "14.8.4", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-14.8.4.tgz", + "integrity": "sha512-+fI7miec/r9VeniFV9ppL4jRCmHNsTxieulTUf/4tvGII3db5hGriKHC4p/diq1SkQ9Sgs7kg6UyydxZtpTz1Q==", + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "14.8.4", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-14.8.4.tgz", + "integrity": "sha512-sZ0UfYRDBNHnfMVBqLqqYnqTB7Ec169ljlmo+SEHR1T+dHUPYy1/DZK4p7QREXlBSFL4cnkswETCbc9xRodm4Q==", + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "14.8.4", + "@textlint/resolver": "14.8.4", + "@textlint/types": "14.8.4", + "chalk": "^4.1.2", + "debug": "^4.4.1", + "js-yaml": "^3.14.1", + "lodash": "^4.17.21", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "license": "MIT" + }, + "node_modules/@textlint/module-interop": { + "version": "14.8.4", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-14.8.4.tgz", + "integrity": "sha512-1LdPYLAVpa27NOt6EqvuFO99s4XLB0c19Hw9xKSG6xQ1K82nUEyuWhzTQKb3KJ5Qx7qj14JlXZLfnEuL6A16Bw==", + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "14.8.4", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-14.8.4.tgz", + "integrity": "sha512-nMDOgDAVwNU9ommh+Db0U+MCMNDPbQ/1HBNjbnHwxZkCpcT6hsAJwBe38CW/DtWVUv8yeR4R40IYNPT84srNwA==", + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "14.8.4", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-14.8.4.tgz", + "integrity": "sha512-9nyY8vVXlr8hHKxa6+37omJhXWCwovMQcgMteuldYd4dOxGm14AK2nXdkgtKEUQnzLGaXy46xwLCfhQy7V7/YA==", + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "14.8.4" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "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/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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", + "optional": true + }, + "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/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "license": "BSD-2-Clause" + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "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/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT", + "optional": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "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/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/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "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", + "engines": { + "node": ">=8" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "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/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "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", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "optional": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.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", + "optional": true + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "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", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "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/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, + "node_modules/rc-config-loader/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/rc-config-loader/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/read-pkg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", + "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.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/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/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/secretlint": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-9.3.4.tgz", + "integrity": "sha512-iNOzgMX/+W1SQNW/TW6eikGChyaPiazr2AEXjzjpoB0R6QJEulvlwhn0KLT1/xjPfdYrk3yiXZM40csUqET8uQ==", + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^9.3.4", + "@secretlint/formatter": "^9.3.4", + "@secretlint/node": "^9.3.4", + "@secretlint/profiler": "^9.3.4", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^8.1.0" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "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/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "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/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/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", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", + "optional": true, + "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": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "license": "CC0-1.0" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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/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-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar 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": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "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-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "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/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "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/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/tools/typescript/essentials-sync/package.json b/tools/typescript/essentials-sync/package.json new file mode 100644 index 0000000..68d5ca5 --- /dev/null +++ b/tools/typescript/essentials-sync/package.json @@ -0,0 +1,35 @@ +{ + "name": "essentials-sync", + "version": "0.1.0", + "description": "Move or sync packages from a company repo into the open-source essentials repo with adversarial review and deterministic scanning.", + "type": "module", + "bin": { + "essentials-sync": "./dist/cli.js" + }, + "main": "./dist/cli.js", + "scripts": { + "build": "tsc", + "dev": "tsx src/cli.ts", + "start": "node dist/cli.js", + "test": "vitest run", + "lint": "tsc --noEmit" + }, + "license": "Apache-2.0", + "engines": { + "node": ">=22" + }, + "dependencies": { + "@cursor/sdk": "latest", + "chalk": "^5.3.0", + "commander": "^12.1.0", + "execa": "^9.5.1", + "secretlint": "^9.0.0", + "@secretlint/secretlint-rule-preset-recommend": "^9.0.0" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "tsx": "^4.19.0", + "typescript": "^5.6.0", + "vitest": "^2.1.0" + } +} diff --git a/tools/typescript/essentials-sync/src/agent.ts b/tools/typescript/essentials-sync/src/agent.ts new file mode 100644 index 0000000..48b8854 --- /dev/null +++ b/tools/typescript/essentials-sync/src/agent.ts @@ -0,0 +1,364 @@ +import { Agent } from "@cursor/sdk"; +import type { SDKAgent } from "@cursor/sdk"; +import chalk from "chalk"; +import type { Finding, ScanReport, SyncPlan } from "./types.js"; +import { + EXTRACT_SYSTEM_PROMPT, + SYNC_SYSTEM_PROMPT, + buildExtractInitialPrompt, + buildExtractRevisionPrompt, + buildSyncInitialPrompt, + buildSyncRevisionPrompt, +} from "./prompts.js"; +import { runScanners, hasCriticalFindings, formatFindings } from "./scanners/index.js"; +import { runReviewer } from "./reviewer.js"; + +export type SessionPhase = "extract" | "sync"; + +export interface RunSyncSessionInputs { + apiKey: string; + primaryModel: string; + reviewModel: string; + plan: SyncPlan; + sourceScan: ScanReport | null; + maxRevisions: number; + adversarialReview: boolean; + extraJargonTerms?: string[]; +} + +export interface RunSyncSessionResult { + status: "clean" | "exhausted-revisions" | "agent-error"; + phase: SessionPhase; + remainingFindings: Finding[]; + iterations: number; + agentId?: string; + lastRunId?: string; + errorMessage?: string; +} + +export async function runSyncSession( + inputs: RunSyncSessionInputs, +): Promise { + if (inputs.plan.phaseAEnabled) { + const extractResult = await runExtractPhase(inputs); + if (extractResult.status !== "clean") { + return { ...extractResult, phase: "extract" }; + } + console.log( + chalk.green( + `[extract] phase clean after ${extractResult.iterations} revision(s); ` + + `proceeding to sync.`, + ), + ); + } + const syncResult = await runSyncPhase(inputs); + return { ...syncResult, phase: "sync" }; +} + +interface PhaseResult { + status: "clean" | "exhausted-revisions" | "agent-error"; + remainingFindings: Finding[]; + iterations: number; + agentId?: string; + lastRunId?: string; + errorMessage?: string; +} + +async function runExtractPhase(inputs: RunSyncSessionInputs): Promise { + const { apiKey, primaryModel, reviewModel, plan, sourceScan, maxRevisions, adversarialReview, extraJargonTerms } = inputs; + if (!plan.extractedPkgAbs || !plan.sourceRepoRoot) { + throw new Error("runExtractPhase called without extract metadata on the plan."); + } + const agent = await Agent.create({ + apiKey, + model: { id: primaryModel }, + local: { cwd: plan.sourceRepoRoot, settingSources: [] }, + }); + try { + const initialPrompt = `${EXTRACT_SYSTEM_PROMPT}\n\n---\n\n${buildExtractInitialPrompt({ plan, sourceScan })}`; + return await scanAndReviseLoop({ + agent, + apiKey, + reviewModel, + plan, + scanRootAbs: plan.extractedPkgAbs, + reviewRootAbs: plan.extractedPkgAbs, + reviewerCwd: plan.sourceRepoRoot, + initialPrompt, + buildRevisionPrompt: (findings, iteration) => + buildExtractRevisionPrompt({ + plan, + iteration, + maxRevisions, + combinedFindings: findings, + }), + maxRevisions, + adversarialReview, + extraJargonTerms, + logPrefix: "[extract]", + }); + } finally { + await agent[Symbol.asyncDispose](); + } +} + +async function runSyncPhase(inputs: RunSyncSessionInputs): Promise { + const { apiKey, primaryModel, reviewModel, plan, sourceScan, maxRevisions, adversarialReview, extraJargonTerms } = inputs; + const agent = await Agent.create({ + apiKey, + model: { id: primaryModel }, + local: { cwd: plan.targetRepoAbs, settingSources: [] }, + }); + try { + const jargonHeavy = sourceScan + ? sourceScan.findings.filter((finding) => finding.source === "jargon").length > 8 + : false; + const initialPrompt = `${SYNC_SYSTEM_PROMPT}\n\n---\n\n${buildSyncInitialPrompt({ plan, sourceScan, jargonHeavy })}`; + return await scanAndReviseLoop({ + agent, + apiKey, + reviewModel, + plan, + scanRootAbs: plan.targetAbs, + reviewRootAbs: plan.targetAbs, + reviewerCwd: plan.targetRepoAbs, + initialPrompt, + buildRevisionPrompt: (findings, iteration) => + buildSyncRevisionPrompt({ + plan, + iteration, + maxRevisions, + combinedFindings: findings, + }), + maxRevisions, + adversarialReview, + extraJargonTerms, + logPrefix: "[primary]", + }); + } finally { + await agent[Symbol.asyncDispose](); + } +} + +interface ScanAndReviseInputs { + agent: SDKAgent; + apiKey: string; + reviewModel: string; + plan: SyncPlan; + scanRootAbs: string; + reviewRootAbs: string; + reviewerCwd: string; + initialPrompt: string; + buildRevisionPrompt: (findings: Finding[], iteration: number) => string; + maxRevisions: number; + adversarialReview: boolean; + extraJargonTerms?: string[]; + logPrefix: string; +} + +async function scanAndReviseLoop({ + agent, + apiKey, + reviewModel, + plan, + scanRootAbs, + reviewRootAbs, + reviewerCwd, + initialPrompt, + buildRevisionPrompt, + maxRevisions, + adversarialReview, + extraJargonTerms, + logPrefix, +}: ScanAndReviseInputs): Promise { + let lastRunId: string | undefined; + + const initialRun = await agent.send(initialPrompt); + lastRunId = initialRun.id; + console.log(chalk.dim(`${logPrefix} agent=${agent.agentId} run=${initialRun.id}`)); + await streamAgentText(initialRun, `${logPrefix} `); + const initialResult = await initialRun.wait(); + if (initialResult.status === "error") { + return { + status: "agent-error", + remainingFindings: [], + iterations: 0, + agentId: agent.agentId, + lastRunId, + errorMessage: initialResult.result ?? "no error message returned by SDK", + }; + } + + // Suppress unused-variable noise from the parent plan when we don't reach the + // SyncPlan-typed branch. + void plan; + + for (let iteration = 1; iteration <= maxRevisions + 1; iteration += 1) { + const deterministic = await runScanners({ + rootPath: scanRootAbs, + jargonSeverity: "critical", + extraJargonTerms, + }); + logScanReport(`${logPrefix.replace(/\[|\]/g, "")} deterministic`, deterministic); + + let combinedFindings: Finding[] = deterministic.findings.filter( + (finding) => finding.severity === "critical", + ); + + if (combinedFindings.length === 0 && adversarialReview) { + console.log(chalk.dim(`[reviewer] running on model ${reviewModel}`)); + const reviewerFindings = await runReviewer({ + apiKey, + reviewModel, + plan, + reviewRootAbs, + cwd: reviewerCwd, + }); + const reviewerCritical = reviewerFindings.filter( + (finding) => finding.severity === "critical", + ); + const reviewerWarnings = reviewerFindings.filter( + (finding) => finding.severity === "warning", + ); + combinedFindings = reviewerCritical; + if (reviewerFindings.length > 0) { + console.log( + chalk.dim( + `[reviewer] returned ${reviewerFindings.length} finding(s) ` + + `(critical=${reviewerCritical.length}, warning=${reviewerWarnings.length})`, + ), + ); + } + if (reviewerWarnings.length > 0) { + console.log( + chalk.yellow( + `[reviewer] ${reviewerWarnings.length} non-blocking finding(s) (review manually):`, + ), + ); + for (const finding of reviewerWarnings) { + console.log( + chalk.yellow( + ` - ${finding.file}:${finding.line} [${finding.type}] ${finding.message}`, + ), + ); + } + } + } + + if (combinedFindings.length === 0) { + return { + status: "clean", + remainingFindings: [], + iterations: iteration - 1, + agentId: agent.agentId, + lastRunId, + }; + } + + if (iteration > maxRevisions) { + return { + status: "exhausted-revisions", + remainingFindings: combinedFindings, + iterations: maxRevisions, + agentId: agent.agentId, + lastRunId, + }; + } + + const revisionPrompt = buildRevisionPrompt(combinedFindings, iteration); + const revisionRun = await agent.send(revisionPrompt); + lastRunId = revisionRun.id; + console.log( + chalk.dim( + `${logPrefix} revision ${iteration}/${maxRevisions} run=${revisionRun.id}`, + ), + ); + await streamAgentText(revisionRun, `${logPrefix} revision ${iteration} `); + const revisionResult = await revisionRun.wait(); + if (revisionResult.status === "error") { + return { + status: "agent-error", + remainingFindings: combinedFindings, + iterations: iteration, + agentId: agent.agentId, + lastRunId, + errorMessage: revisionResult.result ?? "no error message returned by SDK", + }; + } + } + + return { + status: "exhausted-revisions", + remainingFindings: [], + iterations: maxRevisions, + agentId: agent.agentId, + lastRunId, + }; +} + +interface AssistantBlock { + type: string; + text?: unknown; +} + +interface AssistantEvent { + type: string; + message?: { + content?: AssistantBlock[]; + }; +} + +async function streamAgentText( + run: { stream: () => AsyncIterable; supports: (op: "stream") => boolean }, + prefix: string, +): Promise { + if (!run.supports("stream")) { + return; + } + try { + for await (const event of run.stream()) { + const text = extractAssistantText(event); + if (text) { + process.stdout.write(chalk.dim(prefix) + text); + } + } + } catch { + // Stream is best-effort; failures here do not block run.wait(). + } +} + +function extractAssistantText(event: unknown): string | null { + if (!event || typeof event !== "object") return null; + const typed = event as AssistantEvent; + if (typed.type !== "assistant") return null; + const content = typed.message?.content; + if (!Array.isArray(content)) return null; + const parts: string[] = []; + for (const block of content) { + if (block && block.type === "text" && typeof block.text === "string") { + parts.push(block.text); + } + } + return parts.length > 0 ? parts.join("") : null; +} + +function logScanReport(label: string, report: ScanReport): void { + if (!hasCriticalFindings(report) && report.findings.length === 0) { + console.log(chalk.green(`[scan] ${label}: clean (${report.durationMs} ms)`)); + return; + } + const counts = report.findings.reduce>((acc, finding) => { + const key = finding.severity; + acc[key] = (acc[key] ?? 0) + 1; + return acc; + }, {}); + console.log( + chalk.yellow( + `[scan] ${label}: ${report.findings.length} finding(s) ` + + `(critical=${counts.critical ?? 0}, warning=${counts.warning ?? 0}, ${report.durationMs} ms)`, + ), + ); + if (hasCriticalFindings(report)) { + console.log(formatFindings(report)); + } +} diff --git a/tools/typescript/essentials-sync/src/cli.ts b/tools/typescript/essentials-sync/src/cli.ts new file mode 100644 index 0000000..37f8cb6 --- /dev/null +++ b/tools/typescript/essentials-sync/src/cli.ts @@ -0,0 +1,368 @@ +#!/usr/bin/env node +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { Command } from "commander"; +import chalk from "chalk"; +import { CursorAgentError } from "@cursor/sdk"; +import { runScanners, formatFindings } from "./scanners/index.js"; +import { planSync, composeFullPlan } from "./sync.js"; +import { planExtract } from "./extract-plan.js"; +import { runSyncSession } from "./agent.js"; +import { + listAvailableModels, + parseModelSpec, + resolveModels, +} from "./model-resolver.js"; + +loadEnvFiles(); + +const DEFAULT_PRIMARY = "opus"; +const DEFAULT_REVIEW = "codex"; +const DEFAULT_MAX_REVISIONS = 3; + +const program = new Command() + .name("essentials-sync") + .description( + "Extract a jargon-free ess-* shared package out of a team-/account-specific tool" + + " in the source repo, then sync that shared package to the open-source essentials repo." + + " Default behavior runs both phases; pass --no-extract for sources that are already generic.", + ) + .option("-s, --source ", "Absolute path to the source tool directory (tools/python//)") + .option("-r, --target-repo ", "Absolute path to the target repo root (must be a git working tree)") + .option("-t, --target-path ", "Path relative to --target-repo where the package should land (default: packages/python/)") + .option("--source-repo ", "Source repo root (default: git root of --source)") + .option("--package-name ", "Name of the extracted ess-* package (default: derived from --source basename)") + .option("-m, --model ", "Primary agent model id, family sentinel, or 'auto'") + .option("--review-model ", "Adversarial reviewer model id, family sentinel, or 'auto'") + .option("--max-revisions ", "Maximum scan-and-revise iterations per phase", parseInteger, DEFAULT_MAX_REVISIONS) + .option("--dry-run", "Write everything to a temp directory instead of --target-path", false) + .option("--no-source-scan", "Skip the informational source pre-scan") + .option("--no-adversarial-review", "Skip the LLM reviewer; deterministic scanners only") + .option("--no-extract", "Skip the in-source extract phase; assume --source is already a generic package and only run the sync to essentials") + .option("--list-models", "Print the model catalog for the current API key and exit", false) + .parse(process.argv); + +void main(program); + +async function main(cmd: Command): Promise { + const opts = cmd.opts(); + + const apiKey = process.env.CURSOR_API_KEY ?? ""; + if (!apiKey) { + fail(1, "CURSOR_API_KEY is not set. Export it or add it to .env."); + } + + if (opts.listModels) { + await handleListModels(apiKey); + return; + } + + const source = requireOption(opts.source as string | undefined, "--source"); + const targetRepo = requireOption(opts.targetRepo as string | undefined, "--target-repo"); + const explicitTargetPath = opts.targetPath as string | undefined; + const sourceRepoOpt = opts.sourceRepo as string | undefined; + const packageNameOpt = opts.packageName as string | undefined; + + const modelArg = (opts.model as string | undefined) + ?? process.env.CURSOR_MODEL + ?? DEFAULT_PRIMARY; + const reviewModelArg = (opts.reviewModel as string | undefined) + ?? process.env.CURSOR_REVIEW_MODEL + ?? DEFAULT_REVIEW; + + const maxRevisions = (opts.maxRevisions as number | undefined) ?? DEFAULT_MAX_REVISIONS; + const dryRun = Boolean(opts.dryRun); + const noSourceScan = opts.sourceScan === false; + const noAdversarial = opts.adversarialReview === false; + const extractEnabled = opts.extract !== false; + + let extractHalf = null; + if (extractEnabled) { + try { + extractHalf = await planExtract({ + source, + sourceRepo: sourceRepoOpt, + packageName: packageNameOpt, + }); + } catch (error) { + fail(1, error instanceof Error ? error.message : String(error)); + } + if (extractHalf.alreadyExtracted) { + console.log( + chalk.yellow( + `[extract] WARNING: ${extractHalf.extractedPkgAbs} already exists. ` + + `Re-running extract will overwrite it. Pass --no-extract to skip Phase A ` + + `and sync the existing extracted package as-is.`, + ), + ); + } + } + + const resolvedTargetPath = explicitTargetPath + ?? extractHalf?.names.defaultTargetPathRel + ?? requireOption(undefined, "--target-path (required when --no-extract is set)"); + + let syncHalf; + let planTargetRepoForUx = targetRepo; + try { + const planTarget = dryRun + ? await prepareDryRunTarget(resolvedTargetPath) + : { repo: targetRepo, relPath: resolvedTargetPath }; + planTargetRepoForUx = planTarget.repo; + const sourceForSync = extractHalf ? extractHalf.extractedPkgAbs : source; + syncHalf = await planSync({ + source: sourceForSync, + targetRepo: planTarget.repo, + targetPath: planTarget.relPath, + // When extract is enabled, the source will not exist until Phase A + // materializes it, so we cannot assert existence here. + assertSourceExists: !extractHalf, + }); + } catch (error) { + fail(1, error instanceof Error ? error.message : String(error)); + } + + const plan = composeFullPlan({ syncHalf, extractHalf }); + + if (plan.phaseAEnabled) { + console.log( + chalk.cyan( + `[plan] phase-A extract` + + ` original=${plan.wrapperPkgAbs}` + + ` extracted=${plan.extractedPkgAbs}` + + ` package=${plan.packageName}`, + ), + ); + } + console.log( + chalk.cyan( + `[plan] phase-B sync mode=${plan.mode} source=${plan.sourceAbs} target=${plan.targetAbs}`, + ), + ); + + // Jargon overrides live next to the original source (the wrapper / current + // tool directory). They apply to scans of both the extracted package and + // the synced target. + const jargonAnchor = plan.wrapperPkgAbs ?? plan.sourceAbs; + const extraJargonTerms = await loadJargonOverrides(jargonAnchor); + if (extraJargonTerms.length > 0) { + console.log( + chalk.dim( + `[jargon] loaded ${extraJargonTerms.length} extra term(s) from .essentials-sync-jargon.json`, + ), + ); + } + + let sourceScan = null; + if (!noSourceScan) { + // Scan the original tool when extract is enabled (so the agent gets a + // useful summary). For pure sync mode, scan the source directory. + const sourceScanRoot = plan.wrapperPkgAbs ?? plan.sourceAbs; + console.log(chalk.cyan(`[source-scan] running deterministic scanners on ${sourceScanRoot}`)); + sourceScan = await runScanners({ + rootPath: sourceScanRoot, + jargonSeverity: "warning", + extraJargonTerms, + }); + console.log( + chalk.dim( + `[source-scan] ${sourceScan.findings.length} finding(s) in ${sourceScan.durationMs} ms`, + ), + ); + } + + let resolved; + try { + resolved = await resolveModels({ + apiKey, + primarySpec: parseModelSpec(modelArg), + reviewSpec: parseModelSpec(reviewModelArg), + }); + } catch (error) { + fail(1, `Could not resolve models via Cursor.models.list: ${error instanceof Error ? error.message : String(error)}`); + } + + console.log( + chalk.cyan( + `[models] primary=${resolved.primary} (${resolved.primarySource})` + + ` reviewer=${resolved.review} (${resolved.reviewSource})`, + ), + ); + if (resolved.collapsed) { + console.log( + chalk.yellow( + "[models] WARNING: primary and reviewer resolved to the same model. Adversarial diversity is degraded.", + ), + ); + } + + try { + const result = await runSyncSession({ + apiKey, + primaryModel: resolved.primary, + reviewModel: resolved.review, + plan, + sourceScan, + maxRevisions, + adversarialReview: !noAdversarial, + extraJargonTerms, + }); + + if (result.status === "clean") { + console.log( + chalk.green( + `[done] phase ${result.phase} clean after ${result.iterations} revision(s).`, + ), + ); + const reviewRoots: string[] = []; + if (plan.phaseAEnabled && plan.sourceRepoRoot) { + reviewRoots.push(`cd ${plan.sourceRepoRoot} && git status`); + } + reviewRoots.push(`cd ${planTargetRepoForUx} && git status`); + for (const cmd of reviewRoots) { + console.log(chalk.green(`[done] review your changes: ${cmd}`)); + } + process.exit(0); + } + + if (result.status === "exhausted-revisions") { + console.log( + chalk.red( + `[fail] phase ${result.phase} still has ${result.remainingFindings.length} critical finding(s) ` + + `after ${result.iterations} revision(s).`, + ), + ); + const failedRoot = result.phase === "extract" ? plan.extractedPkgAbs ?? plan.targetAbs : plan.targetAbs; + console.log( + formatFindings({ findings: result.remainingFindings, scannedPaths: [failedRoot], durationMs: 0 }), + ); + console.log( + chalk.red( + `[fail] tree left dirty for inspection. agent=${result.agentId} run=${result.lastRunId}`, + ), + ); + process.exit(2); + } + + console.log( + chalk.red( + `[fail] phase ${result.phase} agent run failed mid-execution. agent=${result.agentId} run=${result.lastRunId}`, + ), + ); + if (result.errorMessage) { + console.log(chalk.red(`[fail] SDK error: ${result.errorMessage}`)); + } + process.exit(3); + } catch (error) { + if (error instanceof CursorAgentError) { + console.error( + chalk.red( + `[fail] agent failed to start: ${error.message} (retryable=${ + (error as { isRetryable?: boolean }).isRetryable ?? "unknown" + })`, + ), + ); + process.exit(1); + } + console.error(chalk.red(`[fail] unexpected error: ${error instanceof Error ? error.message : String(error)}`)); + process.exit(1); + } +} + +async function handleListModels(apiKey: string): Promise { + try { + const models = await listAvailableModels(apiKey); + if (models.length === 0) { + console.log("No models returned from Cursor.models.list()."); + process.exit(0); + } + for (const id of models) { + console.log(id); + } + process.exit(0); + } catch (error) { + fail( + 1, + `Could not list models: ${error instanceof Error ? error.message : String(error)}`, + ); + } +} + +async function loadJargonOverrides(sourceAbs: string): Promise { + const { promises: fs } = await import("node:fs"); + const path = await import("node:path"); + const configPath = path.join(sourceAbs, ".essentials-sync-jargon.json"); + try { + const raw = await fs.readFile(configPath, "utf8"); + const parsed = JSON.parse(raw) as unknown; + if (Array.isArray(parsed)) { + return parsed.filter((entry): entry is string => typeof entry === "string"); + } + if (parsed && typeof parsed === "object") { + const terms = (parsed as { terms?: unknown }).terms; + if (Array.isArray(terms)) { + return terms.filter((entry): entry is string => typeof entry === "string"); + } + } + return []; + } catch (error) { + if (error instanceof Error && "code" in error && (error as { code?: string }).code === "ENOENT") { + return []; + } + console.warn( + chalk.yellow(`[jargon] could not read jargon overrides: ${(error as Error).message}`), + ); + return []; + } +} + +async function prepareDryRunTarget(originalRelPath: string): Promise<{ repo: string; relPath: string }> { + const { mkdtemp } = await import("node:fs/promises"); + const { tmpdir } = await import("node:os"); + const path = await import("node:path"); + const root = await mkdtemp(path.join(tmpdir(), "essentials-sync-dryrun-")); + const { promises: fs } = await import("node:fs"); + await fs.mkdir(path.join(root, ".git"), { recursive: true }); + await fs.writeFile(path.join(root, ".git", "HEAD"), "ref: refs/heads/main\n", "utf8"); + console.log(chalk.dim(`[dry-run] target staged at ${root}`)); + return { repo: root, relPath: originalRelPath }; +} + +function requireOption(value: string | undefined, label: string): string { + if (!value) { + fail(1, `Missing required option ${label}.`); + } + return value; +} + +function parseInteger(value: string): number { + const n = Number.parseInt(value, 10); + if (!Number.isFinite(n) || n < 0) { + throw new Error(`Expected a non-negative integer, got: ${value}`); + } + return n; +} + +function fail(exitCode: number, message: string): never { + console.error(chalk.red(message)); + process.exit(exitCode); +} + +function loadEnvFiles(): void { + if (typeof process.loadEnvFile !== "function") return; + const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + const candidates = [ + path.resolve(process.cwd(), ".env"), + path.resolve(packageRoot, ".env"), + ]; + const seen = new Set(); + for (const candidate of candidates) { + if (seen.has(candidate)) continue; + seen.add(candidate); + try { + process.loadEnvFile(candidate); + } catch { + // File missing or unreadable; silently skip. Each candidate is best-effort. + } + } +} diff --git a/tools/typescript/essentials-sync/src/extract-plan.ts b/tools/typescript/essentials-sync/src/extract-plan.ts new file mode 100644 index 0000000..504b272 --- /dev/null +++ b/tools/typescript/essentials-sync/src/extract-plan.ts @@ -0,0 +1,179 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; + +const SLUG_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/; + +export interface ExtractNames { + toolName: string; + packageName: string; + importableName: string; + pyprojectMember: string; + defaultTargetPathRel: string; +} + +export interface ExtractPlan { + names: ExtractNames; + sourceAbs: string; + sourceRepoRoot: string; + extractedPkgAbs: string; + wrapperPkgAbs: string; + alreadyExtracted: boolean; +} + +export interface PlanExtractInputs { + source: string; + sourceRepo?: string; + packageName?: string; +} + +export async function planExtract({ + source, + sourceRepo, + packageName, +}: PlanExtractInputs): Promise { + const sourceAbs = path.resolve(source); + await assertDirectory(sourceAbs, "--source"); + + const sourceRepoRoot = sourceRepo + ? path.resolve(sourceRepo) + : await findGitRoot(sourceAbs); + await assertDirectory(sourceRepoRoot, "--source-repo"); + + const rel = path.relative(sourceRepoRoot, sourceAbs); + if (rel.startsWith("..") || path.isAbsolute(rel)) { + throw new Error( + `--source (${sourceAbs}) is not inside --source-repo (${sourceRepoRoot})`, + ); + } + + const toolName = deriveToolName(rel); + const finalPackageName = packageName + ? validatePackageName(packageName) + : derivePackageName(toolName); + const importableName = finalPackageName.replaceAll("-", "_"); + const pyprojectMember = ["packages", "python", finalPackageName].join("/"); + const extractedPkgAbs = path.join( + sourceRepoRoot, + "packages", + "python", + finalPackageName, + ); + const wrapperPkgAbs = sourceAbs; + const alreadyExtracted = await directoryExists(extractedPkgAbs); + + return { + names: { + toolName, + packageName: finalPackageName, + importableName, + pyprojectMember, + defaultTargetPathRel: pyprojectMember, + }, + sourceAbs, + sourceRepoRoot, + extractedPkgAbs, + wrapperPkgAbs, + alreadyExtracted, + }; +} + +export function deriveToolName(relPathFromRepoRoot: string): string { + const parts = relPathFromRepoRoot.split(path.sep).filter((seg) => seg.length > 0); + if (parts.length !== 3 || parts[0] !== "tools" || parts[1] !== "python") { + throw new Error( + `Extract requires --source to point at a tools/python/ directory ` + + `inside the source repo. Got relative path: '${relPathFromRepoRoot}'`, + ); + } + const name = parts[2]; + if (!name || !SLUG_RE.test(name)) { + throw new Error( + `Invalid tool name derived from --source: '${name}'. ` + + `Expected kebab-case slug (lowercase letters, digits, hyphens).`, + ); + } + return name; +} + +export function derivePackageName(toolName: string): string { + if (toolName.startsWith("ess-")) { + return toolName; + } + return `ess-${toolName}`; +} + +export function validatePackageName(raw: string): string { + const trimmed = raw.trim(); + if (!SLUG_RE.test(trimmed)) { + throw new Error( + `--package-name must be a kebab-case slug (got: '${raw}')`, + ); + } + if (!trimmed.startsWith("ess-")) { + throw new Error( + `--package-name must start with 'ess-' so the extracted package is recognizable ` + + `as an open-sourcable shared library (got: '${raw}').`, + ); + } + return trimmed; +} + +async function findGitRoot(start: string): Promise { + let current = path.resolve(start); + // Path traversal terminates at the filesystem root, where dirname(p) === p. + for (;;) { + if (await pathExists(path.join(current, ".git"))) { + return current; + } + const parent = path.dirname(current); + if (parent === current) { + throw new Error( + `Could not find a git repo root at or above ${start}. ` + + `Pass --source-repo explicitly.`, + ); + } + current = parent; + } +} + +async function assertDirectory(absPath: string, label: string): Promise { + try { + const stat = await fs.stat(absPath); + if (!stat.isDirectory()) { + throw new Error(`${label} is not a directory: ${absPath}`); + } + } catch (error) { + if (isNotFoundError(error)) { + throw new Error(`${label} does not exist: ${absPath}`); + } + throw error; + } +} + +async function directoryExists(p: string): Promise { + try { + const stat = await fs.stat(p); + return stat.isDirectory(); + } catch (error) { + if (isNotFoundError(error)) return false; + throw error; + } +} + +async function pathExists(p: string): Promise { + try { + await fs.stat(p); + return true; + } catch (error) { + if (isNotFoundError(error)) return false; + throw error; + } +} + +function isNotFoundError(error: unknown): boolean { + if (error && typeof error === "object") { + const code = (error as { code?: string }).code; + return code === "ENOENT"; + } + return false; +} diff --git a/tools/typescript/essentials-sync/src/jargon-list.ts b/tools/typescript/essentials-sync/src/jargon-list.ts new file mode 100644 index 0000000..5350a08 --- /dev/null +++ b/tools/typescript/essentials-sync/src/jargon-list.ts @@ -0,0 +1,80 @@ +export interface JargonPattern { + term: string; + regex: RegExp; + message: string; +} + +const wordBoundary = (word: string): RegExp => + new RegExp(`\\b${word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i"); + +const hostnameSuffix = (suffix: string): RegExp => + new RegExp(`[a-z0-9-]+\\.${suffix.replace(/\./g, "\\.")}`, "i"); + +export const DEFAULT_JARGON_PATTERNS: readonly JargonPattern[] = [ + { + term: "cisco", + regex: wordBoundary("cisco"), + message: "Contains the company name 'cisco' (case-insensitive).", + }, + { + term: "*.cisco.com", + regex: hostnameSuffix("cisco.com"), + message: "Contains an internal Cisco hostname.", + }, + { + term: "*.webex.com", + regex: hostnameSuffix("webex.com"), + message: "Contains an internal Webex hostname.", + }, + { + term: "myid", + regex: wordBoundary("myid"), + message: "References 'myid' identity service.", + }, + { + term: "cec", + regex: wordBoundary("cec"), + message: "References 'cec' (Cisco employee credential).", + }, + { + term: "webex", + regex: wordBoundary("webex"), + message: "References Webex product naming.", + }, + { + term: "cisco-ceto", + regex: wordBoundary("cisco-ceto"), + message: "References internal cisco-ceto org.", + }, + { + term: "honeycomb", + regex: wordBoundary("honeycomb"), + message: "References the internal 'honeycomb' repo name.", + }, +]; + +export interface JargonConfig { + patterns: JargonPattern[]; +} + +export function loadJargonConfig(extraTerms: string[] = []): JargonConfig { + const extras: JargonPattern[] = extraTerms.map((raw) => { + const trimmed = raw.trim(); + if (trimmed.startsWith("*.")) { + const suffix = trimmed.slice(2); + return { + term: trimmed, + regex: hostnameSuffix(suffix), + message: `Contains hostname matching ${trimmed}.`, + }; + } + return { + term: trimmed, + regex: wordBoundary(trimmed), + message: `Contains custom jargon term '${trimmed}'.`, + }; + }); + return { + patterns: [...DEFAULT_JARGON_PATTERNS, ...extras], + }; +} diff --git a/tools/typescript/essentials-sync/src/model-resolver.ts b/tools/typescript/essentials-sync/src/model-resolver.ts new file mode 100644 index 0000000..aa80bfc --- /dev/null +++ b/tools/typescript/essentials-sync/src/model-resolver.ts @@ -0,0 +1,157 @@ +import { Cursor } from "@cursor/sdk"; +import type { ModelSpec, ResolvedModels } from "./types.js"; + +const KNOWN_FAMILIES = ["opus", "codex", "claude", "gpt", "gemini", "composer"] as const; + +const PRIMARY_FALLBACKS: readonly string[] = ["opus", "claude", "auto"]; +const REVIEW_FALLBACKS: readonly string[] = ["codex", "gpt", "auto"]; + +export function parseModelSpec(value: string): ModelSpec { + const normalized = value.trim().toLowerCase(); + if (normalized === "auto" || normalized === "") { + return { kind: "auto" }; + } + if (KNOWN_FAMILIES.includes(normalized as (typeof KNOWN_FAMILIES)[number])) { + return { kind: "family", family: normalized }; + } + return { kind: "id", id: value.trim() }; +} + +function pickHighestInFamily(family: string, catalog: string[]): string | null { + const candidates = catalog + .map((id) => { + const lower = id.toLowerCase(); + const familyIdx = lower.indexOf(family); + if (familyIdx < 0) return null; + const before = familyIdx === 0 ? "" : lower[familyIdx - 1]; + const hasCleanBoundary = familyIdx === 0 || before === "-"; + if (!hasCleanBoundary) return null; + const after = lower.slice(familyIdx + family.length); + const suffixSegments = after.split("-").filter((seg) => seg.length > 0).length; + return { id, lower, suffixSegments }; + }) + .filter( + (entry): entry is { id: string; lower: string; suffixSegments: number } => entry !== null, + ); + + if (candidates.length === 0) return null; + + // For family sentinels we prefer the canonical version (no extra suffix + // segments past the version tokens). For codex this picks `gpt-5.3-codex` + // over `gpt-5.3-codex-spark`. We pick the minimum suffix-segment count + // observed and only sort within that tier. + const minSuffix = Math.min(...candidates.map((entry) => entry.suffixSegments)); + const topTier = candidates.filter((entry) => entry.suffixSegments === minSuffix); + topTier.sort((left, right) => right.lower.localeCompare(left.lower)); + return topTier[0]?.id ?? null; +} + +interface ResolveSingleInputs { + spec: ModelSpec; + catalog: string[]; + fallbacks: readonly string[]; + excludeId?: string; +} + +function resolveWithFallbacks({ + spec, + catalog, + fallbacks, + excludeId, +}: ResolveSingleInputs): { id: string; source: "explicit" | "sentinel" | "fallback" } { + if (spec.kind === "id") { + return { id: spec.id, source: "explicit" }; + } + if (spec.kind === "auto") { + return { id: "auto", source: "explicit" }; + } + + const requested = spec.family; + const primaryAttempt = pickHighestInFamily(requested, catalog); + if (primaryAttempt && primaryAttempt !== excludeId) { + return { id: primaryAttempt, source: "sentinel" }; + } + + for (const candidate of fallbacks) { + if (candidate === requested) continue; + if (candidate === "auto") { + return { id: "auto", source: "fallback" }; + } + const picked = pickHighestInFamily(candidate, catalog); + if (picked && picked !== excludeId) { + return { id: picked, source: "fallback" }; + } + } + return { id: "auto", source: "fallback" }; +} + +export interface ResolveInputs { + apiKey: string; + primarySpec: ModelSpec; + reviewSpec: ModelSpec; +} + +export async function resolveModels({ + apiKey, + primarySpec, + reviewSpec, +}: ResolveInputs): Promise { + const catalog = await fetchCatalog(apiKey); + + const primaryResolved = resolveWithFallbacks({ + spec: primarySpec, + catalog, + fallbacks: PRIMARY_FALLBACKS, + }); + + const reviewResolved = resolveWithFallbacks({ + spec: reviewSpec, + catalog, + fallbacks: REVIEW_FALLBACKS, + excludeId: primaryResolved.id, + }); + + return { + primary: primaryResolved.id, + review: reviewResolved.id, + primarySource: primaryResolved.source, + reviewSource: reviewResolved.source, + available: catalog, + collapsed: primaryResolved.id === reviewResolved.id, + }; +} + +async function fetchCatalog(apiKey: string): Promise { + const response = (await Cursor.models.list({ apiKey })) as unknown; + return normalizeCatalog(response); +} + +function normalizeCatalog(response: unknown): string[] { + if (Array.isArray(response)) { + return response.map(extractId).filter((id): id is string => Boolean(id)); + } + if (response && typeof response === "object") { + const maybeArray = + (response as { models?: unknown; data?: unknown }).models + ?? (response as { data?: unknown }).data; + if (Array.isArray(maybeArray)) { + return maybeArray.map(extractId).filter((id): id is string => Boolean(id)); + } + } + return []; +} + +function extractId(item: unknown): string | null { + if (typeof item === "string") return item; + if (item && typeof item === "object") { + const id = (item as { id?: unknown }).id; + if (typeof id === "string") return id; + const name = (item as { name?: unknown }).name; + if (typeof name === "string") return name; + } + return null; +} + +export async function listAvailableModels(apiKey: string): Promise { + return fetchCatalog(apiKey); +} diff --git a/tools/typescript/essentials-sync/src/prompts.ts b/tools/typescript/essentials-sync/src/prompts.ts new file mode 100644 index 0000000..c2bac28 --- /dev/null +++ b/tools/typescript/essentials-sync/src/prompts.ts @@ -0,0 +1,303 @@ +import type { Finding, ScanReport, SyncPlan } from "./types.js"; + +// === Sync system prompt (Phase B: write into the open-source target repo) === +export const SYNC_SYSTEM_PROMPT = `You are the essentials-sync primary agent. Your job is to move a package from a private, company-owned repository into the public open-source essentials repository, generalizing all company-specific content along the way. + +HARD RULES that must hold for every file you write into the target: +1. No secrets of any kind (API keys, tokens, passwords, certificates, private keys). +2. No personally identifiable information (employee names, employee IDs, emails, phone numbers). +3. No internal hostnames or URLs (anything under company-owned domains like cisco.com, webex.com, or other internal infra). +4. No company-specific identifiers or jargon left verbatim. Examples: 'cisco', 'webex', 'myid', 'cec', internal product codenames, internal org names. These must be either removed or generalized. + +NAMING CONVENTION: +- When you create or rename a package destined for the essentials repo, the package name must begin with the prefix 'ess-'. For example, an internal package 'cisco-auth' should become 'ess-auth'. +- The target layout is 'packages/python/ess-*' for Python and 'packages/javascript/ess-*' for JavaScript/TypeScript. + +STRUCTURE PRESERVATION: +- Preserve pyproject.toml and package.json structure. Update only the fields that carry company-specific data (name, authors, urls, keywords, descriptions). +- Preserve or add an Apache 2.0 license header to newly created source files. Do not strip pre-existing license headers unless they reference the company name verbatim. +- Preserve directory structure unless the source layout violates the essentials convention; if it does, reorganize and note the changes. + +When you are uncertain whether a term is internal or generic, treat it as internal and generalize it. +`; + +// === Extract system prompt (Phase A: refactor in place inside honeycomb) === +export const EXTRACT_SYSTEM_PROMPT = `You are the essentials-sync primary agent operating in EXTRACT mode. Your job is to refactor a team- or account-specific tool inside the private honeycomb repository into two sibling packages: + +1. A jargon-free shared library at 'packages/python/ess-/' containing all the reusable logic, parameterized so it has NO hard-coded company-specific defaults. +2. A thin wrapper at 'tools/python//' (the original tool's location, rewritten in place) that imports the shared library and supplies the company-specific defaults. + +REFERENCE PATTERN: this matches the langsmith-hosting / sales-ai-langsmith-hosting split that already exists in honeycomb. Concretely: +- 'packages/python/langsmith-hosting/' is a generic, reusable library with its own CLI, tests, and parameterized configuration. +- 'tools/python/sales-ai-langsmith-hosting/__main__.py' is two non-blank lines that import 'deploy_stack' from the library and call it with the team-specific 'aws_profile'. +- The wrapper depends on the library via '[tool.uv.sources] langsmith-hosting = { workspace = true }'. +Replicate that shape. + +HARD RULES for the EXTRACTED package (the new 'packages/python/ess-/'): +1. No secrets, no PII. +2. No internal hostnames, URLs, or account IDs. +3. No company-specific identifiers or jargon left verbatim ('cisco', 'webex', 'myid', 'cec', internal product codenames, internal org names). +4. No company-specific defaults baked into function signatures, CLI flags, or constants. Defaults that used to be hard-coded must become required parameters, env-var-driven, or removed entirely. +5. Every file written under 'packages/python/ess-/' will be scanned. Any leftover company data here is a failure. + +PERMISSIONS for the WRAPPER (the rewritten 'tools/python//'): +- The wrapper is allowed -- expected, even -- to contain the company-specific values that used to live in the source tool. Hostnames, default account names, internal SSO domains, team-specific AWS profiles, etc. belong here. +- Keep it minimal: import from the shared library, supply the defaults, expose the same CLI entry point name so existing callers do not break. +- The wrapper will NOT be scanned. + +NAMING: +- The extracted package name is given to you in the user prompt; it always starts with 'ess-'. +- The importable module name is the package name with hyphens replaced by underscores (e.g. 'ess-service-now-incident' -> 'ess_service_now_incident'). + +When you are uncertain whether a term is internal or generic, treat it as internal and generalize it. +`; + +export interface PhaseAInputs { + plan: SyncPlan; + sourceScan: ScanReport | null; + jargonHeavy: boolean; +} + +// Sync-phase initial prompt (was buildPhaseAPrompt). +export function buildSyncInitialPrompt({ + plan, + sourceScan, + jargonHeavy, +}: PhaseAInputs): string { + const mode = plan.mode; + const sourceScanSection = sourceScan + ? formatSourceScanSummary(sourceScan) + : "No source pre-scan was performed (--no-source-scan)."; + + const generalizationNote = jargonHeavy + ? `The source contains substantial company-specific jargon. Also write a file named RECOMMENDATIONS.md inside the target directory that: + - proposes a new ess-* package name and rationale + - lists identifiers and strings that were renamed + - calls out any modules or behaviors that could not be cleanly generalized and should be left behind in the private repo` + : "Source pre-scan looks light on company jargon; no RECOMMENDATIONS.md needed unless you find more during the rewrite."; + + return `MODE: ${mode} +SOURCE (read-only, absolute path): ${plan.sourceAbs} +TARGET REPO (your cwd): ${plan.targetRepoAbs} +TARGET PATH (relative to target repo): ${plan.targetPathRel} +TARGET ABSOLUTE PATH: ${plan.targetAbs} + +TASK: +1. Read every file in the SOURCE directory. You have full read access to it. +2. Write the generalized equivalent into TARGET ABSOLUTE PATH. + - In COPY mode, the target path does not exist yet. Create it. + - In SYNC mode, the target path already exists; reconcile by updating files that have meaningfully diverged. Do not blindly overwrite the target. +3. Apply the HARD RULES from your system prompt to every file you write. The target tree will be scanned after you finish; any leftover company data is a failure. +4. ${generalizationNote} + +SOURCE SCAN SUMMARY (informational only, never a blocker on the source side): +${sourceScanSection} + +When you are done, return a short summary of what you wrote, the chosen ess-* name if applicable, and any caveats. +`; +} + +export interface PhaseBInputs { + plan: SyncPlan; + iteration: number; + maxRevisions: number; + combinedFindings: Finding[]; +} + +// Sync-phase revision prompt (was buildPhaseBPrompt). +export function buildSyncRevisionPrompt({ + plan, + iteration, + maxRevisions, + combinedFindings, +}: PhaseBInputs): string { + const formatted = formatFindingsForPrompt(combinedFindings); + + return `REVISION REQUIRED (iteration ${iteration} of ${maxRevisions}). + +The target tree at ${plan.targetAbs} was scanned by deterministic scanners and an adversarial reviewer (running on a different model from yours). The following findings must be resolved before this package can ship: + +${formatted} + +For each finding: + - Open the offending file in the target. + - Apply the minimal change that removes the company-specific content while preserving behavior. + - Do NOT introduce new files unless strictly necessary. + - Do NOT touch files that have no findings. + +The HARD RULES from your system prompt still apply. Once you have addressed every finding, return a short summary of which files you changed. +`; +} + +export interface ExtractInitialInputs { + plan: SyncPlan; + sourceScan: ScanReport | null; +} + +export function buildExtractInitialPrompt({ + plan, + sourceScan, +}: ExtractInitialInputs): string { + if ( + !plan.extractedPkgAbs + || !plan.wrapperPkgAbs + || !plan.sourceRepoRoot + || !plan.packageName + || !plan.importableName + ) { + throw new Error( + "buildExtractInitialPrompt called on a plan that is missing extract metadata.", + ); + } + const sourceScanSection = sourceScan + ? formatSourceScanSummary(sourceScan) + : "No source pre-scan was performed (--no-source-scan)."; + + return `MODE: EXTRACT +SOURCE REPO ROOT (your cwd): ${plan.sourceRepoRoot} +ORIGINAL TOOL (read for behavior, then rewrite in place as a wrapper): ${plan.wrapperPkgAbs} +EXTRACTED PACKAGE PATH (create here, jargon-free): ${plan.extractedPkgAbs} +PACKAGE NAME: ${plan.packageName} +IMPORTABLE MODULE: ${plan.importableName} + +TASK: +1. Read every file under ORIGINAL TOOL to understand its current behavior. +2. Create the EXTRACTED PACKAGE at the path above. It must include: + - Its own pyproject.toml with name '${plan.packageName}', a short generic description, hatchling build backend, and the right [tool.hatch.build.targets.wheel] packages entry pointing at 'src/${plan.importableName}'. + - A 'src/${plan.importableName}/' directory with the generalized core logic. All company-specific defaults must become required parameters, env-var-driven overrides, or be removed. + - Tests under 'tests/' that pass without any honeycomb-specific setup. + - A README.md focused on the generic library (what it does, how to use it, no company references). +3. Rewrite ORIGINAL TOOL in place as a thin wrapper: + - Its pyproject.toml should declare a dependency on '${plan.packageName}' via '[tool.uv.sources] ${plan.packageName} = { workspace = true }'. + - Its source code becomes a small wrapper module that imports from '${plan.importableName}' and supplies the company-specific defaults. Follow the 'sales-ai-langsmith-hosting' shape. + - Keep the same CLI entry point name (so existing callers do not break). + - The wrapper is allowed to contain company-specific defaults (hostnames, AWS profiles, internal SSO domains, etc.). That is its purpose. +4. Update the root pyproject.toml at ${plan.sourceRepoRoot}/pyproject.toml to add '${plan.packageName}' to '[tool.uv.workspace] members' if it is not already listed. Do not modify any other workspace entries. +5. Do NOT delete anything outside ORIGINAL TOOL and EXTRACTED PACKAGE PATH. Do NOT modify other tools or packages. + +SCAN COVERAGE: Only the EXTRACTED PACKAGE will be scanned. The wrapper is excluded from the scan, so company-specific defaults belong there. + +SOURCE SCAN SUMMARY (informational only): +${sourceScanSection} + +When you are done, return a short summary of: the extracted package layout, the wrapper before/after, and any behavior changes worth flagging to reviewers. +`; +} + +export interface ExtractRevisionInputs { + plan: SyncPlan; + iteration: number; + maxRevisions: number; + combinedFindings: Finding[]; +} + +export function buildExtractRevisionPrompt({ + plan, + iteration, + maxRevisions, + combinedFindings, +}: ExtractRevisionInputs): string { + if (!plan.extractedPkgAbs) { + throw new Error( + "buildExtractRevisionPrompt called on a plan that is missing extractedPkgAbs.", + ); + } + const formatted = formatFindingsForPrompt(combinedFindings); + + return `REVISION REQUIRED for the EXTRACTED package (iteration ${iteration} of ${maxRevisions}). + +The extracted package at ${plan.extractedPkgAbs} was scanned by deterministic scanners and an adversarial reviewer. The findings below must be resolved before Phase B (sync to essentials) can run: + +${formatted} + +For each finding: + - Open the offending file inside the extracted package. + - Apply the minimal change that removes the company-specific content while preserving behavior. + - If a default needs to live somewhere, move it into the wrapper at ${plan.wrapperPkgAbs ?? "the original tool location"} instead of keeping it in the extracted package. + - Do NOT modify files outside the extracted package and the wrapper. + +The HARD RULES from your system prompt still apply. Once you have addressed every finding, return a short summary of which files you changed. +`; +} + +export interface ReviewerPromptInputs { + plan: SyncPlan; + // The path the reviewer should treat as the package to inspect. Defaults to + // plan.targetAbs but during Phase A we point it at the extracted package. + reviewRootAbs?: string; +} + +export function buildReviewerPrompt({ + plan, + reviewRootAbs, +}: ReviewerPromptInputs): string { + const root = reviewRootAbs ?? plan.targetAbs; + return `You are a strict open-source release reviewer. The package at ${root} is about to be published to a public, open-source repository. + +Read every file under that path. Flag anything that would embarrass the team or leak company information if released. Be paranoid. Pay particular attention to: + - Internal hostnames, URLs, account IDs, project codenames. + - Personally identifiable information (employee names, IDs, emails, phone numbers). + - Subtle references in docstrings, comments, naming, error messages, or example data that imply an internal audience. + - Implied workflows or assumptions that only make sense inside the company. + - Anything that looks like a secret (API key, token, password, certificate). + +DO NOT flag the following -- they are public, open-source conventions, not internal jargon: + - Package names that start with the 'ess-' prefix (e.g. 'ess-browser', 'ess-auth', 'ess-service-now-incident'). 'ess' is short for 'essentials', the public open-source repo this package belongs to. References to other 'ess-*' packages as dependencies, workspace siblings, or import targets ('from ess_browser import ...', '[tool.uv.sources] ess-browser = { workspace = true }') are expected and correct. + - Generic uv/Python workspace conventions ('[tool.uv.workspace]', 'uv sync --all-packages', the 'packages/python/' and 'tools/python/' directory layout). These are upstream uv patterns, not company-specific. + +Output ONLY a single JSON object, with no surrounding prose, no markdown code fences, and no commentary. Schema: + +{ + "findings": [ + { + "file": "", + "line": , + "type": "secret" | "pii" | "jargon" | "internal-url" | "internal-reference" | "other", + "severity": "critical" | "warning", + "message": "" + } + ] +} + +If the package is clean, return exactly: +{ "findings": [] } +`; +} + +function formatFindingsForPrompt(findings: Finding[]): string { + return findings + .map( + (finding) => + `- [${finding.severity}] ${finding.source}: ${finding.file}:${finding.line} - ${finding.message}` + + (finding.term ? ` (term=${finding.term})` : ""), + ) + .join("\n"); +} + +function formatSourceScanSummary(report: ScanReport): string { + if (report.findings.length === 0) { + return "Source scan: no findings."; + } + const counts: Record = {}; + for (const finding of report.findings) { + const key = `${finding.source}/${finding.type}`; + counts[key] = (counts[key] ?? 0) + 1; + } + const summary = Object.entries(counts) + .map(([key, count]) => ` ${key}: ${count}`) + .join("\n"); + const examples = report.findings + .slice(0, 8) + .map( + (finding) => + ` - ${finding.source}: ${finding.file}:${finding.line} (${finding.term ?? finding.type})`, + ) + .join("\n"); + return `Source scan totals:\n${summary}\n\nFirst few:\n${examples}`; +} + +// Backwards-compat aliases for any external import sites that still reference +// the old names. Safe to remove once nothing imports them. +export const PRIMARY_SYSTEM_PROMPT = SYNC_SYSTEM_PROMPT; +export const buildPhaseAPrompt = buildSyncInitialPrompt; +export const buildPhaseBPrompt = buildSyncRevisionPrompt; diff --git a/tools/typescript/essentials-sync/src/reviewer.ts b/tools/typescript/essentials-sync/src/reviewer.ts new file mode 100644 index 0000000..0f28a54 --- /dev/null +++ b/tools/typescript/essentials-sync/src/reviewer.ts @@ -0,0 +1,134 @@ +import { Agent } from "@cursor/sdk"; +import type { Finding, SyncPlan } from "./types.js"; +import { buildReviewerPrompt } from "./prompts.js"; + +export interface RunReviewerInputs { + apiKey: string; + reviewModel: string; + plan: SyncPlan; + // Path the reviewer should inspect. Defaults to plan.targetAbs (sync phase); + // during the extract phase callers pass the extracted package path so the + // reviewer audits the new shared library rather than the essentials target. + reviewRootAbs?: string; + // Working directory for the reviewer agent. Defaults to plan.targetRepoAbs. + // During the extract phase callers pass plan.sourceRepoRoot (honeycomb) so + // the reviewer can read files inside the extracted package directly. + cwd?: string; +} + +interface ReviewerJsonResult { + findings?: Array<{ + file?: unknown; + line?: unknown; + type?: unknown; + severity?: unknown; + message?: unknown; + }>; +} + +export async function runReviewer({ + apiKey, + reviewModel, + plan, + reviewRootAbs, + cwd, +}: RunReviewerInputs): Promise { + const effectiveReviewRoot = reviewRootAbs ?? plan.targetAbs; + const effectiveCwd = cwd ?? plan.targetRepoAbs; + const prompt = buildReviewerPrompt({ plan, reviewRootAbs: effectiveReviewRoot }); + const result = await Agent.prompt(prompt, { + apiKey, + model: { id: reviewModel }, + local: { cwd: effectiveCwd, settingSources: [] }, + }); + + const rawText = extractText(result); + const json = extractJsonObject(rawText); + if (!json) { + return [ + { + file: effectiveReviewRoot, + line: 1, + type: "other", + severity: "warning", + source: "reviewer", + message: `Reviewer output could not be parsed as JSON. Raw: ${truncate(rawText, 200)}`, + }, + ]; + } + + return convertToFindings(json); +} + +function extractText(result: unknown): string { + if (typeof result === "string") return result; + if (result && typeof result === "object") { + const candidate = (result as { result?: unknown }).result; + if (typeof candidate === "string") return candidate; + const message = (result as { message?: unknown }).message; + if (typeof message === "string") return message; + } + return JSON.stringify(result); +} + +function extractJsonObject(text: string): ReviewerJsonResult | null { + const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i); + const candidate = (fenced?.[1] ?? text).trim(); + const firstBrace = candidate.indexOf("{"); + const lastBrace = candidate.lastIndexOf("}"); + if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) { + return null; + } + const sliced = candidate.slice(firstBrace, lastBrace + 1); + try { + return JSON.parse(sliced) as ReviewerJsonResult; + } catch { + return null; + } +} + +function convertToFindings(json: ReviewerJsonResult): Finding[] { + const items = Array.isArray(json.findings) ? json.findings : []; + const findings: Finding[] = []; + for (const item of items) { + const file = typeof item.file === "string" ? item.file : ""; + const line = typeof item.line === "number" && Number.isFinite(item.line) + ? Math.max(1, Math.floor(item.line)) + : 1; + const severity = item.severity === "warning" ? "warning" : "critical"; + const type = normalizeType(item.type); + const message = typeof item.message === "string" ? item.message : "Reviewer flagged this location."; + findings.push({ + file, + line, + type, + severity, + source: "reviewer", + message, + }); + } + return findings; +} + +function normalizeType(raw: unknown): Finding["type"] { + const known: Finding["type"][] = [ + "secret", + "pii", + "jargon", + "internal-url", + "internal-reference", + "other", + ]; + if (typeof raw === "string") { + const lower = raw.toLowerCase(); + for (const candidate of known) { + if (candidate === lower) return candidate; + } + } + return "other"; +} + +function truncate(text: string, max: number): string { + if (text.length <= max) return text; + return `${text.slice(0, max)}...`; +} diff --git a/tools/typescript/essentials-sync/src/scanners/index.ts b/tools/typescript/essentials-sync/src/scanners/index.ts new file mode 100644 index 0000000..24e4818 --- /dev/null +++ b/tools/typescript/essentials-sync/src/scanners/index.ts @@ -0,0 +1,94 @@ +import type { Finding, ScanReport, Severity } from "../types.js"; +import { scanJargon } from "./jargon.js"; +import { scanPii } from "./pii.js"; +import { scanWithTrufflehog } from "./trufflehog.js"; +import { scanWithSecretlint } from "./secretlint.js"; + +export interface RunScannersOptions { + rootPath: string; + jargonSeverity: Severity; + extraJargonTerms?: string[]; + skipTrufflehog?: boolean; + skipSecretlint?: boolean; +} + +export async function runScanners({ + rootPath, + jargonSeverity, + extraJargonTerms, + skipTrufflehog, + skipSecretlint, +}: RunScannersOptions): Promise { + const start = Date.now(); + const all: Finding[] = []; + + const tasks: Array> = []; + + if (!skipTrufflehog) { + tasks.push( + scanWithTrufflehog(rootPath).catch((error: unknown) => { + return [makeScannerErrorFinding("trufflehog", error, rootPath)]; + }), + ); + } + if (!skipSecretlint) { + tasks.push( + scanWithSecretlint(rootPath).catch((error: unknown) => { + return [makeScannerErrorFinding("secretlint", error, rootPath)]; + }), + ); + } + tasks.push( + scanJargon(rootPath, { + severity: jargonSeverity, + extraTerms: extraJargonTerms, + }), + ); + tasks.push(scanPii(rootPath, { severity: "critical" })); + + const results = await Promise.all(tasks); + for (const findings of results) { + all.push(...findings); + } + + return { + findings: all, + scannedPaths: [rootPath], + durationMs: Date.now() - start, + }; +} + +function makeScannerErrorFinding( + scanner: "trufflehog" | "secretlint", + error: unknown, + rootPath: string, +): Finding { + const message = error instanceof Error ? error.message : String(error); + return { + file: rootPath, + line: 1, + type: "other", + severity: "warning", + source: scanner, + message: `Scanner '${scanner}' failed: ${message}`, + }; +} + +export function hasCriticalFindings(report: ScanReport): boolean { + return report.findings.some((finding) => finding.severity === "critical"); +} + +export function formatFindings(report: ScanReport): string { + if (report.findings.length === 0) { + return "No findings."; + } + const lines: string[] = []; + for (const finding of report.findings) { + lines.push( + `[${finding.severity}] ${finding.source}: ${finding.file}:${finding.line}` + + ` - ${finding.message}` + + (finding.term ? ` (term=${finding.term})` : ""), + ); + } + return lines.join("\n"); +} diff --git a/tools/typescript/essentials-sync/src/scanners/jargon.ts b/tools/typescript/essentials-sync/src/scanners/jargon.ts new file mode 100644 index 0000000..62662d9 --- /dev/null +++ b/tools/typescript/essentials-sync/src/scanners/jargon.ts @@ -0,0 +1,87 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import type { Finding, Severity } from "../types.js"; +import { loadJargonConfig, type JargonConfig } from "../jargon-list.js"; + +const DEFAULT_BINARY_EXTENSIONS = new Set([ + ".png", ".jpg", ".jpeg", ".gif", ".webp", ".pdf", ".zip", ".tar", ".gz", + ".bz2", ".xz", ".7z", ".woff", ".woff2", ".eot", ".ttf", ".otf", ".ico", + ".so", ".dylib", ".dll", ".class", ".jar", ".wasm", ".bin", +]); + +const SKIP_DIRECTORIES = new Set([ + "node_modules", ".git", ".venv", "venv", "__pycache__", ".pytest_cache", + ".ruff_cache", ".mypy_cache", "dist", "build", ".pulumi", +]); + +export interface JargonScanOptions { + severity: Severity; + extraTerms?: string[]; +} + +export async function scanJargon( + rootPath: string, + options: JargonScanOptions, +): Promise { + const config = loadJargonConfig(options.extraTerms); + const findings: Finding[] = []; + for await (const filePath of walkTextFiles(rootPath)) { + const content = await fs.readFile(filePath, "utf8"); + findings.push(...scanContent(filePath, content, config, options.severity)); + } + return findings; +} + +function scanContent( + filePath: string, + content: string, + config: JargonConfig, + severity: Severity, +): Finding[] { + const lines = content.split(/\r?\n/); + const found: Finding[] = []; + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i] ?? ""; + for (const pattern of config.patterns) { + if (pattern.regex.test(line)) { + found.push({ + file: filePath, + line: i + 1, + type: pattern.term.startsWith("*.") ? "internal-url" : "jargon", + severity, + source: "jargon", + message: pattern.message, + term: pattern.term, + }); + } + } + } + return found; +} + +async function* walkTextFiles(rootPath: string): AsyncGenerator { + const stack: string[] = [rootPath]; + while (stack.length > 0) { + const current = stack.pop(); + if (!current) continue; + let entries; + try { + entries = await fs.readdir(current, { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + const fullPath = path.join(current, entry.name); + if (entry.isDirectory()) { + if (SKIP_DIRECTORIES.has(entry.name)) continue; + stack.push(fullPath); + } else if (entry.isFile()) { + const ext = path.extname(entry.name).toLowerCase(); + if (DEFAULT_BINARY_EXTENSIONS.has(ext)) continue; + const stat = await fs.stat(fullPath); + if (stat.size > 5 * 1024 * 1024) continue; + yield fullPath; + } + } + } +} diff --git a/tools/typescript/essentials-sync/src/scanners/pii.ts b/tools/typescript/essentials-sync/src/scanners/pii.ts new file mode 100644 index 0000000..30563e0 --- /dev/null +++ b/tools/typescript/essentials-sync/src/scanners/pii.ts @@ -0,0 +1,110 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import type { Finding, Severity } from "../types.js"; + +const SKIP_DIRECTORIES = new Set([ + "node_modules", ".git", ".venv", "venv", "__pycache__", ".pytest_cache", + ".ruff_cache", ".mypy_cache", "dist", "build", ".pulumi", +]); + +const BINARY_EXTENSIONS = new Set([ + ".png", ".jpg", ".jpeg", ".gif", ".webp", ".pdf", ".zip", ".tar", ".gz", + ".bz2", ".xz", ".7z", ".woff", ".woff2", ".eot", ".ttf", ".otf", ".ico", + ".so", ".dylib", ".dll", ".class", ".jar", ".wasm", ".bin", +]); + +interface PiiPattern { + name: string; + regex: RegExp; + message: string; +} + +// Only patterns that flag real, well-defined PII signals belong here. Anything +// that looks like a "company employee ID" is org-specific and should be added +// via the jargon override file (`.essentials-sync-jargon.json`) rather than +// hardcoded here -- the previous `\b[A-Z]{3,5}\d{4,8}\b` rule false-positived +// on every ServiceNow ticket number (`INC00000001`) and ruff rule code +// (`PLR0913`) in the wild. +const PII_PATTERNS: readonly PiiPattern[] = [ + { + name: "email", + regex: /\b[A-Za-z0-9._%+-]+@(?!example\.(?:com|org|net))[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, + message: "Email address that is not on a documented example domain.", + }, + { + name: "phone", + regex: /\b(?:\+?1[\s.-]?)?\(?[2-9]\d{2}\)?[\s.-]?\d{3}[\s.-]?\d{4}\b/g, + message: "Possible phone number.", + }, + { + name: "ssn", + regex: /\b\d{3}-\d{2}-\d{4}\b/g, + message: "Possible US Social Security Number.", + }, +]; + +export interface PiiScanOptions { + severity: Severity; +} + +export async function scanPii( + rootPath: string, + options: PiiScanOptions, +): Promise { + const findings: Finding[] = []; + for await (const filePath of walkTextFiles(rootPath)) { + const content = await fs.readFile(filePath, "utf8"); + findings.push(...scanContent(filePath, content, options.severity)); + } + return findings; +} + +function scanContent(filePath: string, content: string, severity: Severity): Finding[] { + const lines = content.split(/\r?\n/); + const found: Finding[] = []; + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i] ?? ""; + for (const pattern of PII_PATTERNS) { + pattern.regex.lastIndex = 0; + if (pattern.regex.test(line)) { + found.push({ + file: filePath, + line: i + 1, + type: "pii", + severity, + source: "pii", + message: pattern.message, + term: pattern.name, + }); + } + } + } + return found; +} + +async function* walkTextFiles(rootPath: string): AsyncGenerator { + const stack: string[] = [rootPath]; + while (stack.length > 0) { + const current = stack.pop(); + if (!current) continue; + let entries; + try { + entries = await fs.readdir(current, { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + const fullPath = path.join(current, entry.name); + if (entry.isDirectory()) { + if (SKIP_DIRECTORIES.has(entry.name)) continue; + stack.push(fullPath); + } else if (entry.isFile()) { + const ext = path.extname(entry.name).toLowerCase(); + if (BINARY_EXTENSIONS.has(ext)) continue; + const stat = await fs.stat(fullPath); + if (stat.size > 5 * 1024 * 1024) continue; + yield fullPath; + } + } + } +} diff --git a/tools/typescript/essentials-sync/src/scanners/secretlint.ts b/tools/typescript/essentials-sync/src/scanners/secretlint.ts new file mode 100644 index 0000000..a5f6842 --- /dev/null +++ b/tools/typescript/essentials-sync/src/scanners/secretlint.ts @@ -0,0 +1,93 @@ +import path from "node:path"; +import { execa } from "execa"; +import type { Finding } from "../types.js"; + +interface SecretlintJsonResult { + filePath: string; + messages: Array<{ + ruleId?: string; + message: string; + severity?: number; + loc?: { start?: { line?: number } }; + }>; +} + +export interface SecretlintScanOptions { + configPath?: string; +} + +export async function scanWithSecretlint( + rootPath: string, + options: SecretlintScanOptions = {}, +): Promise { + const packageRoot = packageRootPath(); + const configPath = options.configPath ?? path.join(packageRoot, ".secretlintrc.json"); + + let result; + try { + result = await execa( + "npx", + [ + "--no-install", + "secretlint", + "--secretlintrc", + configPath, + "--format", + "json", + `${rootPath}/**/*`, + ], + { + reject: false, + cwd: packageRoot, + }, + ); + } catch (error) { + if (isMissingBinaryError(error)) { + throw new Error( + "secretlint not installed in this package. Run `npm install` first.", + ); + } + throw error; + } + + return parseSecretlintOutput(result.stdout ?? ""); +} + +function isMissingBinaryError(error: unknown): boolean { + if (error && typeof error === "object") { + const code = (error as { code?: string }).code; + return code === "ENOENT"; + } + return false; +} + +function parseSecretlintOutput(stdout: string): Finding[] { + const trimmed = stdout.trim(); + if (!trimmed) return []; + + let parsed: SecretlintJsonResult[]; + try { + parsed = JSON.parse(trimmed) as SecretlintJsonResult[]; + } catch { + return []; + } + const findings: Finding[] = []; + for (const fileResult of parsed) { + for (const message of fileResult.messages ?? []) { + findings.push({ + file: fileResult.filePath, + line: message.loc?.start?.line ?? 1, + type: "secret", + severity: "critical", + source: "secretlint", + message: `secretlint: ${message.message}`, + term: message.ruleId, + }); + } + } + return findings; +} + +function packageRootPath(): string { + return path.resolve(new URL("../..", import.meta.url).pathname); +} diff --git a/tools/typescript/essentials-sync/src/scanners/trufflehog.ts b/tools/typescript/essentials-sync/src/scanners/trufflehog.ts new file mode 100644 index 0000000..9dbb4ab --- /dev/null +++ b/tools/typescript/essentials-sync/src/scanners/trufflehog.ts @@ -0,0 +1,93 @@ +import { execa } from "execa"; +import type { Finding } from "../types.js"; + +export interface TrufflehogOptions { + binary?: string; +} + +interface TrufflehogJsonLine { + SourceMetadata?: { + Data?: { + Filesystem?: { + file?: string; + line?: number; + }; + }; + }; + DetectorName?: string; + Verified?: boolean; + Raw?: string; +} + +export async function scanWithTrufflehog( + rootPath: string, + options: TrufflehogOptions = {}, +): Promise { + const binary = options.binary ?? "trufflehog"; + + let result; + try { + result = await execa( + binary, + ["filesystem", rootPath, "--json", "--no-update"], + { + reject: false, + stripFinalNewline: false, + }, + ); + } catch (error) { + if (isMissingBinaryError(error)) { + throw new Error( + "trufflehog binary not found. Install it with `brew install trufflehog` " + + "or pass --trufflehog-binary .", + ); + } + throw error; + } + + if (result.exitCode !== 0 && result.exitCode !== 183) { + if (result.stderr && result.stderr.toLowerCase().includes("not found")) { + throw new Error( + "trufflehog binary not found. Install it with `brew install trufflehog`.", + ); + } + } + + return parseTrufflehogOutput(result.stdout ?? ""); +} + +function isMissingBinaryError(error: unknown): boolean { + if (error && typeof error === "object") { + const code = (error as { code?: string }).code; + return code === "ENOENT"; + } + return false; +} + +function parseTrufflehogOutput(stdout: string): Finding[] { + const lines = stdout.split(/\r?\n/).filter((line) => line.trim() !== ""); + const findings: Finding[] = []; + for (const rawLine of lines) { + let parsed: TrufflehogJsonLine; + try { + parsed = JSON.parse(rawLine) as TrufflehogJsonLine; + } catch { + continue; + } + const file = parsed.SourceMetadata?.Data?.Filesystem?.file; + const lineNumber = parsed.SourceMetadata?.Data?.Filesystem?.line ?? 1; + if (!file) continue; + findings.push({ + file, + line: lineNumber, + type: "secret", + severity: "critical", + source: "trufflehog", + message: `Secret detected by trufflehog: detector=${parsed.DetectorName ?? "unknown"}, verified=${ + parsed.Verified ? "true" : "false" + }`, + term: parsed.DetectorName, + }); + } + return findings; +} diff --git a/tools/typescript/essentials-sync/src/sync.ts b/tools/typescript/essentials-sync/src/sync.ts new file mode 100644 index 0000000..cfc26db --- /dev/null +++ b/tools/typescript/essentials-sync/src/sync.ts @@ -0,0 +1,120 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import type { SyncMode, SyncPlan } from "./types.js"; +import type { ExtractPlan } from "./extract-plan.js"; + +export interface PlanSyncInputs { + source: string; + targetRepo: string; + targetPath: string; + // When extract is enabled the source does not exist on disk yet -- Phase A + // will create it. Skip the existence check in that case. + assertSourceExists?: boolean; +} + +export async function planSync({ + source, + targetRepo, + targetPath, + assertSourceExists = true, +}: PlanSyncInputs): Promise { + const sourceAbs = path.resolve(source); + const targetRepoAbs = path.resolve(targetRepo); + const targetPathRel = path.normalize(targetPath); + const targetAbs = path.join(targetRepoAbs, targetPathRel); + + if (assertSourceExists) { + await assertDirectoryExists(sourceAbs, "--source"); + } + await assertDirectoryExists(targetRepoAbs, "--target-repo"); + await assertGitWorkingTree(targetRepoAbs); + + const mode: SyncMode = (await directoryExists(targetAbs)) ? "SYNC" : "COPY"; + + return { + mode, + sourceAbs, + targetRepoAbs, + targetPathRel, + targetAbs, + phaseAEnabled: false, + }; +} + +export interface ComposeFullPlanInputs { + syncHalf: SyncPlan; + extractHalf: ExtractPlan | null; +} + +// Combines the two half-plans into a single SyncPlan. When `extractHalf` is +// null we have a pure sync (legacy mode); when present we add the Phase A +// extract metadata and override `sourceAbs` to point at where Phase A will +// drop the extracted package. +export function composeFullPlan({ + syncHalf, + extractHalf, +}: ComposeFullPlanInputs): SyncPlan { + if (!extractHalf) { + return syncHalf; + } + return { + ...syncHalf, + sourceAbs: extractHalf.extractedPkgAbs, + phaseAEnabled: true, + extractedPkgAbs: extractHalf.extractedPkgAbs, + wrapperPkgAbs: extractHalf.wrapperPkgAbs, + sourceRepoRoot: extractHalf.sourceRepoRoot, + originalToolName: extractHalf.names.toolName, + packageName: extractHalf.names.packageName, + importableName: extractHalf.names.importableName, + }; +} + +async function assertDirectoryExists(absPath: string, label: string): Promise { + try { + const stat = await fs.stat(absPath); + if (!stat.isDirectory()) { + throw new Error(`${label} is not a directory: ${absPath}`); + } + } catch (error) { + if (isNotFoundError(error)) { + throw new Error(`${label} does not exist: ${absPath}`); + } + throw error; + } +} + +async function assertGitWorkingTree(repoPath: string): Promise { + const gitDir = path.join(repoPath, ".git"); + if (!(await directoryExists(gitDir)) && !(await fileExists(gitDir))) { + throw new Error(`--target-repo is not a git working tree (missing .git): ${repoPath}`); + } +} + +async function directoryExists(p: string): Promise { + try { + const stat = await fs.stat(p); + return stat.isDirectory(); + } catch (error) { + if (isNotFoundError(error)) return false; + throw error; + } +} + +async function fileExists(p: string): Promise { + try { + const stat = await fs.stat(p); + return stat.isFile(); + } catch (error) { + if (isNotFoundError(error)) return false; + throw error; + } +} + +function isNotFoundError(error: unknown): boolean { + if (error && typeof error === "object") { + const code = (error as { code?: string }).code; + return code === "ENOENT"; + } + return false; +} diff --git a/tools/typescript/essentials-sync/src/types.ts b/tools/typescript/essentials-sync/src/types.ts new file mode 100644 index 0000000..fc55655 --- /dev/null +++ b/tools/typescript/essentials-sync/src/types.ts @@ -0,0 +1,76 @@ +export type Severity = "critical" | "warning"; + +export type FindingType = + | "secret" + | "pii" + | "jargon" + | "internal-url" + | "internal-reference" + | "other"; + +export type FindingSource = "trufflehog" | "secretlint" | "jargon" | "pii" | "reviewer"; + +export interface Finding { + file: string; + line: number; + type: FindingType; + severity: Severity; + source: FindingSource; + message: string; + term?: string; +} + +export interface ScanReport { + findings: Finding[]; + scannedPaths: string[]; + durationMs: number; +} + +export type SyncMode = "COPY" | "SYNC"; + +export interface SyncPlan { + mode: SyncMode; + // sourceAbs is the directory that Phase B copies INTO essentials. When extract + // is enabled, it points at the path where Phase A will materialize the + // extracted package -- that path will not exist on disk at plan time. + sourceAbs: string; + targetRepoAbs: string; + targetPathRel: string; + targetAbs: string; + phaseAEnabled: boolean; + extractedPkgAbs?: string; + wrapperPkgAbs?: string; + sourceRepoRoot?: string; + originalToolName?: string; + packageName?: string; + importableName?: string; +} + +export type ModelSpec = + | { kind: "id"; id: string } + | { kind: "family"; family: string } + | { kind: "auto" }; + +export interface ResolvedModels { + primary: string; + review: string; + primarySource: "explicit" | "sentinel" | "fallback"; + reviewSource: "explicit" | "sentinel" | "fallback"; + available: string[]; + collapsed: boolean; +} + +export interface CliOptions { + source: string; + targetRepo: string; + targetPath?: string; + sourceRepo?: string; + packageName?: string; + model: string; + reviewModel: string; + maxRevisions: number; + dryRun: boolean; + noSourceScan: boolean; + noAdversarialReview: boolean; + noExtract: boolean; +} diff --git a/tools/typescript/essentials-sync/tests/extract-plan.test.ts b/tools/typescript/essentials-sync/tests/extract-plan.test.ts new file mode 100644 index 0000000..d83e398 --- /dev/null +++ b/tools/typescript/essentials-sync/tests/extract-plan.test.ts @@ -0,0 +1,182 @@ +import { describe, expect, it, beforeEach, afterEach } from "vitest"; +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { tmpdir } from "node:os"; +import { + derivePackageName, + deriveToolName, + planExtract, + validatePackageName, +} from "../src/extract-plan.js"; + +describe("derivePackageName", () => { + it("prefixes bare names with ess-", () => { + expect(derivePackageName("service-now-incident")).toBe("ess-service-now-incident"); + expect(derivePackageName("auth")).toBe("ess-auth"); + }); + + it("leaves names that already start with ess- alone", () => { + expect(derivePackageName("ess-auth")).toBe("ess-auth"); + expect(derivePackageName("ess-service-now-incident")).toBe("ess-service-now-incident"); + }); +}); + +describe("validatePackageName", () => { + it("accepts kebab-case ess-* names", () => { + expect(validatePackageName("ess-auth")).toBe("ess-auth"); + expect(validatePackageName("ess-service-now-incident")).toBe("ess-service-now-incident"); + }); + + it("rejects names that do not start with ess-", () => { + expect(() => validatePackageName("auth")).toThrow(/must start with 'ess-'/); + expect(() => validatePackageName("cisco-auth")).toThrow(/must start with 'ess-'/); + }); + + it("rejects non-kebab-case names", () => { + expect(() => validatePackageName("ess_auth")).toThrow(/kebab-case/); + expect(() => validatePackageName("EssAuth")).toThrow(/kebab-case/); + expect(() => validatePackageName("ess-")).toThrow(/kebab-case/); + }); +}); + +describe("deriveToolName", () => { + const sep = path.sep; + + it("extracts the tool name from a tools/python/ path", () => { + expect(deriveToolName(["tools", "python", "service-now-incident"].join(sep))).toBe( + "service-now-incident", + ); + expect(deriveToolName(["tools", "python", "ess-auth"].join(sep))).toBe("ess-auth"); + }); + + it("rejects paths that are not in tools/python/", () => { + expect(() => deriveToolName(["packages", "python", "ess-auth"].join(sep))).toThrow( + /tools\/python\//, + ); + expect(() => deriveToolName(["tools", "python", "foo", "src"].join(sep))).toThrow( + /tools\/python\//, + ); + expect(() => deriveToolName(["tools", "python"].join(sep))).toThrow( + /tools\/python\//, + ); + }); + + it("rejects invalid kebab slugs", () => { + expect(() => deriveToolName(["tools", "python", "Foo_Bar"].join(sep))).toThrow( + /kebab-case/, + ); + }); +}); + +describe("planExtract", () => { + let root: string; + + beforeEach(async () => { + root = await fs.mkdtemp(path.join(tmpdir(), "essentials-sync-extract-plan-")); + await fs.mkdir(path.join(root, ".git"), { recursive: true }); + await fs.writeFile(path.join(root, ".git", "HEAD"), "ref: refs/heads/main\n"); + }); + + afterEach(async () => { + await fs.rm(root, { recursive: true, force: true }); + }); + + async function makeTool(toolName: string): Promise { + const toolPath = path.join(root, "tools", "python", toolName); + await fs.mkdir(toolPath, { recursive: true }); + await fs.writeFile(path.join(toolPath, "pyproject.toml"), `[project]\nname = "${toolName}"\n`); + return toolPath; + } + + it("derives the package name and paths for a generic tool", async () => { + const toolPath = await makeTool("service-now-incident"); + + const plan = await planExtract({ source: toolPath }); + + expect(plan.names.toolName).toBe("service-now-incident"); + expect(plan.names.packageName).toBe("ess-service-now-incident"); + expect(plan.names.importableName).toBe("ess_service_now_incident"); + expect(plan.names.pyprojectMember).toBe("packages/python/ess-service-now-incident"); + expect(plan.names.defaultTargetPathRel).toBe("packages/python/ess-service-now-incident"); + expect(plan.sourceAbs).toBe(toolPath); + expect(plan.sourceRepoRoot).toBe(root); + expect(plan.extractedPkgAbs).toBe( + path.join(root, "packages", "python", "ess-service-now-incident"), + ); + expect(plan.wrapperPkgAbs).toBe(toolPath); + expect(plan.alreadyExtracted).toBe(false); + }); + + it("preserves an existing ess- prefix instead of double-prefixing", async () => { + const toolPath = await makeTool("ess-auth"); + + const plan = await planExtract({ source: toolPath }); + + expect(plan.names.packageName).toBe("ess-auth"); + expect(plan.names.importableName).toBe("ess_auth"); + }); + + it("honors an explicit --package-name override", async () => { + const toolPath = await makeTool("service-now-incident"); + + const plan = await planExtract({ + source: toolPath, + packageName: "ess-snow", + }); + + expect(plan.names.packageName).toBe("ess-snow"); + expect(plan.names.importableName).toBe("ess_snow"); + expect(plan.extractedPkgAbs).toBe(path.join(root, "packages", "python", "ess-snow")); + }); + + it("detects a pre-existing extracted package", async () => { + const toolPath = await makeTool("service-now-incident"); + await fs.mkdir(path.join(root, "packages", "python", "ess-service-now-incident"), { + recursive: true, + }); + + const plan = await planExtract({ source: toolPath }); + + expect(plan.alreadyExtracted).toBe(true); + }); + + it("walks up from --source to find the git root automatically", async () => { + const toolPath = await makeTool("service-now-incident"); + + const plan = await planExtract({ source: toolPath }); + + expect(plan.sourceRepoRoot).toBe(root); + }); + + it("respects an explicit --source-repo override", async () => { + const toolPath = await makeTool("service-now-incident"); + + const plan = await planExtract({ + source: toolPath, + sourceRepo: root, + }); + + expect(plan.sourceRepoRoot).toBe(root); + }); + + it("rejects a --source that is outside --source-repo", async () => { + const toolPath = await makeTool("service-now-incident"); + const otherRoot = await fs.mkdtemp(path.join(tmpdir(), "essentials-sync-other-")); + try { + await expect( + planExtract({ source: toolPath, sourceRepo: otherRoot }), + ).rejects.toThrow(/not inside/); + } finally { + await fs.rm(otherRoot, { recursive: true, force: true }); + } + }); + + it("rejects sources outside tools/python/", async () => { + const pkgPath = path.join(root, "packages", "python", "ess-auth"); + await fs.mkdir(pkgPath, { recursive: true }); + + await expect(planExtract({ source: pkgPath })).rejects.toThrow( + /tools\/python\//, + ); + }); +}); diff --git a/tools/typescript/essentials-sync/tests/fixtures/clean-package/README.md b/tools/typescript/essentials-sync/tests/fixtures/clean-package/README.md new file mode 100644 index 0000000..811f44f --- /dev/null +++ b/tools/typescript/essentials-sync/tests/fixtures/clean-package/README.md @@ -0,0 +1,3 @@ +# ess-example + +A small clean package fixture. It has no company-specific content, no PII, and no secret-looking strings. diff --git a/tools/typescript/essentials-sync/tests/fixtures/clean-package/src/main.py b/tools/typescript/essentials-sync/tests/fixtures/clean-package/src/main.py new file mode 100644 index 0000000..976b208 --- /dev/null +++ b/tools/typescript/essentials-sync/tests/fixtures/clean-package/src/main.py @@ -0,0 +1,5 @@ +"""A trivial module used to verify the scanner does not produce false positives.""" + + +def greet(name: str) -> str: + return f"Hello, {name}!" diff --git a/tools/typescript/essentials-sync/tests/fixtures/dirty-package/README.md b/tools/typescript/essentials-sync/tests/fixtures/dirty-package/README.md new file mode 100644 index 0000000..1c04d46 --- /dev/null +++ b/tools/typescript/essentials-sync/tests/fixtures/dirty-package/README.md @@ -0,0 +1,3 @@ +# cisco-internal-thing + +This fixture intentionally contains company jargon and a fake email to exercise the scanners. It should never reach the open-source target. diff --git a/tools/typescript/essentials-sync/tests/fixtures/dirty-package/src/client.py b/tools/typescript/essentials-sync/tests/fixtures/dirty-package/src/client.py new file mode 100644 index 0000000..4da7d4b --- /dev/null +++ b/tools/typescript/essentials-sync/tests/fixtures/dirty-package/src/client.py @@ -0,0 +1,8 @@ +"""Pretends to talk to an internal Cisco service. Fixture only.""" + +INTERNAL_HOST = "auth.cisco.com" +CONTACT_EMAIL = "internal-team@cisco.com" + + +def build_url(path: str) -> str: + return f"https://{INTERNAL_HOST}{path}" diff --git a/tools/typescript/essentials-sync/tests/scanners.test.ts b/tools/typescript/essentials-sync/tests/scanners.test.ts new file mode 100644 index 0000000..edc8fe6 --- /dev/null +++ b/tools/typescript/essentials-sync/tests/scanners.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { scanJargon } from "../src/scanners/jargon.js"; +import { scanPii } from "../src/scanners/pii.js"; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const cleanFixture = path.join(here, "fixtures", "clean-package"); +const dirtyFixture = path.join(here, "fixtures", "dirty-package"); + +describe("jargon scanner", () => { + it("returns no findings on a clean fixture", async () => { + const findings = await scanJargon(cleanFixture, { severity: "critical" }); + expect(findings).toEqual([]); + }); + + it("flags cisco terms and hostnames on a dirty fixture", async () => { + const findings = await scanJargon(dirtyFixture, { severity: "critical" }); + expect(findings.length).toBeGreaterThan(0); + expect(findings.some((f) => f.term === "cisco")).toBe(true); + expect(findings.some((f) => f.term === "*.cisco.com")).toBe(true); + }); + + it("respects custom severity", async () => { + const findings = await scanJargon(dirtyFixture, { severity: "warning" }); + expect(findings.every((f) => f.severity === "warning")).toBe(true); + }); +}); + +describe("pii scanner", () => { + it("returns no findings on a clean fixture", async () => { + const findings = await scanPii(cleanFixture, { severity: "critical" }); + expect(findings).toEqual([]); + }); + + it("flags non-example email addresses on a dirty fixture", async () => { + const findings = await scanPii(dirtyFixture, { severity: "critical" }); + expect(findings.some((f) => f.term === "email")).toBe(true); + }); +}); diff --git a/tools/typescript/essentials-sync/tsconfig.json b/tools/typescript/essentials-sync/tsconfig.json new file mode 100644 index 0000000..a35ec3e --- /dev/null +++ b/tools/typescript/essentials-sync/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "sourceMap": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/uv.lock b/uv.lock index 314a7a5..9819686 100644 --- a/uv.lock +++ b/uv.lock @@ -14,6 +14,7 @@ members = [ "langsmith-hosting", "langsmith-network", "pulumi-utils", + "service-now-incident", ] [[package]] @@ -1606,6 +1607,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, ] +[[package]] +name = "service-now-incident" +version = "0.1.0" +source = { editable = "tools/python/service-now-incident" } +dependencies = [ + { name = "click" }, + { name = "ess-browser" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "click", specifier = ">=8.1" }, + { name = "ess-browser", editable = "packages/python/ess-browser" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8.0" }] + [[package]] name = "six" version = "1.17.0" From e0916270d978a8a6cf31de2cbbe9d6ed8314e635 Mon Sep 17 00:00:00 2001 From: Matt Norris Date: Wed, 13 May 2026 09:04:42 -0400 Subject: [PATCH 2/3] feat(ess-service-now-incident): get ServiceNow incident details --- .../python/ess-service-now-incident/README.md | 153 ++++++++++ .../ess-service-now-incident/pyproject.toml | 28 ++ .../src/ess_service_now_incident/__init__.py | 30 ++ .../src/ess_service_now_incident/__main__.py | 18 ++ .../src/ess_service_now_incident/cli.py | 133 +++++++++ .../src/ess_service_now_incident/client.py | 279 ++++++++++++++++++ .../ess_service_now_incident/exceptions.py | 27 ++ .../tests/__init__.py | 0 .../tests/test_cli.py | 130 ++++++++ .../tests/test_client.py | 130 ++++++++ .../tests/test_get_incident.py | 197 +++++++++++++ uv.lock | 48 +-- 12 files changed, 1149 insertions(+), 24 deletions(-) create mode 100644 packages/python/ess-service-now-incident/README.md create mode 100644 packages/python/ess-service-now-incident/pyproject.toml create mode 100644 packages/python/ess-service-now-incident/src/ess_service_now_incident/__init__.py create mode 100644 packages/python/ess-service-now-incident/src/ess_service_now_incident/__main__.py create mode 100644 packages/python/ess-service-now-incident/src/ess_service_now_incident/cli.py create mode 100644 packages/python/ess-service-now-incident/src/ess_service_now_incident/client.py create mode 100644 packages/python/ess-service-now-incident/src/ess_service_now_incident/exceptions.py create mode 100644 packages/python/ess-service-now-incident/tests/__init__.py create mode 100644 packages/python/ess-service-now-incident/tests/test_cli.py create mode 100644 packages/python/ess-service-now-incident/tests/test_client.py create mode 100644 packages/python/ess-service-now-incident/tests/test_get_incident.py diff --git a/packages/python/ess-service-now-incident/README.md b/packages/python/ess-service-now-incident/README.md new file mode 100644 index 0000000..878620b --- /dev/null +++ b/packages/python/ess-service-now-incident/README.md @@ -0,0 +1,153 @@ +# ess-service-now-incident + +Fetch incident records from a [ServiceNow](https://www.servicenow.com/) +instance via an authenticated browser session. + +The library wraps the ServiceNow Table API and runs the call from +inside a persistent Chrome profile (managed by `ess-browser`) so that +SSO cookies survive between runs. No API tokens or basic-auth +credentials are required -- the user authenticates once in the browser, +and subsequent runs reuse the same session. + +## What you get + +- `get_incident()` -- programmatic access to a single incident record. +- `parse_incident_input()` -- parse an incident number or URL into a + structured query. +- `build_cli()` -- factory that returns a `click.Command`, suitable + for building organization-specific CLI wrappers with a baked-in + default instance. +- An `ess-service-now-incident` console script with no organization + defaults: callers must specify the instance via flag, environment + variable, or a full ServiceNow URL. + +## Installation + +In a `uv` workspace, declare the dependency in your `pyproject.toml`: + +```toml +[project] +dependencies = ["ess-service-now-incident"] + +[tool.uv.sources] +ess-service-now-incident = { workspace = true } +``` + +Then run `uv sync --all-packages` from the workspace root. + +## Library usage + +```python +from ess_service_now_incident import get_incident + +record = get_incident( + "INC0000001", + instance="example.service-now.com", +) +print(record["description"]) +``` + +Passing a full ServiceNow URL lets the library extract the instance +hostname automatically: + +```python +record = get_incident( + "https://example.service-now.com/now/sow/record/incident/", +) +``` + +The returned dict contains `number`, `sys_id`, `short_description`, +and `description`. + +## CLI usage + +The package ships an `ess-service-now-incident` console script with +no built-in default for `--instance`. The instance hostname is +resolved in this order: + +1. The host embedded in the `IDENTIFIER` argument when it is a URL. +2. The `--instance` flag. +3. The `SERVICENOW_INSTANCE` environment variable. + +```bash +# By incident number with explicit instance +uv run ess-service-now-incident --instance example.service-now.com INC0000001 + +# By URL (instance derived from the URL host) +uv run ess-service-now-incident \ + "https://example.service-now.com/now/sow/record/incident/" + +# Headed mode (show the browser window for first-time SSO login) +uv run ess-service-now-incident --headed \ + --instance example.service-now.com INC0000001 + +# Emit the full record as JSON +uv run ess-service-now-incident --json \ + --instance example.service-now.com INC0000001 +``` + +### Options + +| Option | Description | +| --------------- | ------------------------------------------------------------ | +| `--headed` | Show the browser window. Use for first-time SSO login. | +| `--profile-dir` | Browser profile directory for session persistence. | +| `--instance` | ServiceNow instance hostname (overridden by URL host). | +| `--json` | Emit the full record as JSON instead of the description. | +| `-v/--verbose` | Verbose logging to stderr. | + +## Building a wrapper with a default instance + +Wrappers can use `build_cli()` to construct a CLI with an +organization-specific default. The user can still override with +`--instance` or `SERVICENOW_INSTANCE`. + +```python +# tools/python/my-team-tool/src/my_team_tool/__main__.py +from ess_service_now_incident import build_cli + +main = build_cli(default_instance="my-org.service-now.com") + +if __name__ == "__main__": + main() +``` + +Wire that up in your wrapper's `pyproject.toml`: + +```toml +[project] +dependencies = ["ess-service-now-incident"] + +[tool.uv.sources] +ess-service-now-incident = { workspace = true } + +[project.scripts] +my-team-tool = "my_team_tool.__main__:main" +``` + +## Errors + +All library errors derive from `ServiceNowIncidentError`: + +| Exception | Raised when | +| ------------------------ | ----------------------------------------------------- | +| `InputParseError` | The input is not a recognisable URL or `INC...` number, or no instance is supplied. | +| `AuthenticationError` | SSO login times out or the Table API returns 401/403. | +| `IncidentNotFoundError` | The Table API returns an empty result set. | +| `APIError` | The Table API returns a non-success HTTP status, or the response is not JSON. | + +## Hostname safety + +The library validates instance hostnames against the +`*.service-now.com` suffix using an exact suffix match. Hostnames like +`evilservice-now.com` or `service-now.com.evil.com` are rejected, +mitigating subdomain-confusion attacks against URL inputs. + +## Testing + +```bash +uv run pytest packages/python/ess-service-now-incident/tests +``` + +The tests stub out the browser session and never touch a real +ServiceNow instance. diff --git a/packages/python/ess-service-now-incident/pyproject.toml b/packages/python/ess-service-now-incident/pyproject.toml new file mode 100644 index 0000000..04ec17d --- /dev/null +++ b/packages/python/ess-service-now-incident/pyproject.toml @@ -0,0 +1,28 @@ +[project] +name = "ess-service-now-incident" +version = "0.1.0" +description = "Fetch ServiceNow incident records via an authenticated browser session" +readme = "README.md" +requires-python = ">=3.12,<3.13" +dependencies = [ + "ess-browser", + "click>=8.1", +] + +[tool.uv.sources] +ess-browser = { workspace = true } + +[project.scripts] +ess-service-now-incident = "ess_service_now_incident.__main__:main" + +[dependency-groups] +dev = [ + "pytest>=8.0", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/ess_service_now_incident"] diff --git a/packages/python/ess-service-now-incident/src/ess_service_now_incident/__init__.py b/packages/python/ess-service-now-incident/src/ess_service_now_incident/__init__.py new file mode 100644 index 0000000..2cf2534 --- /dev/null +++ b/packages/python/ess-service-now-incident/src/ess_service_now_incident/__init__.py @@ -0,0 +1,30 @@ +"""Fetch ServiceNow incident records via an authenticated browser session.""" + +from .cli import INSTANCE_ENV_VAR, build_cli +from .client import ( + DEFAULT_PROFILE_DIR, + IncidentQuery, + get_incident, + parse_incident_input, +) +from .exceptions import ( + APIError, + AuthenticationError, + IncidentNotFoundError, + InputParseError, + ServiceNowIncidentError, +) + +__all__ = [ + "APIError", + "AuthenticationError", + "DEFAULT_PROFILE_DIR", + "INSTANCE_ENV_VAR", + "IncidentNotFoundError", + "IncidentQuery", + "InputParseError", + "ServiceNowIncidentError", + "build_cli", + "get_incident", + "parse_incident_input", +] diff --git a/packages/python/ess-service-now-incident/src/ess_service_now_incident/__main__.py b/packages/python/ess-service-now-incident/src/ess_service_now_incident/__main__.py new file mode 100644 index 0000000..f8c48f0 --- /dev/null +++ b/packages/python/ess-service-now-incident/src/ess_service_now_incident/__main__.py @@ -0,0 +1,18 @@ +"""CLI entry point for ess-service-now-incident. + +The standalone CLI has no built-in default instance; users must either +pass ``--instance``, set the ``SERVICENOW_INSTANCE`` environment +variable, or supply a full ServiceNow URL as the identifier argument. +Build a customized CLI with :func:`ess_service_now_incident.build_cli` +to bake in an organization-specific default. +""" + +from __future__ import annotations + +from .cli import build_cli + +main = build_cli() + + +if __name__ == "__main__": + main() # pylint: disable=no-value-for-parameter # Click supplies args at runtime diff --git a/packages/python/ess-service-now-incident/src/ess_service_now_incident/cli.py b/packages/python/ess-service-now-incident/src/ess_service_now_incident/cli.py new file mode 100644 index 0000000..ba2ec7b --- /dev/null +++ b/packages/python/ess-service-now-incident/src/ess_service_now_incident/cli.py @@ -0,0 +1,133 @@ +"""Click-based CLI factory for ess-service-now-incident. + +Wrappers can call :func:`build_cli` with a ``default_instance`` to bake +in an organization-specific hostname while keeping the same command +shape as the standalone CLI. +""" + +from __future__ import annotations + +import json +import logging +import sys + +import click + +from .client import DEFAULT_PROFILE_DIR, get_incident +from .exceptions import ServiceNowIncidentError + +INSTANCE_ENV_VAR = "SERVICENOW_INSTANCE" + + +def build_cli(*, default_instance: str | None = None) -> click.Command: + """Build a Click command for fetching ServiceNow incidents. + + Args: + default_instance: Hostname baked in as the ``--instance`` + default. At invocation time the ``SERVICENOW_INSTANCE`` + environment variable still takes precedence, and an + explicit ``--instance`` flag wins over both. Pass ``None`` + to require the user to supply an instance (either via the + flag, the environment variable, or a full URL identifier). + + Returns: + A :class:`click.Command` ready to be exposed as a console + script. The command exits with a non-zero status on errors. + """ + show_default = bool(default_instance) + + @click.command() + @click.argument("identifier") + @click.option( + "--headed", + is_flag=True, + default=False, + help="Show the browser window. Use for first-time SSO login.", + ) + @click.option( + "--profile-dir", + default=None, + help=( + f"Browser profile directory for session persistence. " + f"[default: {DEFAULT_PROFILE_DIR}]" + ), + ) + @click.option( + "--instance", + default=default_instance, + envvar=INSTANCE_ENV_VAR, + show_default=show_default, + help=( + "ServiceNow instance hostname (e.g. example.service-now.com). " + f"Falls back to the {INSTANCE_ENV_VAR} environment variable." + ), + ) + @click.option( + "--json", + "output_json", + is_flag=True, + default=False, + help="Output full incident data as JSON.", + ) + @click.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Enable verbose logging.", + ) + def main( # noqa: PLR0913 + identifier: str, + headed: bool, + profile_dir: str | None, + instance: str | None, + output_json: bool, + verbose: bool, + ) -> None: + """Fetch the description of a ServiceNow incident. + + IDENTIFIER is an incident number (e.g. INC0000001) or a full + ServiceNow URL. + """ + logging.basicConfig( + level=logging.DEBUG if verbose else logging.WARNING, + format="%(levelname)s: %(message)s", + stream=sys.stderr, + ) + + try: + result = get_incident( + identifier, + instance=instance, + headed=headed, + profile_dir=profile_dir, + ) + except ServiceNowIncidentError as exc: + click.echo(f"Error: {exc}", err=True) + sys.exit(1) + except Exception as exc: # noqa: BLE001 -- last-resort CLI guard + click.echo(f"Error: {exc}", err=True) + sys.exit(1) + + if output_json: + click.echo(json.dumps(result, indent=2)) + return + + description = result.get("description", "").strip() + if description: + click.echo(description) + return + + short_description = result.get("short_description", "").strip() + if short_description: + click.echo( + "Note: description is empty, showing short_description instead.", + err=True, + ) + click.echo(short_description) + return + + click.echo("No description or short_description found.", err=True) + sys.exit(1) + + return main diff --git a/packages/python/ess-service-now-incident/src/ess_service_now_incident/client.py b/packages/python/ess-service-now-incident/src/ess_service_now_incident/client.py new file mode 100644 index 0000000..e1cbd21 --- /dev/null +++ b/packages/python/ess-service-now-incident/src/ess_service_now_incident/client.py @@ -0,0 +1,279 @@ +"""Fetch incident data from a ServiceNow instance via a browser session.""" + +from __future__ import annotations + +import json +import logging +import re +from dataclasses import dataclass +from pathlib import Path +from typing import Any +from urllib.parse import parse_qs, urlparse + +from ess_browser import BrowserSession +from ess_browser.auth import LOGIN_TIMEOUT_MS + +from .exceptions import ( + APIError, + AuthenticationError, + IncidentNotFoundError, + InputParseError, +) + +logger = logging.getLogger(__name__) + +DEFAULT_PROFILE_DIR = Path.home() / ".ess-service-now-incident" / "browser-profile" + +_API_FIELDS = "description,short_description,number,sys_id" +_NAV_TIMEOUT_MS = 60_000 +_SYS_ID_PATTERN = re.compile(r"^[0-9a-f]{32}$") +_INC_PATTERN = re.compile(r"^INC\d+$", re.IGNORECASE) + +# JavaScript executed inside the browser to call the ServiceNow Table API. +# Uses the browser's session cookies and the g_ck CSRF token for auth. +_FETCH_JS = """\ +async ([url]) => { + const token = window.g_ck || ""; + const resp = await fetch(url, { + credentials: "include", + headers: { + "Accept": "application/json", + "X-UserToken": token + } + }); + const text = await resp.text(); + return { status: resp.status, ok: resp.ok, body: text }; +} +""" + + +@dataclass(frozen=True) +class IncidentQuery: + """Parsed incident identifier.""" + + query_type: str # "number" or "sys_id" + value: str + instance: str | None = None + + +def _normalize_url(raw: str) -> str: + """Prepend ``https://`` if *raw* looks like a schemeless URL.""" + if raw.startswith(("http://", "https://")): + return raw + if raw.startswith("//"): + return f"https:{raw}" + dot = raw.find(".") + slash = raw.find("/") + if dot != -1 and (slash == -1 or dot < slash): + return f"https://{raw}" + return raw + + +def _is_servicenow_host(hostname: str) -> bool: + """Return True if *hostname* is a genuine ``*.service-now.com`` domain.""" + normalized_host = hostname.lower() + return normalized_host == "service-now.com" or normalized_host.endswith( + ".service-now.com" + ) + + +def parse_incident_input(raw: str) -> IncidentQuery: + """Parse a raw incident identifier into a structured query. + + Args: + raw: An incident number (``INC0000001``) or a ServiceNow URL. + + Returns: + An ``IncidentQuery`` describing how to look up the incident. + + Raises: + InputParseError: If *raw* is neither a recognisable URL nor an + incident number. + """ + stripped = raw.strip() + + normalized = _normalize_url(stripped) + parsed = urlparse(normalized) + hostname = (parsed.hostname or "").lower() + + if parsed.scheme in ("http", "https") and hostname: + if not _is_servicenow_host(hostname): + message = ( + f"URL hostname {hostname!r} is not a ServiceNow instance. " + "Expected service-now.com or a *.service-now.com domain." + ) + raise InputParseError(message) + + # Check query-string for sys_id (classic UI links). + query_string_ids = parse_qs(parsed.query).get("sys_id", []) + if query_string_ids and _SYS_ID_PATTERN.match(query_string_ids[0]): + return IncidentQuery("sys_id", query_string_ids[0], hostname) + + # Check path segments for a 32-char hex string (workspace URLs). + for segment in reversed(parsed.path.strip("/").split("/")): + if _SYS_ID_PATTERN.match(segment): + return IncidentQuery("sys_id", segment, hostname) + + message = f"Could not extract a sys_id from URL: {stripped}" + raise InputParseError(message) + + if _INC_PATTERN.match(stripped): + return IncidentQuery("number", stripped.upper()) + + message = ( + f"Unrecognised input: {stripped!r}. " + "Provide an incident number (INC...) or a ServiceNow URL." + ) + raise InputParseError(message) + + +def _build_api_url(instance: str, query: IncidentQuery) -> str: + """Build the ServiceNow Table API URL for the given query.""" + base = f"https://{instance}/api/now/table/incident" + if query.query_type == "sys_id": + return f"{base}/{query.value}?sysparm_fields={_API_FIELDS}" + return f"{base}?sysparm_query=number={query.value}&sysparm_fields={_API_FIELDS}" + + +def _resolve_instance(query: IncidentQuery, override: str | None) -> str: + """Pick the effective ServiceNow instance and validate the hostname.""" + effective = query.instance or override + if not effective: + message = ( + "No ServiceNow instance specified. Pass a full ServiceNow URL " + "as the identifier, or supply instance=." + ) + raise InputParseError(message) + if not _is_servicenow_host(effective): + message = ( + f"Instance {effective!r} is not a ServiceNow domain. " + "Expected service-now.com or a *.service-now.com domain." + ) + raise InputParseError(message) + return effective + + +def _fetch_via_browser( + *, + api_url: str, + target_url: str, + headed: bool, + profile_dir: str, +) -> tuple[int, str]: + """Run the Table API call inside an authenticated browser session. + + Returns ``(status, body)`` for a successful HTTP response. Raises + :class:`AuthenticationError` if SSO fails or the session is rejected, + or :class:`APIError` for non-success HTTP statuses. + """ + with BrowserSession(headed=headed, profile_dir=profile_dir) as session: + page = session.new_page() + + # Navigate to the instance root to trigger SSO if needed. + logger.info("Navigating to %s", target_url) + page.goto(target_url, wait_until="load", timeout=_NAV_TIMEOUT_MS) + + if session.is_auth_redirect(page.url, target_url): + logger.info("SSO redirect detected -- waiting for login") + try: + session.wait_for_login(page, target_url, timeout_ms=LOGIN_TIMEOUT_MS) + except TimeoutError as exc: + hint = ( + " Re-run with --headed to complete login in the browser." + if not headed + else "" + ) + message = f"SSO authentication timed out.{hint}" + raise AuthenticationError(message) from exc + + # Wait for ServiceNow to fully establish the session (sets g_ck). + logger.info("Waiting for ServiceNow session to initialise") + page.wait_for_load_state("networkidle", timeout=_NAV_TIMEOUT_MS) + page.wait_for_function("() => !!window.g_ck", timeout=_NAV_TIMEOUT_MS) + + # Call the Table API from within the authenticated browser context. + logger.info("Fetching %s", api_url) + result = page.evaluate(_FETCH_JS, [api_url]) + + status = result["status"] + body = result["body"] + + if status in (401, 403): + message = ( + f"ServiceNow returned HTTP {status}. " + "Your session may have expired -- re-run with --headed." + ) + raise AuthenticationError(message) + + if not result["ok"]: + raise APIError(status, body[:500]) + + return status, body + + +def _unwrap_result(query: IncidentQuery, status: int, body: str) -> dict[str, Any]: + """Parse the Table API JSON body and unwrap the ``result`` field.""" + try: + data = json.loads(body) + except json.JSONDecodeError as exc: + snippet = body[:200].strip() + message = f"Expected JSON but received: {snippet!r}" + raise APIError(status, message) from exc + + api_result = data.get("result") + if api_result is None: + message = f"Unexpected API response shape: {list(data.keys())}" + raise APIError(status, message) + + if isinstance(api_result, list): + if not api_result: + message = f"No incident found for {query.value}" + raise IncidentNotFoundError(message) + return api_result[0] + + return api_result + + +def get_incident( + raw_input: str, + *, + instance: str | None = None, + headed: bool = False, + profile_dir: str | None = None, +) -> dict[str, Any]: + """Fetch an incident record from a ServiceNow instance. + + The instance hostname is resolved in this order: + 1. The host embedded in *raw_input* if it is a URL. + 2. The *instance* keyword argument. + + Args: + raw_input: An incident number (``INC0000001``) or a ServiceNow URL. + instance: ServiceNow instance hostname. Required when *raw_input* + is a bare incident number. + headed: Show the browser window for first-time SSO login. + profile_dir: Browser profile directory for session persistence. + Defaults to ``DEFAULT_PROFILE_DIR`` when omitted. + + Returns: + A dict with keys ``number``, ``sys_id``, ``short_description``, + and ``description``. + + Raises: + InputParseError: If *raw_input* cannot be parsed or no instance + can be resolved. + AuthenticationError: If SSO authentication fails. + IncidentNotFoundError: If the incident does not exist. + APIError: If the Table API returns a non-success status. + """ + query = parse_incident_input(raw_input) + effective_instance = _resolve_instance(query, instance) + effective_profile = profile_dir or str(DEFAULT_PROFILE_DIR) + + status, body = _fetch_via_browser( + api_url=_build_api_url(effective_instance, query), + target_url=f"https://{effective_instance}/", + headed=headed, + profile_dir=effective_profile, + ) + return _unwrap_result(query, status, body) diff --git a/packages/python/ess-service-now-incident/src/ess_service_now_incident/exceptions.py b/packages/python/ess-service-now-incident/src/ess_service_now_incident/exceptions.py new file mode 100644 index 0000000..c547350 --- /dev/null +++ b/packages/python/ess-service-now-incident/src/ess_service_now_incident/exceptions.py @@ -0,0 +1,27 @@ +"""Domain exceptions for the ess-service-now-incident library.""" + +from __future__ import annotations + + +class ServiceNowIncidentError(Exception): + """Base exception for all ess-service-now-incident errors.""" + + +class IncidentNotFoundError(ServiceNowIncidentError): + """The requested incident does not exist.""" + + +class AuthenticationError(ServiceNowIncidentError): + """SSO authentication failed or the session is expired.""" + + +class APIError(ServiceNowIncidentError): + """The ServiceNow Table API returned an error.""" + + def __init__(self, status: int, message: str) -> None: + self.status = status + super().__init__(f"ServiceNow API error (HTTP {status}): {message}") + + +class InputParseError(ServiceNowIncidentError): + """The input cannot be parsed as an incident number or URL.""" diff --git a/packages/python/ess-service-now-incident/tests/__init__.py b/packages/python/ess-service-now-incident/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/python/ess-service-now-incident/tests/test_cli.py b/packages/python/ess-service-now-incident/tests/test_cli.py new file mode 100644 index 0000000..be7299b --- /dev/null +++ b/packages/python/ess-service-now-incident/tests/test_cli.py @@ -0,0 +1,130 @@ +"""Unit tests for the build_cli factory.""" + +from __future__ import annotations + +import json +from unittest.mock import patch + +import click +from click.testing import CliRunner +from ess_service_now_incident.cli import INSTANCE_ENV_VAR, build_cli + +_INCIDENT = { + "number": "INC0000001", + "sys_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6", + "short_description": "Example short description", + "description": "Description body.", +} + +_PATCH_TARGET = "ess_service_now_incident.cli.get_incident" + + +# Use ``None`` (not ``""``) to mark the env var as unset in ``CliRunner.invoke``. +# ``CliRunner`` removes any key whose value is ``None`` from the child env, which +# is the explicit, version-independent way to say "no SERVICENOW_INSTANCE here". +_ENV_UNSET: dict[str, str | None] = {INSTANCE_ENV_VAR: None} + + +def _invoke( + cli: click.Command, + args: list[str], + env: dict[str, str | None] | None = None, +): + """Invoke ``cli`` with ``get_incident`` mocked, returning the mock and result.""" + runner = CliRunner() + with patch(_PATCH_TARGET, return_value=_INCIDENT) as mock_get: + result = runner.invoke(cli, args, env=env) + return mock_get, result + + +class TestBuildCli: + def test_returns_click_command(self): + cli = build_cli() + assert isinstance(cli, click.Command) + + +class TestInstancePrecedence: + """Runtime tests for the --instance / env var / default precedence.""" + + def test_no_default_and_no_env_passes_none(self): + cli = build_cli(default_instance=None) + mock_get, result = _invoke(cli, ["INC0000001"], env=_ENV_UNSET) + assert result.exit_code == 0 + assert mock_get.call_args.kwargs["instance"] is None + + def test_explicit_default_used_when_no_env(self): + cli = build_cli(default_instance="example.service-now.com") + mock_get, result = _invoke(cli, ["INC0000001"], env=_ENV_UNSET) + assert result.exit_code == 0 + assert mock_get.call_args.kwargs["instance"] == "example.service-now.com" + + def test_env_var_used_when_no_explicit_default(self): + cli = build_cli() + mock_get, result = _invoke( + cli, ["INC0000001"], env={INSTANCE_ENV_VAR: "acme.service-now.com"} + ) + assert result.exit_code == 0 + assert mock_get.call_args.kwargs["instance"] == "acme.service-now.com" + + def test_env_var_overrides_explicit_default(self): + cli = build_cli(default_instance="explicit.service-now.com") + mock_get, result = _invoke( + cli, ["INC0000001"], env={INSTANCE_ENV_VAR: "from-env.service-now.com"} + ) + assert result.exit_code == 0 + assert mock_get.call_args.kwargs["instance"] == "from-env.service-now.com" + + def test_flag_overrides_env_var_and_default(self): + cli = build_cli(default_instance="explicit.service-now.com") + mock_get, result = _invoke( + cli, + ["--instance", "flag.service-now.com", "INC0000001"], + env={INSTANCE_ENV_VAR: "from-env.service-now.com"}, + ) + assert result.exit_code == 0 + assert mock_get.call_args.kwargs["instance"] == "flag.service-now.com" + + def test_empty_env_var_falls_back_to_default(self): + # An empty SERVICENOW_INSTANCE is intentionally treated as "unset" by + # Click's envvar plumbing, so the baked-in default still wins. Pin + # this contract: users who export an empty value won't accidentally + # send "" through to get_incident(). + cli = build_cli(default_instance="explicit.service-now.com") + mock_get, result = _invoke(cli, ["INC0000001"], env={INSTANCE_ENV_VAR: ""}) + assert result.exit_code == 0 + assert mock_get.call_args.kwargs["instance"] == "explicit.service-now.com" + + +class TestCliInvocation: + def test_prints_description_by_default(self): + cli = build_cli(default_instance="example.service-now.com") + runner = CliRunner() + with patch(_PATCH_TARGET, return_value=_INCIDENT): + result = runner.invoke(cli, ["INC0000001"], env=_ENV_UNSET) + assert result.exit_code == 0 + assert "Description body." in result.output + + def test_json_flag_emits_json(self): + cli = build_cli(default_instance="example.service-now.com") + runner = CliRunner() + with patch(_PATCH_TARGET, return_value=_INCIDENT): + result = runner.invoke(cli, ["--json", "INC0000001"], env=_ENV_UNSET) + assert result.exit_code == 0 + assert json.loads(result.output) == _INCIDENT + + def test_falls_back_to_short_description(self): + cli = build_cli(default_instance="example.service-now.com") + runner = CliRunner() + payload = {**_INCIDENT, "description": " "} + with patch(_PATCH_TARGET, return_value=payload): + result = runner.invoke(cli, ["INC0000001"], env=_ENV_UNSET) + assert result.exit_code == 0 + assert "Example short description" in result.output + + def test_exit_nonzero_when_both_descriptions_empty(self): + cli = build_cli(default_instance="example.service-now.com") + runner = CliRunner() + payload = {**_INCIDENT, "description": "", "short_description": ""} + with patch(_PATCH_TARGET, return_value=payload): + result = runner.invoke(cli, ["INC0000001"], env=_ENV_UNSET) + assert result.exit_code == 1 diff --git a/packages/python/ess-service-now-incident/tests/test_client.py b/packages/python/ess-service-now-incident/tests/test_client.py new file mode 100644 index 0000000..c2479cf --- /dev/null +++ b/packages/python/ess-service-now-incident/tests/test_client.py @@ -0,0 +1,130 @@ +"""Unit tests for parse_incident_input and helpers.""" + +from __future__ import annotations + +import pytest +from ess_service_now_incident.client import ( + IncidentQuery, + _is_servicenow_host, + _normalize_url, + parse_incident_input, +) +from ess_service_now_incident.exceptions import InputParseError + +_SYS_ID = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" + + +class TestNormalizeUrl: + def test_already_has_https(self): + assert _normalize_url("https://x.com/p") == "https://x.com/p" + + def test_already_has_http(self): + assert _normalize_url("http://x.com/p") == "http://x.com/p" + + def test_schemeless_url(self): + assert ( + _normalize_url("example.service-now.com/foo") + == "https://example.service-now.com/foo" + ) + + def test_bare_hostname(self): + assert ( + _normalize_url("example.service-now.com") + == "https://example.service-now.com" + ) + + def test_not_a_url(self): + assert _normalize_url("INC0000001") == "INC0000001" + + +class TestIsServicenowHost: + def test_exact_domain(self): + assert _is_servicenow_host("service-now.com") is True + + def test_subdomain(self): + assert _is_servicenow_host("example.service-now.com") is True + + def test_deep_subdomain(self): + assert _is_servicenow_host("a.b.service-now.com") is True + + def test_malicious_prefix(self): + assert _is_servicenow_host("evilservice-now.com") is False + + def test_malicious_suffix(self): + assert _is_servicenow_host("service-now.com.evil.com") is False + + def test_unrelated_domain(self): + assert _is_servicenow_host("example.com") is False + + +class TestParseIncidentNumber: + def test_bare_inc_number(self): + result = parse_incident_input("INC0000001") + assert result == IncidentQuery("number", "INC0000001") + + def test_lowercase_inc_number(self): + result = parse_incident_input("inc0000001") + assert result == IncidentQuery("number", "INC0000001") + + def test_inc_with_whitespace(self): + result = parse_incident_input(" INC0000001 ") + assert result == IncidentQuery("number", "INC0000001") + + +class TestParseUrlWithSysId: + def test_workspace_url(self): + url = f"https://example.service-now.com/now/sow/record/incident/{_SYS_ID}" + result = parse_incident_input(url) + assert result == IncidentQuery("sys_id", _SYS_ID, "example.service-now.com") + + def test_classic_url_with_query_param(self): + url = f"https://example.service-now.com/incident.do?sys_id={_SYS_ID}" + result = parse_incident_input(url) + assert result == IncidentQuery("sys_id", _SYS_ID, "example.service-now.com") + + def test_schemeless_url(self): + url = f"example.service-now.com/now/sow/record/incident/{_SYS_ID}" + result = parse_incident_input(url) + assert result == IncidentQuery("sys_id", _SYS_ID, "example.service-now.com") + + def test_url_without_sys_id_raises(self): + with pytest.raises(InputParseError, match="Could not extract a sys_id"): + parse_incident_input( + "https://example.service-now.com/nav_to.do?uri=incident_list.do" + ) + + +class TestHostnameValidation: + def test_malicious_domain_rejected(self): + url = f"https://evilservice-now.com/incident/{_SYS_ID}" + with pytest.raises(InputParseError, match="not a ServiceNow instance"): + parse_incident_input(url) + + def test_subdomain_attack_rejected(self): + url = f"https://service-now.com.evil.com/incident/{_SYS_ID}" + with pytest.raises(InputParseError, match="not a ServiceNow instance"): + parse_incident_input(url) + + def test_valid_subdomain_accepted(self): + url = f"https://example.service-now.com/now/sow/record/incident/{_SYS_ID}" + result = parse_incident_input(url) + assert result.instance == "example.service-now.com" + + def test_different_subdomain_accepted(self): + url = f"https://acme.service-now.com/now/sow/record/incident/{_SYS_ID}" + result = parse_incident_input(url) + assert result.instance == "acme.service-now.com" + + +class TestInvalidInput: + def test_random_string_raises(self): + with pytest.raises(InputParseError, match="Unrecognised input"): + parse_incident_input("hello world") + + def test_empty_string_raises(self): + with pytest.raises(InputParseError, match="Unrecognised input"): + parse_incident_input("") + + def test_non_servicenow_url_raises(self): + with pytest.raises(InputParseError, match="not a ServiceNow instance"): + parse_incident_input("https://example.com/INC123") diff --git a/packages/python/ess-service-now-incident/tests/test_get_incident.py b/packages/python/ess-service-now-incident/tests/test_get_incident.py new file mode 100644 index 0000000..2e6c46d --- /dev/null +++ b/packages/python/ess-service-now-incident/tests/test_get_incident.py @@ -0,0 +1,197 @@ +"""Unit tests for get_incident with mocked BrowserSession.""" + +from __future__ import annotations + +import json +from unittest.mock import MagicMock, patch + +import pytest +from ess_service_now_incident.client import get_incident +from ess_service_now_incident.exceptions import ( + APIError, + AuthenticationError, + IncidentNotFoundError, + InputParseError, +) + +_INSTANCE = "example.service-now.com" +_INCIDENT = { + "number": "INC0000001", + "sys_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6", + "short_description": "Example short description", + "description": "Full description here.", +} + + +def _make_session( + *, + evaluate_return: dict | None = None, + is_auth_redirect: bool = False, + wait_for_login_error: Exception | None = None, +) -> MagicMock: + page = MagicMock() + page.url = f"https://{_INSTANCE}/" + + if evaluate_return is None: + evaluate_return = { + "status": 200, + "ok": True, + "body": json.dumps({"result": _INCIDENT}), + } + page.evaluate.return_value = evaluate_return + + session = MagicMock() + session.new_page.return_value = page + session.is_auth_redirect.return_value = is_auth_redirect + session.__enter__ = MagicMock(return_value=session) + session.__exit__ = MagicMock(return_value=False) + + if wait_for_login_error: + session.wait_for_login.side_effect = wait_for_login_error + + return session + + +_PATCH_TARGET = "ess_service_now_incident.client.BrowserSession" + + +class TestGetIncidentSuccess: + def test_returns_single_result(self): + session = _make_session() + with patch(_PATCH_TARGET, return_value=session): + result = get_incident("INC0000001", instance=_INSTANCE) + assert result == _INCIDENT + + def test_returns_first_from_list_result(self): + body = json.dumps({"result": [_INCIDENT]}) + session = _make_session( + evaluate_return={"status": 200, "ok": True, "body": body}, + ) + with patch(_PATCH_TARGET, return_value=session): + result = get_incident("INC0000001", instance=_INSTANCE) + assert result == _INCIDENT + + def test_url_argument_supplies_instance(self): + session = _make_session() + url = f"https://{_INSTANCE}/now/sow/record/incident/{_INCIDENT['sys_id']}" + with patch(_PATCH_TARGET, return_value=session): + result = get_incident(url) + assert result == _INCIDENT + + +class TestGetIncidentInstanceValidation: + def test_rejects_non_servicenow_instance(self): + with pytest.raises(InputParseError, match="not a ServiceNow domain"): + get_incident("INC0000001", instance="evil.com") + + def test_requires_instance_for_bare_number(self): + with pytest.raises(InputParseError, match="No ServiceNow instance"): + get_incident("INC0000001") + + def test_accepts_valid_instance(self): + session = _make_session() + with patch(_PATCH_TARGET, return_value=session): + result = get_incident( + "INC0000001", + instance="acme.service-now.com", + ) + assert result == _INCIDENT + + +class TestGetIncidentAuthErrors: + def test_http_401_raises_authentication_error(self): + session = _make_session( + evaluate_return={ + "status": 401, + "ok": False, + "body": "Unauthorized", + }, + ) + with ( + patch(_PATCH_TARGET, return_value=session), + pytest.raises(AuthenticationError, match="HTTP 401"), + ): + get_incident("INC0000001", instance=_INSTANCE) + + def test_http_403_raises_authentication_error(self): + session = _make_session( + evaluate_return={ + "status": 403, + "ok": False, + "body": "Forbidden", + }, + ) + with ( + patch(_PATCH_TARGET, return_value=session), + pytest.raises(AuthenticationError, match="HTTP 403"), + ): + get_incident("INC0000001", instance=_INSTANCE) + + def test_sso_timeout_raises_authentication_error(self): + session = _make_session( + is_auth_redirect=True, + wait_for_login_error=TimeoutError("SSO timed out"), + ) + with ( + patch(_PATCH_TARGET, return_value=session), + pytest.raises(AuthenticationError, match="timed out"), + ): + get_incident("INC0000001", instance=_INSTANCE) + + +class TestGetIncidentApiErrors: + def test_non_ok_http_raises_api_error(self): + session = _make_session( + evaluate_return={ + "status": 500, + "ok": False, + "body": "Internal Server Error", + }, + ) + with ( + patch(_PATCH_TARGET, return_value=session), + pytest.raises(APIError, match="HTTP 500"), + ): + get_incident("INC0000001", instance=_INSTANCE) + + def test_invalid_json_raises_api_error(self): + session = _make_session( + evaluate_return={ + "status": 200, + "ok": True, + "body": "Auth page", + }, + ) + with ( + patch(_PATCH_TARGET, return_value=session), + pytest.raises(APIError, match="Expected JSON"), + ): + get_incident("INC0000001", instance=_INSTANCE) + + def test_unexpected_response_shape_raises_api_error(self): + session = _make_session( + evaluate_return={ + "status": 200, + "ok": True, + "body": json.dumps({"error": "something"}), + }, + ) + with ( + patch(_PATCH_TARGET, return_value=session), + pytest.raises(APIError, match="Unexpected API response"), + ): + get_incident("INC0000001", instance=_INSTANCE) + + def test_empty_list_raises_not_found(self): + session = _make_session( + evaluate_return={ + "status": 200, + "ok": True, + "body": json.dumps({"result": []}), + }, + ) + with ( + patch(_PATCH_TARGET, return_value=session), + pytest.raises(IncidentNotFoundError, match="No incident found"), + ): + get_incident("INC0000001", instance=_INSTANCE) diff --git a/uv.lock b/uv.lock index 9819686..6c601b4 100644 --- a/uv.lock +++ b/uv.lock @@ -8,13 +8,13 @@ members = [ "core-github", "ess-auth", "ess-browser", + "ess-service-now-incident", "essentials", "gcp-gemini", "langsmith-client", "langsmith-hosting", "langsmith-network", "pulumi-utils", - "service-now-incident", ] [[package]] @@ -422,6 +422,29 @@ requires-dist = [{ name = "playwright", specifier = ">=1.40.0" }] [package.metadata.requires-dev] dev = [{ name = "pytest", specifier = ">=8.0" }] +[[package]] +name = "ess-service-now-incident" +version = "0.1.0" +source = { editable = "packages/python/ess-service-now-incident" } +dependencies = [ + { name = "click" }, + { name = "ess-browser" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "click", specifier = ">=8.1" }, + { name = "ess-browser", editable = "packages/python/ess-browser" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8.0" }] + [[package]] name = "essentials" version = "0.1.0" @@ -1607,29 +1630,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, ] -[[package]] -name = "service-now-incident" -version = "0.1.0" -source = { editable = "tools/python/service-now-incident" } -dependencies = [ - { name = "click" }, - { name = "ess-browser" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, -] - -[package.metadata] -requires-dist = [ - { name = "click", specifier = ">=8.1" }, - { name = "ess-browser", editable = "packages/python/ess-browser" }, -] - -[package.metadata.requires-dev] -dev = [{ name = "pytest", specifier = ">=8.0" }] - [[package]] name = "six" version = "1.17.0" From e5129017804c8a8c73caa073f1c0d333bcc36415 Mon Sep 17 00:00:00 2001 From: Matt Norris Date: Wed, 13 May 2026 11:24:30 -0400 Subject: [PATCH 3/3] refactor(essentials-sync): use more generic terms --- tools/typescript/essentials-sync/README.md | 10 +++++----- tools/typescript/essentials-sync/src/prompts.ts | 16 ++++++++-------- tools/typescript/essentials-sync/src/reviewer.ts | 5 +++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tools/typescript/essentials-sync/README.md b/tools/typescript/essentials-sync/README.md index e5cbffb..a776938 100644 --- a/tools/typescript/essentials-sync/README.md +++ b/tools/typescript/essentials-sync/README.md @@ -6,19 +6,19 @@ Extract a jargon-free `ess-*` shared package out of a team- or account-specific ```bash essentials-sync \ - --source ~/code/honeycomb/tools/python/service-now-incident \ + --source ~/code//tools/python/foo-client \ --target-repo ~/code/essentials ``` That invocation produces three artifacts across two repos: ``` -honeycomb/tools/python/service-now-incident/ (rewritten as a thin wrapper, Cisco defaults) -honeycomb/packages/python/ess-service-now-incident/ (NEW: generic core, no defaults) -essentials/packages/python/ess-service-now-incident/ (NEW: copy of the generic core) +/tools/python/foo-client/ (rewritten as a thin wrapper, company defaults) +/packages/python/ess-foo-client/ (NEW: generic core, no defaults) +essentials/packages/python/ess-foo-client/ (NEW: copy of the generic core) ``` -The wrapper depends on the extracted package via `[tool.uv.sources] ess-service-now-incident = { workspace = true }` -- the same shape as `sales-ai-langsmith-hosting` / `langsmith-hosting`. The extracted and synced packages are scanned and reviewed; the wrapper is intentionally left alone so it can keep the company-specific defaults that justify its existence. +The wrapper depends on the extracted package via `[tool.uv.sources] ess-foo-client = { workspace = true }`. The extracted and synced packages are scanned and reviewed; the wrapper is intentionally left alone so it can keep the company-specific defaults that justify its existence. ## How it works diff --git a/tools/typescript/essentials-sync/src/prompts.ts b/tools/typescript/essentials-sync/src/prompts.ts index c2bac28..b7351c9 100644 --- a/tools/typescript/essentials-sync/src/prompts.ts +++ b/tools/typescript/essentials-sync/src/prompts.ts @@ -21,16 +21,16 @@ STRUCTURE PRESERVATION: When you are uncertain whether a term is internal or generic, treat it as internal and generalize it. `; -// === Extract system prompt (Phase A: refactor in place inside honeycomb) === -export const EXTRACT_SYSTEM_PROMPT = `You are the essentials-sync primary agent operating in EXTRACT mode. Your job is to refactor a team- or account-specific tool inside the private honeycomb repository into two sibling packages: +// === Extract system prompt (Phase A: refactor in place inside the source repo) === +export const EXTRACT_SYSTEM_PROMPT = `You are the essentials-sync primary agent operating in EXTRACT mode. Your job is to refactor a team- or account-specific tool inside a private uv-workspace repository into two sibling packages: 1. A jargon-free shared library at 'packages/python/ess-/' containing all the reusable logic, parameterized so it has NO hard-coded company-specific defaults. 2. A thin wrapper at 'tools/python//' (the original tool's location, rewritten in place) that imports the shared library and supplies the company-specific defaults. -REFERENCE PATTERN: this matches the langsmith-hosting / sales-ai-langsmith-hosting split that already exists in honeycomb. Concretely: -- 'packages/python/langsmith-hosting/' is a generic, reusable library with its own CLI, tests, and parameterized configuration. -- 'tools/python/sales-ai-langsmith-hosting/__main__.py' is two non-blank lines that import 'deploy_stack' from the library and call it with the team-specific 'aws_profile'. -- The wrapper depends on the library via '[tool.uv.sources] langsmith-hosting = { workspace = true }'. +REFERENCE PATTERN -- library/wrapper split. Concretely: +- 'packages/python//' is a generic, reusable library with its own CLI, tests, and parameterized configuration (no hard-coded defaults). +- 'tools/python//__main__.py' is roughly two non-blank lines that import the library entry point and call it with the team-/account-specific defaults (e.g. an 'aws_profile', an internal hostname). +- The wrapper depends on the library via '[tool.uv.sources] = { workspace = true }'. Replicate that shape. HARD RULES for the EXTRACTED package (the new 'packages/python/ess-/'): @@ -165,11 +165,11 @@ TASK: 2. Create the EXTRACTED PACKAGE at the path above. It must include: - Its own pyproject.toml with name '${plan.packageName}', a short generic description, hatchling build backend, and the right [tool.hatch.build.targets.wheel] packages entry pointing at 'src/${plan.importableName}'. - A 'src/${plan.importableName}/' directory with the generalized core logic. All company-specific defaults must become required parameters, env-var-driven overrides, or be removed. - - Tests under 'tests/' that pass without any honeycomb-specific setup. + - Tests under 'tests/' that pass without any source-repo-specific setup. - A README.md focused on the generic library (what it does, how to use it, no company references). 3. Rewrite ORIGINAL TOOL in place as a thin wrapper: - Its pyproject.toml should declare a dependency on '${plan.packageName}' via '[tool.uv.sources] ${plan.packageName} = { workspace = true }'. - - Its source code becomes a small wrapper module that imports from '${plan.importableName}' and supplies the company-specific defaults. Follow the 'sales-ai-langsmith-hosting' shape. + - Its source code becomes a small wrapper module that imports from '${plan.importableName}' and supplies the company-specific defaults. Follow the library/wrapper shape described in your system prompt. - Keep the same CLI entry point name (so existing callers do not break). - The wrapper is allowed to contain company-specific defaults (hostnames, AWS profiles, internal SSO domains, etc.). That is its purpose. 4. Update the root pyproject.toml at ${plan.sourceRepoRoot}/pyproject.toml to add '${plan.packageName}' to '[tool.uv.workspace] members' if it is not already listed. Do not modify any other workspace entries. diff --git a/tools/typescript/essentials-sync/src/reviewer.ts b/tools/typescript/essentials-sync/src/reviewer.ts index 0f28a54..363e3a6 100644 --- a/tools/typescript/essentials-sync/src/reviewer.ts +++ b/tools/typescript/essentials-sync/src/reviewer.ts @@ -11,8 +11,9 @@ export interface RunReviewerInputs { // reviewer audits the new shared library rather than the essentials target. reviewRootAbs?: string; // Working directory for the reviewer agent. Defaults to plan.targetRepoAbs. - // During the extract phase callers pass plan.sourceRepoRoot (honeycomb) so - // the reviewer can read files inside the extracted package directly. + // During the extract phase callers pass plan.sourceRepoRoot (the private + // source repo) so the reviewer can read files inside the extracted package + // directly. cwd?: string; }