diff --git a/extensions/pi-graphify/CHANGELOG.md b/extensions/pi-graphify/CHANGELOG.md new file mode 100644 index 0000000..533dd4c --- /dev/null +++ b/extensions/pi-graphify/CHANGELOG.md @@ -0,0 +1,61 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.2] - 2026-05-09 + +### Changed + +- Added `.pi/gateway/` to `.gitignore`. +- Clarified README configuration table formatting and `statusbar` option description. +- Synced tracked `graphify-out` artifacts after regeneration. + +## [0.1.1] - 2026-05-09 + +### Added + +- `graphify_watch` tool: watch a directory for file changes, auto-rebuild graph on code edits. +- `graphify_cluster` tool: re-run community detection on an existing graph without re-extraction. +- `/graphify watch ` command with autocomplete. +- `/graphify cluster` command. +- `/graphify hook ` command to manage git hooks. +- Optional `pi-statusbar` widget integration with auto-refresh on session start and agent start. +- Runner functions for `watch`, `clusterOnly`, `hookAction`, `pushNeo4j`, `saveResult`, `cloneRepo`, `mergeGraphs`, `generateTree`. +- Unit tests for runner module (10 passing). +- Integration tests using @gaodes/pi-test-harness (12 passing): all 8 tools exercised via playbook DSL with mocked bash, plus command-level forwarding coverage. +- `@gaodes/pi-test-harness` as dev dependency. +- Bundled `graphify` skill: full-pipeline orchestration guide (semantic extraction, community labeling, export formats, video transcription, guided exploration). Replaces the standalone skill in `~/.pi/agent/skills/graphify/`. +- Config key standardized to `pi-graphify` with automatic migration from legacy `graphify`. + +### Fixed + +- npm package metadata now includes `publishConfig.access=public` plus GitHub `bugs`/`homepage` fields for clean scoped-package publishing. +- Added `@biomejs/biome` as a dev dependency so `npm run lint` works in clean environments. +- Relaxed local typecheck strictness that was pulling in `@gaodes/pi-utils-ui` source-level unused-variable diagnostics from `node_modules`. +- Graphify build initialization now auto-manages `.gitignore`: creates the file if missing, removes legacy `graphify-out/` ignore entries, and keeps only `graphify-out/cache/`, `graphify-out/.graphify_python`, and `graphify-out/cost.json` ignored so `graphify-out/` stays tracked. +- Lint script scoped to `src/` instead of `.`. +- Removed leaked `graphify-out/cache` from `src/`. +- Bundled `graphify` skill command snippets now use consistent temporary-file paths (`.graphify_python`, `.graphify_detect.json`, `.graphify_chunk_*.json`, `.graphify_semantic_new.json`). +- Corrected GraphML export snippet in bundled `graphify` skill. +- `/graphify` command now forwards build flags (`--mode deep`, `--no-viz`, `--obsidian`, `--svg`, `--graphml`, `--neo4j`) to `graphify_build` explicitly. +- Removed misleading `--watch` build-flag autocomplete (watching is via `/graphify watch `). +- `--debounce ` is now parsed for `/graphify watch` and forwarded in the watch tool prompt. +- Added command integration tests to lock behavior for `/graphify` build-flag forwarding and watch debounce parsing. + +## [0.1.0] - 2025-05-06 + +### Added + +- Initial release: knowledge graph tools and `/graphify` command for Pi. +- Tools: `graphify_build`, `graphify_query`, `graphify_path`, `graphify_explain`, `graphify_add`, `graphify_update`, `graphify_watch`, `graphify_cluster`. +- Single `/graphify` command with autocomplete for subcommands and flags (build, query, path, explain, add, update, watch, cluster, hook). + +[Unreleased]: https://gitlab.elches.dev/agents/primecodex/packages/pi-graphify/-/compare/v0.1.2...main +[0.1.2]: https://gitlab.elches.dev/agents/primecodex/packages/pi-graphify/-/compare/v0.1.1...v0.1.2 +[0.1.1]: https://gitlab.elches.dev/agents/primecodex/packages/pi-graphify/-/compare/v0.1.0...v0.1.1 +[0.1.0]: https://gitlab.elches.dev/agents/primecodex/packages/pi-graphify/-/tags/v0.1.0 diff --git a/extensions/pi-graphify/LICENSE b/extensions/pi-graphify/LICENSE new file mode 100644 index 0000000..fddab92 --- /dev/null +++ b/extensions/pi-graphify/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 El Che + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/pi-graphify/README.md b/extensions/pi-graphify/README.md new file mode 100644 index 0000000..1f65a7b --- /dev/null +++ b/extensions/pi-graphify/README.md @@ -0,0 +1,97 @@ +# @gaodes/pi-graphify + +Turn any folder of files (code, docs, papers, images, video) into a queryable knowledge graph with community detection, an honest audit trail, and three outputs: interactive HTML, GraphRAG-ready JSON, and a plain-language GRAPH_REPORT.md. + +[**Source**](https://gitlab.elches.dev/agents/primecodex/packages/pi-graphify) · [**npm**](https://www.npmjs.com/package/@gaodes/pi-graphify) + +Inspired by [graphify](https://github.com/safishamsi/graphify) — the AI coding assistant skill. This extension wraps graphify's Python CLI for native Pi integration. + +It also bundles a `graphify` skill (`skills/graphify/SKILL.md`) for full-pipeline orchestration. Use `/skill:graphify` when you want the guided multi-step workflow; use the tools and `/graphify` command for fast operational calls. + +## Tools + +| Tool | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `graphify_build` | Build a knowledge graph from a directory (full pipeline: detect → extract → cluster → visualize) | +| `graphify_query` | Query the graph — BFS for broad context, DFS for tracing specific paths | +| `graphify_path` | Find the shortest path between two concepts in the graph | +| `graphify_explain` | Plain-language explanation of a node — everything connected to it | +| `graphify_add` | Fetch a URL and add it to the corpus, then update the graph | +| `graphify_update` | Incremental update — re-extract only changed files | +| `graphify_watch` | Watch a directory for changes, auto-rebuild graph on code edits | +| `graphify_cluster` | Re-run community detection on an existing graph (no re-extraction) | + +## Commands + +| Command | Description | +| ----------- | ------------------------------------------------------------------ | +| `/graphify` | Single entry point with autocomplete for all subcommands and flags | + +### Subcommands and flags + +``` +/graphify # build graph (full pipeline) +/graphify --mode deep # thorough extraction, richer INFERRED edges +/graphify --update # incremental — re-extract only changed files +/graphify --cluster-only # rerun clustering on existing graph +/graphify --no-viz # skip visualization, just report + JSON +/graphify --obsidian # generate Obsidian vault +/graphify --svg # export graph.svg +/graphify --graphml # export for Gephi / yEd +/graphify --neo4j # generate cypher.txt for Neo4j + +/graphify query "" # BFS traversal — broad context +/graphify query "" --dfs # DFS — trace a specific path +/graphify query "" --budget 1500 # cap answer at N tokens +/graphify path "ConceptA" "ConceptB" # shortest path between two concepts +/graphify explain "ConceptName" # plain-language explanation of a node +/graphify add # fetch URL, save to ./raw, update graph +/graphify add --author "Name" # tag who wrote it +/graphify update # incremental update +/graphify watch # watch folder, auto-rebuild on changes +/graphify cluster # rerun clustering on existing graph +/graphify hook install # install git hooks for auto-rebuild +/graphify hook uninstall # remove git hooks +/graphify hook status # check hook status +``` + +## Prerequisites + +- Python 3.10+ +- `graphifyy` package (auto-installed on first run): `pip install graphifyy` or `uv tool install graphifyy` + +## Install + +```bash +pi install @gaodes/pi-graphify +``` + +## Configuration + +Key: `pi-graphify` in `prime-settings.json` (legacy `graphify` key auto-migrates on load). + +| Setting | Type | Default | Description | +| ------------ | --------- | ----------------- | ------------------------------------- | +| `enabled` | `boolean` | `true` | Enable/disable the extension | +| `pythonPath` | `string` | `"python3"` | Path to Python interpreter | +| `outputDir` | `string` | `"graphify-out"` | Output directory name | +| `statusbar` | `object` | built-in defaults | Optional pi-statusbar widget settings | + +## Git tracking policy + +On build initialization, the extension ensures `.gitignore` exists and applies Graphify-specific rules: + +- keeps `graphify-out/` **tracked** +- ignores only: + - `graphify-out/cache/` + - `graphify-out/.graphify_python` + - `graphify-out/.graphify_root` + - `graphify-out/cost.json` + +If a legacy `graphify-out/` ignore entry exists, it is removed automatically. + +## Source + +- Canonical: `~/agents/primecodex/packages/pi-graphify/` +- GitLab: `agents/primecodex/packages/pi-graphify` +- GitHub: `github.com/gaodes/pi-graphify` diff --git a/extensions/pi-graphify/package-lock.json b/extensions/pi-graphify/package-lock.json new file mode 100644 index 0000000..9894e4b --- /dev/null +++ b/extensions/pi-graphify/package-lock.json @@ -0,0 +1,5314 @@ +{ + "name": "@gaodes/pi-graphify", + "version": "0.1.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@gaodes/pi-graphify", + "version": "0.1.2", + "license": "MIT", + "dependencies": { + "@gaodes/pi-utils-ui": "^0.3.0" + }, + "devDependencies": { + "@biomejs/biome": "^2.4.3", + "@earendil-works/pi-ai": ">=0.69.0", + "@earendil-works/pi-coding-agent": ">=0.69.0", + "@earendil-works/pi-tui": ">=0.69.0", + "@gaodes/pi-test-harness": ">=0.1.0", + "@types/node": "^22.0.0", + "typebox": ">=1.0.0", + "typescript": "^5.0.0", + "vitest": "^4.1.5" + }, + "peerDependencies": { + "@earendil-works/pi-ai": ">=0.69.0", + "@earendil-works/pi-coding-agent": ">=0.69.0", + "@earendil-works/pi-tui": ">=0.69.0", + "typebox": ">=1.0.0" + }, + "peerDependenciesMeta": { + "@earendil-works/pi-ai": { + "optional": true + }, + "@earendil-works/pi-coding-agent": { + "optional": true + }, + "@earendil-works/pi-tui": { + "optional": true + }, + "typebox": { + "optional": true + } + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.91.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", + "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1045.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1045.0.tgz", + "integrity": "sha512-aPC6gAz9uKRiwfnKB7peTs6yD0FpSzmVnSkx0f2QtJfosFM6J6KtBvR1lMKby050K4C4PAyEScwA5YTsGfTcGA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/credential-provider-node": "^3.972.39", + "@aws-sdk/eventstream-handler-node": "^3.972.14", + "@aws-sdk/middleware-eventstream": "^3.972.10", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.38", + "@aws-sdk/middleware-websocket": "^3.972.16", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/token-providers": "3.1045.0", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.24", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/eventstream-serde-config-resolver": "^4.3.14", + "@smithy/eventstream-serde-node": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.974.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.8.tgz", + "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/xml-builder": "^3.972.22", + "@smithy/core": "^3.23.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.34.tgz", + "integrity": "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.36", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.36.tgz", + "integrity": "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-stream": "^4.5.25", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.38.tgz", + "integrity": "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/credential-provider-env": "^3.972.34", + "@aws-sdk/credential-provider-http": "^3.972.36", + "@aws-sdk/credential-provider-login": "^3.972.38", + "@aws-sdk/credential-provider-process": "^3.972.34", + "@aws-sdk/credential-provider-sso": "^3.972.38", + "@aws-sdk/credential-provider-web-identity": "^3.972.38", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.38.tgz", + "integrity": "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.39.tgz", + "integrity": "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.34", + "@aws-sdk/credential-provider-http": "^3.972.36", + "@aws-sdk/credential-provider-ini": "^3.972.38", + "@aws-sdk/credential-provider-process": "^3.972.34", + "@aws-sdk/credential-provider-sso": "^3.972.38", + "@aws-sdk/credential-provider-web-identity": "^3.972.38", + "@aws-sdk/types": "^3.973.8", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.34.tgz", + "integrity": "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.38.tgz", + "integrity": "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/token-providers": "3.1041.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1041.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1041.0.tgz", + "integrity": "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.38.tgz", + "integrity": "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.14.tgz", + "integrity": "sha512-m4X56gxG76/CKfxNVbOFuYwnAZcHgS6HOH8lgp15HoGHIAVTcZfZrXvcYzJFOMLEJgVn+JHBu6EiNV+xSNXXFg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.10.tgz", + "integrity": "sha512-QUqLs7Af1II9X4fCRAu+EGHG3KHyOp4RkuLhRKoA3NuFlh6TL8i+zXBl8w2LUxqm44B/Kom45hgSlwA1SpTsXQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", + "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.10.tgz", + "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.11.tgz", + "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.37.tgz", + "integrity": "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.38.tgz", + "integrity": "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@smithy/core": "^3.23.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-retry": "^4.3.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.16.tgz", + "integrity": "sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-format-url": "^3.972.10", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.6.tgz", + "integrity": "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.38", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/signature-v4-multi-region": "^3.996.25", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.24", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.13.tgz", + "integrity": "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/config-resolver": "^4.4.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.25.tgz", + "integrity": "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "^3.972.37", + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1045.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1045.0.tgz", + "integrity": "sha512-/o4qcty0DmQola0DBniRVeBakYY6ALOvKEFo1AtJpTmMn/cJ+Fk3RWGe5ieT/f/eYbHG9k5E7poKge/E+WGv4Q==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", + "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.8.tgz", + "integrity": "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-endpoints": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.10.tgz", + "integrity": "sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.10.tgz", + "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.24.tgz", + "integrity": "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.38", + "@aws-sdk/types": "^3.973.8", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", + "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@nodable/entities": "2.1.0", + "@smithy/types": "^4.14.1", + "fast-xml-parser": "5.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.15.tgz", + "integrity": "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.4.15", + "@biomejs/cli-darwin-x64": "2.4.15", + "@biomejs/cli-linux-arm64": "2.4.15", + "@biomejs/cli-linux-arm64-musl": "2.4.15", + "@biomejs/cli-linux-x64": "2.4.15", + "@biomejs/cli-linux-x64-musl": "2.4.15", + "@biomejs/cli-win32-arm64": "2.4.15", + "@biomejs/cli-win32-x64": "2.4.15" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.15.tgz", + "integrity": "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.15.tgz", + "integrity": "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.15.tgz", + "integrity": "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.15.tgz", + "integrity": "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.15.tgz", + "integrity": "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.15.tgz", + "integrity": "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.15.tgz", + "integrity": "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.15.tgz", + "integrity": "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "devOptional": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@earendil-works/pi-agent-core": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.74.0.tgz", + "integrity": "sha512-6GMR7/wwjEJ1EsXLWEz03QOWin4AMrJ/AZoMpgm5DJ6GHsF6q6GOhQbj5Zip4dow3vo/TmBAVqM+vmGfrjGAFQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-ai": "^0.74.0", + "typebox": "^1.1.24" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-ai": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.74.0.tgz", + "integrity": "sha512-7M7qcrZY/KEkH4wFkX3eqzvmKru4O88wezNKoN0KD2m4aAOmp9tdW2xCmUgSTSWlKB7b2Xw9QtAgrzHtg6t6iw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.91.1", + "@aws-sdk/client-bedrock-runtime": "^3.1030.0", + "@google/genai": "^1.40.0", + "@mistralai/mistralai": "^2.2.0", + "chalk": "^5.6.2", + "openai": "6.26.0", + "partial-json": "^0.1.7", + "proxy-agent": "^6.5.0", + "typebox": "^1.1.24", + "undici": "^7.19.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.74.0.tgz", + "integrity": "sha512-Q5GikbB5vRBrsrrf/uvet53rPSQ1sn5I5mO+l7sIobdXYpS04/X2oOc2UHFm90fNdkl3yU+ANTZL0zOtHbnqRw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-agent-core": "^0.74.0", + "@earendil-works/pi-ai": "^0.74.0", + "@earendil-works/pi-tui": "^0.74.0", + "@silvia-odwyer/photon-node": "^0.3.4", + "chalk": "^5.5.0", + "cli-highlight": "^2.1.11", + "diff": "^8.0.2", + "extract-zip": "^2.0.1", + "file-type": "^21.1.1", + "glob": "^13.0.1", + "hosted-git-info": "^9.0.2", + "ignore": "^7.0.5", + "jiti": "^2.7.0", + "marked": "^15.0.12", + "minimatch": "^10.2.3", + "proper-lockfile": "^4.1.2", + "strip-ansi": "^7.1.0", + "typebox": "^1.1.24", + "undici": "^7.19.1", + "uuid": "^14.0.0", + "yaml": "^2.8.2" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=20.6.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "^0.3.5" + } + }, + "node_modules/@earendil-works/pi-tui": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.74.0.tgz", + "integrity": "sha512-1aIfXZp7D/z+1VlZX8BZcs6pgO8rjmil7kwyhctNDsWvce3Yfl8GVgu4eq+I0Mjhr8Cj+ipBiv9CLIzdoyCOIQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/mime-types": "^2.1.4", + "chalk": "^5.5.0", + "get-east-asian-width": "^1.3.0", + "marked": "^15.0.12", + "mime-types": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "koffi": "^2.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@gaodes/pi-test-harness": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@gaodes/pi-test-harness/-/pi-test-harness-1.0.2.tgz", + "integrity": "sha512-DAwX3LJii1W9kTXmFiioScVZY6RvHj0jEpCXekAm8fRDvoKCehSbmf9ws8Da4/CC5ootcBMrqxtP/9HaZp3zcA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@earendil-works/pi-agent-core": "*", + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": ">=0.50.0" + } + }, + "node_modules/@gaodes/pi-utils-ui": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@gaodes/pi-utils-ui/-/pi-utils-ui-0.3.2.tgz", + "integrity": "sha512-TxN4/dEqkcf+BE/3uoo9d5gc9xFjTXmIhueZvtAhyn2/N6fE8YCV0dp1niSJ80ceNGxQoCB9PIxSyJjly06xsw==", + "license": "MIT", + "peerDependencies": { + "@earendil-works/pi-coding-agent": ">=0.74.0 <1", + "@earendil-works/pi-tui": ">=0.74.0 <1" + }, + "peerDependenciesMeta": { + "@earendil-works/pi-coding-agent": { + "optional": true + }, + "@earendil-works/pi-tui": { + "optional": true + } + } + }, + "node_modules/@google/genai": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", + "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "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/@mariozechner/clipboard": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.5.tgz", + "integrity": "sha512-D3F+UrU9CR7roJt0zDLp6Oc+4/KlLDIrN4frH+6V90SJNW2KKUec1oCQIPaaDjCqeOsQyX9dyqYbImIQIM45PA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.2", + "@mariozechner/clipboard-darwin-universal": "0.3.2", + "@mariozechner/clipboard-darwin-x64": "0.3.2", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-musl": "0.3.2", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" + } + }, + "node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", + "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", + "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", + "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", + "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", + "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", + "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", + "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", + "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", + "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", + "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz", + "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.25.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.128.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz", + "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", + "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz", + "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz", + "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz", + "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.17", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.17.tgz", + "integrity": "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.17", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.17.tgz", + "integrity": "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.14.tgz", + "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.1", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.14.tgz", + "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.14.tgz", + "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.14.tgz", + "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.14.tgz", + "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", + "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", + "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", + "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.14.tgz", + "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.32", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.32.tgz", + "integrity": "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-middleware": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.7.tgz", + "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/service-error-classification": "^4.3.1", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.20.tgz", + "integrity": "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.14.tgz", + "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.14.tgz", + "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.6.1.tgz", + "integrity": "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.14.tgz", + "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.14.tgz", + "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.14.tgz", + "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.14.tgz", + "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.3.1.tgz", + "integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.9.tgz", + "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.14.tgz", + "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.13", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.13.tgz", + "integrity": "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-stream": "^4.5.25", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.14.tgz", + "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.49.tgz", + "integrity": "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.54", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.54.tgz", + "integrity": "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.17", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.2.tgz", + "integrity": "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.14.tgz", + "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.8.tgz", + "integrity": "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.3.1", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.25", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.25.tgz", + "integrity": "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "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/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.18.tgz", + "integrity": "sha512-9v00a+dn2yWVsYDEunWC4g/TcRKVq3r8N5FuZp7u0SGrPvdN9c2yXI9bBuf5Fl0hNCb+QTIePTn5pJs2pwBOQQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "devOptional": true, + "license": "MIT" + }, + "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/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "devOptional": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "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==", + "devOptional": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "devOptional": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", + "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "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/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "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==", + "devOptional": true, + "license": "ISC" + }, + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/koffi": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.2.tgz", + "integrity": "sha512-owU0MRwv6xkrVqCd+33uw6BaYppkTRXbO/rVdJNI2dvZG0gzyRhYwW25eWtc5pauwK8TGh3AbkFONSezdykfSA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "devOptional": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "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/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "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==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/protobufjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.7.tgz", + "integrity": "sha512-NGnrxS/nLKUo5nkbVQxlC71sB4hdfImdYIbFeSCidxtwATx0AHRPcANSLd0q5Bb2BkoSWo2iisQhGg5/r+ihbA==", + "devOptional": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.1", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz", + "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.128.0", + "@rolldown/pluginutils": "1.0.0-rc.18" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-x64": "1.0.0-rc.18", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" + } + }, + "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==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, + "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==", + "devOptional": true, + "license": "MIT", + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strnum": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "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": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "devOptional": true, + "license": "0BSD" + }, + "node_modules/typebox": { + "version": "1.1.38", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", + "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", + "devOptional": true, + "license": "MIT" + }, + "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/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "devOptional": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vite": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz", + "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "rolldown": "1.0.0-rc.18", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "devOptional": true, + "license": "MIT", + "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/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "devOptional": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "devOptional": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/extensions/pi-graphify/package.json b/extensions/pi-graphify/package.json new file mode 100644 index 0000000..4930252 --- /dev/null +++ b/extensions/pi-graphify/package.json @@ -0,0 +1,81 @@ +{ + "name": "@gaodes/pi-graphify", + "version": "0.1.2", + "description": "Turn any folder into a queryable knowledge graph — build, query, explore, and update graphs from inside Pi", + "keywords": [ + "pi-package", + "pi-extension" + ], + "type": "module", + "private": false, + "main": "./src/tools/index.ts", + "files": [ + "src/", + "skills/", + "README.md", + "CHANGELOG.md" + ], + "scripts": { + "typecheck": "tsc --noEmit", + "lint": "biome check src/", + "lint:fix": "biome check --write src/", + "test": "vitest run", + "test:watch": "vitest" + }, + "pi": { + "extensions": [ + "./src/tools/index.ts", + "./src/commands/index.ts" + ], + "skills": [ + "skills" + ] + }, + "dependencies": { + "@gaodes/pi-utils-ui": "^0.3.0" + }, + "devDependencies": { + "@earendil-works/pi-ai": ">=0.69.0", + "@earendil-works/pi-coding-agent": ">=0.69.0", + "@earendil-works/pi-tui": ">=0.69.0", + "@types/node": "^22.0.0", + "typebox": ">=1.0.0", + "typescript": "^5.0.0", + "vitest": "^4.1.5", + "@gaodes/pi-test-harness": ">=0.1.0", + "@biomejs/biome": "^2.4.3" + }, + "peerDependencies": { + "@earendil-works/pi-ai": ">=0.69.0", + "@earendil-works/pi-coding-agent": ">=0.69.0", + "@earendil-works/pi-tui": ">=0.69.0", + "typebox": ">=1.0.0" + }, + "peerDependenciesMeta": { + "@earendil-works/pi-ai": { + "optional": true + }, + "@earendil-works/pi-coding-agent": { + "optional": true + }, + "@earendil-works/pi-tui": { + "optional": true + }, + "typebox": { + "optional": true + } + }, + "repository": { + "type": "git", + "url": "git+https://github.com/gaodes/pi-graphify.git" + }, + "publishConfig": { + "access": "public" + }, + "bugs": { + "url": "https://github.com/gaodes/pi-graphify/issues" + }, + "homepage": "https://github.com/gaodes/pi-graphify#readme", + "license": "MIT", + "author": "El Che" +} diff --git a/extensions/pi-graphify/skills/graphify/SKILL.md b/extensions/pi-graphify/skills/graphify/SKILL.md new file mode 100644 index 0000000..4a9d05a --- /dev/null +++ b/extensions/pi-graphify/skills/graphify/SKILL.md @@ -0,0 +1,881 @@ +--- +name: graphify +description: "Full-pipeline knowledge graph orchestration for graphify. Use when the user asks to build a graph from scratch, run a deep extraction, generate specific export formats (Obsidian, SVG, GraphML, Neo4j), transcribe video, or any operation that requires multi-step orchestration beyond the extension's tools. The extension tools (graphify_build, graphify_query, etc.) handle simple operations; this skill handles the full build pipeline." +--- + +# /graphify + +Turn any folder of files into a navigable knowledge graph with community detection, an honest audit trail, and multiple outputs: interactive HTML, GraphRAG-ready JSON, Obsidian vault, and a plain-language GRAPH_REPORT.md. + +## Extension Tools vs. This Skill + +The pi-graphify extension registers 8 tools the LLM can call autonomously: + +| Tool | When to use | +|------|-------------| +| `graphify_build` | Quick build with standard settings | +| `graphify_query` | BFS/DFS traversal questions | +| `graphify_path` | Shortest path between concepts | +| `graphify_explain` | Plain-language explanation of a node | +| `graphify_add` | Fetch a URL and add to corpus | +| `graphify_update` | Incremental re-extraction | +| `graphify_cluster` | Re-run community detection only | +| `graphify_watch` | Watch directory for changes | + +**Use this skill instead of the tools when:** + +- The user runs `/graphify` explicitly (they want the full pipeline) +- The user wants export formats the tools don't produce (Obsidian vault, SVG, GraphML, Neo4j cypher) +- The corpus contains video/audio that needs transcription +- The user wants the guided exploration flow after building +- The user wants to run semantic extraction with subagent dispatch (the tools only call the Python CLI, not multi-step orchestration) + +For simple operations (query, explain, path, add, update, cluster), prefer the extension tools — they're faster and the LLM can call them autonomously. + +## Usage + +### `/graphify` command surface (implemented in this extension) + +``` +/graphify # full pipeline on current directory +/graphify # full pipeline on specific path +/graphify --mode deep # thorough extraction, richer INFERRED edges +/graphify --update # incremental - re-extract only new/changed files +/graphify --cluster-only # rerun clustering on existing graph +/graphify --no-viz # skip visualization, just report + JSON +/graphify --obsidian # generate Obsidian vault +/graphify --svg # also export graph.svg (embeds in Notion, GitHub) +/graphify --graphml # export graph.graphml (Gephi, yEd) +/graphify --neo4j # generate graphify-out/cypher.txt for Neo4j +/graphify add # fetch URL, save to ./raw, update graph +/graphify add --author "Name" # tag who wrote it +/graphify add --contributor "Name" # tag who added it to the corpus +/graphify query "" # BFS traversal - broad context +/graphify query "" --dfs # DFS - trace a specific path +/graphify query "" --budget 1500 # cap answer at N tokens +/graphify path "AuthModule" "Database" # shortest path between two concepts +/graphify explain "SwinTransformer" # plain-language explanation of a node +/graphify update # incremental update (subcommand form) +/graphify watch # watch folder, auto-rebuild on changes +/graphify cluster # rerun clustering on existing graph +/graphify hook # manage git hooks +``` + +### Advanced orchestration options (skill-level, not `/graphify` flags) + +Use these only when driving the full pipeline from this skill (for example via `/skill:graphify ...`) or when the user asks explicitly: + +- `--neo4j-push `: push graph directly to Neo4j +- `--mcp`: start MCP stdio server for live graph queries +- `--whisper-model `: override transcription model + +## What graphify is for + +graphify is built around Andrej Karpathy's /raw folder workflow: drop anything into a folder - papers, tweets, screenshots, code, notes - and get a structured knowledge graph that shows you what you didn't know was connected. + +Three things it does that your AI assistant alone cannot: +1. **Persistent graph** - relationships are stored in `graphify-out/graph.json` and survive across sessions. Ask questions weeks later without re-reading everything. +2. **Honest audit trail** - every edge is tagged EXTRACTED, INFERRED, or AMBIGUOUS. You know what was found vs invented. +3. **Cross-document surprise** - community detection finds connections between concepts in different files that you would never think to ask about directly. + +## What You Must Do When Invoked + +If no path was given, use `.` (current directory). Do not ask the user for a path. + +### Quick operations → use extension tools + +For `query`, `path`, `explain`, `add`, `update`, `cluster`, and `watch` operations, call the corresponding extension tool directly: + +| Operation | Tool to call | +|-----------|-------------| +| `query ""` | `graphify_query` with `question` param | +| `query "" --dfs` | `graphify_query` with `question` and `mode: "dfs"` | +| `path "A" "B"` | `graphify_path` with `from` and `to` | +| `explain "Node"` | `graphify_explain` with `concept` | +| `add ` | `graphify_add` with `url` | +| `update ` or build flag `--update` | `graphify_update` with `path` | +| `cluster` or build flag `--cluster-only` | `graphify_cluster` | +| `watch ` | `graphify_watch` with `path` | + +### Full pipeline → follow these steps + +Follow these steps in order. Do not skip steps. + +#### Step 1 - Ensure graphify is installed + +```bash +# Detect the correct Python interpreter (handles pipx, venv, system installs) +GRAPHIFY_BIN=$(which graphify 2>/dev/null) +if [ -n "$GRAPHIFY_BIN" ]; then + PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + case "$PYTHON" in + *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; + esac +else + PYTHON="python3" +fi +"$PYTHON" -c "import graphify" 2>/dev/null || "$PYTHON" -m pip install graphifyy -q 2>/dev/null || "$PYTHON" -m pip install graphifyy -q --break-system-packages 2>&1 | tail -3 +mkdir -p graphify-out +# Write interpreter path for all subsequent steps +"$PYTHON" -c "import sys; open('.graphify_python', 'w').write(sys.executable)" +``` + +If the import succeeds, print nothing and move straight to Step 2. + +**In every subsequent bash block, replace `python3` with `$(cat .graphify_python)` to use the correct interpreter.** + +#### Step 2 - Detect files + +```bash +$(cat .graphify_python) -c " +import json +from graphify.detect import detect +from pathlib import Path +result = detect(Path('INPUT_PATH')) +print(json.dumps(result)) +" > .graphify_detect.json +``` + +Replace INPUT_PATH with the actual path the user provided. Do NOT cat or print the JSON - read it silently and present a clean summary instead: + +``` +Corpus: X files · ~Y words + code: N files (.py .ts .go ...) + docs: N files (.md .txt ...) + papers: N files (.pdf ...) + images: N files + video: N files (.mp4 .mp3 ...) +``` + +Omit any category with 0 files from the summary. + +Then act on it: +- If `total_files` is 0: stop with "No supported files found in [path]." +- If `skipped_sensitive` is non-empty: mention file count skipped, not the file names. +- If `total_words` > 2,000,000 OR `total_files` > 200: show the warning and the top 5 subdirectories by file count, then ask which subfolder to run on. Wait for the user's answer before proceeding. +- Otherwise: proceed directly to Step 2.5 if video files were detected, or Step 3 if not. + +#### Step 2.5 - Transcribe video / audio files (only if video files detected) + +Skip this step entirely if `detect` returned zero `video` files. + +Video and audio files cannot be read directly. Transcribe them to text first, then treat the transcripts as doc files in Step 3. + +**Strategy:** Read the god nodes from the detect output or analysis file. You are already a language model - write a one-sentence domain hint yourself from those labels. Then pass it to Whisper as the initial prompt. No separate API call needed. + +**However**, if the corpus has *only* video files and no other docs/code, use the generic fallback prompt: `"Use proper punctuation and paragraph breaks."` + +**Step 1 - Write the Whisper prompt yourself.** + +Read the top god node labels from detect output or analysis, then compose a short domain hint sentence, for example: + +- Labels: `transformer, attention, encoder, decoder` -> `"Machine learning research on transformer architectures and attention mechanisms. Use proper punctuation and paragraph breaks."` +- Labels: `kubernetes, deployment, pod, helm` -> `"DevOps discussion about Kubernetes deployments and Helm charts. Use proper punctuation and paragraph breaks."` + +Set it as `GRAPHIFY_WHISPER_PROMPT` in the environment before running the transcription command. + +**Step 2 - Transcribe:** + +```bash +$(cat .graphify_python) -c " +import json, os +from pathlib import Path +from graphify.transcribe import transcribe_all + +detect = json.loads(Path('.graphify_detect.json').read_text()) +video_files = detect.get('files', {}).get('video', []) +prompt = os.environ.get('GRAPHIFY_WHISPER_PROMPT', 'Use proper punctuation and paragraph breaks.') + +transcript_paths = transcribe_all(video_files, initial_prompt=prompt) +print(json.dumps(transcript_paths)) +" > graphify-out/.graphify_transcripts.json +``` + +After transcription: +- Read the transcript paths from `graphify-out/.graphify_transcripts.json` +- Add them to the docs list before dispatching semantic subagents in Step 3B +- Print how many transcripts were created: `Transcribed N video file(s) -> treating as docs` +- If transcription fails for a file, print a warning and continue with the rest + +**Whisper model:** Default is `base`. If the user passed `--whisper-model `, set `GRAPHIFY_WHISPER_MODEL=` in the environment before running the command above. + +#### Step 3 - Extract entities and relationships + +**Before starting:** note whether `--mode deep` was given. You must pass `DEEP_MODE=true` to every subagent in Step B2 if it was. Track this from the original invocation - do not lose it. + +This step has two parts: **structural extraction** (deterministic, free) and **semantic extraction** (your AI model, costs tokens). + +**Run Part A (AST) and Part B (semantic) in parallel. Dispatch all semantic subagents AND start AST extraction in the same message. Both can run simultaneously since they operate on different file types. Merge results in Part C as before.** + +Note: Parallelizing AST + semantic saves 5-15s on large corpora. AST is deterministic and fast; start it while subagents are processing docs/papers. + +##### Part A - Structural extraction for code files + +For any code files detected, run AST extraction in parallel with Part B subagents: + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.extract import collect_files, extract +from pathlib import Path +import json + +code_files = [] +detect = json.loads(Path('.graphify_detect.json').read_text()) +for f in detect.get('files', {}).get('code', []): + code_files.extend(collect_files(Path(f)) if Path(f).is_dir() else [Path(f)]) + +if code_files: + result = extract(code_files) + Path('.graphify_ast.json').write_text(json.dumps(result, indent=2)) + print(f'AST: {len(result[\"nodes\"])} nodes, {len(result[\"edges\"])} edges') +else: + Path('.graphify_ast.json').write_text(json.dumps({'nodes':[],'edges':[],'input_tokens':0,'output_tokens':0})) + print('No code files - skipping AST extraction') +" +``` + +##### Part B - Semantic extraction (parallel subagents) + +**Fast path:** If detection found zero docs, papers, and images (code-only corpus), skip Part B entirely and go straight to Part C. AST handles code - there is nothing for semantic subagents to do. + +> **OpenClaw platform:** Multi-agent support is still early on OpenClaw. Extraction runs sequentially — you read and extract each file yourself. This is slower than parallel platforms but fully reliable. + +Print: `"Semantic extraction: N files (sequential — OpenClaw)"` + +**Step B0 - Check extraction cache first** + +Before dispatching any subagents, check which files already have cached extraction results: + +```bash +$(cat .graphify_python) -c " +import json +from graphify.cache import check_semantic_cache +from pathlib import Path + +detect = json.loads(Path('.graphify_detect.json').read_text()) +all_files = [f for files in detect['files'].values() for f in files] + +cached_nodes, cached_edges, cached_hyperedges, uncached = check_semantic_cache(all_files) + +if cached_nodes or cached_edges or cached_hyperedges: + Path('.graphify_cached.json').write_text(json.dumps({'nodes': cached_nodes, 'edges': cached_edges, 'hyperedges': cached_hyperedges})) +Path('.graphify_uncached.txt').write_text('\n'.join(uncached)) +print(f'Cache: {len(all_files)-len(uncached)} files hit, {len(uncached)} files need extraction') +" +``` + +Only dispatch subagents for files listed in `.graphify_uncached.txt`. If all files are cached, skip to Part C directly. + +**Step B1 - Split into chunks** + +Load files from `.graphify_uncached.txt`. Split into chunks of 20-25 files each. Each image gets its own chunk (vision needs separate context). When splitting, group files from the same directory together so related artifacts land in the same chunk and cross-file relationships are more likely to be extracted. + +**Step B2 - Sequential extraction (OpenClaw)** + +Process each file one at a time. For each file: + +1. Read the file contents +2. Extract nodes, edges, and hyperedges applying the same rules: + - EXTRACTED: relationship explicit in source (import, call, citation) + - INFERRED: reasonable inference (shared structure, implied dependency) + - AMBIGUOUS: uncertain — flag it, do not omit + - Code files: semantic edges AST cannot find. Do not re-extract imports. + - Doc/paper files: named concepts, entities, citations. Store rationale (WHY decisions were made) as a `rationale` attribute on the relevant node, not as a separate node. Use `file_type:"rationale"` for concept-like nodes (ideas, principles, mechanisms). Do NOT invent file_types like `concept`. When adding `calls` edges: source is caller, target is callee. + - Image files: use vision — understand what the image IS, not just OCR + - DEEP_MODE (if --mode deep): be aggressive with INFERRED edges + - Semantic similarity: if two concepts solve the same problem without a structural link, add `semantically_similar_to` INFERRED edge (confidence 0.6-0.95). Non-obvious cross-file links only. + - Hyperedges: if 3+ nodes share a concept/flow not captured by pairwise edges, add a hyperedge. Max 3 per file. + - confidence_score REQUIRED on every edge: EXTRACTED=1.0, INFERRED=0.6-0.9 (reason individually), AMBIGUOUS=0.1-0.3 +3. Accumulate results across all files + +Schema for each file's output: +{"nodes":[{"id":"filestem_entityname","label":"Human Readable Name","file_type":"code|document|paper|image|rationale","source_file":"relative/path","source_location":null,"source_url":null,"captured_at":null,"author":null,"contributor":null}],"edges":[{"source":"node_id","target":"node_id","relation":"calls|implements|references|cites|conceptually_related_to|shares_data_with|semantically_similar_to|rationale_for","confidence":"EXTRACTED|INFERRED|AMBIGUOUS","confidence_score":1.0,"source_file":"relative/path","source_location":null,"weight":1.0}],"hyperedges":[{"id":"snake_case_id","label":"Human Readable Label","nodes":["node_id1","node_id2","node_id3"],"relation":"participate_in|implement|form","confidence":"EXTRACTED|INFERRED","confidence_score":0.75,"source_file":"relative/path"}],"input_tokens":0,"output_tokens":0} + +After processing all files, write the accumulated result to `.graphify_semantic_new.json`. + +**Step B3 - Cache and merge** + +For the accumulated result: + +If more than half the chunks failed, stop and tell the user. + +Merge all chunk files into `.graphify_semantic_new.json`. **After each Agent call completes, read the real token counts from the Agent tool result's `usage` field and write them back into the chunk JSON before merging** — the chunk JSON itself always has placeholder zeros. Then run: +```bash +$(cat .graphify_python) -c " +import json, glob +from pathlib import Path + +chunks = sorted(glob.glob('.graphify_chunk_*.json')) +all_nodes, all_edges, all_hyperedges = [], [], [] +total_in, total_out = 0, 0 +for c in chunks: + d = json.loads(Path(c).read_text()) + all_nodes += d.get('nodes', []) + all_edges += d.get('edges', []) + all_hyperedges += d.get('hyperedges', []) + total_in += d.get('input_tokens', 0) + total_out += d.get('output_tokens', 0) +Path('.graphify_semantic_new.json').write_text(json.dumps({ + 'nodes': all_nodes, 'edges': all_edges, 'hyperedges': all_hyperedges, + 'input_tokens': total_in, 'output_tokens': total_out, +}, indent=2)) +print(f'Merged {len(chunks)} chunks: {total_in:,} in / {total_out:,} out tokens') +" +``` + +Save new results to cache: +```bash +$(cat .graphify_python) -c " +import json +from graphify.cache import save_semantic_cache +from pathlib import Path + +new = json.loads(Path('.graphify_semantic_new.json').read_text()) if Path('.graphify_semantic_new.json').exists() else {'nodes':[],'edges':[],'hyperedges':[]} +saved = save_semantic_cache(new.get('nodes', []), new.get('edges', []), new.get('hyperedges', [])) +print(f'Cached {saved} files') +" +``` + +Merge cached + new results into `.graphify_semantic.json`: +```bash +$(cat .graphify_python) -c " +import json +from pathlib import Path + +cached = json.loads(Path('.graphify_cached.json').read_text()) if Path('.graphify_cached.json').exists() else {'nodes':[],'edges':[],'hyperedges':[]} +new = json.loads(Path('.graphify_semantic_new.json').read_text()) if Path('.graphify_semantic_new.json').exists() else {'nodes':[],'edges':[],'hyperedges':[]} + +all_nodes = cached['nodes'] + new.get('nodes', []) +all_edges = cached['edges'] + new.get('edges', []) +all_hyperedges = cached.get('hyperedges', []) + new.get('hyperedges', []) +seen = set() +deduped = [] +for n in all_nodes: + if n['id'] not in seen: + seen.add(n['id']) + deduped.append(n) + +merged = { + 'nodes': deduped, + 'edges': all_edges, + 'hyperedges': all_hyperedges, + 'input_tokens': new.get('input_tokens', 0), + 'output_tokens': new.get('output_tokens', 0), +} +Path('.graphify_semantic.json').write_text(json.dumps(merged, indent=2)) +print(f'Extraction complete - {len(deduped)} nodes, {len(all_edges)} edges ({len(cached[\"nodes\"])} from cache, {len(new.get(\"nodes\",[]))} new)') +" +``` +Clean up temp files: `rm -f .graphify_cached.json .graphify_uncached.txt .graphify_semantic_new.json` + +##### Part C - Merge AST + semantic into final extraction + +```bash +$(cat .graphify_python) -c " +import sys, json +from pathlib import Path + +ast = json.loads(Path('.graphify_ast.json').read_text()) +sem = json.loads(Path('.graphify_semantic.json').read_text()) + +# Merge: AST nodes first, semantic nodes deduplicated by id +seen = {n['id'] for n in ast['nodes']} +merged_nodes = list(ast['nodes']) +for n in sem['nodes']: + if n['id'] not in seen: + merged_nodes.append(n) + seen.add(n['id']) + +merged_edges = ast['edges'] + sem['edges'] +merged_hyperedges = sem.get('hyperedges', []) +merged = { + 'nodes': merged_nodes, + 'edges': merged_edges, + 'hyperedges': merged_hyperedges, + 'input_tokens': sem.get('input_tokens', 0), + 'output_tokens': sem.get('output_tokens', 0), +} +Path('.graphify_extract.json').write_text(json.dumps(merged, indent=2)) +total = len(merged_nodes) +edges = len(merged_edges) +print(f'Merged: {total} nodes, {edges} edges ({len(ast[\"nodes\"])} AST + {len(sem[\"nodes\"])} semantic)') +" +``` + +#### Step 4 - Build graph, cluster, analyze, generate outputs + +```bash +mkdir -p graphify-out +$(cat .graphify_python) -c " +import sys, json +from graphify.build import build_from_json +from graphify.cluster import cluster, score_all +from graphify.analyze import god_nodes, surprising_connections, suggest_questions +from graphify.report import generate +from graphify.export import to_json +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +detection = json.loads(Path('.graphify_detect.json').read_text()) + +G = build_from_json(extraction) +communities = cluster(G) +cohesion = score_all(G, communities) +tokens = {'input': extraction.get('input_tokens', 0), 'output': extraction.get('output_tokens', 0)} +gods = god_nodes(G) +surprises = surprising_connections(G, communities) +labels = {cid: 'Community ' + str(cid) for cid in communities} +# Placeholder questions - regenerated with real labels in Step 5 +questions = suggest_questions(G, communities, labels) + +report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, 'INPUT_PATH', suggested_questions=questions) +Path('graphify-out/GRAPH_REPORT.md').write_text(report) +to_json(G, communities, 'graphify-out/graph.json') + +analysis = { + 'communities': {str(k): v for k, v in communities.items()}, + 'cohesion': {str(k): v for k, v in cohesion.items()}, + 'gods': gods, + 'surprises': surprises, + 'questions': questions, +} +Path('.graphify_analysis.json').write_text(json.dumps(analysis, indent=2)) +if G.number_of_nodes() == 0: + print('ERROR: Graph is empty - extraction produced no nodes.') + print('Possible causes: all files were skipped, binary-only corpus, or extraction failed.') + raise SystemExit(1) +print(f'Graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges, {len(communities)} communities') +" +``` + +If this step prints `ERROR: Graph is empty`, stop and tell the user what happened - do not proceed to labeling or visualization. + +Replace INPUT_PATH with the actual path. + +#### Step 5 - Label communities + +Read `.graphify_analysis.json`. For each community key, look at its node labels and write a 2-5 word plain-language name (e.g. "Attention Mechanism", "Training Pipeline", "Data Loading"). + +Then regenerate the report and save the labels for the visualizer: + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.build import build_from_json +from graphify.cluster import score_all +from graphify.analyze import god_nodes, surprising_connections, suggest_questions +from graphify.report import generate +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +detection = json.loads(Path('.graphify_detect.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) + +G = build_from_json(extraction) +communities = {int(k): v for k, v in analysis['communities'].items()} +cohesion = {int(k): v for k, v in analysis['cohesion'].items()} +tokens = {'input': extraction.get('input_tokens', 0), 'output': extraction.get('output_tokens', 0)} + +# LABELS - replace these with the names you chose above +labels = LABELS_DICT + +# Regenerate questions with real community labels (labels affect question phrasing) +questions = suggest_questions(G, communities, labels) + +report = generate(G, communities, cohesion, labels, analysis['gods'], analysis['surprises'], detection, tokens, 'INPUT_PATH', suggested_questions=questions) +Path('graphify-out/GRAPH_REPORT.md').write_text(report) +Path('.graphify_labels.json').write_text(json.dumps({str(k): v for k, v in labels.items()})) +print('Report updated with community labels') +" +``` + +Replace `LABELS_DICT` with the actual dict you constructed (e.g. `{0: "Attention Mechanism", 1: "Training Pipeline"}`). +Replace INPUT_PATH with the actual path. + +#### Step 6 - Generate Obsidian vault (opt-in) + HTML + +**Generate HTML always** (unless `--no-viz`). **Obsidian vault only if `--obsidian` was explicitly given** — skip it otherwise, it generates one file per node. + +If `--obsidian` was given: + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.build import build_from_json +from graphify.export import to_obsidian, to_canvas +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) +labels_raw = json.loads(Path('.graphify_labels.json').read_text()) if Path('.graphify_labels.json').exists() else {} + +G = build_from_json(extraction) +communities = {int(k): v for k, v in analysis['communities'].items()} +cohesion = {int(k): v for k, v in analysis['cohesion'].items()} +labels = {int(k): v for k, v in labels_raw.items()} + +n = to_obsidian(G, communities, 'graphify-out/obsidian', community_labels=labels or None, cohesion=cohesion) +print(f'Obsidian vault: {n} notes in graphify-out/obsidian/') + +to_canvas(G, communities, 'graphify-out/obsidian/graph.canvas', community_labels=labels or None) +print('Canvas: graphify-out/obsidian/graph.canvas - open in Obsidian for structured community layout') +print() +print('Open graphify-out/obsidian/ as a vault in Obsidian.') +print(' Graph view - nodes colored by community (set automatically)') +print(' graph.canvas - structured layout with communities as groups') +print(' _COMMUNITY_* - overview notes with cohesion scores and dataview queries') +" +``` + +Generate the HTML graph (always, unless `--no-viz`): + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.build import build_from_json +from graphify.export import to_html +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) +labels_raw = json.loads(Path('.graphify_labels.json').read_text()) if Path('.graphify_labels.json').exists() else {} + +G = build_from_json(extraction) +communities = {int(k): v for k, v in analysis['communities'].items()} +labels = {int(k): v for k, v in labels_raw.items()} + +if G.number_of_nodes() > 5000: + print(f'Graph has {G.number_of_nodes()} nodes - too large for HTML viz. Use Obsidian vault instead.') +else: + to_html(G, communities, 'graphify-out/graph.html', community_labels=labels or None) + print('graph.html written - open in any browser, no server needed') +" +``` + +#### Step 7 - Export formats (optional, only if flagged) + +##### Neo4j export (only if --neo4j or --neo4j-push flag) + +**If `--neo4j`** - generate a Cypher file for manual import: + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.build import build_from_json +from graphify.export import to_cypher +from pathlib import Path + +G = build_from_json(json.loads(Path('.graphify_extract.json').read_text())) +to_cypher(G, 'graphify-out/cypher.txt') +print('cypher.txt written - import with: cypher-shell < graphify-out/cypher.txt') +" +``` + +**If `--neo4j-push `** - push directly to a running Neo4j instance. Ask the user for credentials if not provided: + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.build import build_from_json +from graphify.cluster import cluster +from graphify.export import push_to_neo4j +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) +G = build_from_json(extraction) +communities = {int(k): v for k, v in analysis['communities'].items()} + +result = push_to_neo4j(G, uri='NEO4J_URI', user='NEO4J_USER', password='NEO4J_PASSWORD', communities=communities) +print(f'Pushed to Neo4j: {result[\"nodes\"]} nodes, {result[\"edges\"]} edges') +" +``` + +Replace `NEO4J_URI`, `NEO4J_USER`, `NEO4J_PASSWORD` with actual values. Default URI is `bolt://localhost:7687`, default user is `neo4j`. Uses MERGE - safe to re-run without creating duplicates. + +##### SVG export (only if --svg flag) + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.build import build_from_json +from graphify.export import to_svg +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) +labels_raw = json.loads(Path('.graphify_labels.json').read_text()) if Path('.graphify_labels.json').exists() else {} + +G = build_from_json(extraction) +communities = {int(k): v for k, v in analysis['communities'].items()} +labels = {int(k): v for k, v in labels_raw.items()} + +to_svg(G, communities, 'graphify-out/graph.svg', community_labels=labels or None) +print('graph.svg written - embeds in Obsidian, Notion, GitHub READMEs') +" +``` + +##### GraphML export (only if --graphml flag) + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.build import build_from_json +from graphify.export import to_graphml +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) + +G = build_from_json(extraction) +communities = {int(k): v for k, v in analysis['communities'].items()} + +to_graphml(G, communities, 'graphify-out/graph.graphml') +print('graph.graphml written - open in Gephi, yEd, or any GraphML tool') +" +``` + +##### MCP server (only if --mcp flag) + +```bash +python3 -m graphify.serve graphify-out/graph.json +``` + +This starts a stdio MCP server that exposes tools: `query_graph`, `get_node`, `get_neighbors`, `get_community`, `god_nodes`, `graph_stats`, `shortest_path`. Add to Claude Desktop or any MCP-compatible agent orchestrator so other agents can query the graph live. + +To configure in Claude Desktop, add to `claude_desktop_config.json`: +```json +{ + "mcpServers": { + "graphify": { + "command": "python3", + "args": ["-m", "graphify.serve", "/absolute/path/to/graphify-out/graph.json"] + } + } +} +``` + +#### Step 8 - Token reduction benchmark (only if total_words > 5000) + +If `total_words` from `.graphify_detect.json` is greater than 5,000, run: + +```bash +$(cat .graphify_python) -c " +import json +from graphify.benchmark import run_benchmark, print_benchmark +from pathlib import Path + +detection = json.loads(Path('.graphify_detect.json').read_text()) +result = run_benchmark('graphify-out/graph.json', corpus_words=detection['total_words']) +print_benchmark(result) +" +``` + +Print the output directly in chat. If `total_words <= 5000`, skip silently - the graph value is structural clarity, not token compression, for small corpora. + +--- + +#### Step 9 - Save manifest, update cost tracker, clean up, and report + +```bash +$(cat .graphify_python) -c " +import json +from pathlib import Path +from datetime import datetime, timezone +from graphify.detect import save_manifest + +# Save manifest for --update +detect = json.loads(Path('.graphify_detect.json').read_text()) +save_manifest(detect['files']) + +# Update cumulative cost tracker +extract = json.loads(Path('.graphify_extract.json').read_text()) +input_tok = extract.get('input_tokens', 0) +output_tok = extract.get('output_tokens', 0) + +cost_path = Path('graphify-out/cost.json') +if cost_path.exists(): + cost = json.loads(cost_path.read_text()) +else: + cost = {'runs': [], 'total_input_tokens': 0, 'total_output_tokens': 0} + +cost['runs'].append({ + 'date': datetime.now(timezone.utc).isoformat(), + 'input_tokens': input_tok, + 'output_tokens': output_tok, + 'files': detect.get('total_files', 0), +}) +cost['total_input_tokens'] += input_tok +cost['total_output_tokens'] += output_tok +cost_path.write_text(json.dumps(cost, indent=2)) + +print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') +print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') +" +rm -f .graphify_detect.json .graphify_extract.json .graphify_ast.json .graphify_semantic.json .graphify_analysis.json .graphify_labels.json .graphify_chunk_*.json +rm -f graphify-out/.needs_update 2>/dev/null || true +``` + +Tell the user (omit the obsidian line unless --obsidian was given): +``` +Graph complete. Outputs in PATH_TO_DIR/graphify-out/ + + graph.html - interactive graph, open in browser + GRAPH_REPORT.md - audit report + graph.json - raw graph data + obsidian/ - Obsidian vault (only if --obsidian was given) +``` + +If graphify saved you time, consider supporting it: https://github.com/sponsors/safishamsi + +Replace PATH_TO_DIR with the actual absolute path of the directory that was processed. + +Then paste these sections from GRAPH_REPORT.md directly into the chat: +- God Nodes +- Surprising Connections +- Suggested Questions + +Do NOT paste the full report - just those three sections. Keep it concise. + +Then immediately offer to explore. Pick the single most interesting suggested question from the report - the one that crosses the most community boundaries or has the most surprising bridge node - and ask: + +> "The most interesting question this graph can answer: **[question]**. Want me to trace it?" + +If the user says yes, use the `graphify_query` extension tool to answer the question and walk them through it using the graph structure. Keep going as long as they want to explore. Each answer should end with a natural follow-up ("this connects to X - want to go deeper?") so the session feels like navigation, not a one-shot report. + +The graph is the map. Your job after the pipeline is to be the guide. + +--- + +## For --update (incremental re-extraction) + +Use when you've added or modified files since the last run. Only re-extracts changed files - saves tokens and time. + +**If all changed files are code files:** use the `graphify_update` extension tool — it handles code-only updates without LLM semantic extraction. + +**If changed files include docs, papers, or images:** follow the full pipeline Steps 1–9, but replace Step 2's `detect` with `detect_incremental`: + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.detect import detect_incremental, save_manifest +from pathlib import Path + +result = detect_incremental(Path('INPUT_PATH')) +new_total = result.get('new_total', 0) +print(json.dumps(result, indent=2)) +Path('.graphify_incremental.json').write_text(json.dumps(result)) +if new_total == 0: + print('No files changed since last run. Nothing to update.') + raise SystemExit(0) +print(f'{new_total} new/changed file(s) to re-extract.') +" +``` + +Then check whether all changed files are code files: + +```bash +$(cat .graphify_python) -c " +import json +from pathlib import Path + +result = json.loads(open('.graphify_incremental.json').read()) if Path('.graphify_incremental.json').exists() else {} +code_exts = {'.py','.ts','.js','.go','.rs','.java','.cpp','.c','.rb','.swift','.kt','.cs','.scala','.php','.cc','.cxx','.hpp','.h','.kts'} +new_files = result.get('new_files', {}) +all_changed = [f for files in new_files.values() for f in files] +code_only = all(Path(f).suffix.lower() in code_exts for f in all_changed) +print('code_only:', code_only) +" +``` + +If `code_only` is True: print `[graphify update] Code-only changes detected - skipping semantic extraction (no LLM needed)`, run only Step 3A (AST) on the changed files, skip Step 3B entirely (no subagents), then go straight to merge and Steps 4–8. + +If `code_only` is False (any changed file is a doc/paper/image): run the full Steps 3A–3C pipeline as normal. + +Then: + +```bash +$(cat .graphify_python) -c " +import sys, json +from graphify.build import build_from_json +from graphify.export import to_json +from networkx.readwrite import json_graph +import networkx as nx +from pathlib import Path + +# Load existing graph +existing_data = json.loads(Path('graphify-out/graph.json').read_text()) +G_existing = json_graph.node_link_graph(existing_data, edges='links') + +# Load new extraction +new_extraction = json.loads(Path('.graphify_extract.json').read_text()) +G_new = build_from_json(new_extraction) + +# Merge: new nodes/edges into existing graph +G_existing.update(G_new) +print(f'Merged: {G_existing.number_of_nodes()} nodes, {G_existing.number_of_edges()} edges') +" +``` + +Then run Steps 4–8 on the merged graph as normal. + +After Step 4, show the graph diff: + +```bash +$(cat .graphify_python) -c " +import json +from graphify.analyze import graph_diff +from graphify.build import build_from_json +from networkx.readwrite import json_graph +import networkx as nx +from pathlib import Path + +# Load old graph (before update) from backup written before merge +old_data = json.loads(Path('.graphify_old.json').read_text()) if Path('.graphify_old.json').exists() else None +new_extract = json.loads(Path('.graphify_extract.json').read_text()) +G_new = build_from_json(new_extract) + +if old_data: + G_old = json_graph.node_link_graph(old_data, edges='links') + diff = graph_diff(G_old, G_new) + print(diff['summary']) + if diff['new_nodes']: + print('New nodes:', ', '.join(n['label'] for n in diff['new_nodes'][:5])) + if diff['new_edges']: + print('New edges:', len(diff['new_edges'])) +" +``` + +Before the merge step, save the old graph: `cp graphify-out/graph.json .graphify_old.json` +Clean up after: `rm -f .graphify_old.json` + +--- + +## For --cluster-only + +Use the `graphify_cluster` extension tool. + +--- + +## For query, path, explain, add, watch + +Use the corresponding extension tools: + +| Subcommand | Tool | +|------------|------| +| `query` | `graphify_query` | +| `path` | `graphify_path` | +| `explain` | `graphify_explain` | +| `add` | `graphify_add` | +| `watch` | `graphify_watch` | + +--- + +## Honesty Rules + +- Never invent an edge. If unsure, use AMBIGUOUS. +- Never skip the corpus check warning. +- Always show token cost in the report. +- Never hide cohesion scores behind symbols - show the raw number. +- Never run HTML viz on a graph with more than 5,000 nodes without warning the user. diff --git a/extensions/pi-graphify/src/commands/graphify.command.integration.test.ts b/extensions/pi-graphify/src/commands/graphify.command.integration.test.ts new file mode 100644 index 0000000..8bde0f1 --- /dev/null +++ b/extensions/pi-graphify/src/commands/graphify.command.integration.test.ts @@ -0,0 +1,88 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createTestSession, says, type TestSession, when } from "@gaodes/pi-test-harness"; +import { afterEach, describe, expect, it } from "vitest"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PROJECT_ROOT = path.resolve(__dirname, "../.."); +const COMMANDS_ENTRY = path.resolve(PROJECT_ROOT, "src/commands/index.ts"); + +function createBashMock() { + return (params: Record) => { + const cmd = String(params.command ?? ""); + if (cmd.includes("cat graphify-out/.graphify_python")) { + return `$ ${cmd}\n/usr/bin/python3`; + } + if (cmd.includes("import graphify") && cmd.includes("echo $?")) { + return `$ ${cmd}\n0`; + } + return `$ ${cmd}\n`; + }; +} + +function textOfContent(content: unknown): string { + if (!Array.isArray(content)) return String(content ?? ""); + return content + .map((c) => (typeof c === "object" && c !== null && "text" in c ? String(c.text ?? "") : "")) + .join(""); +} + +describe("/graphify command integration", () => { + let t: TestSession; + + afterEach(() => t?.dispose()); + + async function createSession() { + const session = await createTestSession({ + extensions: [COMMANDS_ENTRY], + mockTools: { + bash: createBashMock(), + }, + }); + + // Polyfill setTools for pi-agent-core compatibility + // biome-ignore lint/suspicious/noExplicitAny: compatibility shim accessing untyped internals + const agent = (session.session as any).agent; + if (agent && !agent.setTools) { + agent.setTools = (tools: unknown[]) => { + agent.state.tools = tools; + }; + } + + return session; + } + + it("forwards build flags into graphify_build params contract", async () => { + t = await createSession(); + + await t.run( + when("/graphify . --mode deep --no-viz --obsidian --svg --graphml --neo4j", [says("ok")]), + ); + + const userMessages = t.events.messages.filter((m) => m.role === "user"); + expect(userMessages.length).toBeGreaterThan(0); + + const promptText = textOfContent(userMessages[0].content); + expect(promptText).toContain("Use the graphify_build tool with these exact params"); + expect(promptText).toContain('"path":"."'); + expect(promptText).toContain('"mode":"deep"'); + expect(promptText).toContain('"no_viz":true'); + expect(promptText).toContain('"obsidian":true'); + expect(promptText).toContain('"svg":true'); + expect(promptText).toContain('"graphml":true'); + expect(promptText).toContain('"neo4j":true'); + }); + + it("parses --debounce for watch subcommand", async () => { + t = await createSession(); + + await t.run(when("/graphify watch . --debounce 7", [says("ok")])); + + const userMessages = t.events.messages.filter((m) => m.role === "user"); + expect(userMessages.length).toBeGreaterThan(0); + + const promptText = textOfContent(userMessages[0].content); + expect(promptText).toContain("Use the graphify_watch tool"); + expect(promptText).toContain('watch "." for changes with debounce 7s'); + }); +}); diff --git a/extensions/pi-graphify/src/commands/index.ts b/extensions/pi-graphify/src/commands/index.ts new file mode 100644 index 0000000..98ed2a3 --- /dev/null +++ b/extensions/pi-graphify/src/commands/index.ts @@ -0,0 +1,564 @@ +import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent"; +import type { AutocompleteItem } from "@earendil-works/pi-tui"; +import { ensurePrimeSettings, loadConfig, type ResolvedConfig } from "../config"; +import type { ExecFn } from "../lib/runner"; +import { + clusterOnly, + detectPython, + ensureInstalled, + explainNode, + findPath, + hookAction, + queryGraph, + updateGraph, +} from "../lib/runner"; + +// --------------------------------------------------------------------------- +// Autocomplete definitions +// --------------------------------------------------------------------------- + +const SUBCOMMANDS: AutocompleteItem[] = [ + { + value: "", + label: "", + description: "Build graph from directory (full pipeline)", + }, + { + value: "query", + label: "query", + description: "Query the graph — BFS for broad context, DFS for tracing paths", + }, + { + value: "path", + label: "path", + description: "Find shortest path between two concepts", + }, + { + value: "explain", + label: "explain", + description: "Plain-language explanation of a node", + }, + { + value: "add", + label: "add", + description: "Fetch a URL and add it to the corpus", + }, + { + value: "update", + label: "update", + description: "Incremental update — re-extract only changed files", + }, + { + value: "watch", + label: "watch", + description: "Watch directory for changes, auto-rebuild graph", + }, + { + value: "cluster", + label: "cluster", + description: "Re-run clustering on existing graph (no re-extraction)", + }, + { + value: "hook", + label: "hook", + description: "Manage git hooks (install/uninstall/status)", + }, +]; + +const BUILD_FLAGS: AutocompleteItem[] = [ + { + value: "--mode deep", + label: "--mode deep", + description: "More aggressive relationship inference", + }, + { value: "--no-viz", label: "--no-viz", description: "Skip HTML visualization" }, + { value: "--obsidian", label: "--obsidian", description: "Generate Obsidian vault" }, + { value: "--svg", label: "--svg", description: "Export graph.svg" }, + { value: "--graphml", label: "--graphml", description: "Export for Gephi / yEd" }, + { value: "--neo4j", label: "--neo4j", description: "Generate cypher.txt for Neo4j" }, + { + value: "--update", + label: "--update", + description: "Incremental — re-extract only changed files", + }, + { + value: "--cluster-only", + label: "--cluster-only", + description: "Rerun clustering on existing graph", + }, +]; + +const QUERY_FLAGS: AutocompleteItem[] = [ + { value: "--dfs", label: "--dfs", description: "DFS traversal — trace a specific path" }, + { + value: "--budget", + label: "--budget N", + description: "Token budget for the answer (default 2000)", + }, +]; + +function getCompletions(argumentPrefix: string): AutocompleteItem[] { + const parts = argumentPrefix.trim().split(/\s+/); + + if (parts.length <= 1) { + const prefix = parts[0] ?? ""; + if (prefix.startsWith("--")) { + return BUILD_FLAGS.filter((f) => f.value.startsWith(prefix)); + } + return SUBCOMMANDS.filter((s) => s.value === "" || s.value.startsWith(prefix.toLowerCase())); + } + + const subcommand = parts[0].toLowerCase(); + + switch (subcommand) { + case "query": { + if (parts[parts.length - 1]?.startsWith("--")) { + return QUERY_FLAGS.filter((f) => f.value.startsWith(parts[parts.length - 1] ?? "")); + } + return [ + { + value: `query "${parts.slice(1).join(" ")}`, + label: `"${parts.slice(1).join(" ")}..."`, + description: "Your question (wrap in quotes)", + }, + ]; + } + case "path": { + return [ + { + value: `path ${parts.slice(1).join(" ")}`, + label: parts.slice(1).join(" ") || '"A" "B"', + description: "Two concept names in quotes", + }, + ]; + } + case "explain": { + return [ + { + value: `explain ${parts.slice(1).join(" ")}`, + label: parts.slice(1).join(" ") || "ConceptName", + description: "Name of the concept to explain", + }, + ]; + } + case "add": { + const addFlags: AutocompleteItem[] = [ + { value: "--author", label: '--author "Name"', description: "Tag who wrote it" }, + { + value: "--contributor", + label: '--contributor "Name"', + description: "Tag who added it", + }, + ]; + if (parts[parts.length - 1]?.startsWith("--")) { + return addFlags.filter((f) => f.value.startsWith(parts[parts.length - 1] ?? "")); + } + return [ + { + value: `add ${parts.slice(1).join(" ")}`, + label: parts.slice(1).join(" ") || "", + description: "URL to fetch and add", + }, + ]; + } + case "update": { + return [ + { + value: `update ${parts.slice(1).join(" ")}`, + label: parts.slice(1).join(" ") || ".", + description: "Directory path to update", + }, + ]; + } + case "watch": { + return [ + { + value: `watch ${parts.slice(1).join(" ")}`, + label: parts.slice(1).join(" ") || ".", + description: "Directory path to watch", + }, + ]; + } + case "cluster": { + return [{ value: "cluster", label: "cluster", description: "Re-cluster existing graph" }]; + } + case "hook": { + const hookActions: AutocompleteItem[] = [ + { value: "hook install", label: "install", description: "Install git hooks" }, + { value: "hook uninstall", label: "uninstall", description: "Remove git hooks" }, + { value: "hook status", label: "status", description: "Check hook status" }, + ]; + const partial = parts.slice(1).join(" ").toLowerCase(); + return hookActions.filter((h) => h.label.startsWith(partial || h.label)); + } + default: { + if (parts[parts.length - 1]?.startsWith("--")) { + return BUILD_FLAGS.filter((f) => f.value.startsWith(parts[parts.length - 1] ?? "")); + } + return BUILD_FLAGS; + } + } +} + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +interface ParsedArgs { + subcommand: + | "build" + | "query" + | "path" + | "explain" + | "add" + | "update" + | "watch" + | "cluster" + | "hook"; + positionals: string[]; + flags: Record; +} + +function parseArgs(raw: string): ParsedArgs { + const tokens = raw.trim().split(/\s+/).filter(Boolean); + const flags: Record = {}; + const positionals: string[] = []; + + let i = 0; + let subcommand: ParsedArgs["subcommand"] = "build"; + + if (tokens.length > 0) { + const first = tokens[0].toLowerCase(); + if ( + ["query", "path", "explain", "add", "update", "watch", "cluster", "hook"].includes(first) && + !first.startsWith("-") + ) { + subcommand = first as ParsedArgs["subcommand"]; + i = 1; + } + } + + while (i < tokens.length) { + const token = tokens[i]; + if (token === "--mode" && tokens[i + 1]) { + flags.mode = tokens[i + 1]; + i += 2; + } else if (token === "--budget" && tokens[i + 1]) { + flags.budget = tokens[i + 1]; + i += 2; + } else if (token === "--author" && tokens[i + 1]) { + flags.author = tokens[i + 1]; + i += 2; + } else if (token === "--contributor" && tokens[i + 1]) { + flags.contributor = tokens[i + 1]; + i += 2; + } else if (token === "--debounce" && tokens[i + 1]) { + flags.debounce = tokens[i + 1]; + i += 2; + } else if (token.startsWith("--")) { + flags[token.slice(2)] = true; + i++; + } else { + positionals.push(token); + i++; + } + } + + return { subcommand, positionals, flags }; +} + +// --------------------------------------------------------------------------- +// Exec adapter for commands +// --------------------------------------------------------------------------- + +function createExec(pi: ExtensionAPI, cwd: string): ExecFn { + return async (command, options) => { + const result = await pi.exec("sh", ["-c", command], { + cwd: options?.cwd ?? cwd, + signal: options?.signal, + }); + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: result.code, + }; + }; +} + +// --------------------------------------------------------------------------- +// Command handlers +// --------------------------------------------------------------------------- + +async function handleBuild( + pi: ExtensionAPI, + ctx: ExtensionCommandContext, + config: ResolvedConfig, + positionals: string[], + flags: Record, +) { + const inputPath = positionals[0] ?? "."; + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd); + await ensureInstalled(exec, python, ctx.cwd); + + if (flags["cluster-only"] === true) { + const result = await pi.exec( + "sh", + [ + "-c", + `${python} -c " +import json +from networkx.readwrite import json_graph +from graphify.cluster import cluster, score_all +from graphify.report import generate +from graphify.export import to_json +from pathlib import Path + +data = json.loads(Path('graphify-out/graph.json').read_text()) +G = json_graph.node_link_graph(data, edges='links') +communities = cluster(G) +to_json(G, communities, 'graphify-out/graph.json') +print(f'Re-clustered: {len(communities)} communities') +"`, + ], + { cwd: ctx.cwd }, + ); + await ctx.ui.notify(result.stdout.trim() || "Re-clustered graph."); + return; + } + + if (flags.update === true) { + const result = await updateGraph(exec, python, ctx.cwd, inputPath); + await ctx.ui.notify( + result.newFiles === 0 + ? "No files changed. Graph is up to date." + : `Updated: ${result.newFiles} files re-extracted. ${result.nodes} nodes, ${result.edges} edges.`, + ); + return; + } + + const buildArgs = { + path: inputPath, + ...(flags.mode === "deep" ? { mode: "deep" } : {}), + ...(flags["no-viz"] === true ? { no_viz: true } : {}), + ...(flags.obsidian === true ? { obsidian: true } : {}), + ...(flags.svg === true ? { svg: true } : {}), + ...(flags.graphml === true ? { graphml: true } : {}), + ...(flags.neo4j === true ? { neo4j: true } : {}), + }; + + // Full build — send a message to the agent so it uses the tool with explicit params + pi.sendUserMessage( + `Use the graphify_build tool with these exact params: ${JSON.stringify(buildArgs)}. After the graph is built, read graphify-out/GRAPH_REPORT.md and show me the God Nodes, Surprising Connections, and Suggested Questions.`, + ); +} + +async function handleQuery( + pi: ExtensionAPI, + ctx: ExtensionCommandContext, + config: ResolvedConfig, + positionals: string[], + flags: Record, +) { + const question = positionals.join(" ").replace(/^["']|["']$/g, ""); + if (!question) { + await ctx.ui.notify('Usage: /graphify query ""', "warning"); + return; + } + + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd); + await ensureInstalled(exec, python, ctx.cwd); + + const mode = flags.dfs === true ? "dfs" : "bfs"; + const budget = typeof flags.budget === "string" ? Number.parseInt(flags.budget, 10) : 2000; + + const result = await queryGraph(exec, python, ctx.cwd, { question, mode, budget }); + + pi.sendUserMessage( + `Based on the graph query result below, answer this question: "${question}"\n\nGraph traversal result:\n${result}`, + ); +} + +async function handlePath( + pi: ExtensionAPI, + ctx: ExtensionCommandContext, + config: ResolvedConfig, + positionals: string[], +) { + if (positionals.length < 2) { + await ctx.ui.notify('Usage: /graphify path "ConceptA" "ConceptB"', "warning"); + return; + } + + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd); + await ensureInstalled(exec, python, ctx.cwd); + + const result = await findPath(exec, python, ctx.cwd, positionals[0], positionals[1]); + + pi.sendUserMessage(`Explain this graph path in plain language:\n${result}`); +} + +async function handleExplain( + pi: ExtensionAPI, + ctx: ExtensionCommandContext, + config: ResolvedConfig, + positionals: string[], +) { + const concept = positionals.join(" ").replace(/^["']|["']$/g, ""); + if (!concept) { + await ctx.ui.notify('Usage: /graphify explain "ConceptName"', "warning"); + return; + } + + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd); + await ensureInstalled(exec, python, ctx.cwd); + + const result = await explainNode(exec, python, ctx.cwd, concept); + + pi.sendUserMessage( + `Based on the graph data below, provide a plain-language explanation of "${concept}":\n${result}`, + ); +} + +async function handleAdd( + pi: ExtensionAPI, + positionals: string[], + flags: Record, +) { + const url = positionals[0]; + if (!url) { + await pi.sendUserMessage("Usage: /graphify add "); + return; + } + + const authorStr = typeof flags.author === "string" ? ` (author: ${flags.author})` : ""; + const contributorStr = + typeof flags.contributor === "string" ? ` (contributor: ${flags.contributor})` : ""; + + pi.sendUserMessage( + `Use the graphify_add tool to fetch and add this URL to the corpus: ${url}${authorStr}${contributorStr}. After adding it, run an incremental graph update.`, + ); +} + +async function handleUpdate(pi: ExtensionAPI, positionals: string[]) { + const inputPath = positionals[0] ?? "."; + pi.sendUserMessage( + `Use the graphify_update tool to incrementally update the knowledge graph for path "${inputPath}".`, + ); +} + +async function handleWatch( + pi: ExtensionAPI, + _ctx: ExtensionCommandContext, + _config: ResolvedConfig, + positionals: string[], + flags: Record, +) { + const inputPath = positionals[0] ?? "."; + const debounce = typeof flags.debounce === "string" ? flags.debounce : "3"; + pi.sendUserMessage( + `Use the graphify_watch tool to watch "${inputPath}" for changes with debounce ${debounce}s. Run it as a background process.`, + ); +} + +async function handleCluster( + pi: ExtensionAPI, + ctx: ExtensionCommandContext, + config: ResolvedConfig, +) { + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd); + await ensureInstalled(exec, python, ctx.cwd); + + try { + const result = await clusterOnly(exec, python, ctx.cwd); + await ctx.ui.notify(`Re-clustered: ${result.communities} communities`); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + await ctx.ui.notify(`Cluster failed: ${message}`, "error"); + } +} + +async function handleHook( + pi: ExtensionAPI, + ctx: ExtensionCommandContext, + config: ResolvedConfig, + positionals: string[], +) { + const action = positionals[0]; + if (!action || !["install", "uninstall", "status"].includes(action)) { + await ctx.ui.notify("Usage: /graphify hook ", "warning"); + return; + } + + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd); + await ensureInstalled(exec, python, ctx.cwd); + + const result = await hookAction( + exec, + python, + ctx.cwd, + action as "install" | "uninstall" | "status", + ); + await ctx.ui.notify(result); +} + +// --------------------------------------------------------------------------- +// Command registration +// --------------------------------------------------------------------------- + +export default function (pi: ExtensionAPI) { + ensurePrimeSettings(); + + const config = loadConfig(process.cwd()); + if (!config.enabled) return; + + pi.registerCommand("graphify", { + description: "Knowledge graph: build, query, explore, and update graphs from directories", + getArgumentCompletions(argumentPrefix: string): AutocompleteItem[] { + return getCompletions(argumentPrefix); + }, + async handler(args: string, ctx: ExtensionCommandContext) { + const parsed = parseArgs(args); + + try { + switch (parsed.subcommand) { + case "build": + await handleBuild(pi, ctx, config, parsed.positionals, parsed.flags); + break; + case "query": + await handleQuery(pi, ctx, config, parsed.positionals, parsed.flags); + break; + case "path": + await handlePath(pi, ctx, config, parsed.positionals); + break; + case "explain": + await handleExplain(pi, ctx, config, parsed.positionals); + break; + case "add": + await handleAdd(pi, parsed.positionals, parsed.flags); + break; + case "update": + await handleUpdate(pi, parsed.positionals); + break; + case "watch": + await handleWatch(pi, ctx, config, parsed.positionals, parsed.flags); + break; + case "cluster": + await handleCluster(pi, ctx, config); + break; + case "hook": + await handleHook(pi, ctx, config, parsed.positionals); + break; + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + await ctx.ui.notify(`Graphify error: ${message}`, "error"); + } + }, + }); +} diff --git a/extensions/pi-graphify/src/config.ts b/extensions/pi-graphify/src/config.ts new file mode 100644 index 0000000..e7602e1 --- /dev/null +++ b/extensions/pi-graphify/src/config.ts @@ -0,0 +1,162 @@ +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { getAgentDir } from "@earendil-works/pi-coding-agent"; +import type { StatusbarConfig } from "./statusbar.js"; + +export const EXTENSION_ID = "pi-graphify"; +export const PRIME_SETTINGS_FILE = "prime-settings.json"; + +export interface RawConfig { + enabled?: boolean; + pythonPath?: string; + outputDir?: string; + statusbar?: StatusbarConfig; +} + +export interface ResolvedConfig { + enabled: boolean; + pythonPath: string; + outputDir: string; + statusbar?: StatusbarConfig; +} + +export const DEFAULT_CONFIG: ResolvedConfig = { + enabled: true, + pythonPath: "python3", + outputDir: "graphify-out", + statusbar: { + enabled: true, + icon: "f035b", + icon_color: "accent", + icon_color_uninitialized: "dim", + text_font_color: "dim", + text_font_color_uninitialized: "dim", + show_icon: true, + show_text: true, + placement: { line: 2, side: "left", index: 4 }, + separator_before: { icon: "eb8a", icon_color: "dim" }, + separator_after: { icon: "eb8a", icon_color: "dim" }, + }, +}; + +function readJsonFile(path: string): Record | undefined { + try { + return JSON.parse(readFileSync(path, "utf-8")) as Record; + } catch { + return undefined; + } +} + +export function resolveConfig(...configs: Array): ResolvedConfig { + const resolved: ResolvedConfig = { ...DEFAULT_CONFIG }; + for (const raw of configs) { + if (!raw) continue; + if (raw.enabled !== undefined) resolved.enabled = raw.enabled; + if (raw.pythonPath !== undefined) resolved.pythonPath = raw.pythonPath; + if (raw.outputDir !== undefined) resolved.outputDir = raw.outputDir; + if (raw.statusbar !== undefined) resolved.statusbar = raw.statusbar; + } + return resolved; +} + +export function loadConfig(cwd: string): ResolvedConfig { + const globalPath = join(getAgentDir(), PRIME_SETTINGS_FILE); + const projectPath = join(cwd, ".pi", PRIME_SETTINGS_FILE); + const globalSettings = existsSync(globalPath) ? readJsonFile(globalPath) : undefined; + const projectSettings = existsSync(projectPath) ? readJsonFile(projectPath) : undefined; + + return resolveConfig( + globalSettings?.[EXTENSION_ID] as RawConfig | undefined, + projectSettings?.[EXTENSION_ID] as RawConfig | undefined, + ); +} + +// --------------------------------------------------------------------------- +// Auto-seed defaults into global prime-settings.json +// --------------------------------------------------------------------------- + +const DEFAULT_STATUSBAR_CONFIG: StatusbarConfig = { + enabled: true, + icon: "f035b", + icon_color: "accent", + icon_color_uninitialized: "dim", + text_font_color: "dim", + text_font_color_uninitialized: "dim", + show_icon: true, + show_text: true, + placement: { line: 2, side: "left", index: 4 }, + separator_before: { icon: "eb8a", icon_color: "dim" }, + separator_after: { icon: "eb8a", icon_color: "dim" }, +}; + +const DEFAULT_EXTENSION_SETTINGS: Record = { + enabled: true, + pythonPath: "python3", + outputDir: "graphify-out", + statusbar: DEFAULT_STATUSBAR_CONFIG, +}; + +/** + * Ensure the extension's config exists in prime-settings.json. + * + * - If the key "pi-graphify" is missing, seeds full defaults. + * - If only the legacy key "graphify" exists, migrates it to "pi-graphify". + * - Expands a minimal statusbar config to the full config. + * - Only writes when changes are actually needed. + */ +export function ensurePrimeSettings(): void { + const agentDir = getAgentDir(); + const primeSettingsPath = join(agentDir, PRIME_SETTINGS_FILE); + + // Only operate when prime-settings.json already exists (real Pi install) + if (!existsSync(primeSettingsPath)) return; + + let settings: Record; + try { + settings = JSON.parse(readFileSync(primeSettingsPath, "utf-8")) as Record; + } catch { + return; + } + + let changed = false; + + // Migrate legacy "graphify" key → "pi-graphify" + if ("graphify" in settings && !(EXTENSION_ID in settings)) { + settings[EXTENSION_ID] = settings.graphify; + delete settings.graphify; + changed = true; + } + + // Seed defaults if key is missing + if (!(EXTENSION_ID in settings)) { + settings[EXTENSION_ID] = { ...DEFAULT_EXTENSION_SETTINGS }; + changed = true; + } + + // Seed or expand statusbar sub-key inside existing pi-graphify config + const extensionSettings = settings[EXTENSION_ID] as Record; + if (!("statusbar" in extensionSettings)) { + extensionSettings.statusbar = { ...DEFAULT_STATUSBAR_CONFIG }; + changed = true; + } else { + const sb = extensionSettings.statusbar as Record; + // Expand a minimal statusbar config to the full config + const needsExpansion = + !("icon" in sb) && + !("placement" in sb) && + !("separator_before" in sb) && + !("separator_after" in sb); + if (needsExpansion) { + extensionSettings.statusbar = { ...DEFAULT_STATUSBAR_CONFIG, ...sb }; + changed = true; + } + } + + if (!changed) return; + + try { + writeFileSync(primeSettingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf-8"); + } catch (error) { + console.warn("Failed to write prime-settings.json:", error); + } +} diff --git a/extensions/pi-graphify/src/lib/runner.test.ts b/extensions/pi-graphify/src/lib/runner.test.ts new file mode 100644 index 0000000..abf30b6 --- /dev/null +++ b/extensions/pi-graphify/src/lib/runner.test.ts @@ -0,0 +1,226 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it, vi } from "vitest"; +import { detectFiles, detectPython, ensureGraphifyGitignore, ensureInstalled } from "./runner"; + +// --------------------------------------------------------------------------- +// Mock exec function +// --------------------------------------------------------------------------- + +function createMockExec( + responses: Record, +) { + return vi.fn(async (cmd: string, _opts?: unknown) => { + for (const [pattern, response] of Object.entries(responses)) { + if (cmd.includes(pattern)) return response; + } + return { stdout: "", stderr: "unknown command", exitCode: 1 }; + }); +} + +// --------------------------------------------------------------------------- +// detectPython +// --------------------------------------------------------------------------- + +describe("detectPython", () => { + it("returns cached python from graphify-out/.graphify_python", async () => { + const mockExec = createMockExec({ + "cat graphify-out/.graphify_python": { + stdout: "/usr/bin/python3\n", + stderr: "", + exitCode: 0, + }, + }); + + const result = await detectPython(mockExec, "python3", "/tmp/test"); + expect(result).toBe("/usr/bin/python3"); + }); + + it("detects python from graphify shebang when no cache", async () => { + const mockExec = createMockExec({ + "cat graphify-out": { stdout: "", stderr: "", exitCode: 1 }, + "which graphify": { stdout: "/usr/local/bin/graphify\n", stderr: "", exitCode: 0 }, + "head -1": { stdout: "#!/usr/local/bin/python3.11\n", stderr: "", exitCode: 0 }, + }); + + const result = await detectPython(mockExec, "python3", "/tmp/test"); + expect(result).toBe("/usr/local/bin/python3.11"); + }); + + it("falls back to config python when graphify not found", async () => { + const mockExec = createMockExec({ + "cat graphify-out": { stdout: "", stderr: "", exitCode: 1 }, + "which graphify": { stdout: "", stderr: "", exitCode: 1 }, + }); + + const result = await detectPython(mockExec, "python3.12", "/tmp/test"); + expect(result).toBe("python3.12"); + }); +}); + +// --------------------------------------------------------------------------- +// ensureInstalled +// --------------------------------------------------------------------------- + +describe("ensureInstalled", () => { + it("skips install when graphify is importable", async () => { + const mockExec = vi.fn(async (cmd: string, _opts?: unknown) => { + // All calls return success + if (cmd.includes("import graphify")) { + return { stdout: "0", stderr: "", exitCode: 0 }; + } + return { stdout: "", stderr: "", exitCode: 0 }; + }); + + await ensureInstalled(mockExec, "python3", "/tmp/test"); + // Only called once (the initial check), no pip install + expect(mockExec).toHaveBeenCalledTimes(1); + expect(mockExec).not.toHaveBeenCalledWith( + expect.stringContaining("pip install"), + expect.anything(), + ); + }); + + it("installs graphifyy when not importable", async () => { + let callIdx = 0; + const mockExec = vi.fn(async (cmd: string, _opts?: unknown) => { + callIdx++; + // First call: check import → fails + if (callIdx === 1 && cmd.includes("import graphify")) { + return { stdout: "1", stderr: "", exitCode: 0 }; + } + // Second call: pip install → succeeds + if (cmd.includes("pip install")) { + return { stdout: "", stderr: "", exitCode: 0 }; + } + // Third call: verify → succeeds + if (callIdx === 3 && cmd.includes("import graphify")) { + return { stdout: "0", stderr: "", exitCode: 0 }; + } + return { stdout: "", stderr: "", exitCode: 0 }; + }); + + await ensureInstalled(mockExec, "python3", "/tmp/test"); + expect(mockExec).toHaveBeenCalledWith( + expect.stringContaining("pip install graphifyy"), + expect.anything(), + ); + }); +}); + +// --------------------------------------------------------------------------- +// detectFiles +// --------------------------------------------------------------------------- + +describe("detectFiles", () => { + it("parses detection output correctly", async () => { + const detectionResult = { + total_files: 5, + total_words: 1200, + files: { + code: ["/tmp/test/main.ts", "/tmp/test/util.ts"], + document: ["/tmp/test/README.md"], + paper: [], + image: [], + video: [], + }, + }; + + const mockExec = createMockExec({ + "from graphify.detect import detect": { + stdout: JSON.stringify(detectionResult), + stderr: "", + exitCode: 0, + }, + }); + + const result = await detectFiles(mockExec, "python3", "/tmp/test", "/tmp/test"); + expect(result.total_files).toBe(5); + expect(result.total_words).toBe(1200); + expect(result.files.code).toHaveLength(2); + expect(result.files.document).toHaveLength(1); + }); + + it("throws on detection failure", async () => { + const mockExec = createMockExec({ + "from graphify.detect import detect": { + stdout: "", + stderr: "ModuleNotFoundError: No module named 'graphify'", + exitCode: 1, + }, + }); + + await expect(detectFiles(mockExec, "python3", "/tmp/test", "/tmp/test")).rejects.toThrow( + "graphify detect failed", + ); + }); +}); + +// --------------------------------------------------------------------------- +// ensureGraphifyGitignore +// --------------------------------------------------------------------------- + +describe("ensureGraphifyGitignore", () => { + it("creates .gitignore with graphify cache exclusions when missing", async () => { + const dir = await mkdtemp(join(tmpdir(), "pi-graphify-test-")); + + try { + const result = await ensureGraphifyGitignore(dir); + expect(result.updated).toBe(true); + + const content = await readFile(join(dir, ".gitignore"), "utf-8"); + expect(content).toContain("graphify-out/cache/"); + expect(content).toContain("graphify-out/.graphify_python"); + expect(content).toContain("graphify-out/.graphify_root"); + expect(content).toContain("graphify-out/cost.json"); + expect(content).not.toContain("graphify-out/\n"); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + it("removes legacy graphify-out/ ignore and preserves other entries", async () => { + const dir = await mkdtemp(join(tmpdir(), "pi-graphify-test-")); + + try { + await writeFile( + join(dir, ".gitignore"), + "node_modules/\ngraphify-out/\ncustom-file.txt\n", + "utf-8", + ); + + const result = await ensureGraphifyGitignore(dir); + expect(result.updated).toBe(true); + + const content = await readFile(join(dir, ".gitignore"), "utf-8"); + expect(content).toContain("node_modules/"); + expect(content).toContain("custom-file.txt"); + expect(content).toContain("graphify-out/cache/"); + expect(content).toContain("graphify-out/.graphify_python"); + expect(content).toContain("graphify-out/.graphify_root"); + expect(content).toContain("graphify-out/cost.json"); + expect(content).not.toContain("\ngraphify-out/\n"); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + it("is idempotent when required entries already exist", async () => { + const dir = await mkdtemp(join(tmpdir(), "pi-graphify-test-")); + + try { + const expected = + "node_modules/\ngraphify-out/cache/\ngraphify-out/.graphify_python\ngraphify-out/.graphify_root\ngraphify-out/cost.json\n"; + await writeFile(join(dir, ".gitignore"), expected, "utf-8"); + + const result = await ensureGraphifyGitignore(dir); + expect(result.updated).toBe(false); + + const content = await readFile(join(dir, ".gitignore"), "utf-8"); + expect(content).toBe(expected); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/extensions/pi-graphify/src/lib/runner.ts b/extensions/pi-graphify/src/lib/runner.ts new file mode 100644 index 0000000..4c35ee7 --- /dev/null +++ b/extensions/pi-graphify/src/lib/runner.ts @@ -0,0 +1,1029 @@ +/** + * Graphify CLI runner — pure domain logic, no Pi imports. + * + * All functions accept a generic `exec` callback so the tools layer can + * inject `pi.exec()` while keeping this module independently testable. + * + * NOTE: pi.exec() signature is (command: string, args: string[], options?: ExecOptions). + * The exec adapter in the tools/commands layer wraps this to accept a single shell string. + */ + +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +export interface ExecOptions { + cwd?: string; + signal?: AbortSignal; +} + +export interface ExecResult { + stdout: string; + stderr: string; + exitCode: number; +} + +export type ExecFn = (command: string, options?: ExecOptions) => Promise; + +const GRAPHIFY_GITIGNORE_REQUIRED = [ + "graphify-out/cache/", + "graphify-out/.graphify_python", + "graphify-out/.graphify_root", + "graphify-out/cost.json", +] as const; + +const GRAPHIFY_GITIGNORE_LEGACY = ["graphify-out/", "/graphify-out/"] as const; + +// --------------------------------------------------------------------------- +// Python / graphify detection +// --------------------------------------------------------------------------- + +/** Detect the correct Python interpreter from a graphify installation. */ +export async function detectPython( + exec: ExecFn, + configPython: string, + cwd: string, + signal?: AbortSignal, +): Promise { + const cacheResult = await exec("cat graphify-out/.graphify_python 2>/dev/null || true", { + cwd, + signal, + }); + if (cacheResult.stdout.trim()) return cacheResult.stdout.trim(); + + const whichResult = await exec("which graphify 2>/dev/null || true", { cwd, signal }); + if (whichResult.stdout.trim()) { + const binPath = whichResult.stdout.trim(); + const shebang = await exec(`head -1 '${binPath}' | tr -d '#!'`, { cwd, signal }); + let python = shebang.stdout.trim().replace(/^#!\s*/, ""); + if (!python || !/^[a-zA-Z0-9/_.-]+$/.test(python)) { + python = configPython; + } + return python; + } + + return configPython; +} + +/** Ensure graphify is importable; auto-install if needed. */ +export async function ensureInstalled( + exec: ExecFn, + python: string, + cwd: string, + signal?: AbortSignal, +): Promise { + const check = await exec(`${python} -c "import graphify" 2>/dev/null; echo $?`, { + cwd, + signal, + }); + if (check.stdout.trim() === "0") return; + + await exec( + `${python} -m pip install graphifyy -q 2>/dev/null || ${python} -m pip install graphifyy -q --break-system-packages 2>&1 | tail -3`, + { cwd, signal }, + ); + + const verify = await exec(`${python} -c "import graphify" 2>/dev/null; echo $?`, { + cwd, + signal, + }); + if (verify.stdout.trim() !== "0") { + throw new Error( + "Could not install graphifyy. Install manually: pip install graphifyy (or uv tool install graphifyy)", + ); + } +} + +// --------------------------------------------------------------------------- +// Detect files +// --------------------------------------------------------------------------- + +export interface DetectResult { + total_files: number; + total_words: number; + files: Record; + skipped_sensitive?: number; +} + +export async function detectFiles( + exec: ExecFn, + python: string, + cwd: string, + inputPath: string, + signal?: AbortSignal, +): Promise { + const result = await exec( + `${python} -c "import json; from graphify.detect import detect; from pathlib import Path; r=detect(Path('${escapeShell(inputPath)}')); print(json.dumps(r))"`, + { cwd, signal }, + ); + if (result.exitCode !== 0) { + throw new Error(`graphify detect failed: ${result.stderr}`); + } + return JSON.parse(result.stdout.trim()) as DetectResult; +} + +// --------------------------------------------------------------------------- +// Build graph (full pipeline) +// --------------------------------------------------------------------------- + +export interface BuildOptions { + inputPath: string; + mode?: "standard" | "deep"; + noViz?: boolean; + obsidian?: boolean; + svg?: boolean; + graphml?: boolean; + neo4j?: boolean; + neo4jUri?: string; +} + +export async function buildGraph( + exec: ExecFn, + python: string, + cwd: string, + options: BuildOptions, + signal?: AbortSignal, + onUpdate?: (message: string) => void, +): Promise<{ nodes: number; edges: number; communities: number }> { + const { inputPath } = options; + const outDir = "graphify-out"; + + const gitignoreResult = await ensureGraphifyGitignore(cwd); + if (gitignoreResult.updated) { + onUpdate?.("Updated .gitignore for graphify artifacts."); + } + + await exec(`mkdir -p ${outDir}`, { cwd, signal }); + + await exec( + `${python} -c "import sys; open('${outDir}/.graphify_python', 'w').write(sys.executable)"`, + { + cwd, + signal, + }, + ); + + onUpdate?.("Detecting files..."); + const detection = await detectFiles(exec, python, cwd, inputPath, signal); + + if (detection.total_files === 0) { + throw new Error(`No supported files found in ${inputPath}.`); + } + + onUpdate?.( + `Corpus: ${detection.total_files} files · ~${detection.total_words.toLocaleString()} words`, + ); + + // Save detection result for downstream steps + await writeFile(join(cwd, ".graphify_detect.json"), JSON.stringify(detection, null, 2), "utf-8"); + + // AST extraction + onUpdate?.("Extracting structural relationships (AST)..."); + const astResult = await exec( + `${python} -c " +import sys, json +from graphify.extract import collect_files, extract +from pathlib import Path + +code_files = [] +detect = json.loads(Path('.graphify_detect.json').read_text()) +for f in detect.get('files', {}).get('code', []): + code_files.extend(collect_files(Path(f)) if Path(f).is_dir() else [Path(f)]) + +if code_files: + result = extract(code_files) + Path('.graphify_ast.json').write_text(json.dumps(result, indent=2)) + print(f'AST: {len(result['nodes'])} nodes, {len(result['edges'])} edges') +else: + Path('.graphify_ast.json').write_text(json.dumps({'nodes':[],'edges':[],'input_tokens':0,'output_tokens':0})) + print('No code files - skipping AST extraction') +"`, + { cwd, signal }, + ); + + if (astResult.exitCode !== 0) { + throw new Error(`AST extraction failed: ${astResult.stderr}`); + } + onUpdate?.(astResult.stdout.trim()); + + // Semantic extraction (placeholder for code-only corpus) + const semanticFiles = [ + ...(detection.files.document ?? []), + ...(detection.files.paper ?? []), + ...(detection.files.image ?? []), + ]; + if (semanticFiles.length > 0) { + onUpdate?.( + `Semantic extraction: ${semanticFiles.length} files — extracting entities and relationships...`, + ); + } + + // Always write semantic placeholder for merge step + await exec( + `${python} -c "import json; from pathlib import Path; Path('.graphify_semantic.json').write_text(json.dumps({'nodes':[],'edges':[],'hyperedges':[],'input_tokens':0,'output_tokens':0}))"`, + { cwd, signal }, + ); + + // Merge AST + semantic + onUpdate?.("Merging extraction results..."); + const mergeResult = await exec( + `${python} -c " +import json; from pathlib import Path +ast = json.loads(Path('.graphify_ast.json').read_text()) +sem = json.loads(Path('.graphify_semantic.json').read_text()) +seen = {n['id'] for n in ast['nodes']} +merged_nodes = list(ast['nodes']) +for n in sem['nodes']: + if n['id'] not in seen: merged_nodes.append(n); seen.add(n['id']) +merged_edges = ast['edges'] + sem['edges'] +merged = {'nodes': merged_nodes, 'edges': merged_edges, 'hyperedges': sem.get('hyperedges',[]), 'input_tokens': sem.get('input_tokens',0), 'output_tokens': sem.get('output_tokens',0)} +Path('.graphify_extract.json').write_text(json.dumps(merged, indent=2)) +print(f'Merged: {len(merged_nodes)} nodes, {len(merged_edges)} edges') +"`, + { cwd, signal }, + ); + if (mergeResult.exitCode !== 0) { + throw new Error(`Merge failed: ${mergeResult.stderr}`); + } + onUpdate?.(mergeResult.stdout.trim()); + + // Build, cluster, analyze + onUpdate?.("Building graph and detecting communities..."); + const escapedPath = escapeShell(inputPath); + const buildResult = await exec( + `${python} -c " +import json +from graphify.build import build_from_json +from graphify.cluster import cluster, score_all +from graphify.analyze import god_nodes, surprising_connections, suggest_questions +from graphify.report import generate +from graphify.export import to_json +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +detection = json.loads(Path('.graphify_detect.json').read_text()) if Path('.graphify_detect.json').exists() else {} + +G = build_from_json(extraction) +if G.number_of_nodes() == 0: + print('ERROR: Graph is empty') + raise SystemExit(1) +communities = cluster(G) +cohesion = score_all(G, communities) +tokens = {'input': extraction.get('input_tokens',0), 'output': extraction.get('output_tokens',0)} +gods = god_nodes(G) +surprises = surprising_connections(G, communities) +labels = {cid: 'Community ' + str(cid) for cid in communities} +questions = suggest_questions(G, communities, labels) + +report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, '${escapedPath}', suggested_questions=questions) +Path('graphify-out/GRAPH_REPORT.md').write_text(report) +to_json(G, communities, 'graphify-out/graph.json') + +analysis = {'communities': {str(k):v for k,v in communities.items()}, 'cohesion': {str(k):v for k,v in cohesion.items()}, 'gods': gods, 'surprises': surprises, 'questions': questions} +Path('.graphify_analysis.json').write_text(json.dumps(analysis, indent=2)) +print(f'Graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges, {len(communities)} communities') +"`, + { cwd, signal }, + ); + + if (buildResult.exitCode !== 0) { + throw new Error(`Build failed: ${buildResult.stderr || buildResult.stdout}`); + } + onUpdate?.(buildResult.stdout.trim()); + + // Generate HTML (unless no-viz) + if (!options.noViz) { + onUpdate?.("Generating interactive HTML visualization..."); + await exec( + `${python} -c " +import json +from graphify.build import build_from_json +from graphify.export import to_html +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) +labels_raw = json.loads(Path('.graphify_labels.json').read_text()) if Path('.graphify_labels.json').exists() else {} + +G = build_from_json(extraction) +communities = {int(k):v for k,v in analysis['communities'].items()} +labels = {int(k):v for k,v in labels_raw.items()} + +if G.number_of_nodes() > 5000: + print(f'Graph has {G.number_of_nodes()} nodes - too large for HTML viz.') +else: + to_html(G, communities, 'graphify-out/graph.html', community_labels=labels or None) + print('graph.html written') +"`, + { cwd, signal }, + ); + } + + // Optional exports + if (options.obsidian) { + onUpdate?.("Generating Obsidian vault..."); + await exec( + `${python} -c " +import json +from graphify.build import build_from_json +from graphify.export import to_obsidian, to_canvas +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) +labels_raw = json.loads(Path('.graphify_labels.json').read_text()) if Path('.graphify_labels.json').exists() else {} + +G = build_from_json(extraction) +communities = {int(k):v for k,v in analysis['communities'].items()} +cohesion = {int(k):v for k,v in analysis['cohesion'].items()} +labels = {int(k):v for k,v in labels_raw.items()} + +n = to_obsidian(G, communities, 'graphify-out/obsidian', community_labels=labels or None, cohesion=cohesion) +print(f'Obsidian vault: {n} notes') +to_canvas(G, communities, 'graphify-out/obsidian/graph.canvas', community_labels=labels or None) +"`, + { cwd, signal }, + ); + } + + if (options.svg) { + await exec( + `${python} -c " +import json +from graphify.build import build_from_json +from graphify.export import to_svg +from pathlib import Path +extraction = json.loads(Path('.graphify_extract.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) +labels_raw = json.loads(Path('.graphify_labels.json').read_text()) if Path('.graphify_labels.json').exists() else {} +G = build_from_json(extraction) +communities = {int(k):v for k,v in analysis['communities'].items()} +labels = {int(k):v for k,v in labels_raw.items()} +to_svg(G, communities, 'graphify-out/graph.svg', community_labels=labels or None) +print('graph.svg written') +"`, + { cwd, signal }, + ); + } + + if (options.graphml) { + await exec( + `${python} -c " +import json +from graphify.build import build_from_json +from graphify.export import to_graphml +from pathlib import Path +extraction = json.loads(Path('.graphify_extract.json').read_text()) +analysis = json.loads(Path('.graphify_analysis.json').read_text()) +G = build_from_json(extraction) +communities = {int(k):v for k,v in analysis['communities'].items()} +to_graphml(G, communities, 'graphify-out/graph.graphml') +print('graph.graphml written') +"`, + { cwd, signal }, + ); + } + + if (options.neo4j) { + await exec( + `${python} -c " +import json +from graphify.build import build_from_json +from graphify.export import to_cypher +from pathlib import Path +G = build_from_json(json.loads(Path('.graphify_extract.json').read_text())) +to_cypher(G, 'graphify-out/cypher.txt') +print('cypher.txt written') +"`, + { cwd, signal }, + ); + } + + // Save manifest and cost + await exec( + `${python} -c " +import json +from pathlib import Path +from datetime import datetime, timezone +from graphify.detect import save_manifest + +detect = json.loads(Path('.graphify_detect.json').read_text()) if Path('.graphify_detect.json').exists() else {} +if detect: save_manifest(detect.get('files', {})) + +extract = json.loads(Path('.graphify_extract.json').read_text()) if Path('.graphify_extract.json').exists() else {} +input_tok = extract.get('input_tokens', 0) +output_tok = extract.get('output_tokens', 0) + +cost_path = Path('graphify-out/cost.json') +if cost_path.exists(): + cost = json.loads(cost_path.read_text()) +else: + cost = {'runs': [], 'total_input_tokens': 0, 'total_output_tokens': 0} + +cost['runs'].append({'date': datetime.now(timezone.utc).isoformat(), 'input_tokens': input_tok, 'output_tokens': output_tok, 'files': detect.get('total_files',0)}) +cost['total_input_tokens'] += input_tok +cost['total_output_tokens'] += output_tok +cost_path.write_text(json.dumps(cost, indent=2)) +"`, + { cwd, signal }, + ); + + // Clean up temp files + await exec( + `rm -f .graphify_detect.json .graphify_extract.json .graphify_ast.json .graphify_semantic.json .graphify_analysis.json .graphify_labels.json`, + { cwd, signal }, + ); + + const match = buildResult.stdout.match(/Graph: (\d+) nodes, (\d+) edges, (\d+) communities/); + return { + nodes: match ? Number.parseInt(match[1], 10) : 0, + edges: match ? Number.parseInt(match[2], 10) : 0, + communities: match ? Number.parseInt(match[3], 10) : 0, + }; +} + +// --------------------------------------------------------------------------- +// Query +// --------------------------------------------------------------------------- + +export interface QueryOptions { + question: string; + mode: "bfs" | "dfs"; + budget?: number; +} + +export async function queryGraph( + exec: ExecFn, + python: string, + cwd: string, + options: QueryOptions, + signal?: AbortSignal, +): Promise { + const { question, mode, budget = 2000 } = options; + const escapedQuestion = escapeShell(question); + + const result = await exec( + `${python} -c " +import json, sys +from networkx.readwrite import json_graph +from pathlib import Path + +if not Path('graphify-out/graph.json').exists(): + print('ERROR: No graph found. Build one first.') + sys.exit(1) + +data = json.loads(Path('graphify-out/graph.json').read_text()) +G = json_graph.node_link_graph(data, edges='links') + +question = '${escapedQuestion}' +mode = '${mode}' +terms = [t.lower() for t in question.split() if len(t) > 3] + +scored = [] +for nid, ndata in G.nodes(data=True): + label = ndata.get('label', '').lower() + score = sum(1 for t in terms if t in label) + if score > 0: scored.append((score, nid)) +scored.sort(reverse=True) +start_nodes = [nid for _, nid in scored[:3]] + +if not start_nodes: + print('No matching nodes found for: ' + ' '.join(terms)) + sys.exit(0) + +subgraph_nodes = set() +subgraph_edges = [] + +if mode == 'dfs': + visited = set() + stack = [(n, 0) for n in reversed(start_nodes)] + while stack: + node, depth = stack.pop() + if node in visited or depth > 6: continue + visited.add(node) + subgraph_nodes.add(node) + for neighbor in G.neighbors(node): + if neighbor not in visited: + stack.append((neighbor, depth + 1)) + subgraph_edges.append((node, neighbor)) +else: + frontier = set(start_nodes) + subgraph_nodes = set(start_nodes) + for _ in range(3): + next_frontier = set() + for n in frontier: + for neighbor in G.neighbors(n): + if neighbor not in subgraph_nodes: + next_frontier.add(neighbor) + subgraph_edges.append((n, neighbor)) + subgraph_nodes.update(next_frontier) + frontier = next_frontier + +def relevance(nid): + label = G.nodes[nid].get('label', '').lower() + return sum(1 for t in terms if t in label) + +ranked_nodes = sorted(subgraph_nodes, key=relevance, reverse=True) + +lines = [f'Traversal: {mode.upper()} | Start: {[G.nodes[n].get('label', n) for n in start_nodes]} | {len(subgraph_nodes)} nodes'] +for nid in ranked_nodes: + d = G.nodes[nid] + lines.append(f' NODE {d.get('label', nid)} [src={d.get('source_file', '')}]') +for u, v in subgraph_edges: + if u in subgraph_nodes and v in subgraph_nodes: + d = G.edges[u, v] + lines.append(f' EDGE {G.nodes[u].get('label', u)} --{d.get('relation', '')} [{d.get('confidence', '')}]--> {G.nodes[v].get('label', v)}') + +output = chr(10).join(lines) +if len(output) > ${budget * 4}: + output = output[:${budget * 4}] + f'\\n... (truncated)' +print(output) +"`, + { cwd, signal }, + ); + + if (result.exitCode !== 0) { + throw new Error(`Query failed: ${result.stderr || result.stdout}`); + } + + return result.stdout.trim(); +} + +// --------------------------------------------------------------------------- +// Path (shortest path between two concepts) +// --------------------------------------------------------------------------- + +export async function findPath( + exec: ExecFn, + python: string, + cwd: string, + fromConcept: string, + toConcept: string, + signal?: AbortSignal, +): Promise { + const escapedFrom = JSON.stringify(fromConcept).replace(/'/g, "'\\''"); + const escapedTo = JSON.stringify(toConcept).replace(/'/g, "'\\''"); + + const result = await exec( + `${python} -c " +import json, sys +import networkx as nx +from networkx.readwrite import json_graph +from pathlib import Path + +if not Path('graphify-out/graph.json').exists(): + print('ERROR: No graph found. Build one first.') + sys.exit(1) + +data = json.loads(Path('graphify-out/graph.json').read_text()) +G = json_graph.node_link_graph(data, edges='links') + +a_term = '${escapedFrom}' +b_term = '${escapedTo}' + +def find_node(term): + term = term.lower() + scored = sorted([(sum(1 for w in term.split() if w in G.nodes[n].get('label','').lower()), n) for n in G.nodes()], reverse=True) + return scored[0][1] if scored and scored[0][0] > 0 else None + +src = find_node(a_term) +tgt = find_node(b_term) + +if not src or not tgt: + print(f'Could not find nodes matching: {a_term!r} or {b_term!r}') + sys.exit(0) + +try: + path = nx.shortest_path(G, src, tgt) + print(f'Shortest path ({len(path)-1} hops):') + for i, nid in enumerate(path): + label = G.nodes[nid].get('label', nid) + if i < len(path) - 1: + edge = G.edges[nid, path[i+1]] + rel = edge.get('relation', '') + conf = edge.get('confidence', '') + print(f' {label} --{rel}--> [{conf}]') + else: + print(f' {label}') +except nx.NetworkXNoPath: + print(f'No path found between {a_term!r} and {b_term!r}') +"`, + { cwd, signal }, + ); + + if (result.exitCode !== 0 && !result.stdout.trim()) { + throw new Error(`Path search failed: ${result.stderr}`); + } + + return result.stdout.trim(); +} + +// --------------------------------------------------------------------------- +// Explain (explain a single node) +// --------------------------------------------------------------------------- + +export async function explainNode( + exec: ExecFn, + python: string, + cwd: string, + concept: string, + signal?: AbortSignal, +): Promise { + const escapedConcept = escapeShell(concept); + + const result = await exec( + `${python} -c " +import json, sys +from networkx.readwrite import json_graph +from pathlib import Path + +if not Path('graphify-out/graph.json').exists(): + print('ERROR: No graph found. Build one first.') + sys.exit(1) + +data = json.loads(Path('graphify-out/graph.json').read_text()) +G = json_graph.node_link_graph(data, edges='links') + +term = '${escapedConcept}' +term_lower = term.lower() + +scored = sorted([(sum(1 for w in term_lower.split() if w in G.nodes[n].get('label','').lower()), n) for n in G.nodes()], reverse=True) +if not scored or scored[0][0] == 0: + print(f'No node matching {term!r}') + sys.exit(0) + +nid = scored[0][1] +d = G.nodes[nid] +print(f'NODE: {d.get('label', nid)}') +print(f' source: {d.get('source_file', 'unknown')}') +print(f' type: {d.get('file_type', 'unknown')}') +print(f' degree: {G.degree(nid)}') +print() +print('CONNECTIONS:') +for neighbor in G.neighbors(nid): + edge = G.edges[nid, neighbor] + nlabel = G.nodes[neighbor].get('label', neighbor) + rel = edge.get('relation', '') + conf = edge.get('confidence', '') + src_file = G.nodes[neighbor].get('source_file', '') + print(f' --{rel}--> {nlabel} [{conf}] ({src_file})') +"`, + { cwd, signal }, + ); + + if (result.exitCode !== 0 && !result.stdout.trim()) { + throw new Error(`Explain failed: ${result.stderr}`); + } + + return result.stdout.trim(); +} + +// --------------------------------------------------------------------------- +// Add URL +// --------------------------------------------------------------------------- + +export interface AddOptions { + url: string; + author?: string; + contributor?: string; +} + +export async function addUrl( + exec: ExecFn, + python: string, + cwd: string, + options: AddOptions, + signal?: AbortSignal, +): Promise { + const { url, author, contributor } = options; + const escapedUrl = escapeShell(url); + const authorKwarg = author ? `, author='${escapeShell(author)}'` : ""; + const contributorKwarg = contributor ? `, contributor='${escapeShell(contributor)}'` : ""; + + const result = await exec( + `${python} -c " +import sys +from graphify.ingest import ingest +from pathlib import Path + +try: + out = ingest('${escapedUrl}', Path('./raw')${authorKwarg}${contributorKwarg}) + print(f'Saved to {out}') +except ValueError as e: + print(f'error: {e}', file=sys.stderr) + sys.exit(1) +except RuntimeError as e: + print(f'error: {e}', file=sys.stderr) + sys.exit(1) +"`, + { cwd, signal }, + ); + + if (result.exitCode !== 0) { + throw new Error(`Failed to add URL: ${result.stderr || result.stdout}`); + } + + return result.stdout.trim(); +} + +// --------------------------------------------------------------------------- +// Update (incremental re-extraction) +// --------------------------------------------------------------------------- + +export async function updateGraph( + exec: ExecFn, + python: string, + cwd: string, + inputPath: string, + signal?: AbortSignal, + onUpdate?: (message: string) => void, +): Promise<{ newFiles: number; nodes: number; edges: number }> { + onUpdate?.("Checking for changes..."); + + const escapedPath = escapeShell(inputPath); + const result = await exec( + `${python} -c " +import json +from graphify.detect import detect_incremental +from pathlib import Path + +r = detect_incremental(Path('${escapedPath}')) +print(json.dumps(r, indent=2)) +"`, + { cwd, signal }, + ); + + if (result.exitCode !== 0) { + throw new Error(`Incremental detect failed: ${result.stderr}`); + } + + const incremental = JSON.parse(result.stdout.trim()) as { + new_total: number; + new_files?: Record; + }; + + if (incremental.new_total === 0) { + return { newFiles: 0, nodes: 0, edges: 0 }; + } + + onUpdate?.(`${incremental.new_total} files changed — re-extracting...`); + + const buildResult = await buildGraph(exec, python, cwd, { inputPath }, signal, onUpdate); + + return { + newFiles: incremental.new_total, + nodes: buildResult.nodes, + edges: buildResult.edges, + }; +} + +// --------------------------------------------------------------------------- +// Watch (file watcher) +// --------------------------------------------------------------------------- + +export async function startWatch( + exec: ExecFn, + python: string, + cwd: string, + inputPath: string, + debounce: number = 3, + signal?: AbortSignal, + onUpdate?: (message: string) => void, +): Promise { + onUpdate?.(`Watching ${inputPath} for changes...`); + const escapedPath = escapeShell(inputPath); + const result = await exec(`${python} -m graphify.watch '${escapedPath}' --debounce ${debounce}`, { + cwd, + signal, + }); + return result.stdout.trim() || result.stderr.trim(); +} + +// --------------------------------------------------------------------------- +// Cluster-only (rerun clustering) +// --------------------------------------------------------------------------- + +export async function clusterOnly( + exec: ExecFn, + python: string, + cwd: string, + signal?: AbortSignal, +): Promise<{ communities: number }> { + const result = await exec( + `${python} -c " +import json, sys +from graphify.cluster import cluster, score_all +from graphify.report import generate +from graphify.export import to_json +from networkx.readwrite import json_graph +from pathlib import Path + +data = json.loads(Path('graphify-out/graph.json').read_text()) +import networkx as nx +G = json_graph.node_link_graph(data, edges='links') +communities = cluster(G) +to_json(G, communities, 'graphify-out/graph.json') +print(f'Re-clustered: {len(communities)} communities') +"`, + { cwd, signal }, + ); + + if (result.exitCode !== 0) { + throw new Error(`Cluster-only failed: ${result.stderr || result.stdout}`); + } + + const match = result.stdout.trim().match(/(\d+) communities/); + return { communities: match ? Number.parseInt(match[1], 10) : 0 }; +} + +// --------------------------------------------------------------------------- +// Tree HTML (collapsible tree visualization) +// --------------------------------------------------------------------------- + +export async function generateTree( + exec: ExecFn, + python: string, + cwd: string, + options?: { graphPath?: string; outputPath?: string; root?: string; label?: string }, + signal?: AbortSignal, +): Promise { + const graphPath = options?.graphPath ?? "graphify-out/graph.json"; + const outputPath = options?.outputPath ?? "graphify-out/GRAPH_TREE.html"; + let cmd = `${python} -m graphify tree --graph '${escapeShell(graphPath)}' --output '${escapeShell(outputPath)}'`; + if (options?.root) cmd += ` --root '${escapeShell(options.root)}'`; + if (options?.label) cmd += ` --label '${escapeShell(options.label)}'`; + + const result = await exec(cmd, { cwd, signal }); + if (result.exitCode !== 0) { + throw new Error(`Tree generation failed: ${result.stderr}`); + } + return outputPath; +} + +// --------------------------------------------------------------------------- +// Git hooks (install/uninstall/status) +// --------------------------------------------------------------------------- + +export async function hookAction( + exec: ExecFn, + python: string, + cwd: string, + action: "install" | "uninstall" | "status", + signal?: AbortSignal, +): Promise { + const result = await exec(`${python} -m graphify hook ${action}`, { cwd, signal }); + if (result.exitCode !== 0 && action !== "status") { + throw new Error(`Hook ${action} failed: ${result.stderr}`); + } + return result.stdout.trim() || result.stderr.trim(); +} + +// --------------------------------------------------------------------------- +// Neo4j push +// --------------------------------------------------------------------------- + +export async function pushNeo4j( + exec: ExecFn, + python: string, + cwd: string, + uri: string, + user: string, + credentials: string, + signal?: AbortSignal, +): Promise { + const result = await exec( + `NEO4J_URI='${escapeShell(uri)}' NEO4J_USER='${escapeShell(user)}' NEO4J_CREDS='${escapeShell(credentials)}' ${python} -c " +import json, os +from graphify.build import build_from_json +from graphify.export import push_to_neo4j +from pathlib import Path + +extraction = json.loads(Path('.graphify_extract.json').read_text()) if Path('.graphify_extract.json').exists() else None +if extraction: + G = build_from_json(extraction) +else: + from networkx.readwrite import json_graph; import networkx as nx + data = json.loads(Path('graphify-out/graph.json').read_text()) + G = json_graph.node_link_graph(data, edges='links') + +r = push_to_neo4j(G, uri=os.environ['NEO4J_URI'], user=os.environ['NEO4J_USER'], credentials=os.environ['NEO4J_CREDS']) +print(f'Pushed to Neo4j: {r['nodes']} nodes, {r['edges']} edges') +"`, + { cwd, signal }, + ); + if (result.exitCode !== 0) { + throw new Error(`Neo4j push failed: ${result.stderr || result.stdout}`); + } + return result.stdout.trim(); +} + +// --------------------------------------------------------------------------- +// Save result (feedback loop) +// --------------------------------------------------------------------------- + +export async function saveResult( + exec: ExecFn, + python: string, + cwd: string, + options: { question: string; answer: string; type: string; nodes: string[] }, + signal?: AbortSignal, +): Promise { + const q = escapeShell(options.question); + const a = escapeShell(options.answer); + const t = escapeShell(options.type); + const nodes = options.nodes.map((n) => `'${escapeShell(n)}'`).join(" "); + + const result = await exec( + `${python} -m graphify save-result --question '${q}' --answer '${a}' --type '${t}' --nodes ${nodes}`, + { cwd, signal }, + ); + return result.stdout.trim() || result.stderr.trim(); +} + +// --------------------------------------------------------------------------- +// Clone repo +// --------------------------------------------------------------------------- + +export async function cloneRepo( + exec: ExecFn, + python: string, + cwd: string, + githubUrl: string, + signal?: AbortSignal, +): Promise { + const result = await exec(`${python} -m graphify clone '${escapeShell(githubUrl)}'`, { + cwd, + signal, + }); + if (result.exitCode !== 0) { + throw new Error(`Clone failed: ${result.stderr}`); + } + return result.stdout.trim(); +} + +// --------------------------------------------------------------------------- +// Merge graphs +// --------------------------------------------------------------------------- + +export async function mergeGraphs( + exec: ExecFn, + python: string, + cwd: string, + graphs: string[], + outPath?: string, + signal?: AbortSignal, +): Promise { + const graphArgs = graphs.map((g) => `'${escapeShell(g)}'`).join(" "); + let cmd = `${python} -m graphify merge-graphs ${graphArgs}`; + if (outPath) cmd += ` --out '${escapeShell(outPath)}'`; + + const result = await exec(cmd, { cwd, signal }); + if (result.exitCode !== 0) { + throw new Error(`Merge failed: ${result.stderr}`); + } + return result.stdout.trim(); +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +export async function ensureGraphifyGitignore(cwd: string): Promise<{ updated: boolean }> { + const path = join(cwd, ".gitignore"); + let original = ""; + + try { + original = await readFile(path, "utf-8"); + } catch { + original = ""; + } + + const lines = original.length > 0 ? original.split(/\r?\n/) : []; + const filtered = lines.filter((line) => { + const trimmed = line.trim(); + return !GRAPHIFY_GITIGNORE_LEGACY.includes( + trimmed as (typeof GRAPHIFY_GITIGNORE_LEGACY)[number], + ); + }); + + for (const entry of GRAPHIFY_GITIGNORE_REQUIRED) { + if (!filtered.some((line) => line.trim() === entry)) { + filtered.push(entry); + } + } + + let next = filtered.join("\n"); + if (next.length > 0 && !next.endsWith("\n")) { + next += "\n"; + } + + if (next === original) { + return { updated: false }; + } + + await writeFile(path, next, "utf-8"); + return { updated: true }; +} + +function escapeShell(str: string): string { + return str.replace(/'/g, "'\\''"); +} diff --git a/extensions/pi-graphify/src/statusbar.ts b/extensions/pi-graphify/src/statusbar.ts new file mode 100644 index 0000000..4c68166 --- /dev/null +++ b/extensions/pi-graphify/src/statusbar.ts @@ -0,0 +1,242 @@ +import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; +import type { ResolvedConfig } from "./config.js"; + +const MODULE_ID = "pi-graphify"; + +// ── Config Interface ─────────────────────────────────────────────────────── + +export interface StatusbarConfig { + enabled?: boolean; + placement?: { + line?: number; // 1-based line number + side?: "left" | "right"; + index?: number; // Position within side (0 = first) + }; + show_icon?: boolean; + show_text?: boolean; + icon?: string; // Hex code + icon_color?: string; // "accent", "dim", "success", "warning", "error" + icon_color_uninitialized?: string; + text_font_color?: string; + text_font_color_uninitialized?: string; + separator_before?: { icon?: string; text?: string; icon_color?: string }; + separator_after?: { icon?: string; text?: string; icon_color?: string }; + auto_heal?: { + enabled?: boolean; + max_retries?: number; + retry_interval_ms?: number; + }; +} + +// ── State ────────────────────────────────────────────────────────────────── + +export interface StatusbarState { + consecutiveFailures: number; + lastErrorAt: number; + healedAt: number; + isDegraded: boolean; +} + +function makeStatusbarState(): StatusbarState { + return { consecutiveFailures: 0, lastErrorAt: 0, healedAt: 0, isDegraded: false }; +} + +// ── Helpers ──────────────────────────────────────────────────────────────── + +function emitStatusbarEvent( + pi: ExtensionAPI, + event: string, + data: Record, +): boolean { + try { + pi.events.emit(event, data); + return true; + } catch { + return false; + } +} + +function getDefaultPlacement(): NonNullable { + return { line: 2, side: "left", index: 4 }; +} + +function resolveStatusbarConfig(config: ResolvedConfig): StatusbarConfig { + const sb = config.statusbar ?? {}; + return { + enabled: sb.enabled !== false, + placement: { ...getDefaultPlacement(), ...sb.placement }, + show_icon: sb.show_icon !== false, + show_text: sb.show_text !== false, + icon: sb.icon ?? "f035b", + icon_color: sb.icon_color ?? "accent", + icon_color_uninitialized: sb.icon_color_uninitialized ?? "dim", + text_font_color: sb.text_font_color ?? "dim", + text_font_color_uninitialized: sb.text_font_color_uninitialized ?? "dim", + separator_before: sb.separator_before, + separator_after: sb.separator_after, + auto_heal: { + enabled: sb.auto_heal?.enabled !== false, + max_retries: sb.auto_heal?.max_retries ?? 3, + retry_interval_ms: sb.auto_heal?.retry_interval_ms ?? 5000, + }, + }; +} + +function shouldAttemptUpdate( + state: StatusbarState, + autoHeal: StatusbarConfig["auto_heal"], +): boolean { + if (!state.isDegraded) return true; + if (!autoHeal?.enabled) return false; + + const now = Date.now(); + const retryInterval = autoHeal.retry_interval_ms ?? 5000; + const maxRetries = autoHeal.max_retries ?? 3; + + if (state.consecutiveFailures >= maxRetries) { + return now - state.lastErrorAt > retryInterval * 2; + } + + return now - state.lastErrorAt > retryInterval; +} + +// ── Public API ───────────────────────────────────────────────────────────── + +export function registerGraphifyStatusbar(pi: ExtensionAPI, config: ResolvedConfig): void { + const sbConfig = resolveStatusbarConfig(config); + if (!sbConfig.enabled) return; + + const contributePayload: Record = { + id: MODULE_ID, + label: "Graphify", + description: "Knowledge graph status", + default_placement: sbConfig.placement, + default_style: { + show_icon: sbConfig.show_icon, + icon: sbConfig.icon, + icon_color: sbConfig.icon_color, + show_text: sbConfig.show_text, + text_font_color: sbConfig.text_font_color, + text_font_caps: "small", + text_font_style: "regular", + }, + priority: 0, + }; + if (sbConfig.separator_before) contributePayload.separator_before = sbConfig.separator_before; + if (sbConfig.separator_after) contributePayload.separator_after = sbConfig.separator_after; + + emitStatusbarEvent(pi, "statusbar:widget:contribute", contributePayload); + + emitStatusbarEvent(pi, "statusbar:module:register", { + id: MODULE_ID, + text: "initializing...", + visible: true, + placement: sbConfig.placement, + style: { + show_icon: sbConfig.show_icon, + icon: sbConfig.icon, + icon_color: sbConfig.icon_color, + show_text: sbConfig.show_text, + text_font_color: sbConfig.text_font_color, + text_font_caps: "small", + text_font_style: "regular", + }, + }); +} + +export function unregisterGraphifyStatusbar(pi: ExtensionAPI): void { + emitStatusbarEvent(pi, "statusbar:module:unregister", { id: MODULE_ID }); +} + +export async function updateGraphifyStatusbar( + pi: ExtensionAPI, + config: ResolvedConfig, + ctx: ExtensionContext, + state: StatusbarState, +): Promise { + const sbConfig = resolveStatusbarConfig(config); + if (!sbConfig.enabled) return; + if (!shouldAttemptUpdate(state, sbConfig.auto_heal)) return; + + try { + const checkResult = await pi.exec( + "sh", + ["-c", "test -f graphify-out/graph.json && echo 'yes' || echo 'no'"], + { cwd: ctx.cwd }, + ); + + let text: string; + let iconColor = sbConfig.icon_color ?? "accent"; + let textColor = sbConfig.text_font_color ?? "dim"; + + if (checkResult.stdout.trim() === "yes") { + const statsResult = await pi.exec( + "sh", + [ + "-c", + `python3 -c " +import json +from pathlib import Path +try: + data = json.loads(Path('graphify-out/graph.json').read_text()) + nodes = len(data.get('nodes', [])) + edges = len(data.get('links', [])) + print(f'{nodes}n {edges}e') +except Exception: + print('graph ready') +" 2>/dev/null || echo "graph ready"`, + ], + { cwd: ctx.cwd }, + ); + text = statsResult.stdout.trim() || "graph ready"; + } else { + text = "no graph"; + iconColor = sbConfig.icon_color_uninitialized ?? "dim"; + textColor = sbConfig.text_font_color_uninitialized ?? "dim"; + } + + emitStatusbarEvent(pi, "statusbar:module:register", { + id: MODULE_ID, + text, + visible: true, + placement: sbConfig.placement, + style: { + show_icon: sbConfig.show_icon, + icon: sbConfig.icon, + icon_color: iconColor, + show_text: sbConfig.show_text, + text_font_color: textColor, + text_font_caps: "small", + text_font_style: "regular", + }, + }); + + if (state.isDegraded) { + state.healedAt = Date.now(); + } + state.consecutiveFailures = 0; + state.isDegraded = false; + } catch (_error) { + state.consecutiveFailures += 1; + state.lastErrorAt = Date.now(); + state.isDegraded = true; + + emitStatusbarEvent(pi, "statusbar:module:register", { + id: MODULE_ID, + text: "error", + visible: true, + placement: sbConfig.placement, + style: { + show_icon: sbConfig.show_icon, + icon: sbConfig.icon, + icon_color: "error", + show_text: sbConfig.show_text, + text_font_color: "error", + text_font_caps: "small", + text_font_style: "regular", + }, + }); + } +} + +export { makeStatusbarState as createStatusbarState }; diff --git a/extensions/pi-graphify/src/tools/graphify-tools.ts b/extensions/pi-graphify/src/tools/graphify-tools.ts new file mode 100644 index 0000000..9c08bde --- /dev/null +++ b/extensions/pi-graphify/src/tools/graphify-tools.ts @@ -0,0 +1,965 @@ +import type { + AgentToolResult, + AgentToolUpdateCallback, + ExtensionAPI, + ExtensionContext, + Theme, + ToolRenderResultOptions, +} from "@earendil-works/pi-coding-agent"; +import { defineTool, truncateHead } from "@earendil-works/pi-coding-agent"; +import { Text } from "@earendil-works/pi-tui"; +import { ToolBody, ToolCallHeader, ToolFooter } from "@gaodes/pi-utils-ui"; +import { type Static, Type } from "typebox"; +import type { ResolvedConfig } from "../config"; +import type { ExecFn } from "../lib/runner"; +import { + addUrl, + buildGraph, + clusterOnly, + detectPython, + ensureInstalled, + explainNode, + findPath, + queryGraph, + startWatch, + updateGraph, +} from "../lib/runner"; + +// --------------------------------------------------------------------------- +// Shared exec adapter — wraps pi.exec(command, args[], opts) → ExecFn +// --------------------------------------------------------------------------- + +function createExec(pi: ExtensionAPI, cwd: string): ExecFn { + return async (command, options) => { + const result = await pi.exec("sh", ["-c", command], { + cwd: options?.cwd ?? cwd, + signal: options?.signal, + }); + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: result.code, + }; + }; +} + +// --------------------------------------------------------------------------- +// graphify_build +// --------------------------------------------------------------------------- + +const buildParameters = Type.Object({ + path: Type.String({ description: "Directory path to build graph from" }), + mode: Type.Optional( + Type.Union([Type.Literal("standard"), Type.Literal("deep")], { + description: "Extraction mode: 'deep' for more aggressive relationship inference", + }), + ), + no_viz: Type.Optional(Type.Boolean({ description: "Skip HTML visualization" })), + obsidian: Type.Optional(Type.Boolean({ description: "Generate Obsidian vault" })), + svg: Type.Optional(Type.Boolean({ description: "Export graph.svg" })), + graphml: Type.Optional(Type.Boolean({ description: "Export graph.graphml" })), + neo4j: Type.Optional(Type.Boolean({ description: "Generate cypher.txt for Neo4j" })), +}); + +type BuildParams = Static; + +interface BuildDetails { + path: string; + nodes: number; + edges: number; + communities: number; + outputDir: string; +} + +export function createBuildTool(pi: ExtensionAPI, config: ResolvedConfig) { + return defineTool({ + name: "graphify_build", + label: "Graphify Build", + description: + "Build a knowledge graph from a directory. Runs the full pipeline: file detection, entity/relationship extraction, community detection, and output generation (HTML, JSON, report).", + parameters: buildParameters, + promptSnippet: "Use graphify_build to create a knowledge graph from any directory of files.", + promptGuidelines: [ + "Call graphify_build before graphify_query, graphify_path, or graphify_explain — those tools require an existing graph.", + "Provide the exact directory path. Use '.' for the current directory.", + ], + + async execute( + _toolCallId: string, + params: BuildParams, + signal: AbortSignal, + onUpdate: AgentToolUpdateCallback | undefined, + ctx: ExtensionContext, + ): Promise> { + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd, signal); + await ensureInstalled(exec, python, ctx.cwd, signal); + + const result = await buildGraph( + exec, + python, + ctx.cwd, + { + inputPath: params.path, + mode: params.mode ?? "standard", + noViz: params.no_viz, + obsidian: params.obsidian, + svg: params.svg, + graphml: params.graphml, + neo4j: params.neo4j, + }, + signal, + (msg) => + onUpdate?.({ + content: [{ type: "text", text: msg }], + details: {} as BuildDetails, + }), + ); + + return { + content: [ + { + type: "text", + text: `Graph built: ${result.nodes} nodes, ${result.edges} edges, ${result.communities} communities. Output in ${config.outputDir}/`, + }, + ], + details: { + path: params.path, + nodes: result.nodes, + edges: result.edges, + communities: result.communities, + outputDir: config.outputDir, + }, + }; + }, + + renderCall(params: BuildParams, theme: Theme) { + const optionArgs: Array<{ label: string; value: string }> = []; + if (params.mode === "deep") optionArgs.push({ label: "mode", value: "deep" }); + if (params.no_viz) optionArgs.push({ label: "no-viz", value: "true" }); + if (params.obsidian) optionArgs.push({ label: "obsidian", value: "true" }); + if (params.svg) optionArgs.push({ label: "svg", value: "true" }); + if (params.graphml) optionArgs.push({ label: "graphml", value: "true" }); + if (params.neo4j) optionArgs.push({ label: "neo4j", value: "true" }); + return new ToolCallHeader( + { + toolName: "Graphify", + action: "build", + mainArg: params.path, + optionArgs, + showColon: true, + }, + theme, + ); + }, + + renderResult( + result: AgentToolResult, + options: ToolRenderResultOptions, + theme: Theme, + ) { + if (options.isPartial) { + return new Text(theme.fg("muted", "Graphify: building graph..."), 0, 0); + } + + const details = result.details as BuildDetails | undefined; + if (!details?.nodes) { + const textBlock = result.content.find((c) => c.type === "text"); + const errorMsg = (textBlock?.type === "text" && textBlock.text) || "Build failed"; + return new Text(theme.fg("error", errorMsg), 0, 0); + } + + return new ToolBody( + { + fields: [ + { + label: "Graph", + value: `${details.nodes} nodes | ${details.edges} edges | ${details.communities} communities`, + showCollapsed: false, + }, + { label: "Path", value: details.path, showCollapsed: true }, + { label: "Output", value: details.outputDir, showCollapsed: true }, + ], + footer: new ToolFooter(theme, { + items: [{ label: "status", value: "complete" }], + separator: " | ", + }), + includeSpacerBeforeFooter: true, + }, + options, + theme, + ); + }, + }); +} + +// --------------------------------------------------------------------------- +// graphify_query +// --------------------------------------------------------------------------- + +const queryParameters = Type.Object({ + question: Type.String({ description: "Natural language question to answer from the graph" }), + mode: Type.Optional( + Type.Union([Type.Literal("bfs"), Type.Literal("dfs")], { + description: "Traversal mode: bfs (broad context) or dfs (trace a specific path)", + }), + ), + budget: Type.Optional( + Type.Number({ description: "Token budget for the answer (default 2000)", default: 2000 }), + ), +}); + +type QueryParams = Static; + +interface QueryDetails { + question: string; + mode: string; + result: string; +} + +export function createQueryTool(pi: ExtensionAPI, config: ResolvedConfig) { + return defineTool({ + name: "graphify_query", + label: "Graphify Query", + description: + "Query the knowledge graph using BFS (broad context) or DFS (trace a path). Requires an existing graph built with graphify_build.", + parameters: queryParameters, + promptSnippet: + "Use graphify_query to answer questions about a codebase using its knowledge graph.", + promptGuidelines: [ + "Run graphify_build before graphify_query — the graph must exist first.", + "Use BFS mode for 'what is X connected to?' questions.", + "Use DFS mode for 'how does X reach Y?' questions.", + ], + + async execute( + _toolCallId: string, + params: QueryParams, + signal: AbortSignal, + _onUpdate: AgentToolUpdateCallback | undefined, + ctx: ExtensionContext, + ): Promise> { + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd, signal); + await ensureInstalled(exec, python, ctx.cwd, signal); + + const queryResult = await queryGraph( + exec, + python, + ctx.cwd, + { + question: params.question, + mode: params.mode ?? "bfs", + budget: params.budget, + }, + signal, + ); + + return { + content: [{ type: "text", text: queryResult }], + details: { + question: params.question, + mode: params.mode ?? "bfs", + result: queryResult, + }, + }; + }, + + renderCall(params: QueryParams, theme: Theme) { + return new ToolCallHeader( + { + toolName: "Graphify", + action: "query", + mainArg: params.question, + optionArgs: [ + ...(params.mode === "dfs" ? [{ label: "mode", value: "dfs" }] : []), + ...(params.budget ? [{ label: "budget", value: String(params.budget) }] : []), + ], + showColon: true, + }, + theme, + ); + }, + + renderResult( + result: AgentToolResult, + options: ToolRenderResultOptions, + theme: Theme, + ) { + if (options.isPartial) { + return new Text(theme.fg("muted", "Graphify: querying graph..."), 0, 0); + } + + const details = result.details as QueryDetails | undefined; + if (!details?.result) { + const textBlock = result.content.find((c) => c.type === "text"); + const errorMsg = (textBlock?.type === "text" && textBlock.text) || "Query failed"; + return new Text(theme.fg("error", errorMsg), 0, 0); + } + + const truncated = truncateHead(details.result, { + maxBytes: 50000, + maxLines: 2000, + }); + + return new ToolBody( + { + fields: [ + { label: "Mode", value: details.mode.toUpperCase(), showCollapsed: true }, + { label: "Question", value: details.question, showCollapsed: true }, + { label: "Result", value: truncated.content, showCollapsed: false }, + ], + footer: truncated.truncated + ? new ToolFooter(theme, { + items: [ + { + label: "lines", + value: `${truncated.outputLines}/${truncated.totalLines}`, + }, + ], + separator: " | ", + }) + : undefined, + includeSpacerBeforeFooter: true, + }, + options, + theme, + ); + }, + }); +} + +// --------------------------------------------------------------------------- +// graphify_path +// --------------------------------------------------------------------------- + +const pathParameters = Type.Object({ + from: Type.String({ description: "Starting concept name" }), + to: Type.String({ description: "Target concept name" }), +}); + +type PathParams = Static; + +interface PathDetails { + from: string; + to: string; + result: string; +} + +export function createPathTool(pi: ExtensionAPI, config: ResolvedConfig) { + return defineTool({ + name: "graphify_path", + label: "Graphify Path", + description: + "Find the shortest path between two concepts in the knowledge graph. Requires an existing graph.", + parameters: pathParameters, + promptSnippet: + "Use graphify_path to trace connections between two concepts in the knowledge graph.", + promptGuidelines: [ + "Both concepts must exist as nodes in the graph.", + "Use concept names that appear in graph node labels.", + ], + + async execute( + _toolCallId: string, + params: PathParams, + signal: AbortSignal, + _onUpdate: AgentToolUpdateCallback | undefined, + ctx: ExtensionContext, + ): Promise> { + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd, signal); + await ensureInstalled(exec, python, ctx.cwd, signal); + + const pathResult = await findPath(exec, python, ctx.cwd, params.from, params.to, signal); + + return { + content: [{ type: "text", text: pathResult }], + details: { from: params.from, to: params.to, result: pathResult }, + }; + }, + + renderCall(params: PathParams, theme: Theme) { + return new ToolCallHeader( + { + toolName: "Graphify", + action: "path", + mainArg: `${params.from} → ${params.to}`, + showColon: true, + }, + theme, + ); + }, + + renderResult( + result: AgentToolResult, + options: ToolRenderResultOptions, + theme: Theme, + ) { + if (options.isPartial) { + return new Text(theme.fg("muted", "Graphify: finding path..."), 0, 0); + } + + const details = result.details as PathDetails | undefined; + if (!details?.result) { + const textBlock = result.content.find((c) => c.type === "text"); + const errorMsg = (textBlock?.type === "text" && textBlock.text) || "Path not found"; + return new Text(theme.fg("error", errorMsg), 0, 0); + } + + return new ToolBody( + { + fields: [ + { + label: "Path", + value: `${details.from} → ${details.to}`, + showCollapsed: true, + }, + { label: "Result", value: details.result, showCollapsed: false }, + ], + }, + options, + theme, + ); + }, + }); +} + +// --------------------------------------------------------------------------- +// graphify_explain +// --------------------------------------------------------------------------- + +const explainParameters = Type.Object({ + concept: Type.String({ description: "Name of the concept/node to explain" }), +}); + +type ExplainParams = Static; + +interface ExplainDetails { + concept: string; + result: string; +} + +export function createExplainTool(pi: ExtensionAPI, config: ResolvedConfig) { + return defineTool({ + name: "graphify_explain", + label: "Graphify Explain", + description: + "Explain a concept from the knowledge graph — shows everything connected to it. Requires an existing graph.", + parameters: explainParameters, + promptSnippet: + "Use graphify_explain to get a plain-language explanation of a concept and all its connections in the graph.", + promptGuidelines: [ + "graphify_explain works best with concept names that appear as node labels in the graph.", + ], + + async execute( + _toolCallId: string, + params: ExplainParams, + signal: AbortSignal, + _onUpdate: AgentToolUpdateCallback | undefined, + ctx: ExtensionContext, + ): Promise> { + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd, signal); + await ensureInstalled(exec, python, ctx.cwd, signal); + + const explainResult = await explainNode(exec, python, ctx.cwd, params.concept, signal); + + return { + content: [{ type: "text", text: explainResult }], + details: { concept: params.concept, result: explainResult }, + }; + }, + + renderCall(params: ExplainParams, theme: Theme) { + return new ToolCallHeader( + { + toolName: "Graphify", + action: "explain", + mainArg: params.concept, + showColon: true, + }, + theme, + ); + }, + + renderResult( + result: AgentToolResult, + options: ToolRenderResultOptions, + theme: Theme, + ) { + if (options.isPartial) { + return new Text(theme.fg("muted", "Graphify: explaining node..."), 0, 0); + } + + const details = result.details as ExplainDetails | undefined; + if (!details?.result) { + const textBlock = result.content.find((c) => c.type === "text"); + const errorMsg = (textBlock?.type === "text" && textBlock.text) || "Explain failed"; + return new Text(theme.fg("error", errorMsg), 0, 0); + } + + return new ToolBody( + { + fields: [ + { label: "Concept", value: details.concept, showCollapsed: true }, + { label: "Details", value: details.result, showCollapsed: false }, + ], + }, + options, + theme, + ); + }, + }); +} + +// --------------------------------------------------------------------------- +// graphify_add +// --------------------------------------------------------------------------- + +const addParameters = Type.Object({ + url: Type.String({ description: "URL to fetch and add to the corpus" }), + author: Type.Optional(Type.String({ description: "Author of the content" })), + contributor: Type.Optional(Type.String({ description: "Who added this to the corpus" })), +}); + +type AddParams = Static; + +interface AddDetails { + url: string; + savedTo: string; +} + +export function createAddTool(pi: ExtensionAPI, config: ResolvedConfig) { + return defineTool({ + name: "graphify_add", + label: "Graphify Add", + description: + "Fetch a URL (paper, tweet, PDF, image, webpage) and add it to the corpus. Then update the graph.", + parameters: addParameters, + promptSnippet: "Use graphify_add to fetch a URL and incorporate it into the knowledge graph.", + promptGuidelines: [ + "graphify_add fetches the content and runs an incremental graph update automatically.", + "Supports arXiv papers, Twitter/X, PDFs, images, and general web pages.", + ], + + async execute( + _toolCallId: string, + params: AddParams, + signal: AbortSignal, + onUpdate: AgentToolUpdateCallback | undefined, + ctx: ExtensionContext, + ): Promise> { + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd, signal); + await ensureInstalled(exec, python, ctx.cwd, signal); + + onUpdate?.({ + content: [{ type: "text", text: `Fetching ${params.url}...` }], + details: {} as AddDetails, + }); + + const savedTo = await addUrl( + exec, + python, + ctx.cwd, + { + url: params.url, + author: params.author, + contributor: params.contributor, + }, + signal, + ); + + onUpdate?.({ + content: [{ type: "text", text: "Updating graph with new content..." }], + details: {} as AddDetails, + }); + + await updateGraph(exec, python, ctx.cwd, "./raw", signal); + + return { + content: [{ type: "text", text: `Added ${params.url} to corpus and updated graph.` }], + details: { url: params.url, savedTo }, + }; + }, + + renderCall(params: AddParams, theme: Theme) { + return new ToolCallHeader( + { + toolName: "Graphify", + action: "add", + mainArg: params.url, + optionArgs: [ + ...(params.author ? [{ label: "author", value: params.author }] : []), + ...(params.contributor ? [{ label: "contributor", value: params.contributor }] : []), + ], + showColon: true, + }, + theme, + ); + }, + + renderResult( + result: AgentToolResult, + options: ToolRenderResultOptions, + theme: Theme, + ) { + if (options.isPartial) { + return new Text(theme.fg("muted", "Graphify: adding URL..."), 0, 0); + } + + const details = result.details as AddDetails | undefined; + if (!details?.url) { + const textBlock = result.content.find((c) => c.type === "text"); + const errorMsg = (textBlock?.type === "text" && textBlock.text) || "Add failed"; + return new Text(theme.fg("error", errorMsg), 0, 0); + } + + return new ToolBody( + { + fields: [ + { label: "URL", value: details.url, showCollapsed: true }, + { label: "Saved", value: details.savedTo, showCollapsed: true }, + ], + footer: new ToolFooter(theme, { + items: [{ label: "graph", value: "updated" }], + separator: " | ", + }), + includeSpacerBeforeFooter: true, + }, + options, + theme, + ); + }, + }); +} + +// --------------------------------------------------------------------------- +// graphify_update +// --------------------------------------------------------------------------- + +const updateParameters = Type.Object({ + path: Type.String({ + description: "Directory path to update (re-extract changed files only)", + }), +}); + +type UpdateParams = Static; + +interface UpdateDetails { + path: string; + newFiles: number; + nodes: number; + edges: number; +} + +export function createUpdateTool(pi: ExtensionAPI, config: ResolvedConfig) { + return defineTool({ + name: "graphify_update", + label: "Graphify Update", + description: + "Incrementally update the knowledge graph — re-extract only new or changed files. Much faster than a full rebuild.", + parameters: updateParameters, + promptSnippet: + "Use graphify_update for incremental graph updates after files change, instead of rebuilding from scratch.", + promptGuidelines: [ + "graphify_update is cheaper and faster than graphify_build — it only processes changed files.", + "Run graphify_update after adding or modifying files in an already-graphed directory.", + ], + + async execute( + _toolCallId: string, + params: UpdateParams, + signal: AbortSignal, + onUpdate: AgentToolUpdateCallback | undefined, + ctx: ExtensionContext, + ): Promise> { + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd, signal); + await ensureInstalled(exec, python, ctx.cwd, signal); + + const updateResult = await updateGraph(exec, python, ctx.cwd, params.path, signal, (msg) => + onUpdate?.({ + content: [{ type: "text", text: msg }], + details: {} as UpdateDetails, + }), + ); + + if (updateResult.newFiles === 0) { + return { + content: [ + { + type: "text", + text: "No files changed since last build. Graph is up to date.", + }, + ], + details: { path: params.path, newFiles: 0, nodes: 0, edges: 0 }, + }; + } + + return { + content: [ + { + type: "text", + text: `Updated graph: ${updateResult.newFiles} files re-extracted. Graph now has ${updateResult.nodes} nodes and ${updateResult.edges} edges.`, + }, + ], + details: { + path: params.path, + newFiles: updateResult.newFiles, + nodes: updateResult.nodes, + edges: updateResult.edges, + }, + }; + }, + + renderCall(params: UpdateParams, theme: Theme) { + return new ToolCallHeader( + { + toolName: "Graphify", + action: "update", + mainArg: params.path, + showColon: true, + }, + theme, + ); + }, + + renderResult( + result: AgentToolResult, + options: ToolRenderResultOptions, + theme: Theme, + ) { + if (options.isPartial) { + return new Text(theme.fg("muted", "Graphify: updating graph..."), 0, 0); + } + + const details = result.details as UpdateDetails | undefined; + if (!details) { + const textBlock = result.content.find((c) => c.type === "text"); + const errorMsg = (textBlock?.type === "text" && textBlock.text) || "Update failed"; + return new Text(theme.fg("error", errorMsg), 0, 0); + } + + if (details.newFiles === 0) { + return new Text(theme.fg("muted", "Graph is up to date — no files changed."), 0, 0); + } + + return new ToolBody( + { + fields: [ + { + label: "Updated", + value: `${details.newFiles} files re-extracted`, + showCollapsed: false, + }, + { + label: "Graph", + value: `${details.nodes} nodes | ${details.edges} edges`, + showCollapsed: true, + }, + { label: "Path", value: details.path, showCollapsed: true }, + ], + footer: new ToolFooter(theme, { + items: [{ label: "status", value: "updated" }], + separator: " | ", + }), + includeSpacerBeforeFooter: true, + }, + options, + theme, + ); + }, + }); +} + +// --------------------------------------------------------------------------- +// graphify_watch +// --------------------------------------------------------------------------- + +const watchParameters = Type.Object({ + path: Type.String({ description: "Directory path to watch" }), + debounce: Type.Optional( + Type.Number({ + description: "Debounce seconds before triggering rebuild (default 3)", + default: 3, + }), + ), +}); + +type WatchParams = Static; + +interface WatchDetails { + path: string; + message: string; +} + +export function createWatchTool(pi: ExtensionAPI, config: ResolvedConfig) { + return defineTool({ + name: "graphify_watch", + label: "Graphify Watch", + description: + "Watch a directory for file changes and auto-rebuild the graph. Code changes trigger AST rebuild; doc changes flag for manual update.", + parameters: watchParameters, + promptSnippet: + "Use graphify_watch to start a file watcher that auto-updates the knowledge graph when code changes.", + promptGuidelines: [ + "graphify_watch runs in the foreground — use the process tool to run it in the background.", + "Code-only changes are rebuilt automatically. Doc/image changes require manual /graphify --update.", + ], + + async execute( + _toolCallId: string, + params: WatchParams, + signal: AbortSignal, + onUpdate: AgentToolUpdateCallback | undefined, + ctx: ExtensionContext, + ): Promise> { + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd, signal); + await ensureInstalled(exec, python, ctx.cwd, signal); + + const message = await startWatch( + exec, + python, + ctx.cwd, + params.path, + params.debounce ?? 3, + signal, + (msg) => + onUpdate?.({ content: [{ type: "text", text: msg }], details: {} as WatchDetails }), + ); + + return { + content: [{ type: "text", text: message }], + details: { path: params.path, message }, + }; + }, + + renderCall(params: WatchParams, theme: Theme) { + return new ToolCallHeader( + { + toolName: "Graphify", + action: "watch", + mainArg: params.path, + optionArgs: params.debounce + ? [{ label: "debounce", value: String(params.debounce) }] + : [], + showColon: true, + }, + theme, + ); + }, + + renderResult( + result: AgentToolResult, + options: ToolRenderResultOptions, + theme: Theme, + ) { + if (options.isPartial) { + return new Text(theme.fg("muted", "Graphify: watching for changes..."), 0, 0); + } + const details = result.details as WatchDetails | undefined; + if (!details?.message) { + return new Text(theme.fg("muted", "Watch ended."), 0, 0); + } + return new Text(theme.fg("muted", details.message), 0, 0); + }, + }); +} + +// --------------------------------------------------------------------------- +// graphify_cluster +// --------------------------------------------------------------------------- + +const clusterParameters = Type.Object({}); + +type ClusterParams = Static; + +interface ClusterDetails { + communities: number; +} + +export function createClusterTool(pi: ExtensionAPI, config: ResolvedConfig) { + return defineTool({ + name: "graphify_cluster", + label: "Graphify Cluster", + description: + "Re-run community detection on an existing graph.json and regenerate the report. No re-extraction needed.", + parameters: clusterParameters, + promptSnippet: + "Use graphify_cluster to re-cluster an existing graph without re-extracting files.", + promptGuidelines: [ + "graphify_cluster is cheap — it only reruns the clustering algorithm on existing data.", + "Requires an existing graphify-out/graph.json.", + ], + + async execute( + _toolCallId: string, + _params: ClusterParams, + signal: AbortSignal, + _onUpdate: AgentToolUpdateCallback | undefined, + ctx: ExtensionContext, + ): Promise> { + const exec = createExec(pi, ctx.cwd); + const python = await detectPython(exec, config.pythonPath, ctx.cwd, signal); + await ensureInstalled(exec, python, ctx.cwd, signal); + + const result = await clusterOnly(exec, python, ctx.cwd, signal); + + return { + content: [{ type: "text", text: `Re-clustered: ${result.communities} communities` }], + details: { communities: result.communities }, + }; + }, + + renderCall(_params: ClusterParams, theme: Theme) { + return new ToolCallHeader( + { toolName: "Graphify", action: "cluster", mainArg: "re-cluster", showColon: true }, + theme, + ); + }, + + renderResult( + result: AgentToolResult, + options: ToolRenderResultOptions, + theme: Theme, + ) { + if (options.isPartial) { + return new Text(theme.fg("muted", "Graphify: re-clustering..."), 0, 0); + } + const details = result.details as ClusterDetails | undefined; + if (!details?.communities) { + return new Text(theme.fg("error", "Cluster failed"), 0, 0); + } + return new ToolBody( + { + fields: [ + { label: "Communities", value: String(details.communities), showCollapsed: false }, + ], + }, + options, + theme, + ); + }, + }); +} + +// --------------------------------------------------------------------------- +// Re-export all creators +// --------------------------------------------------------------------------- + +export function createAllTools(pi: ExtensionAPI, config: ResolvedConfig) { + return [ + createBuildTool(pi, config), + createQueryTool(pi, config), + createPathTool(pi, config), + createExplainTool(pi, config), + createAddTool(pi, config), + createUpdateTool(pi, config), + createWatchTool(pi, config), + createClusterTool(pi, config), + ]; +} diff --git a/extensions/pi-graphify/src/tools/graphify.integration.test.ts b/extensions/pi-graphify/src/tools/graphify.integration.test.ts new file mode 100644 index 0000000..7e97ad6 --- /dev/null +++ b/extensions/pi-graphify/src/tools/graphify.integration.test.ts @@ -0,0 +1,390 @@ +/** + * Integration tests for pi-graphify extension using @gaodes/pi-test-harness. + * + * These tests exercise the full extension lifecycle: + * - Extension loading and tool registration + * - Tool execution via playbook DSL (when/calls/says) + * - Mock bash responses simulating the graphify Python CLI + * + * The extension's tools call pi.exec("sh", ["-c", ...]) which routes through + * the built-in "bash" tool. We mock that to return deterministic responses. + */ + +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { calls, createTestSession, says, type TestSession, when } from "@gaodes/pi-test-harness"; +import { afterEach, describe, expect, it } from "vitest"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PROJECT_ROOT = path.resolve(__dirname, "../.."); +const TOOLS_ENTRY = path.resolve(PROJECT_ROOT, "src/tools/index.ts"); + +// --------------------------------------------------------------------------- +// Mock responses for the graphify Python CLI +// --------------------------------------------------------------------------- + +/** Successful detect output */ +const MOCK_DETECT_OUTPUT = JSON.stringify({ + total_files: 3, + total_words: 500, + files: { + code: ["src/main.ts", "src/util.ts"], + document: ["README.md"], + paper: [], + image: [], + video: [], + }, +}); + +/** Mock python check -- graphify already installed */ +const MOCK_PYTHON_CHECK = "0"; + +/** Mock detect Python from cache */ +const MOCK_PYTHON_PATH = "/usr/bin/python3"; + +/** Successful query output */ +const MOCK_QUERY_OUTPUT = `Traversal: BFS | Start: ['Main Module'] | 2 nodes + NODE Main Module [src=src/main.ts loc=] + NODE Util Module [src=src/util.ts loc=] + EDGE Main Module --imports [EXTRACTED]--> Util Module`; + +/** Successful explain output */ +const MOCK_EXPLAIN_OUTPUT = `NODE: Main Module + source: src/main.ts + type: code + degree: 1 +CONNECTIONS: + --imports--> Util Module [EXTRACTED] (src/util.ts)`; + +/** Successful ingest output */ +const MOCK_INGEST_OUTPUT = "Saved to ./raw/article.md"; + +// --------------------------------------------------------------------------- +// Bash mock that matches graphify command patterns +// --------------------------------------------------------------------------- + +function createGraphifyBashMock(responses?: Record) { + const defaultResponses: Record = { + ".graphify_python": MOCK_PYTHON_PATH, + "import graphify": MOCK_PYTHON_CHECK, + "from graphify.detect import detect": MOCK_DETECT_OUTPUT, + "from graphify.extract import collect_files": JSON.stringify({ + nodes: [ + { id: "src_main", label: "Main Module", file_type: "code", source_file: "src/main.ts" }, + ], + edges: [], + input_tokens: 0, + output_tokens: 0, + }), + graphify_semantic: JSON.stringify({ + nodes: [], + edges: [], + hyperedges: [], + input_tokens: 0, + output_tokens: 0, + }), + "from graphify.build import build_from_json": "Graph: 2 nodes, 1 edges, 1 communities", + "Traversal:": MOCK_QUERY_OUTPUT, + "NODE:": MOCK_EXPLAIN_OUTPUT, + "from graphify.ingest import ingest": MOCK_INGEST_OUTPUT, + detect_incremental: JSON.stringify({ new_total: 0, new_files: {} }), + "from graphify.cluster import cluster": "Re-clustered: 3 communities", + "hook status": "Git hooks are not installed.", + mkdir: "", + "cat graphify-out": MOCK_PYTHON_PATH, + save_manifest: "", + benchmark: "Token reduction: 12.5x", + "rm -f": "", + write_text: "", + god_nodes: "[]", + surprising_connections: "[]", + "from graphify.report import generate": "", + to_json: "", + to_html: "graph.html written", + shortest_path: `Shortest path (1 hops): + Main Module --imports--> [EXTRACTED] + Util Module`, + "graphify.watch": "Watching . for changes...", + }; + + const all = { ...defaultResponses, ...responses }; + + return (params: Record) => { + const cmd = String(params.command || ""); + + // Detect what kind of graphify command this is and respond accordingly + // The mock must match the *purpose* of the command, not just substrings, + // because graphify-out paths appear in many commands. + + // Python detection: very first commands + if (cmd.includes("cat graphify-out/.graphify_python")) { + return `$ ${cmd}\n${all[".graphify_python"]}`; + } + if (cmd.includes("import graphify") && cmd.includes("echo $?")) { + return `$ ${cmd}\n${all["import graphify"]}`; + } + if (cmd.includes("pip install graphifyy")) { + return `$ ${cmd}\n`; + } + + // mkdir + if (cmd.startsWith("mkdir")) { + return `$ ${cmd}\n${all.mkdir}`; + } + + // Cleanup + if (cmd.includes("rm -f ")) { + return `$ ${cmd}\n${all["rm -f"]}`; + } + + // Detect files + if (cmd.includes("from graphify.detect import detect")) { + return `$ ${cmd}\n${all["from graphify.detect import detect"]}`; + } + + // AST extraction + if (cmd.includes("from graphify.extract import collect_files")) { + return `$ ${cmd}\n${all["from graphify.extract import collect_files"]}`; + } + + // Semantic merge + if (cmd.includes("graphify_semantic") || cmd.includes("graphify_cached")) { + return `$ ${cmd}\n${all.graphify_semantic}`; + } + + // Build graph + if (cmd.includes("from graphify.build import build_from_json")) { + return `$ ${cmd}\n${all["from graphify.build import build_from_json"]}`; + } + + // Query + if ( + cmd.includes("Traversal:") || + cmd.includes("mode = 'bfs'") || + cmd.includes("mode = 'dfs'") + ) { + return `$ ${cmd}\n${all["Traversal:"]}`; + } + + // Explain + if (cmd.includes("CONNECTIONS:")) { + return `$ ${cmd}\n${all["NODE:"]}`; + } + + // Path + if (cmd.includes("shortest_path")) { + return `$ ${cmd}\n${all.shortest_path}`; + } + + // Ingest + if (cmd.includes("from graphify.ingest import ingest")) { + return `$ ${cmd}\n${all["from graphify.ingest import ingest"]}`; + } + + // Incremental update + if (cmd.includes("detect_incremental")) { + return `$ ${cmd}\n${all.detect_incremental}`; + } + + // Cluster + if (cmd.includes("from graphify.cluster import cluster") && !cmd.includes("build_from_json")) { + return `$ ${cmd}\n${all["from graphify.cluster import cluster"]}`; + } + + // Hook + if (cmd.includes("hook status")) { + return `$ ${cmd}\n${all["hook status"]}`; + } + + // Watch + if (cmd.includes("graphify.watch")) { + return `$ ${cmd}\n${all["graphify.watch"]}`; + } + + // Manifest / benchmark / report / export + if (cmd.includes("save_manifest")) return `$ ${cmd}\n${all.save_manifest}`; + if (cmd.includes("benchmark")) return `$ ${cmd}\n${all.benchmark}`; + if (cmd.includes("from graphify.report import generate")) + return `$ ${cmd}\n${all["from graphify.report import generate"]}`; + if (cmd.includes("to_json")) return `$ ${cmd}\n${all.to_json}`; + if (cmd.includes("to_html")) return `$ ${cmd}\n${all.to_html}`; + if (cmd.includes("write_text")) return `$ ${cmd}\n${all.write_text}`; + + // Graph existence check + if (cmd.includes("graphify-out/graph.json") && cmd.includes("exists()")) { + // For explain/path/query — graph exists + return `$ ${cmd}\n${all["NODE:"]}`; + } + + return `$ ${cmd}\n`; + }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("pi-graphify extension", () => { + let t: TestSession; + + afterEach(() => t?.dispose()); + + /** Helper: create a test session with graphify bash mock */ + async function createSession(overrides?: Record) { + const session = await createTestSession({ + extensions: [TOOLS_ENTRY], + mockTools: { + bash: createGraphifyBashMock(overrides), + }, + }); + + // Polyfill setTools for pi-agent-core compatibility + // (test-harness 1.0.1 calls setTools but pi-coding-agent 0.73 uses state.tools setter) + // biome-ignore lint/suspicious/noExplicitAny: compatibility shim accessing untyped internals + const agent = (session.session as any).agent; + if (agent && !agent.setTools) { + agent.setTools = (tools: unknown[]) => { + agent.state.tools = tools; + }; + } + + return session; + } + + // -- Extension loading -------------------------------------------------- + + it("loads and registers all 8 tools", async () => { + t = await createSession(); + + await t.run(when("Build a knowledge graph from .", [calls("graphify_build", { path: "." })])); + + const buildCalls = t.events.toolCallsFor("graphify_build"); + expect(buildCalls.length).toBeGreaterThanOrEqual(1); + expect(buildCalls[0].blocked).toBe(false); + }); + + // -- graphify_build ----------------------------------------------------- + + it("builds a graph from a directory", async () => { + t = await createSession(); + + await t.run( + when("Build a knowledge graph from the current directory", [ + calls("graphify_build", { path: "." }), + says("graph"), + ]), + ); + + const results = t.events.toolResultsFor("graphify_build"); + expect(results.length).toBeGreaterThanOrEqual(1); + }); + + it("respects --no-viz flag", async () => { + t = await createSession(); + + await t.run( + when("Build graph without visualization", [ + calls("graphify_build", { path: ".", noViz: true }), + ]), + ); + + expect(t.events.toolResultsFor("graphify_build").length).toBeGreaterThanOrEqual(1); + }); + + // -- graphify_query ----------------------------------------------------- + + it("performs BFS traversal", async () => { + t = await createSession({ + "from pathlib import Path": "graphify-out/graph.json", + }); + + await t.run( + when("Query the graph: what does Main Module connect to?", [ + calls("graphify_query", { question: "What does Main Module connect to?" }), + ]), + ); + + expect(t.events.toolResultsFor("graphify_query").length).toBeGreaterThanOrEqual(1); + }); + + // -- graphify_explain --------------------------------------------------- + + it("returns node details", async () => { + t = await createSession({ + NODE: MOCK_EXPLAIN_OUTPUT, + }); + + await t.run( + when("Explain the Main Module concept in the graph", [ + calls("graphify_explain", { concept: "Main Module" }), + ]), + ); + + expect(t.events.toolResultsFor("graphify_explain").length).toBeGreaterThanOrEqual(1); + }); + + // -- graphify_add ------------------------------------------------------- + + it("fetches a URL and adds to corpus", async () => { + t = await createSession(); + + await t.run( + when("Add this article to the graph: https://example.com/article", [ + calls("graphify_add", { url: "https://example.com/article" }), + ]), + ); + + expect(t.events.toolResultsFor("graphify_add").length).toBeGreaterThanOrEqual(1); + }); + + // -- graphify_update ---------------------------------------------------- + + it("checks for changed files", async () => { + t = await createSession({ + detect_incremental: JSON.stringify({ + new_total: 2, + new_files: { code: ["src/main.ts"] }, + }), + }); + + await t.run(when("Update the graph incrementally", [calls("graphify_update", { path: "." })])); + + expect(t.events.toolResultsFor("graphify_update").length).toBeGreaterThanOrEqual(1); + }); + + // -- graphify_cluster --------------------------------------------------- + + it("re-runs community detection", async () => { + t = await createSession(); + + await t.run(when("Re-cluster the existing graph", [calls("graphify_cluster", {})])); + + expect(t.events.toolResultsFor("graphify_cluster").length).toBeGreaterThanOrEqual(1); + }); + + // -- graphify_watch ----------------------------------------------------- + + it("starts watching a directory", async () => { + t = await createSession(); + + await t.run( + when("Watch the current directory for changes", [calls("graphify_watch", { path: "." })]), + ); + + expect(t.events.toolResultsFor("graphify_watch").length).toBeGreaterThanOrEqual(1); + }); + + // -- graphify_path ------------------------------------------------------ + + it("finds shortest path between concepts", async () => { + t = await createSession(); + + await t.run( + when("Find the path from Main Module to Util Module", [ + calls("graphify_path", { from: "Main Module", to: "Util Module" }), + ]), + ); + + expect(t.events.toolResultsFor("graphify_path").length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/extensions/pi-graphify/src/tools/index.ts b/extensions/pi-graphify/src/tools/index.ts new file mode 100644 index 0000000..893ba57 --- /dev/null +++ b/extensions/pi-graphify/src/tools/index.ts @@ -0,0 +1,46 @@ +import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; +import { ensurePrimeSettings, loadConfig } from "../config"; +import { + createStatusbarState, + registerGraphifyStatusbar, + type StatusbarState, + unregisterGraphifyStatusbar, + updateGraphifyStatusbar, +} from "../statusbar.js"; +import { createAllTools } from "./graphify-tools"; + +type ToolsExtensionState = { + statusbarState: StatusbarState; +}; + +function createToolsExtensionState(): ToolsExtensionState { + return { + statusbarState: createStatusbarState(), + }; +} + +export default function (pi: ExtensionAPI) { + ensurePrimeSettings(); + + const config = loadConfig(process.cwd()); + if (!config.enabled) return; + + for (const tool of createAllTools(pi, config)) { + pi.registerTool(tool); + } + + const state = createToolsExtensionState(); + + pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => { + registerGraphifyStatusbar(pi, config); + await updateGraphifyStatusbar(pi, config, ctx, state.statusbarState); + }); + + pi.on("before_agent_start", async (_event: unknown, ctx: ExtensionContext) => { + await updateGraphifyStatusbar(pi, config, ctx, state.statusbarState); + }); + + pi.on("session_shutdown", async () => { + unregisterGraphifyStatusbar(pi); + }); +}