diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index cec110b..35779f5 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -29,8 +29,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: 10 - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b5dbd1..8438aca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [master] + branches: [main, master] pull_request: - branches: [master] + branches: [main, master] jobs: check: @@ -16,8 +16,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: 10 - name: Setup Node.js uses: actions/setup-node@v4 @@ -28,17 +26,17 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Typecheck + - name: Type check run: pnpm run typecheck - name: Smoke test run: pnpm run smoke-test - - name: Test + - name: Run tests run: pnpm run test - - name: Lint + - name: Lint (Biome) run: pnpm run lint - - name: Format check + - name: Check formatting (Biome) run: pnpm run format:check diff --git a/.husky/pre-commit b/.husky/pre-commit index 0e3990a..3f1e75c 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,7 +1,22 @@ #!/usr/bin/env sh +set -eu -# fnm: set up the correct Node/pnpm environment -export PATH="$HOME/.fnm:$PATH" -eval "$(fnm env --shell bash)" +if ! command -v node >/dev/null 2>&1; then + echo "pre-commit: node was not found in PATH." >&2 + echo "If you use a Node version manager, expose it in ~/.config/husky/init.sh so Git hooks use your own Node." >&2 + exit 127 +fi -pnpm run check \ No newline at end of file +if command -v corepack >/dev/null 2>&1; then + if corepack pnpm --version >/dev/null 2>&1; then + exec corepack pnpm run check + fi +fi + +if command -v pnpm >/dev/null 2>&1; then + exec pnpm run check +fi + +echo "pre-commit: pnpm was not found in PATH." >&2 +echo "Install pnpm, or expose it in ~/.config/husky/init.sh for Git hooks." >&2 +exit 127 diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 9474aea..0000000 --- a/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -package-lock.json -pnpm-lock.yaml -.git/ diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 280d7c2..0000000 --- a/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "semi": true, - "trailingComma": "es5", - "singleQuote": false, - "printWidth": 100, - "tabWidth": 2, - "useTabs": false -} diff --git a/README.md b/README.md index c42cf77..9f61a60 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) A better way to manage Pi extensions. Browse, install, enable/disable, and remove extensions from one place. +Built on top of Pi's native package install, update, and config flows, so extmgr stays aligned with current upstream behavior. **🌐 [pi-extmgr landing page](https://ayagmar.github.io/pi-extmgr)** @@ -15,9 +16,9 @@ A better way to manage Pi extensions. Browse, install, enable/disable, and remov pi install npm:pi-extmgr ``` -Then reload Pi. +If Pi is already running, use `/reload`. -Requires Node.js `>=22.5.0`. +Requires Node.js `>=22`. ## Features @@ -155,7 +156,7 @@ Examples: - **Staged local changes**: Toggle local extensions on/off, then press `S` to apply all at once. - **Package extension config**: Select a package and press `c` (or Enter/A → Configure) to enable/disable individual package entrypoints. - - After saving package extension config, restart pi to fully apply changes. + - After saving package extension config, run /reload to apply changes. - **Two install modes**: - **Managed** (npm): Auto-updates with `pi update`, stored in pi's package cache, supports Pi package manifest/convention loading - **Local** (standalone): Copies to `~/.pi/agent/extensions/{package}/`, so it only accepts runnable standalone layouts (manifest-declared/root entrypoints), requires `tar` on `PATH`, and rejects packages whose runtime `dependencies` are not already bundled with the package contents diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..e029594 --- /dev/null +++ b/biome.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.9/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": true, + "includes": ["**", "!docs/assets/tailwind.css", "!docs/package-lock.json"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "trailingCommas": "es5", + "semicolons": "always" + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedImports": "error", + "noUnusedVariables": "warn" + }, + "performance": { + "noNamespaceImport": "error" + }, + "style": { + "noCommonJs": "error", + "useImportType": { + "level": "error", + "options": { + "style": "inlineType" + } + }, + "useNodejsImportProtocol": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "nursery": { + "noFloatingPromises": "error" + } + } + }, + "overrides": [ + { + "includes": ["test/**/*.ts"], + "linter": { + "rules": { + "nursery": { + "noFloatingPromises": "off" + } + } + } + }, + { + "includes": ["docs/**/*"], + "linter": { + "enabled": false + } + } + ], + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/docs/index.html b/docs/index.html index 7a1a851..76fae82 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,7 +6,7 @@ pi-extmgr — Extension Manager for Pi @@ -70,8 +70,8 @@

class="text-neutral-300 hover:text-white underline underline-offset-4" >Pi - extensions from a unified interface. No more digging through directories or editing - config files. + extensions from a unified interface. Built on Pi's native package flows, without the + extra directory spelunking or config-file editing.

@@ -369,15 +369,9 @@

Install

pi install npm:pi-extmgr

- Then reload - Pi - and type + If Pi is already running, use + /reload, + then type /extensions to open the manager.

diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 9bb0fba..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,52 +0,0 @@ -import tseslint from "@typescript-eslint/eslint-plugin"; -import tsParser from "@typescript-eslint/parser"; -import prettierConfig from "eslint-config-prettier"; - -export default [ - { - ignores: ["node_modules/**", "dist/**"], - }, - { - files: ["**/*.ts"], - languageOptions: { - parser: tsParser, - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - projectService: true, - tsconfigRootDir: import.meta.dirname, - }, - }, - plugins: { - "@typescript-eslint": tseslint, - }, - rules: { - ...tseslint.configs.recommended.rules, - ...(tseslint.configs["recommended-type-checked"]?.rules ?? {}), - ...(tseslint.configs.stylistic?.rules ?? {}), - "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/consistent-type-imports": [ - "error", - { prefer: "type-imports", fixStyle: "inline-type-imports" }, - ], - "@typescript-eslint/no-floating-promises": "error", - "@typescript-eslint/no-unused-vars": [ - "warn", - { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, - ], - "no-restricted-syntax": [ - "error", - { - selector: "ImportNamespaceSpecifier", - message: "Use named imports instead of 'import *'", - }, - { - selector: "ImportExpression", - message: "Avoid dynamic imports. Use top-level imports instead.", - }, - ], - }, - }, - // Prettier must be last to override conflicting rules - prettierConfig, -]; diff --git a/package.json b/package.json index c1018aa..c64f322 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,14 @@ "README.md" ], "scripts": { - "lint": "eslint . --max-warnings=0", - "lint:fix": "eslint . --fix", - "format": "prettier --write .", - "format:check": "prettier --check .", + "lint": "biome lint . --error-on-warnings", + "lint:fix": "biome check --write .", + "format": "biome format --write .", + "format:check": "biome format .", "typecheck": "tsc --noEmit -p tsconfig.json", "smoke-test": "node --import=tsx ./scripts/smoke-test.mjs", "test": "node --import=tsx --test ./test/*.test.ts", - "check": "pnpm run typecheck && pnpm run smoke-test && pnpm run test && pnpm run lint && pnpm run format:check", + "check": "tsc --noEmit -p tsconfig.json && node --import=tsx ./scripts/smoke-test.mjs && node --import=tsx --test ./test/*.test.ts && pnpm run lint && pnpm run format:check", "prepublishOnly": "pnpm run check", "prepare": "husky" }, @@ -42,22 +42,19 @@ "@mariozechner/pi-tui": "*" }, "devDependencies": { - "@mariozechner/pi-coding-agent": "^0.62.0", - "@mariozechner/pi-tui": "^0.62.0", - "@types/node": "^22.13.10", - "@typescript-eslint/eslint-plugin": "^8.42.0", - "@typescript-eslint/parser": "^8.42.0", - "eslint": "^9.38.0", - "eslint-config-prettier": "^10.1.8", + "@biomejs/biome": "^2.4.9", + "@mariozechner/pi-coding-agent": "^0.63.1", + "@mariozechner/pi-tui": "^0.63.1", + "@types/node": "^22.19.10", "husky": "^9.1.7", - "prettier": "^3.8.1", - "tsx": "^4.19.3", + "tsx": "^4.21.0", "typescript": "^5.9.3" }, "author": "ayagmar", "license": "MIT", + "packageManager": "pnpm@10.33.0", "engines": { - "node": ">=22.5.0" + "node": ">=22" }, "repository": { "type": "git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a49b980..a300228 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,35 +8,23 @@ importers: .: devDependencies: + '@biomejs/biome': + specifier: ^2.4.9 + version: 2.4.9 '@mariozechner/pi-coding-agent': - specifier: ^0.62.0 - version: 0.62.0(ws@8.20.0)(zod@4.3.6) + specifier: ^0.63.1 + version: 0.63.1(ws@8.20.0)(zod@4.3.6) '@mariozechner/pi-tui': - specifier: ^0.62.0 - version: 0.62.0 + specifier: ^0.63.1 + version: 0.63.1 '@types/node': - specifier: ^22.13.10 - version: 22.19.9 - '@typescript-eslint/eslint-plugin': - specifier: ^8.42.0 - version: 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/parser': - specifier: ^8.42.0 - version: 8.54.0(eslint@9.39.2)(typescript@5.9.3) - eslint: - specifier: ^9.38.0 - version: 9.39.2 - eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.2) + specifier: ^22.19.10 + version: 22.19.15 husky: specifier: ^9.1.7 version: 9.1.7 - prettier: - specifier: ^3.8.1 - version: 3.8.1 tsx: - specifier: ^4.19.3 + specifier: ^4.21.0 version: 4.21.0 typescript: specifier: ^5.9.3 @@ -190,6 +178,63 @@ packages: resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} + '@biomejs/biome@2.4.9': + resolution: {integrity: sha512-wvZW92FrwitTcacvCBT8xdAbfbxWfDLwjYMmU3djjqQTh7Ni4ZdiWIT/x5VcZ+RQuxiKzIOzi5D+dcyJDFZMsA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.9': + resolution: {integrity: sha512-d5G8Gf2RpH5pYwiHLPA+UpG3G9TLQu4WM+VK6sfL7K68AmhcEQ9r+nkj/DvR/GYhYox6twsHUtmWWWIKfcfQQA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.9': + resolution: {integrity: sha512-LNCLNgqDMG7BLdc3a8aY/dwKPK7+R8/JXJoXjCvZh2gx8KseqBdFDKbhrr7HCWF8SzNhbTaALhTBoh/I6rf9lA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.9': + resolution: {integrity: sha512-8RCww5xnPn2wpK4L/QDGDOW0dq80uVWfppPxHIUg6mOs9B6gRmqPp32h1Ls3T8GnW8Wo5A8u7vpTwz4fExN+sw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.4.9': + resolution: {integrity: sha512-4adnkAUi6K4C/emPRgYznMOcLlUqZdXWM6aIui4VP4LraE764g6Q4YguygnAUoxKjKIXIWPteKMgRbN0wsgwcg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.4.9': + resolution: {integrity: sha512-5TD+WS9v5vzXKzjetF0hgoaNFHMcpQeBUwKKVi3JbG1e9UCrFuUK3Gt185fyTzvRdwYkJJEMqglRPjmesmVv4A==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.4.9': + resolution: {integrity: sha512-L10na7POF0Ks/cgLFNF1ZvIe+X4onLkTi5oP9hY+Rh60Q+7fWzKDDCeGyiHUFf1nGIa9dQOOUPGe2MyYg8nMSQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.4.9': + resolution: {integrity: sha512-aDZr0RBC3sMGJOU10BvG7eZIlWLK/i51HRIfScE2lVhfts2dQTreowLiJJd+UYg/tHKxS470IbzpuKmd0MiD6g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.9': + resolution: {integrity: sha512-NS4g/2G9SoQ4ktKtz31pvyc/rmgzlcIDCGU/zWbmHJAqx6gcRj2gj5Q/guXhoWTzCUaQZDIqiCQXHS7BcGYc0w==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} @@ -349,44 +394,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.21.1': - resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@3.3.3': - resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.39.2': - resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@google/genai@1.46.0': resolution: {integrity: sha512-ewPMN5JkKfgU5/kdco9ZhXBHDPhVqZpMQqIFQhwsHLf8kyZfx1cNpw1pHo1eV6PGEW7EhIBFi3aYZraFndAXqg==} engines: {node: '>=20.0.0'} @@ -396,22 +403,6 @@ packages: '@modelcontextprotocol/sdk': optional: true - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - '@mariozechner/clipboard-darwin-arm64@0.3.2': resolution: {integrity: sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==} engines: {node: '>= 10'} @@ -434,30 +425,35 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@mariozechner/clipboard-linux-arm64-musl@0.3.2': resolution: {integrity: sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@mariozechner/clipboard-linux-riscv64-gnu@0.3.2': resolution: {integrity: sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@mariozechner/clipboard-linux-x64-gnu@0.3.2': resolution: {integrity: sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@mariozechner/clipboard-linux-x64-musl@0.3.2': resolution: {integrity: sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@mariozechner/clipboard-win32-arm64-msvc@0.3.2': resolution: {integrity: sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==} @@ -479,22 +475,22 @@ packages: resolution: {integrity: sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==} hasBin: true - '@mariozechner/pi-agent-core@0.62.0': - resolution: {integrity: sha512-SBjqgDrgKOaC+IGzFGB3jXQErv9H1QMYnWFvUg6ra6dG0ZgWFBUZb6unidngWLsmaxSDWes6KeKiVFMsr2VSEQ==} + '@mariozechner/pi-agent-core@0.63.1': + resolution: {integrity: sha512-h0B20xfs/iEVR2EC4gwiE8hKI1TPeB8REdRJMgV+uXKH7gpeIZ9+s8Dp9nX35ZR0QUjkNey2+ULk2DxQtdg14Q==} engines: {node: '>=20.0.0'} - '@mariozechner/pi-ai@0.62.0': - resolution: {integrity: sha512-mJgryZ5RgBQG++tiETMtCQQJoH2MAhKetCfqI98NMvGydu7L9x2qC2JekQlRaAgIlTgv4MRH1UXHMEs4UweE/Q==} + '@mariozechner/pi-ai@0.63.1': + resolution: {integrity: sha512-wjgwY+yfrFO6a9QdAfjWpH7iSrDean6GsKDDMohNcLCy6PreMxHOZvNM0NwJARL1tZoZovr7ikAQfLGFZbnjsw==} engines: {node: '>=20.0.0'} hasBin: true - '@mariozechner/pi-coding-agent@0.62.0': - resolution: {integrity: sha512-f1NnExqsHuA6w8UVlBtPsvTBhdkMc0h1JD9SzGCdWTLou5GHJr2JIP6DlwV9IKWAnM+sAelaoFez+14wLP2zOQ==} + '@mariozechner/pi-coding-agent@0.63.1': + resolution: {integrity: sha512-XSoMyLtuMA7ePK1UBWqSJ/BBdtBdJUHY9nbtnNyG6GeW7Gbgd+iqljIuwmAUf8wlYL981UIfYM/WIPQ6t/dIxw==} engines: {node: '>=20.6.0'} hasBin: true - '@mariozechner/pi-tui@0.62.0': - resolution: {integrity: sha512-/At11PPe8l319MnUoK4wN5L/uVCU6bDdiIUzH8Ez0stOkjSF6isRXScZ+RMM+6iCKsD4muBTX8Cmcif+3/UWHA==} + '@mariozechner/pi-tui@0.63.1': + resolution: {integrity: sha512-G5p+eh1EPkFCNaaggX6vRrqttnDscK6npgmEOknoCQXZtch8XNgh9Lf3VJ0A2lZXSgR7IntG5dfXHPH/Ki64wA==} engines: {node: '>=20.0.0'} '@mistralai/mistralai@1.14.1': @@ -738,17 +734,11 @@ packages: '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/mime-types@2.1.4': resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==} - '@types/node@22.19.9': - resolution: {integrity: sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==} + '@types/node@22.19.15': + resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} @@ -756,75 +746,6 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.54.0': - resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.54.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/parser@8.54.0': - resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/project-service@8.54.0': - resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/scope-manager@8.54.0': - resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.54.0': - resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/type-utils@8.54.0': - resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/types@8.54.0': - resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.54.0': - resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/utils@8.54.0': - resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/visitor-keys@8.54.0': - resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -837,9 +758,6 @@ packages: ajv: optional: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} @@ -858,16 +776,10 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - ast-types@0.13.4: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -885,12 +797,6 @@ packages: bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - brace-expansion@5.0.4: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} @@ -901,10 +807,6 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -928,13 +830,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -952,9 +847,6 @@ packages: supports-color: optional: true - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -981,60 +873,16 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - escodegen@2.1.0: resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} engines: {node: '>=6.0'} hasBin: true - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint@9.39.2: - resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -1054,12 +902,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1073,38 +915,14 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - file-type@21.3.4: resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} engines: {node: '>=20'} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -1141,18 +959,10 @@ packages: resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} engines: {node: '>= 14'} - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - google-auth-library@10.6.2: resolution: {integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==} engines: {node: '>=18'} @@ -1191,87 +1001,37 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - ignore@7.0.5: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-schema-to-ts@3.1.1: resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} engines: {node: '>=16'} - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - jwa@2.0.1: resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - koffi@2.15.2: resolution: {integrity: sha512-r9tjJLVRSOhCRWdVyQlF3/Ugzeg13jlzS4czS82MAgLff4W+BcYOW7g8Y62t9O5JYjYOLAjAovAZDNlDfZNu+g==} - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -1300,13 +1060,6 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -1317,9 +1070,6 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - netmask@2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} @@ -1352,18 +1102,6 @@ packages: zod: optional: true - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - p-retry@4.6.2: resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} engines: {node: '>=8'} @@ -1376,10 +1114,6 @@ packages: resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} engines: {node: '>= 14'} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} @@ -1392,18 +1126,10 @@ packages: partial-json@0.1.7: resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - path-expression-matcher@1.2.0: resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} engines: {node: '>=14.0.0'} - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - path-scurry@2.0.2: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} @@ -1411,19 +1137,6 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} - engines: {node: '>=14'} - hasBin: true - proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} @@ -1441,10 +1154,6 @@ packages: pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1453,10 +1162,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -1471,19 +1176,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -1518,10 +1210,6 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - strnum@2.2.2: resolution: {integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==} @@ -1540,10 +1228,6 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - token-types@6.1.2: resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} @@ -1551,12 +1235,6 @@ packages: ts-algebra@2.0.0: resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1565,10 +1243,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1585,22 +1259,10 @@ packages: resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==} engines: {node: '>=20.18.1'} - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -1640,10 +1302,6 @@ packages: yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -2042,6 +1700,41 @@ snapshots: '@babel/runtime@7.29.2': {} + '@biomejs/biome@2.4.9': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.9 + '@biomejs/cli-darwin-x64': 2.4.9 + '@biomejs/cli-linux-arm64': 2.4.9 + '@biomejs/cli-linux-arm64-musl': 2.4.9 + '@biomejs/cli-linux-x64': 2.4.9 + '@biomejs/cli-linux-x64-musl': 2.4.9 + '@biomejs/cli-win32-arm64': 2.4.9 + '@biomejs/cli-win32-x64': 2.4.9 + + '@biomejs/cli-darwin-arm64@2.4.9': + optional: true + + '@biomejs/cli-darwin-x64@2.4.9': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.9': + optional: true + + '@biomejs/cli-linux-arm64@2.4.9': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.9': + optional: true + + '@biomejs/cli-linux-x64@2.4.9': + optional: true + + '@biomejs/cli-win32-arm64@2.4.9': + optional: true + + '@biomejs/cli-win32-x64@2.4.9': + optional: true + '@borewit/text-codec@0.2.2': {} '@esbuild/aix-ppc64@0.27.3': @@ -2122,52 +1815,6 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)': - dependencies: - eslint: 9.39.2 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/config-array@0.21.1': - dependencies: - '@eslint/object-schema': 2.1.7 - debug: 4.4.3 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.4.2': - dependencies: - '@eslint/core': 0.17.0 - - '@eslint/core@0.17.0': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/eslintrc@3.3.3': - dependencies: - ajv: 6.12.6 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.39.2': {} - - '@eslint/object-schema@2.1.7': {} - - '@eslint/plugin-kit@0.4.1': - dependencies: - '@eslint/core': 0.17.0 - levn: 0.4.1 - '@google/genai@1.46.0': dependencies: google-auth-library: 10.6.2 @@ -2179,17 +1826,6 @@ snapshots: - supports-color - utf-8-validate - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.7': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.4.3 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.4.3': {} - '@mariozechner/clipboard-darwin-arm64@0.3.2': optional: true @@ -2239,9 +1875,9 @@ snapshots: std-env: 3.10.0 yoctocolors: 2.1.2 - '@mariozechner/pi-agent-core@0.62.0(ws@8.20.0)(zod@4.3.6)': + '@mariozechner/pi-agent-core@0.63.1(ws@8.20.0)(zod@4.3.6)': dependencies: - '@mariozechner/pi-ai': 0.62.0(ws@8.20.0)(zod@4.3.6) + '@mariozechner/pi-ai': 0.63.1(ws@8.20.0)(zod@4.3.6) transitivePeerDependencies: - '@modelcontextprotocol/sdk' - aws-crt @@ -2251,7 +1887,7 @@ snapshots: - ws - zod - '@mariozechner/pi-ai@0.62.0(ws@8.20.0)(zod@4.3.6)': + '@mariozechner/pi-ai@0.63.1(ws@8.20.0)(zod@4.3.6)': dependencies: '@anthropic-ai/sdk': 0.73.0(zod@4.3.6) '@aws-sdk/client-bedrock-runtime': 3.1015.0 @@ -2275,13 +1911,14 @@ snapshots: - ws - zod - '@mariozechner/pi-coding-agent@0.62.0(ws@8.20.0)(zod@4.3.6)': + '@mariozechner/pi-coding-agent@0.63.1(ws@8.20.0)(zod@4.3.6)': dependencies: '@mariozechner/jiti': 2.6.5 - '@mariozechner/pi-agent-core': 0.62.0(ws@8.20.0)(zod@4.3.6) - '@mariozechner/pi-ai': 0.62.0(ws@8.20.0)(zod@4.3.6) - '@mariozechner/pi-tui': 0.62.0 + '@mariozechner/pi-agent-core': 0.63.1(ws@8.20.0)(zod@4.3.6) + '@mariozechner/pi-ai': 0.63.1(ws@8.20.0)(zod@4.3.6) + '@mariozechner/pi-tui': 0.63.1 '@silvia-odwyer/photon-node': 0.3.4 + ajv: 8.18.0 chalk: 5.6.2 cli-highlight: 2.1.11 diff: 8.0.4 @@ -2307,7 +1944,7 @@ snapshots: - ws - zod - '@mariozechner/pi-tui@0.62.0': + '@mariozechner/pi-tui@0.63.1': dependencies: '@types/mime-types': 2.1.4 chalk: 5.6.2 @@ -2669,13 +2306,9 @@ snapshots: '@tootallnate/quickjs-emscripten@0.23.0': {} - '@types/estree@1.0.8': {} - - '@types/json-schema@7.0.15': {} - '@types/mime-types@2.1.4': {} - '@types/node@22.19.9': + '@types/node@22.19.15': dependencies: undici-types: 6.21.0 @@ -2683,119 +2316,15 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.19.9 + '@types/node': 22.19.15 optional: true - '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 - eslint: 9.39.2 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.54.0(eslint@9.39.2)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 - debug: 4.4.3 - eslint: 9.39.2 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 - debug: 4.4.3 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.54.0': - dependencies: - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/visitor-keys': 8.54.0 - - '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': - dependencies: - typescript: 5.9.3 - - '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2)(typescript@5.9.3) - debug: 4.4.3 - eslint: 9.39.2 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.54.0': {} - - '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/visitor-keys': 8.54.0 - debug: 4.4.3 - minimatch: 9.0.5 - semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.54.0(eslint@9.39.2)(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - eslint: 9.39.2 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.54.0': - dependencies: - '@typescript-eslint/types': 8.54.0 - eslint-visitor-keys: 4.2.1 - - acorn-jsx@5.3.2(acorn@8.15.0): - dependencies: - acorn: 8.15.0 - - acorn@8.15.0: {} - agent-base@7.1.4: {} ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 @@ -2813,14 +2342,10 @@ snapshots: any-promise@1.3.0: {} - argparse@2.0.1: {} - ast-types@0.13.4: dependencies: tslib: 2.8.1 - balanced-match@1.0.2: {} - balanced-match@4.0.4: {} base64-js@1.5.1: {} @@ -2831,15 +2356,6 @@ snapshots: bowser@2.14.1: {} - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - brace-expansion@5.0.4: dependencies: balanced-match: 4.0.4 @@ -2848,8 +2364,6 @@ snapshots: buffer-equal-constant-time@1.0.1: {} - callsites@3.1.0: {} - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2878,14 +2392,6 @@ snapshots: color-name@1.1.4: {} - concat-map@0.0.1: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - data-uri-to-buffer@4.0.1: {} data-uri-to-buffer@6.0.2: {} @@ -2894,8 +2400,6 @@ snapshots: dependencies: ms: 2.1.3 - deep-is@0.1.4: {} - degenerator@5.0.1: dependencies: ast-types: 0.13.4 @@ -2945,8 +2449,6 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@4.0.0: {} - escodegen@2.1.0: dependencies: esprima: 4.0.1 @@ -2955,74 +2457,8 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-prettier@10.1.8(eslint@9.39.2): - dependencies: - eslint: 9.39.2 - - eslint-scope@8.4.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.1: {} - - eslint@9.39.2: - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.2 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - transitivePeerDependencies: - - supports-color - - espree@10.4.0: - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 4.2.1 - esprima@4.0.1: {} - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - estraverse@5.3.0: {} esutils@2.0.3: {} @@ -3041,10 +2477,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - fast-uri@3.1.0: {} fast-xml-builder@1.1.4: @@ -3061,19 +2493,11 @@ snapshots: dependencies: pend: 1.2.0 - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - file-type@21.3.4: dependencies: '@tokenizer/inflate': 0.4.1 @@ -3083,18 +2507,6 @@ snapshots: transitivePeerDependencies: - supports-color - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - - flatted@3.3.3: {} - formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -3138,18 +2550,12 @@ snapshots: transitivePeerDependencies: - supports-color - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - glob@13.0.6: dependencies: minimatch: 10.2.4 minipass: 7.1.3 path-scurry: 2.0.2 - globals@14.0.0: {} - google-auth-library@10.6.2: dependencies: base64-js: 1.5.1 @@ -3191,50 +2597,23 @@ snapshots: ieee754@1.2.1: {} - ignore@5.3.2: {} - ignore@7.0.5: {} - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - ip-address@10.1.0: {} - is-extglob@2.1.1: {} - is-fullwidth-code-point@3.0.0: {} - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - isexe@2.0.0: {} - - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - json-bigint@1.0.0: dependencies: bignumber.js: 9.3.1 - json-buffer@3.0.1: {} - json-schema-to-ts@3.1.1: dependencies: '@babel/runtime': 7.29.2 ts-algebra: 2.0.0 - json-schema-traverse@0.4.1: {} - json-schema-traverse@1.0.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} - jwa@2.0.1: dependencies: buffer-equal-constant-time: 1.0.1 @@ -3246,24 +2625,9 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - koffi@2.15.2: optional: true - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.merge@4.6.2: {} - long@5.3.2: {} lru-cache@11.2.7: {} @@ -3282,14 +2646,6 @@ snapshots: dependencies: brace-expansion: 5.0.4 - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - minipass@7.1.3: {} ms@2.1.3: {} @@ -3300,8 +2656,6 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - natural-compare@1.4.0: {} - netmask@2.0.2: {} node-domexception@1.0.0: {} @@ -3323,23 +2677,6 @@ snapshots: ws: 8.20.0 zod: 4.3.6 - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - p-retry@4.6.2: dependencies: '@types/retry': 0.12.0 @@ -3363,10 +2700,6 @@ snapshots: degenerator: 5.0.1 netmask: 2.0.2 - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - parse5-htmlparser2-tree-adapter@6.0.1: dependencies: parse5: 6.0.1 @@ -3377,12 +2710,8 @@ snapshots: partial-json@0.1.7: {} - path-exists@4.0.0: {} - path-expression-matcher@1.2.0: {} - path-key@3.1.1: {} - path-scurry@2.0.2: dependencies: lru-cache: 11.2.7 @@ -3390,12 +2719,6 @@ snapshots: pend@1.2.0: {} - picomatch@4.0.3: {} - - prelude-ls@1.2.1: {} - - prettier@3.8.1: {} - proper-lockfile@4.1.2: dependencies: graceful-fs: 4.2.11 @@ -3414,7 +2737,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.19.9 + '@types/node': 22.19.15 long: 5.3.2 proxy-agent@6.5.0: @@ -3437,14 +2760,10 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 - punycode@2.3.1: {} - require-directory@2.1.1: {} require-from-string@2.0.2: {} - resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} retry@0.12.0: {} @@ -3453,14 +2772,6 @@ snapshots: safe-buffer@5.2.1: {} - semver@7.7.4: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - signal-exit@3.0.7: {} smart-buffer@4.2.0: {} @@ -3497,8 +2808,6 @@ snapshots: dependencies: ansi-regex: 6.2.2 - strip-json-comments@3.1.1: {} - strnum@2.2.2: {} strtok3@10.3.5: @@ -3517,11 +2826,6 @@ snapshots: dependencies: any-promise: 1.3.0 - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - token-types@6.1.2: dependencies: '@borewit/text-codec': 0.2.2 @@ -3530,10 +2834,6 @@ snapshots: ts-algebra@2.0.0: {} - ts-api-utils@2.4.0(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - tslib@2.8.1: {} tsx@4.21.0: @@ -3543,10 +2843,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - typescript@5.9.3: {} uint8array-extras@1.5.0: {} @@ -3555,18 +2851,8 @@ snapshots: undici@7.24.5: {} - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - web-streams-polyfill@3.3.3: {} - which@2.0.2: - dependencies: - isexe: 2.0.0 - - word-wrap@1.2.5: {} - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -3598,8 +2884,6 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 - yocto-queue@0.1.0: {} - yoctocolors@2.1.2: {} zod-to-json-schema@3.25.1(zod@4.3.6): diff --git a/src/commands/auto-update.ts b/src/commands/auto-update.ts index ba42096..fc22a0f 100644 --- a/src/commands/auto-update.ts +++ b/src/commands/auto-update.ts @@ -1,7 +1,7 @@ -import type { - ExtensionAPI, - ExtensionCommandContext, - ExtensionContext, +import { + type ExtensionAPI, + type ExtensionCommandContext, + type ExtensionContext, } from "@mariozechner/pi-coding-agent"; import { disableAutoUpdate, diff --git a/src/commands/cache.ts b/src/commands/cache.ts index 09b9fb3..534cc75 100644 --- a/src/commands/cache.ts +++ b/src/commands/cache.ts @@ -1,4 +1,4 @@ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import { clearSearchCache } from "../packages/discovery.js"; import { clearRemotePackageInfoCache } from "../ui/remote.js"; import { clearCache } from "../utils/cache.js"; diff --git a/src/commands/history.ts b/src/commands/history.ts index d38646f..822ce73 100644 --- a/src/commands/history.ts +++ b/src/commands/history.ts @@ -1,10 +1,10 @@ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import { + type ChangeAction, formatChangeEntry, + type HistoryFilters, queryGlobalHistory, querySessionChanges, - type ChangeAction, - type HistoryFilters, } from "../utils/history.js"; import { notify } from "../utils/notify.js"; import { formatListOutput } from "../utils/ui-helpers.js"; diff --git a/src/commands/install.ts b/src/commands/install.ts index b4bcc76..4d6c3f5 100644 --- a/src/commands/install.ts +++ b/src/commands/install.ts @@ -1,5 +1,5 @@ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; -import { installPackage, type InstallScope } from "../packages/install.js"; +import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import { type InstallScope, installPackage } from "../packages/install.js"; import { notify } from "../utils/notify.js"; export const INSTALL_USAGE = "Usage: /extensions install [--project|--global]"; diff --git a/src/commands/registry.ts b/src/commands/registry.ts index e6611e7..c21da53 100644 --- a/src/commands/registry.ts +++ b/src/commands/registry.ts @@ -1,7 +1,5 @@ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; -import type { AutocompleteItem } from "@mariozechner/pi-tui"; -import { showInteractive, showInstalledPackagesLegacy, showListOnly } from "../ui/unified.js"; -import { showRemote } from "../ui/remote.js"; +import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import { type AutocompleteItem } from "@mariozechner/pi-tui"; import { promptRemove, removePackage, @@ -9,12 +7,14 @@ import { updatePackage, updatePackages, } from "../packages/management.js"; +import { showRemote } from "../ui/remote.js"; +import { showInstalledPackagesLegacy, showInteractive, showListOnly } from "../ui/unified.js"; import { notify } from "../utils/notify.js"; -import { handleInstallSubcommand, INSTALL_USAGE } from "./install.js"; -import { handleHistorySubcommand } from "./history.js"; import { handleAutoUpdateSubcommand } from "./auto-update.js"; import { clearMetadataCacheCommand } from "./cache.js"; -import type { CommandDefinition, CommandId } from "./types.js"; +import { handleHistorySubcommand } from "./history.js"; +import { handleInstallSubcommand, INSTALL_USAGE } from "./install.js"; +import { type CommandDefinition, type CommandId } from "./types.js"; const REMOVE_USAGE = "Usage: /extensions remove "; diff --git a/src/commands/types.ts b/src/commands/types.ts index 262cd10..6acdfb2 100644 --- a/src/commands/types.ts +++ b/src/commands/types.ts @@ -1,4 +1,4 @@ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; export type CommandId = | "local" diff --git a/src/extensions/discovery.ts b/src/extensions/discovery.ts index 4f56145..f26e567 100644 --- a/src/extensions/discovery.ts +++ b/src/extensions/discovery.ts @@ -4,12 +4,13 @@ * This module handles discovery and management of local Pi extensions * in both global (~/.pi/agent/extensions) and project (.pi/extensions) scopes. */ + +import { type Dirent } from "node:fs"; import { readdir, rename, rm } from "node:fs/promises"; -import { basename, dirname, join, relative } from "node:path"; import { homedir } from "node:os"; -import type { Dirent } from "node:fs"; -import type { ExtensionEntry, Scope, State } from "../types/index.js"; +import { basename, dirname, join, relative } from "node:path"; import { DISABLED_SUFFIX } from "../constants.js"; +import { type ExtensionEntry, type Scope, type State } from "../types/index.js"; import { fileExists, readSummary } from "../utils/fs.js"; interface RootConfig { diff --git a/src/index.ts b/src/index.ts index 7da52e3..0541265 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,21 +3,12 @@ * * Entry point - exports the main extension function */ -import type { - ExtensionAPI, - ExtensionCommandContext, - ExtensionContext, -} from "@mariozechner/pi-coding-agent"; -import { isPackageSource } from "./utils/format.js"; -import { installPackage } from "./packages/install.js"; -import { tokenizeArgs } from "./utils/command.js"; -import { updateExtmgrStatus } from "./utils/status.js"; import { - startAutoUpdateTimer, - stopAutoUpdateTimer, - type ContextProvider, -} from "./utils/auto-update.js"; -import { hydrateAutoUpdateConfig, getAutoUpdateConfig } from "./utils/settings.js"; + type ExtensionAPI, + type ExtensionCommandContext, + type ExtensionContext, +} from "@mariozechner/pi-coding-agent"; +import { createAutoUpdateNotificationHandler } from "./commands/auto-update.js"; import { getExtensionsAutocompleteItems, resolveCommand, @@ -25,7 +16,16 @@ import { showNonInteractiveHelp, showUnknownCommandMessage, } from "./commands/registry.js"; -import { createAutoUpdateNotificationHandler } from "./commands/auto-update.js"; +import { installPackage } from "./packages/install.js"; +import { + type ContextProvider, + startAutoUpdateTimer, + stopAutoUpdateTimer, +} from "./utils/auto-update.js"; +import { tokenizeArgs } from "./utils/command.js"; +import { isPackageSource } from "./utils/format.js"; +import { getAutoUpdateConfig, hydrateAutoUpdateConfig } from "./utils/settings.js"; +import { updateExtmgrStatus } from "./utils/status.js"; async function executeExtensionsCommand( args: string, diff --git a/src/packages/catalog.ts b/src/packages/catalog.ts index e02fafc..a5c1d75 100644 --- a/src/packages/catalog.ts +++ b/src/packages/catalog.ts @@ -1,11 +1,11 @@ import { DefaultPackageManager, getAgentDir, - SettingsManager, type PackageSource, type ProgressEvent, + SettingsManager, } from "@mariozechner/pi-coding-agent"; -import type { InstalledPackage, Scope } from "../types/index.js"; +import { type InstalledPackage, type Scope } from "../types/index.js"; import { normalizePackageIdentity, parsePackageNameAndVersion } from "../utils/package-source.js"; type PiScope = "user" | "project"; @@ -57,14 +57,15 @@ function createPackageRecord( }; } -function dedupeInstalledPackages(packages: InstalledPackage[]): InstalledPackage[] { +function dedupeInstalledPackages(packages: InstalledPackage[], cwd: string): InstalledPackage[] { const byIdentity = new Map(); for (const pkg of packages) { - const identity = normalizePackageIdentity( - pkg.source, - pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : undefined - ); + const baseCwd = pkg.scope === "project" ? cwd : getAgentDir(); + const identity = normalizePackageIdentity(pkg.source, { + ...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}), + cwd: baseCwd, + }); if (!byIdentity.has(identity)) { byIdentity.set(identity, pkg); @@ -97,7 +98,7 @@ function createDefaultPackageCatalog(cwd: string): PackageCatalog { const installed = [...projectPackages, ...globalPackages]; return Promise.resolve( - options?.dedupe === false ? installed : dedupeInstalledPackages(installed) + options?.dedupe === false ? installed : dedupeInstalledPackages(installed, cwd) ); }, diff --git a/src/packages/discovery.ts b/src/packages/discovery.ts index 6bcf92a..a18f469 100644 --- a/src/packages/discovery.ts +++ b/src/packages/discovery.ts @@ -3,19 +3,20 @@ */ import { readFile } from "node:fs/promises"; import { join } from "node:path"; -import type { - ExtensionAPI, - ExtensionCommandContext, - ExtensionContext, +import { + type ExtensionAPI, + type ExtensionCommandContext, + type ExtensionContext, + getAgentDir, } from "@mariozechner/pi-coding-agent"; -import type { InstalledPackage, NpmPackage, SearchCache } from "../types/index.js"; import { CACHE_TTL, TIMEOUTS } from "../constants.js"; -import { readSummary } from "../utils/fs.js"; +import { type InstalledPackage, type NpmPackage, type SearchCache } from "../types/index.js"; import { parseNpmSource } from "../utils/format.js"; +import { readSummary } from "../utils/fs.js"; +import { fetchWithTimeout } from "../utils/network.js"; +import { execNpm } from "../utils/npm-exec.js"; import { normalizePackageIdentity } from "../utils/package-source.js"; import { getPackageCatalog } from "./catalog.js"; -import { execNpm } from "../utils/npm-exec.js"; -import { fetchWithTimeout } from "../utils/network.js"; const NPM_SEARCH_API = "https://registry.npmjs.org/-/v1/search"; const NPM_SEARCH_PAGE_SIZE = 250; @@ -69,13 +70,13 @@ export function isCacheValid(query: string): boolean { // Import persistent cache import { - getCachedSearch, - setCachedSearch, getCachedPackage, - setCachedPackage, - getPackageDescriptions, getCachedPackageSize, + getCachedSearch, + getPackageDescriptions, + setCachedPackage, setCachedPackageSize, + setCachedSearch, } from "../utils/cache.js"; function toNpmPackage(entry: NpmSearchResultObject): NpmPackage | undefined { @@ -201,11 +202,13 @@ export async function getInstalledPackages( return packages; } -function getInstalledPackageIdentity(pkg: InstalledPackage): string { - return normalizePackageIdentity( - pkg.source, - pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : undefined - ); +function getInstalledPackageIdentity(pkg: InstalledPackage, options?: { cwd?: string }): string { + const baseCwd = pkg.scope === "project" ? options?.cwd : getAgentDir(); + + return normalizePackageIdentity(pkg.source, { + ...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}), + ...(baseCwd ? { cwd: baseCwd } : {}), + }); } export async function isSourceInstalled( @@ -214,10 +217,10 @@ export async function isSourceInstalled( options?: { scope?: "global" | "project" } ): Promise { const installed = await getPackageCatalog(ctx.cwd).listInstalledPackages({ dedupe: false }); - const expected = normalizePackageIdentity(source); + const expected = normalizePackageIdentity(source, { cwd: ctx.cwd }); return installed.some((pkg) => { - if (getInstalledPackageIdentity(pkg) !== expected) { + if (getInstalledPackageIdentity(pkg, { cwd: ctx.cwd }) !== expected) { return false; } return options?.scope ? pkg.scope === options.scope : true; diff --git a/src/packages/extensions.ts b/src/packages/extensions.ts index 7e157a1..d5381fe 100644 --- a/src/packages/extensions.ts +++ b/src/packages/extensions.ts @@ -1,12 +1,17 @@ -import { mkdir, readFile, writeFile, rename, rm, readdir } from "node:fs/promises"; -import type { Dirent } from "node:fs"; +import { execFile } from "node:child_process"; +import { type Dirent } from "node:fs"; +import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises"; +import { homedir } from "node:os"; import { dirname, join, matchesGlob, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { homedir } from "node:os"; -import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { getAgentDir } from "@mariozechner/pi-coding-agent"; -import type { InstalledPackage, PackageExtensionEntry, Scope, State } from "../types/index.js"; +import { + type InstalledPackage, + type PackageExtensionEntry, + type Scope, + type State, +} from "../types/index.js"; import { parseNpmSource } from "../utils/format.js"; import { fileExists, readSummary } from "../utils/fs.js"; import { resolveNpmCommand } from "../utils/npm-exec.js"; diff --git a/src/packages/install.ts b/src/packages/install.ts index 5ccf9dd..c141f05 100644 --- a/src/packages/install.ts +++ b/src/packages/install.ts @@ -1,30 +1,30 @@ /** * Package installation logic */ -import { mkdir, rm, writeFile, cp } from "node:fs/promises"; -import { join } from "node:path"; +import { cp, mkdir, rm, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; -import type { - ExtensionAPI, - ExtensionCommandContext, - ProgressEvent, +import { join } from "node:path"; +import { + type ExtensionAPI, + type ExtensionCommandContext, + type ProgressEvent, } from "@mariozechner/pi-coding-agent"; +import { TIMEOUTS } from "../constants.js"; +import { runTaskWithLoader } from "../ui/async-task.js"; import { normalizePackageSource } from "../utils/format.js"; import { fileExists } from "../utils/fs.js"; -import { clearSearchCache } from "./discovery.js"; -import { getPackageCatalog } from "./catalog.js"; -import { discoverPackageExtensionEntrypoints, readPackageManifest } from "./extensions.js"; import { logPackageInstall } from "../utils/history.js"; -import { clearUpdatesAvailable } from "../utils/settings.js"; -import { notify, error as notifyError, success } from "../utils/notify.js"; -import { confirmAction, confirmReload, showProgress } from "../utils/ui-helpers.js"; import { tryOperation } from "../utils/mode.js"; -import { runTaskWithLoader } from "../ui/async-task.js"; -import { updateExtmgrStatus } from "../utils/status.js"; +import { fetchWithTimeout } from "../utils/network.js"; +import { notify, error as notifyError, success } from "../utils/notify.js"; import { execNpm } from "../utils/npm-exec.js"; import { normalizePackageIdentity } from "../utils/package-source.js"; -import { fetchWithTimeout } from "../utils/network.js"; -import { TIMEOUTS } from "../constants.js"; +import { clearUpdatesAvailable } from "../utils/settings.js"; +import { updateExtmgrStatus } from "../utils/status.js"; +import { confirmAction, confirmReload, showProgress } from "../utils/ui-helpers.js"; +import { getPackageCatalog } from "./catalog.js"; +import { clearSearchCache } from "./discovery.js"; +import { discoverPackageExtensionEntrypoints, readPackageManifest } from "./extensions.js"; export type InstallScope = "global" | "project"; @@ -171,7 +171,7 @@ export async function installPackage( // Check if it's a GitHub URL to a .ts file - handle as direct download const githubTsMatch = source.match( - /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.+\.ts)$/ + /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+\.ts)$/ ); const githubInfo = safeExtractGithubMatch(githubTsMatch); if (githubInfo) { @@ -211,6 +211,7 @@ export async function installPackage( title: "Install Package", message: `Installing ${normalized}...`, cancellable: false, + fallbackWithoutLoader: true, }, async ({ setMessage }) => { await getPackageCatalog(ctx.cwd).install(normalized, scope, (event) => { @@ -231,7 +232,7 @@ export async function installPackage( clearSearchCache(); logPackageInstall(pi, normalized, normalized, undefined, scope, true); success(ctx, `Installed ${normalized} (${scope})`); - clearUpdatesAvailable(pi, ctx, [normalizePackageIdentity(normalized)]); + clearUpdatesAvailable(pi, ctx, [normalizePackageIdentity(normalized, { cwd: ctx.cwd })]); const reloaded = await confirmReload(ctx, "Package installed."); if (!reloaded) { diff --git a/src/packages/management.ts b/src/packages/management.ts index 20d789e..f49c689 100644 --- a/src/packages/management.ts +++ b/src/packages/management.ts @@ -1,33 +1,34 @@ /** * Package management (update, remove) */ -import type { - ExtensionAPI, - ExtensionCommandContext, - ProgressEvent, -} from "@mariozechner/pi-coding-agent"; -import type { InstalledPackage } from "../types/index.js"; import { - getInstalledPackages, - getInstalledPackagesAllScopes, - clearSearchCache, -} from "./discovery.js"; -import { getPackageCatalog } from "./catalog.js"; + type ExtensionAPI, + type ExtensionCommandContext, + getAgentDir, + type ProgressEvent, +} from "@mariozechner/pi-coding-agent"; +import { UI } from "../constants.js"; +import { type InstalledPackage } from "../types/index.js"; +import { runTaskWithLoader } from "../ui/async-task.js"; import { formatInstalledPackageLabel } from "../utils/format.js"; +import { logPackageRemove, logPackageUpdate } from "../utils/history.js"; +import { requireUI } from "../utils/mode.js"; +import { notify, error as notifyError, success } from "../utils/notify.js"; import { normalizePackageIdentity } from "../utils/package-source.js"; -import { logPackageUpdate, logPackageRemove } from "../utils/history.js"; import { clearUpdatesAvailable } from "../utils/settings.js"; -import { notify, error as notifyError, success } from "../utils/notify.js"; +import { updateExtmgrStatus } from "../utils/status.js"; import { confirmAction, confirmReload, - showProgress, formatListOutput, + showProgress, } from "../utils/ui-helpers.js"; -import { requireUI } from "../utils/mode.js"; -import { runTaskWithLoader } from "../ui/async-task.js"; -import { updateExtmgrStatus } from "../utils/status.js"; -import { UI } from "../constants.js"; +import { getPackageCatalog } from "./catalog.js"; +import { + clearSearchCache, + getInstalledPackages, + getInstalledPackagesAllScopes, +} from "./discovery.js"; export interface PackageMutationOutcome { reloaded: boolean; @@ -56,7 +57,7 @@ async function updatePackageInternal( ): Promise { showProgress(ctx, "Updating", source); - const updateIdentity = normalizePackageIdentity(source); + const updateIdentity = normalizePackageIdentity(source, { cwd: ctx.cwd }); try { const updates = await getPackageCatalog(ctx.cwd).checkForAvailableUpdates(); @@ -78,6 +79,7 @@ async function updatePackageInternal( title: "Update Package", message: `Updating ${source}...`, cancellable: false, + fallbackWithoutLoader: true, }, async ({ setMessage }) => { await getPackageCatalog(ctx.cwd).update(source, (event) => { @@ -128,6 +130,7 @@ async function updatePackagesInternal( title: "Update Packages", message: "Updating all packages...", cancellable: false, + fallbackWithoutLoader: true, }, async ({ setMessage }) => { await getPackageCatalog(ctx.cwd).update(undefined, (event) => { @@ -186,8 +189,31 @@ export async function updatePackagesWithOutcome( return updatePackagesInternal(ctx, pi); } -function packageIdentity(source: string): string { - return normalizePackageIdentity(source); +function packageIdentity( + source: string, + options?: { resolvedPath?: string; cwd?: string } +): string { + return normalizePackageIdentity(source, options); +} + +function packageSourceIdentities(source: string, ctx: ExtensionCommandContext): Set { + return new Set([ + packageIdentity(source, { cwd: ctx.cwd }), + packageIdentity(source, { cwd: getAgentDir() }), + ]); +} + +function installedPackageMatchesSource( + pkg: InstalledPackage, + identities: Set, + ctx: ExtensionCommandContext +): boolean { + return identities.has( + packageIdentity(pkg.source, { + ...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}), + cwd: pkg.scope === "project" ? ctx.cwd : getAgentDir(), + }) + ); } async function getInstalledPackagesAllScopesForRemoval( @@ -244,7 +270,6 @@ function buildRemovalTargets( return addTarget("global"); case "project": return addTarget("project"); - case "cancel": default: return []; } @@ -285,6 +310,7 @@ async function executeRemovalTargets( title: "Remove Package", message: `Removing ${target.source}...`, cancellable: false, + fallbackWithoutLoader: true, }, async ({ setMessage }) => { await getPackageCatalog(ctx.cwd).remove(target.source, target.scope, (event) => { @@ -338,8 +364,8 @@ async function removePackageInternal( pi: ExtensionAPI ): Promise { const installed = await getInstalledPackagesAllScopesForRemoval(ctx); - const identity = packageIdentity(source); - const matching = installed.filter((p) => packageIdentity(p.source) === identity); + const identities = packageSourceIdentities(source, ctx); + const matching = installed.filter((pkg) => installedPackageMatchesSource(pkg, identities, ctx)); const hasBothScopes = matching.some((pkg) => pkg.scope === "global") && @@ -385,13 +411,13 @@ async function removePackageInternal( .filter((result) => result.success) .map((result) => result.target); - const remaining = (await getInstalledPackagesAllScopesForRemoval(ctx)).filter( - (p) => packageIdentity(p.source) === identity + const remaining = (await getInstalledPackagesAllScopesForRemoval(ctx)).filter((pkg) => + installedPackageMatchesSource(pkg, identities, ctx) ); notifyRemovalSummary(source, remaining, failures, ctx); if (failures.length === 0 && remaining.length === 0) { - clearUpdatesAvailable(pi, ctx, [identity]); + clearUpdatesAvailable(pi, ctx, identities); } const successfulRemovalCount = successfulTargets.length; diff --git a/src/types/index.ts b/src/types/index.ts index f5c1e3c..a9ce88a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -46,25 +46,32 @@ export interface PackageExtensionEntry { state: State; } -export interface UnifiedItem { - type: "local" | "package"; +export interface LocalUnifiedItem { + type: "local"; id: string; displayName: string; summary: string; scope: Scope; - // Local extension fields - state?: State | undefined; - activePath?: string | undefined; - disabledPath?: string | undefined; - originalState?: State | undefined; - // Package fields - source?: string | undefined; + state: State; + activePath: string; + disabledPath: string; + originalState: State; +} + +export interface PackageUnifiedItem { + type: "package"; + id: string; + displayName: string; + scope: Scope; + source: string; version?: string | undefined; description?: string | undefined; size?: number | undefined; // Package size in bytes updateAvailable?: boolean | undefined; } +export type UnifiedItem = LocalUnifiedItem | PackageUnifiedItem; + export interface SearchCache { query: string; results: NpmPackage[]; diff --git a/src/ui/async-task.ts b/src/ui/async-task.ts index bddf198..ae88fae 100644 --- a/src/ui/async-task.ts +++ b/src/ui/async-task.ts @@ -1,9 +1,9 @@ -import type { - ExtensionCommandContext, - ExtensionContext, - Theme, +import { + DynamicBorder, + type ExtensionCommandContext, + type ExtensionContext, + type Theme, } from "@mariozechner/pi-coding-agent"; -import { DynamicBorder } from "@mariozechner/pi-coding-agent"; import { CancellableLoader, Container, Loader, Spacer, Text, type TUI } from "@mariozechner/pi-tui"; import { hasCustomUI } from "../utils/mode.js"; @@ -12,6 +12,8 @@ type AnyContext = ExtensionCommandContext | ExtensionContext; const TASK_ABORTED = Symbol("task-aborted"); const TASK_FAILED = Symbol("task-failed"); +type TaskSuccess = { type: "ok"; value: T }; + export interface TaskControls { signal: AbortSignal; setMessage: (message: string) => void; @@ -21,6 +23,7 @@ interface LoaderConfig { title: string; message: string; cancellable?: boolean; + fallbackWithoutLoader?: boolean; } function createLoaderComponent( @@ -69,82 +72,115 @@ function createLoaderComponent( return { container, loader, signal }; } +function runTaskWithoutLoader(task: (controls: TaskControls) => Promise): Promise { + return Promise.resolve().then(() => + task({ + signal: new AbortController().signal, + setMessage: () => undefined, + }) + ); +} + export async function runTaskWithLoader( ctx: AnyContext, config: LoaderConfig, task: (controls: TaskControls) => Promise ): Promise { if (!hasCustomUI(ctx)) { - return task({ - signal: new AbortController().signal, - setMessage: () => undefined, - }); + return runTaskWithoutLoader(task); } let taskError: unknown; - - const result = await ctx.ui.custom( - (tui, theme, _keybindings, done) => { - let finished = false; - const finish = (value: T | typeof TASK_ABORTED | typeof TASK_FAILED): void => { - if (finished) { - return; - } - finished = true; - done(value); - }; - - const { container, loader, signal } = createLoaderComponent( - tui, - theme, - config.title, - config.message, - config.cancellable ?? true, - () => finish(TASK_ABORTED) - ); - - void task({ + let startedTask: Promise | undefined; + let cleanupStartedTaskUI: (() => void) | undefined; + + const result = await ctx.ui.custom< + TaskSuccess | typeof TASK_ABORTED | typeof TASK_FAILED | undefined + >((tui, theme, _keybindings, done) => { + let finished = false; + const finish = ( + value: TaskSuccess | typeof TASK_ABORTED | typeof TASK_FAILED | undefined + ): void => { + if (finished) { + return; + } + finished = true; + done(value); + }; + + const { container, loader, signal } = createLoaderComponent( + tui, + theme, + config.title, + config.message, + config.cancellable ?? true, + () => finish(TASK_ABORTED) + ); + + cleanupStartedTaskUI = () => { + if (loader instanceof CancellableLoader) { + loader.dispose(); + return; + } + + loader.stop(); + }; + + startedTask = Promise.resolve().then(() => + task({ signal, setMessage: (message) => { loader.setMessage(message); tui.requestRender(); }, }) - .then((value) => finish(value)) - .catch((error) => { - if (signal.aborted) { - finish(TASK_ABORTED); - return; - } - - taskError = error; - finish(TASK_FAILED); - }); - - return { - render(width: number) { - return container.render(width); - }, - invalidate() { - container.invalidate(); - }, - handleInput(data: string) { - if (loader instanceof CancellableLoader) { - loader.handleInput(data); - tui.requestRender(); - } - }, - dispose() { - if (loader instanceof CancellableLoader) { - loader.dispose(); - return; - } + ); - loader.stop(); - }, - }; + void startedTask + .then((value) => finish({ type: "ok", value })) + .catch((error) => { + if (signal.aborted) { + finish(TASK_ABORTED); + return; + } + + taskError = error; + finish(TASK_FAILED); + }); + + return { + render(width: number) { + return container.render(width); + }, + invalidate() { + container.invalidate(); + }, + handleInput(data: string) { + if (loader instanceof CancellableLoader) { + loader.handleInput(data); + tui.requestRender(); + } + }, + dispose() { + if (loader instanceof CancellableLoader) { + loader.dispose(); + return; + } + + loader.stop(); + }, + }; + }); + + if (result === undefined) { + if (startedTask) { + return startedTask.finally(() => cleanupStartedTaskUI?.()); } - ); + if (config.fallbackWithoutLoader) { + return runTaskWithoutLoader(task); + } + return undefined; + } if (result === TASK_ABORTED) { return undefined; @@ -154,5 +190,5 @@ export async function runTaskWithLoader( throw taskError; } - return result; + return result.value; } diff --git a/src/ui/footer.ts b/src/ui/footer.ts index f400f07..a1d80ed 100644 --- a/src/ui/footer.ts +++ b/src/ui/footer.ts @@ -1,10 +1,9 @@ /** * Footer helpers for the unified extension manager UI */ -import type { UnifiedItem, State } from "../types/index.js"; +import { type State, type UnifiedItem } from "../types/index.js"; export interface FooterState { - hasToggleRows: boolean; hasLocals: boolean; hasPackages: boolean; } @@ -13,11 +12,8 @@ export interface FooterState { * Build footer state from visible items. */ export function buildFooterState(items: UnifiedItem[]): FooterState { - const hasLocals = items.some((i) => i.type === "local"); - return { - hasToggleRows: hasLocals, - hasLocals, + hasLocals: items.some((i) => i.type === "local"), hasPackages: items.some((i) => i.type === "package"), }; } @@ -47,8 +43,8 @@ export function buildFooterShortcuts(state: FooterState): string { const parts: string[] = []; parts.push("↑↓ Navigate"); - if (state.hasToggleRows) parts.push("Space/Enter Toggle"); - if (state.hasToggleRows) parts.push("S Save"); + if (state.hasLocals) parts.push("Space/Enter Toggle"); + if (state.hasLocals) parts.push("S Save"); if (state.hasPackages) parts.push("Enter/A Actions"); if (state.hasPackages) parts.push("c Configure"); if (state.hasPackages) parts.push("u Update"); diff --git a/src/ui/help.ts b/src/ui/help.ts index 807222a..08c4306 100644 --- a/src/ui/help.ts +++ b/src/ui/help.ts @@ -1,7 +1,7 @@ /** * Help display */ -import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import { type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; export function showHelp(ctx: ExtensionCommandContext): void { const lines = [ @@ -17,7 +17,7 @@ export function showHelp(ctx: ExtensionCommandContext): void { " Space/Enter Toggle local extension enabled/disabled", " S Save changes to local extensions", " Enter/A Open actions for selected package", - " c Configure selected package extensions (restart required after save)", + " c Configure selected package extensions (reload after save)", " u Update selected package", " X Remove selected item (package or local extension)", " i Quick install by source", diff --git a/src/ui/package-config.ts b/src/ui/package-config.ts index b18ba62..ca6e1f4 100644 --- a/src/ui/package-config.ts +++ b/src/ui/package-config.ts @@ -1,31 +1,37 @@ /** * Package extension configuration panel. */ -import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@mariozechner/pi-coding-agent"; -import { DynamicBorder, getSettingsListTheme } from "@mariozechner/pi-coding-agent"; +import { + DynamicBorder, + type ExtensionAPI, + type ExtensionCommandContext, + getSettingsListTheme, + type Theme, +} from "@mariozechner/pi-coding-agent"; import { Container, Key, matchesKey, + type SettingItem, SettingsList, Spacer, Text, - type SettingItem, } from "@mariozechner/pi-tui"; -import type { InstalledPackage, PackageExtensionEntry, State } from "../types/index.js"; +import { UI } from "../constants.js"; import { applyPackageExtensionStateChanges, discoverPackageExtensions, validatePackageExtensionSettings, } from "../packages/extensions.js"; -import { notify } from "../utils/notify.js"; +import { type InstalledPackage, type PackageExtensionEntry, type State } from "../types/index.js"; +import { fileExists } from "../utils/fs.js"; import { logExtensionToggle } from "../utils/history.js"; import { requireCustomUI, runCustomUI } from "../utils/mode.js"; -import { runTaskWithLoader } from "./async-task.js"; +import { notify } from "../utils/notify.js"; import { getPackageSourceKind } from "../utils/package-source.js"; import { getSettingsListSelectedIndex } from "../utils/settings-list.js"; -import { fileExists } from "../utils/fs.js"; -import { UI } from "../constants.js"; +import { confirmReload } from "../utils/ui-helpers.js"; +import { runTaskWithLoader } from "./async-task.js"; import { getChangeMarker, getPackageIcon, getScopeIcon, getStatusIcon } from "./theme.js"; export interface PackageConfigRow { @@ -168,10 +174,14 @@ async function showConfigurePanel( getSettingsListTheme(), (id: string, newValue: string) => { const row = rowById.get(id); - if (!row || !row.available) return; + if (!row?.available) return; const state = newValue as State; - staged.set(id, state); + if (state === row.originalState) { + staged.delete(id); + } else { + staged.set(id, state); + } const settingsItem = settingsItems.find((item) => item.id === id); if (settingsItem) { @@ -286,35 +296,6 @@ export async function applyPackageExtensionChanges( return { changed: changedRows.length, errors }; } -async function promptRestartForPackageConfig(ctx: ExtensionCommandContext): Promise { - if (!ctx.hasUI) { - notify( - ctx, - "Restart pi to apply package extension configuration changes. /reload may not be enough.", - "warning" - ); - return false; - } - - const restartNow = await ctx.ui.confirm( - "Restart Required", - "Package extension configuration changed.\nA full pi restart is required to apply it.\nExit pi now?" - ); - - if (!restartNow) { - notify( - ctx, - "Restart pi manually to apply package extension configuration changes. /reload may not be enough.", - "warning" - ); - return false; - } - - notify(ctx, "Shutting down pi. Start it again to apply changes.", "info"); - ctx.shutdown(); - return true; -} - export async function configurePackageExtensions( pkg: InstalledPackage, ctx: ExtensionCommandContext, @@ -393,24 +374,31 @@ export async function configurePackageExtensions( const apply = await applyPackageExtensionChanges(rows, staged, pkg, ctx.cwd, pi); + if (apply.changed === 0) { + if (apply.errors.length > 0) { + notify( + ctx, + `Applied ${apply.changed} change(s), ${apply.errors.length} failed.\n${apply.errors.join("\n")}`, + "warning" + ); + continue; + } + + notify(ctx, "No changes to apply.", "info"); + return { changed: 0, reloaded: false }; + } + if (apply.errors.length > 0) { notify( ctx, `Applied ${apply.changed} change(s), ${apply.errors.length} failed.\n${apply.errors.join("\n")}`, "warning" ); - } else if (apply.changed === 0) { - notify(ctx, "No changes to apply.", "info"); - return { changed: 0, reloaded: false }; } else { notify(ctx, `Applied ${apply.changed} package extension change(s).`, "info"); } - if (apply.changed === 0) { - return { changed: 0, reloaded: false }; - } - - const restarted = await promptRestartForPackageConfig(ctx); - return { changed: apply.changed, reloaded: restarted }; + const reloaded = await confirmReload(ctx, "Package extension configuration changed."); + return { changed: apply.changed, reloaded }; } } diff --git a/src/ui/remote.ts b/src/ui/remote.ts index 9f55632..adee817 100644 --- a/src/ui/remote.ts +++ b/src/ui/remote.ts @@ -1,23 +1,26 @@ /** * Remote package browsing UI */ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; -import { DynamicBorder } from "@mariozechner/pi-coding-agent"; -import { Container, SelectList, Text, type SelectItem } from "@mariozechner/pi-tui"; -import type { BrowseAction, NpmPackage } from "../types/index.js"; -import { PAGE_SIZE, TIMEOUTS, CACHE_LIMITS } from "../constants.js"; -import { truncate, dynamicTruncate, formatBytes } from "../utils/format.js"; -import { parseChoiceByLabel, splitCommandArgs } from "../utils/command.js"; import { - searchNpmPackages, + DynamicBorder, + type ExtensionAPI, + type ExtensionCommandContext, +} from "@mariozechner/pi-coding-agent"; +import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui"; +import { CACHE_LIMITS, PAGE_SIZE, TIMEOUTS } from "../constants.js"; +import { getSearchCache, - setSearchCache, isCacheValid, + searchNpmPackages, + setSearchCache, } from "../packages/discovery.js"; import { installPackage, installPackageLocally } from "../packages/install.js"; -import { execNpm } from "../utils/npm-exec.js"; -import { notify } from "../utils/notify.js"; +import { type BrowseAction, type NpmPackage } from "../types/index.js"; +import { parseChoiceByLabel, splitCommandArgs } from "../utils/command.js"; +import { dynamicTruncate, formatBytes, truncate } from "../utils/format.js"; import { requireCustomUI, runCustomUI } from "../utils/mode.js"; +import { notify } from "../utils/notify.js"; +import { execNpm } from "../utils/npm-exec.js"; import { runTaskWithLoader } from "./async-task.js"; interface PackageInfoCacheEntry { diff --git a/src/ui/theme.ts b/src/ui/theme.ts index 614cf42..fbacfac 100644 --- a/src/ui/theme.ts +++ b/src/ui/theme.ts @@ -1,7 +1,7 @@ /** * Theme utilities for consistent UI styling across dark/light themes */ -import type { Theme } from "@mariozechner/pi-coding-agent"; +import { type Theme } from "@mariozechner/pi-coding-agent"; /** * Status icons that work across themes @@ -62,7 +62,7 @@ export function getScopeIcon( */ export function getChangeMarker(theme: Theme, hasChanges: boolean): string { if (!hasChanges) return ""; - return " " + theme.fg("warning", "*"); + return ` ${theme.fg("warning", "*")}`; } /** diff --git a/src/ui/unified.ts b/src/ui/unified.ts index cee1094..84f34ab 100644 --- a/src/ui/unified.ts +++ b/src/ui/unified.ts @@ -2,19 +2,23 @@ * Unified extension manager UI * Displays local extensions and installed packages in one view */ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; -import type { Theme } from "@mariozechner/pi-coding-agent"; -import { getSettingsListTheme, DynamicBorder } from "@mariozechner/pi-coding-agent"; +import { + DynamicBorder, + type ExtensionAPI, + type ExtensionCommandContext, + getSettingsListTheme, + type Theme, +} from "@mariozechner/pi-coding-agent"; import { Container, + Key, + matchesKey, + type SettingItem, SettingsList, - Text, Spacer, - type SettingItem, - matchesKey, - Key, + Text, } from "@mariozechner/pi-tui"; -import type { UnifiedItem, State, UnifiedAction, InstalledPackage } from "../types/index.js"; +import { UI } from "../constants.js"; import { discoverExtensions, removeLocalExtension, @@ -22,34 +26,40 @@ import { } from "../extensions/discovery.js"; import { getInstalledPackages } from "../packages/discovery.js"; import { - updatePackageWithOutcome, removePackageWithOutcome, - updatePackagesWithOutcome, showInstalledPackagesList, + updatePackagesWithOutcome, + updatePackageWithOutcome, } from "../packages/management.js"; -import { showRemote } from "./remote.js"; -import { showHelp } from "./help.js"; -import { runTaskWithLoader } from "./async-task.js"; -import { formatEntry as formatExtEntry, dynamicTruncate, formatBytes } from "../utils/format.js"; import { - getStatusIcon, - getPackageIcon, - getScopeIcon, - getChangeMarker, - formatSize, -} from "./theme.js"; -import { buildFooterState, buildFooterShortcuts, getPendingToggleChangeCount } from "./footer.js"; -import { logExtensionDelete, logExtensionToggle } from "../utils/history.js"; + type InstalledPackage, + type LocalUnifiedItem, + type State, + type UnifiedAction, + type UnifiedItem, +} from "../types/index.js"; import { getKnownUpdates, promptAutoUpdateWizard } from "../utils/auto-update.js"; -import { updateExtmgrStatus } from "../utils/status.js"; import { parseChoiceByLabel } from "../utils/command.js"; +import { dynamicTruncate, formatBytes, formatEntry as formatExtEntry } from "../utils/format.js"; +import { logExtensionDelete, logExtensionToggle } from "../utils/history.js"; +import { hasCustomUI, runCustomUI } from "../utils/mode.js"; import { notify } from "../utils/notify.js"; -import { confirmReload } from "../utils/ui-helpers.js"; import { getPackageSourceKind, normalizePackageIdentity } from "../utils/package-source.js"; -import { hasCustomUI, runCustomUI } from "../utils/mode.js"; import { getSettingsListSelectedIndex } from "../utils/settings-list.js"; -import { UI } from "../constants.js"; +import { updateExtmgrStatus } from "../utils/status.js"; +import { confirmReload } from "../utils/ui-helpers.js"; +import { runTaskWithLoader } from "./async-task.js"; +import { buildFooterShortcuts, buildFooterState, getPendingToggleChangeCount } from "./footer.js"; +import { showHelp } from "./help.js"; import { configurePackageExtensions } from "./package-config.js"; +import { showRemote } from "./remote.js"; +import { + formatSize, + getChangeMarker, + getPackageIcon, + getScopeIcon, + getStatusIcon, +} from "./theme.js"; async function showInteractiveFallback( ctx: ExtensionCommandContext, @@ -190,8 +200,8 @@ async function showInteractiveOnce( if (!item) continue; if (item.type === "local") { - const currentState = staged.get(item.id) ?? item.state!; - const changed = staged.has(item.id) && currentState !== item.originalState; + const currentState = staged.get(item.id) ?? item.state; + const changed = currentState !== item.originalState; settingsItem.label = formatUnifiedItemLabel(item, currentState, theme, changed); } else { settingsItem.label = formatUnifiedItemLabel(item, "enabled", theme, false); @@ -209,7 +219,11 @@ async function showInteractiveOnce( if (!item || item.type !== "local") return; const state = newValue as State; - staged.set(id, state); + if (state === item.originalState) { + staged.delete(id); + } else { + staged.set(id, state); + } const settingsItem = settingsItems.find((x) => x.id === id); if (settingsItem) { @@ -380,16 +394,7 @@ export function buildUnifiedItems( isDuplicate = true; break; } - if ( - pkgResolvedNormalized && - (localPath.startsWith(`${pkgResolvedNormalized}/`) || - pkgResolvedNormalized.startsWith(localPath)) - ) { - isDuplicate = true; - break; - } - const localDir = localPath.split("/").slice(0, -1).join("/"); - if (pkgResolvedNormalized && pkgResolvedNormalized === localDir) { + if (pkgResolvedNormalized && localPath.startsWith(`${pkgResolvedNormalized}/`)) { isDuplicate = true; break; } @@ -400,7 +405,6 @@ export function buildUnifiedItems( type: "package", id: `pkg:${pkg.source}`, displayName: pkg.name, - summary: pkg.description || `${pkg.source} (${pkg.scope})`, scope: pkg.scope, source: pkg.source, version: pkg.version, @@ -432,8 +436,8 @@ function buildSettingsItems( ): SettingItem[] { return items.map((item) => { if (item.type === "local") { - const currentState = staged.get(item.id) ?? item.state!; - const changed = staged.has(item.id) && staged.get(item.id) !== item.originalState; + const currentState = staged.get(item.id) ?? item.state; + const changed = currentState !== item.originalState; return { id: item.id, label: formatUnifiedItemLabel(item, currentState, theme, changed), @@ -458,7 +462,7 @@ function formatUnifiedItemLabel( changed = false ): string { if (item.type === "local") { - const statusIcon = getStatusIcon(theme, state === "enabled" ? "enabled" : "disabled"); + const statusIcon = getStatusIcon(theme, state); const scopeIcon = getScopeIcon(theme, item.scope); const changeMarker = getChangeMarker(theme, changed); const name = theme.bold(item.displayName); @@ -466,7 +470,7 @@ function formatUnifiedItemLabel( return `${statusIcon} [${scopeIcon}] ${name} - ${summary}${changeMarker}`; } - const sourceKind = getPackageSourceKind(item.source ?? ""); + const sourceKind = getPackageSourceKind(item.source); const pkgIcon = getPackageIcon( theme, sourceKind === "npm" || sourceKind === "git" || sourceKind === "local" ? sourceKind : "local" @@ -500,8 +504,8 @@ function formatUnifiedItemLabel( return `${pkgIcon} [${scopeIcon}] ${name}${version}${updateBadge} - ${summary}`; } -function getToggleItemsForApply(items: UnifiedItem[]): UnifiedItem[] { - return items.filter((item) => item.type === "local"); +function getToggleItemsForApply(items: UnifiedItem[]): LocalUnifiedItem[] { + return items.filter((item): item is LocalUnifiedItem => item.type === "local"); } async function applyToggleChangesFromManager( @@ -510,7 +514,7 @@ async function applyToggleChangesFromManager( ctx: ExtensionCommandContext, pi: ExtensionAPI, options?: { promptReload?: boolean } -): Promise<{ changed: number; reloaded: boolean }> { +): Promise<{ changed: number; reloaded: boolean; hasErrors: boolean }> { const toggleItems = getToggleItemsForApply(items); const apply = await applyStagedChanges(toggleItems, staged, pi); @@ -529,24 +533,14 @@ async function applyToggleChangesFromManager( const shouldPromptReload = options?.promptReload ?? true; if (shouldPromptReload) { - const shouldReload = await ctx.ui.confirm( - "Reload Required", - "Local extensions changed. Reload pi now?" - ); - - if (shouldReload) { - await ctx.reload(); - return { changed: apply.changed, reloaded: true }; - } - } else { - ctx.ui.notify( - "Changes saved. Reload pi later to fully apply extension state updates.", - "info" - ); + const reloaded = await confirmReload(ctx, "Local extensions changed."); + return { changed: apply.changed, reloaded, hasErrors: apply.errors.length > 0 }; } + + ctx.ui.notify("Changes saved. Reload pi later to fully apply extension state updates.", "info"); } - return { changed: apply.changed, reloaded: false }; + return { changed: apply.changed, reloaded: false, hasErrors: apply.errors.length > 0 }; } async function resolvePendingChangesBeforeLeave( @@ -556,7 +550,7 @@ async function resolvePendingChangesBeforeLeave( ctx: ExtensionCommandContext, pi: ExtensionAPI, destinationLabel: string -): Promise<"continue" | "stay" | "exit"> { +): Promise<"continue" | "stay"> { const pendingCount = getPendingToggleChangeCount(staged, byId); if (pendingCount === 0) return "continue"; @@ -574,10 +568,10 @@ async function resolvePendingChangesBeforeLeave( return "continue"; } - const result = await applyToggleChangesFromManager(items, staged, ctx, pi, { + const apply = await applyToggleChangesFromManager(items, staged, ctx, pi, { promptReload: false, }); - return result.reloaded ? "exit" : "continue"; + return apply.changed === 0 && apply.hasErrors ? "stay" : "continue"; } const PALETTE_OPTIONS = { @@ -648,7 +642,6 @@ async function navigateWithPendingGuard( QUICK_DESTINATION_LABELS[destination] ); if (pending === "stay") return "stay"; - if (pending === "exit") return "exit"; switch (destination) { case "install": @@ -703,6 +696,7 @@ async function handleUnifiedAction( if (choice === "Save and exit") { const apply = await applyToggleChangesFromManager(items, staged, ctx, pi); if (apply.reloaded) return true; + if (apply.changed === 0 && apply.hasErrors) return false; } } @@ -712,7 +706,6 @@ async function handleUnifiedAction( if (result.type === "remote") { const pending = await resolvePendingChangesBeforeLeave(items, staged, byId, ctx, pi, "Remote"); if (pending === "stay") return false; - if (pending === "exit") return true; await showRemote("", ctx, pi); return false; @@ -721,7 +714,6 @@ async function handleUnifiedAction( if (result.type === "help") { const pending = await resolvePendingChangesBeforeLeave(items, staged, byId, ctx, pi, "Help"); if (pending === "stay") return false; - if (pending === "exit") return true; showHelp(ctx); return false; @@ -778,7 +770,6 @@ async function handleUnifiedAction( pendingDestination ); if (pending === "stay") return false; - if (pending === "exit") return true; if (item.type === "local") { if (result.action !== "remove") return false; @@ -790,7 +781,7 @@ async function handleUnifiedAction( if (!confirmed) return false; const removal = await removeLocalExtension( - { activePath: item.activePath!, disabledPath: item.disabledPath! }, + { activePath: item.activePath, disabledPath: item.disabledPath }, ctx.cwd ); if (!removal.ok) { @@ -805,16 +796,11 @@ async function handleUnifiedAction( "info" ); - const reloaded = await confirmReload(ctx, "Extension removed."); - if (reloaded) { - return true; - } - - return false; + return await confirmReload(ctx, "Extension removed."); } const pkg: InstalledPackage = { - source: item.source!, + source: item.source, name: item.displayName, ...(item.version ? { version: item.version } : {}), scope: item.scope, @@ -860,7 +846,7 @@ async function handleUnifiedAction( } async function applyStagedChanges( - items: UnifiedItem[], + items: LocalUnifiedItem[], staged: Map, pi: ExtensionAPI ) { @@ -868,10 +854,6 @@ async function applyStagedChanges( const errors: string[] = []; for (const item of items) { - if (item.type !== "local" || !item.originalState || !item.activePath || !item.disabledPath) { - continue; - } - const target = staged.get(item.id) ?? item.originalState; if (target === item.originalState) continue; diff --git a/src/utils/auto-update.ts b/src/utils/auto-update.ts index eb3e0ff..4619ef2 100644 --- a/src/utils/auto-update.ts +++ b/src/utils/auto-update.ts @@ -1,25 +1,25 @@ /** * Auto-update logic and background checker */ -import type { - ExtensionAPI, - ExtensionCommandContext, - ExtensionContext, +import { + type ExtensionAPI, + type ExtensionCommandContext, + type ExtensionContext, } from "@mariozechner/pi-coding-agent"; import { getPackageCatalog } from "../packages/catalog.js"; +import { logAutoUpdateConfig } from "./history.js"; import { notify } from "./notify.js"; +import { normalizePackageIdentity } from "./package-source.js"; import { + type AutoUpdateConfig, + calculateNextCheck, getAutoUpdateConfig, - saveAutoUpdateConfig, getScheduleInterval, - calculateNextCheck, parseDuration, - type AutoUpdateConfig, + saveAutoUpdateConfig, } from "./settings.js"; -import { normalizePackageIdentity } from "./package-source.js"; -import { logAutoUpdateConfig } from "./history.js"; -import { startTimer, stopTimer, isTimerRunning } from "./timer.js"; +import { isTimerRunning, startTimer, stopTimer } from "./timer.js"; // Context provider for safe session handling export type ContextProvider = () => (ExtensionCommandContext | ExtensionContext) | undefined; diff --git a/src/utils/cache.ts b/src/utils/cache.ts index ad84d53..16b82e1 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -1,11 +1,11 @@ /** * Persistent cache for package metadata to reduce npm API calls */ -import { readFile, writeFile, mkdir, access, rename, rm } from "node:fs/promises"; -import { join } from "node:path"; +import { access, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; -import type { NpmPackage, InstalledPackage } from "../types/index.js"; +import { join } from "node:path"; import { CACHE_LIMITS } from "../constants.js"; +import { type InstalledPackage, type NpmPackage } from "../types/index.js"; import { parseNpmSource } from "./format.js"; const CACHE_DIR = process.env.PI_EXTMGR_CACHE_DIR diff --git a/src/utils/command.ts b/src/utils/command.ts index 0243ae2..0a9dc80 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -18,7 +18,7 @@ export function tokenizeArgs(input: string): string[] { }; for (let i = 0; i < input.length; i++) { - const char = input[i]!; + const char = input.charAt(i); const next = input[i + 1]; if (inSingleQuote) { diff --git a/src/utils/format.ts b/src/utils/format.ts index 57379da..85d499f 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -1,11 +1,12 @@ /** * Formatting utilities */ -import type { ExtensionEntry, InstalledPackage } from "../types/index.js"; +import { type ExtensionEntry, type InstalledPackage } from "../types/index.js"; export function truncate(text: string, maxLength: number): string { if (text.length <= maxLength) return text; - return text.slice(0, maxLength - 3) + "..."; + if (maxLength <= 3) return text.slice(0, maxLength); + return `${text.slice(0, maxLength - 3)}...`; } /** @@ -54,7 +55,7 @@ export function formatBytes(bytes: number): string { const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; + return `${parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`; } const GIT_PATTERNS = { diff --git a/src/utils/history.ts b/src/utils/history.ts index 9f851d6..741d8f1 100644 --- a/src/utils/history.ts +++ b/src/utils/history.ts @@ -2,10 +2,12 @@ * Extension change history tracking using pi.appendEntry() * This persists extension management actions to the session */ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; + +import { type Dirent } from "node:fs"; import { readdir, readFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join } from "node:path"; +import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; export type ChangeAction = | "extension_toggle" @@ -278,7 +280,7 @@ function isRecord(value: unknown): value is Record { async function walkSessionFiles(dir: string): Promise { const result: string[] = []; - let entries; + let entries: Dirent[]; try { entries = await readdir(dir, { withFileTypes: true, encoding: "utf8" }); } catch { diff --git a/src/utils/mode.ts b/src/utils/mode.ts index 479fbe3..840401e 100644 --- a/src/utils/mode.ts +++ b/src/utils/mode.ts @@ -1,7 +1,7 @@ /** * UI capability helpers */ -import type { ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import { type ExtensionCommandContext, type ExtensionContext } from "@mariozechner/pi-coding-agent"; import { notify } from "./notify.js"; type AnyContext = ExtensionCommandContext | ExtensionContext; diff --git a/src/utils/notify.ts b/src/utils/notify.ts index 98f52cc..2ba3657 100644 --- a/src/utils/notify.ts +++ b/src/utils/notify.ts @@ -1,7 +1,7 @@ /** * Centralized notification handling for UI and non-UI modes */ -import type { ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import { type ExtensionCommandContext, type ExtensionContext } from "@mariozechner/pi-coding-agent"; export type NotifyLevel = "info" | "warning" | "error"; diff --git a/src/utils/npm-exec.ts b/src/utils/npm-exec.ts index 077182b..e7dff6d 100644 --- a/src/utils/npm-exec.ts +++ b/src/utils/npm-exec.ts @@ -1,6 +1,6 @@ import path from "node:path"; import { execPath, platform } from "node:process"; -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { type ExtensionAPI } from "@mariozechner/pi-coding-agent"; interface NpmCommandResolutionOptions { platform?: NodeJS.Platform; diff --git a/src/utils/package-source.ts b/src/utils/package-source.ts index 18a21af..41a41ef 100644 --- a/src/utils/package-source.ts +++ b/src/utils/package-source.ts @@ -1,6 +1,9 @@ /** * Package source parsing helpers shared across discovery/management flows. */ +import { homedir } from "node:os"; +import { join, resolve as resolvePath } from "node:path"; +import { fileURLToPath } from "node:url"; import { parseNpmSource } from "./format.js"; export type PackageSourceKind = "npm" | "git" | "local" | "unknown"; @@ -61,9 +64,35 @@ export function stripGitSourcePrefix(source: string): string { return withoutGitPlus.startsWith("git:") ? withoutGitPlus.slice(4) : withoutGitPlus; } +function resolveLocalSourceForIdentity(source: string, cwd?: string): string { + if (source.startsWith("file://")) { + try { + return fileURLToPath(source); + } catch { + return source; + } + } + + if (source.startsWith("~/")) { + return join(homedir(), source.slice(2)); + } + + if ( + cwd && + (source.startsWith("./") || + source.startsWith("../") || + source.startsWith(".\\") || + source.startsWith("..\\")) + ) { + return resolvePath(cwd, source); + } + + return source; +} + export function normalizePackageIdentity( source: string, - options?: { resolvedPath?: string } + options?: { resolvedPath?: string; cwd?: string } ): string { const normalized = sanitizeSource(source); const kind = getPackageSourceKind(normalized); @@ -80,7 +109,9 @@ export function normalizePackageIdentity( } if (kind === "local") { - return `local:${normalizeLocalSourceIdentity(options?.resolvedPath ?? normalized)}`; + const localSource = + options?.resolvedPath ?? resolveLocalSourceForIdentity(normalized, options?.cwd); + return `local:${normalizeLocalSourceIdentity(localSource)}`; } return `raw:${normalized.replace(/\\/g, "/").toLowerCase()}`; diff --git a/src/utils/retry.ts b/src/utils/retry.ts index d0b5a8f..30a95cd 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -23,7 +23,7 @@ export async function retryWithBackoff( if (attempt < maxAttempts) { const delay = backoff === "exponential" - ? delayMs * Math.pow(2, attempt - 1) + ? delayMs * 2 ** (attempt - 1) : backoff === "linear" ? delayMs * attempt : delayMs; diff --git a/src/utils/settings.ts b/src/utils/settings.ts index c2c87d0..856590b 100644 --- a/src/utils/settings.ts +++ b/src/utils/settings.ts @@ -2,14 +2,15 @@ * Auto-update settings storage * Persists to disk so config survives across pi sessions. */ -import type { - ExtensionAPI, - ExtensionCommandContext, - ExtensionContext, -} from "@mariozechner/pi-coding-agent"; -import { readFile, writeFile, mkdir, rename, rm } from "node:fs/promises"; + +import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join } from "node:path"; +import { + type ExtensionAPI, + type ExtensionCommandContext, + type ExtensionContext, +} from "@mariozechner/pi-coding-agent"; import { fileExists } from "./fs.js"; import { normalizePackageIdentity } from "./package-source.js"; @@ -328,8 +329,16 @@ export function parseDuration(input: string): { ms: number; display: string } | /^(\d+)\s*(h|hr|hrs|hour|hours|d|day|days|w|wk|wks|week|weeks|m|mo|mos|month|months)$/ ); if (durationMatch) { - const value = parseInt(durationMatch[1]!, 10); - const unit = durationMatch[2]![0]; // First character of unit + const [, rawValue, rawUnit] = durationMatch; + if (!rawValue || !rawUnit) { + return undefined; + } + + const value = Number.parseInt(rawValue, 10); + const unit = rawUnit[0]; + if (!unit) { + return undefined; + } let ms: number; let display: string; diff --git a/src/utils/status.ts b/src/utils/status.ts index cc1b546..541ead6 100644 --- a/src/utils/status.ts +++ b/src/utils/status.ts @@ -1,10 +1,11 @@ /** * Status bar helpers for extmgr */ -import type { - ExtensionAPI, - ExtensionCommandContext, - ExtensionContext, +import { + type ExtensionAPI, + type ExtensionCommandContext, + type ExtensionContext, + getAgentDir, } from "@mariozechner/pi-coding-agent"; import { getPackageCatalog, type PackageCatalog } from "../packages/catalog.js"; import { getAutoUpdateStatus } from "./auto-update.js"; @@ -15,14 +16,15 @@ type CatalogInstalledPackages = Awaited - normalizePackageIdentity( - pkg.source, - pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : undefined - ) + normalizePackageIdentity(pkg.source, { + ...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}), + cwd: pkg.scope === "project" ? cwd : getAgentDir(), + }) ) ); return knownUpdates.filter((identity) => installedIdentities.has(identity)); @@ -52,7 +54,7 @@ export async function updateExtmgrStatus( // Validate updates against actually installed packages (handles external pi update) const knownUpdates = autoUpdateConfig.updatesAvailable ?? []; - const validUpdates = filterStaleUpdates(knownUpdates, packages); + const validUpdates = filterStaleUpdates(knownUpdates, packages, ctx.cwd); // If stale updates were filtered, persist the correction if (validUpdates.length !== knownUpdates.length) { diff --git a/src/utils/ui-helpers.ts b/src/utils/ui-helpers.ts index 459ecbf..7e6c18a 100644 --- a/src/utils/ui-helpers.ts +++ b/src/utils/ui-helpers.ts @@ -1,9 +1,9 @@ /** * Common UI helper patterns */ -import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; -import { notify } from "./notify.js"; +import { type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import { UI } from "../constants.js"; +import { notify } from "./notify.js"; /** * Confirm and trigger reload diff --git a/test/async-task.test.ts b/test/async-task.test.ts new file mode 100644 index 0000000..b0e46e0 --- /dev/null +++ b/test/async-task.test.ts @@ -0,0 +1,112 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { runTaskWithLoader } from "../src/ui/async-task.js"; + +void test("runTaskWithLoader falls back to running the task when custom UI degrades", async () => { + let runs = 0; + + const result = await runTaskWithLoader( + { + hasUI: true, + ui: { + custom: async () => undefined, + }, + } as never, + { + title: "Test", + message: "Running...", + cancellable: false, + fallbackWithoutLoader: true, + }, + async () => { + runs += 1; + return "ok"; + } + ); + + assert.equal(result, "ok"); + assert.equal(runs, 1); +}); + +void test("runTaskWithLoader does not rerun the task when custom UI returned undefined after starting it", async () => { + let runs = 0; + + const result = await runTaskWithLoader( + { + hasUI: true, + ui: { + custom: async ( + factory: ( + tui: unknown, + theme: unknown, + keybindings: unknown, + done: (value: unknown) => void + ) => unknown + ) => { + factory( + { requestRender: () => undefined }, + { fg: (_name: string, text: string) => text, bold: (text: string) => text }, + {}, + () => undefined + ); + return undefined; + }, + }, + } as never, + { + title: "Test", + message: "Running...", + cancellable: false, + fallbackWithoutLoader: true, + }, + async () => { + runs += 1; + return "ok"; + } + ); + + assert.equal(result, "ok"); + assert.equal(runs, 1); +}); + +void test("runTaskWithLoader preserves undefined task results without treating them as cancellation", async () => { + let runs = 0; + + const result = await runTaskWithLoader( + { + hasUI: false, + } as never, + { + title: "Test", + message: "Running...", + cancellable: false, + }, + async () => { + runs += 1; + return undefined; + } + ); + + assert.equal(result, undefined); + assert.equal(runs, 1); +}); + +void test("runTaskWithLoader surfaces synchronous task throws as rejections", async () => { + await assert.rejects( + () => + runTaskWithLoader( + { + hasUI: false, + } as never, + { + title: "Test", + message: "Running...", + cancellable: false, + }, + () => { + throw new Error("boom"); + } + ), + /boom/ + ); +}); diff --git a/test/auto-update.test.ts b/test/auto-update.test.ts index b53d435..25c532d 100644 --- a/test/auto-update.test.ts +++ b/test/auto-update.test.ts @@ -1,6 +1,6 @@ -import test from "node:test"; import assert from "node:assert/strict"; -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import test from "node:test"; +import { type ExtensionAPI } from "@mariozechner/pi-coding-agent"; import extensionsManager from "../src/index.js"; import { setPackageCatalogFactory } from "../src/packages/catalog.js"; import { @@ -138,10 +138,10 @@ void test("session switch to disabled auto-update stops existing timer", async ( try { extensionsManager(pi as unknown as ExtensionAPI); - await handlers["session_start"]?.({}, enabledCtx); + await handlers.session_start?.({}, enabledCtx); assert.equal(isAutoUpdateRunning(), true); - await handlers["session_switch"]?.({}, disabledCtx); + await handlers.session_switch?.({}, disabledCtx); assert.equal(isAutoUpdateRunning(), false); } finally { restoreCatalog(); diff --git a/test/cache-history.test.ts b/test/cache-history.test.ts index c6b3971..d69a470 100644 --- a/test/cache-history.test.ts +++ b/test/cache-history.test.ts @@ -1,6 +1,6 @@ -import test from "node:test"; import assert from "node:assert/strict"; -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import test from "node:test"; +import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import { clearMetadataCacheCommand } from "../src/commands/cache.js"; import { getSearchCache, setSearchCache } from "../src/packages/discovery.js"; import { @@ -56,6 +56,10 @@ void test("history records local extension deletion and auto-update config chang changes.map((change) => change.action), ["extension_delete", "auto_update_config"] ); - assert.match(formatChangeEntry(changes[0]!), /Deleted/); - assert.match(formatChangeEntry(changes[1]!), /Auto-update set to weekly/); + + const [firstChange, secondChange] = changes; + assert.ok(firstChange); + assert.ok(secondChange); + assert.match(formatChangeEntry(firstChange), /Deleted/); + assert.match(formatChangeEntry(secondChange), /Auto-update set to weekly/); }); diff --git a/test/command-orchestration.test.ts b/test/command-orchestration.test.ts index d318791..3ac943c 100644 --- a/test/command-orchestration.test.ts +++ b/test/command-orchestration.test.ts @@ -1,5 +1,5 @@ -import test from "node:test"; import assert from "node:assert/strict"; +import test from "node:test"; import { getExtensionsAutocompleteItems, resolveCommand, diff --git a/test/command-tokenizer.test.ts b/test/command-tokenizer.test.ts index 19aa588..cccde3d 100644 --- a/test/command-tokenizer.test.ts +++ b/test/command-tokenizer.test.ts @@ -1,5 +1,5 @@ -import test from "node:test"; import assert from "node:assert/strict"; +import test from "node:test"; import { tokenizeArgs } from "../src/utils/command.js"; void test("tokenizeArgs preserves legacy whitespace splitting", () => { diff --git a/test/discovery-parser.test.ts b/test/discovery-parser.test.ts index a603dd0..ad51960 100644 --- a/test/discovery-parser.test.ts +++ b/test/discovery-parser.test.ts @@ -1,8 +1,9 @@ -import test from "node:test"; import assert from "node:assert/strict"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import test from "node:test"; +import { getAgentDir } from "@mariozechner/pi-coding-agent"; import { getInstalledPackages, isSourceInstalled } from "../src/packages/discovery.js"; import { isPackageSource, normalizePackageSource, parseNpmSource } from "../src/utils/format.js"; import { getPackageSourceKind, normalizePackageIdentity } from "../src/utils/package-source.js"; @@ -119,6 +120,67 @@ void test("isSourceInstalled keeps case-sensitive local paths distinct", async ( } }); +void test("isSourceInstalled resolves project-relative local paths against cwd", async () => { + const cwd = "/workspace/project"; + const restoreCatalog = mockPackageCatalog({ + packages: [ + { + source: "./vendor/demo", + name: "demo", + scope: "project", + resolvedPath: "/workspace/project/vendor/demo", + }, + ], + }); + + try { + const { ctx } = createMockHarness({ cwd }); + assert.equal(await isSourceInstalled("./vendor/demo", ctx), true); + assert.equal(await isSourceInstalled("/workspace/project/vendor/demo", ctx), true); + } finally { + restoreCatalog(); + } +}); + +void test("isSourceInstalled resolves project-relative local paths without resolvedPath metadata", async () => { + const cwd = "/workspace/project"; + const restoreCatalog = mockPackageCatalog({ + packages: [ + { + source: "./vendor/demo", + name: "demo", + scope: "project", + }, + ], + }); + + try { + const { ctx } = createMockHarness({ cwd }); + assert.equal(await isSourceInstalled("/workspace/project/vendor/demo", ctx), true); + } finally { + restoreCatalog(); + } +}); + +void test("isSourceInstalled resolves global-relative local paths against agent dir", async () => { + const restoreCatalog = mockPackageCatalog({ + packages: [ + { + source: "./vendor/demo", + name: "demo", + scope: "global", + }, + ], + }); + + try { + const { ctx } = createMockHarness({ cwd: "/workspace/project" }); + assert.equal(await isSourceInstalled(`${getAgentDir()}/vendor/demo`, ctx), true); + } finally { + restoreCatalog(); + } +}); + void test("normalizePackageSource preserves git and local path sources", () => { assert.equal( normalizePackageSource("git@github.com:user/repo.git"), diff --git a/test/format.test.ts b/test/format.test.ts new file mode 100644 index 0000000..703860a --- /dev/null +++ b/test/format.test.ts @@ -0,0 +1,15 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { truncate } from "../src/utils/format.js"; + +void test("truncate never exceeds maxLength when maxLength is 3 or less", () => { + assert.equal(truncate("abcdef", 3), "abc"); + assert.equal(truncate("abcdef", 2), "ab"); + assert.equal(truncate("abcdef", 1), "a"); + assert.equal(truncate("abcdef", 0), ""); +}); + +void test("truncate keeps ellipsis behavior for longer limits", () => { + assert.equal(truncate("abcdef", 4), "a..."); + assert.equal(truncate("abcdef", 5), "ab..."); +}); diff --git a/test/helpers/mocks.ts b/test/helpers/mocks.ts index 912a19b..757f281 100644 --- a/test/helpers/mocks.ts +++ b/test/helpers/mocks.ts @@ -1,4 +1,4 @@ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; export interface ExecResult { code: number; @@ -43,6 +43,7 @@ export function createMockHarness(options: MockHarnessOptions = {}): { selectPrompts: string[]; confirmPrompts: string[]; customCallCount: () => number; + reloadCount: () => number; } { const calls: ExecCall[] = []; const entries: { type: "custom"; customType: string; data: unknown }[] = []; @@ -52,6 +53,7 @@ export function createMockHarness(options: MockHarnessOptions = {}): { const selectPrompts: string[] = []; const confirmPrompts: string[] = []; let customCalls = 0; + let reloadCalls = 0; const installedRecords: { source: string; scope: "global" | "project" }[] = []; const customExecImpl = options.execImpl; @@ -170,6 +172,10 @@ export function createMockHarness(options: MockHarnessOptions = {}): { hasUI: options.hasUI ?? false, cwd: options.cwd ?? "/tmp", ui, + reload: () => { + reloadCalls += 1; + return Promise.resolve(); + }, sessionManager: { getEntries: () => entries, }, @@ -186,5 +192,6 @@ export function createMockHarness(options: MockHarnessOptions = {}): { selectPrompts, confirmPrompts, customCallCount: () => customCalls, + reloadCount: () => reloadCalls, }; } diff --git a/test/helpers/package-catalog.ts b/test/helpers/package-catalog.ts index 8531eff..396fe06 100644 --- a/test/helpers/package-catalog.ts +++ b/test/helpers/package-catalog.ts @@ -1,10 +1,10 @@ -import type { ProgressEvent } from "@mariozechner/pi-coding-agent"; -import type { InstalledPackage, Scope } from "../../src/types/index.js"; +import { type ProgressEvent } from "@mariozechner/pi-coding-agent"; import { - setPackageCatalogFactory, type AvailablePackageUpdate, type PackageCatalog, + setPackageCatalogFactory, } from "../../src/packages/catalog.js"; +import { type InstalledPackage, type Scope } from "../../src/types/index.js"; import { normalizePackageIdentity, parsePackageNameAndVersion, diff --git a/test/install-remove.test.ts b/test/install-remove.test.ts index e22544e..810d992 100644 --- a/test/install-remove.test.ts +++ b/test/install-remove.test.ts @@ -1,9 +1,9 @@ -import test from "node:test"; import assert from "node:assert/strict"; import { access, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import test from "node:test"; +import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import { installFromUrl, installPackage, installPackageLocally } from "../src/packages/install.js"; import { removePackage, updatePackage, updatePackages } from "../src/packages/management.js"; import { createMockHarness } from "./helpers/mocks.js"; @@ -210,6 +210,122 @@ void test("removePackage keeps case-sensitive local paths distinct", async () => assert.deepEqual(removals, [{ source: "/opt/extensions/foo/index.ts", scope: "global" }]); }); +void test("removePackage resolves equivalent absolute local paths against cwd", async () => { + const removals: { source: string; scope: "global" | "project" }[] = []; + const cwd = "/workspace/project"; + const restoreCatalog = mockPackageCatalog({ + packages: [ + { + source: "./vendor/demo", + name: "demo", + scope: "project", + resolvedPath: "/workspace/project/vendor/demo", + }, + ], + removeImpl: (source, scope) => { + removals.push({ source, scope }); + }, + }); + + try { + const { pi, ctx } = createMockHarness({ cwd }); + await removePackage("/workspace/project/vendor/demo", ctx, pi); + } finally { + restoreCatalog(); + } + + assert.deepEqual(removals, [{ source: "./vendor/demo", scope: "project" }]); +}); + +void test("removePackage keeps remaining project-relative installs visible after scoped removal", async () => { + const removals: { source: string; scope: "global" | "project" }[] = []; + const cwd = "/workspace/project"; + const restoreCatalog = mockPackageCatalog({ + packages: [ + { + source: "./vendor/demo", + name: "demo", + scope: "project", + resolvedPath: "/workspace/project/vendor/demo", + }, + { + source: "/workspace/project/vendor/demo", + name: "demo", + scope: "global", + }, + ], + removeImpl: (source, scope) => { + removals.push({ source, scope }); + }, + }); + + try { + const { pi, ctx, notifications, reloadCount } = createMockHarness({ + cwd, + hasUI: true, + selectResult: "Global only", + confirmImpl: (title) => title === "Remove Package", + }); + + await removePackage("/workspace/project/vendor/demo", ctx, pi); + + assert.deepEqual(removals, [{ source: "/workspace/project/vendor/demo", scope: "global" }]); + assert.equal(reloadCount(), 0); + assert.ok( + notifications.some((entry) => + entry.message.includes("Removed from selected scope(s). Still installed in: project.") + ) + ); + } finally { + restoreCatalog(); + } +}); + +void test("removePackage keeps remaining global-relative installs visible after scoped removal", async () => { + const removals: { source: string; scope: "global" | "project" }[] = []; + const cwd = "/workspace/project"; + const restoreCatalog = mockPackageCatalog({ + packages: [ + { + source: "./vendor/demo", + name: "demo", + scope: "global", + }, + { + source: "./vendor/demo", + name: "demo", + scope: "project", + resolvedPath: "/workspace/project/vendor/demo", + }, + ], + removeImpl: (source, scope) => { + removals.push({ source, scope }); + }, + }); + + try { + const { pi, ctx, notifications, reloadCount, selectPrompts } = createMockHarness({ + cwd, + hasUI: true, + selectResult: "Project only", + confirmImpl: (title) => title === "Remove Package", + }); + + await removePackage("./vendor/demo", ctx, pi); + + assert.deepEqual(selectPrompts, ["Remove scope"]); + assert.deepEqual(removals, [{ source: "./vendor/demo", scope: "project" }]); + assert.equal(reloadCount(), 0); + assert.ok( + notifications.some((entry) => + entry.message.includes("Removed from selected scope(s). Still installed in: global.") + ) + ); + } finally { + restoreCatalog(); + } +}); + void test("updatePackage treats missing available updates as a no-op", async () => { const output: string[] = []; const originalLog = console.log; diff --git a/test/npm-exec.test.ts b/test/npm-exec.test.ts index 73518ba..8340423 100644 --- a/test/npm-exec.test.ts +++ b/test/npm-exec.test.ts @@ -1,5 +1,5 @@ -import test from "node:test"; import assert from "node:assert/strict"; +import test from "node:test"; import { resolveNpmCommand } from "../src/utils/npm-exec.js"; void test("resolveNpmCommand uses npm directly on non-windows", () => { diff --git a/test/package-config.test.ts b/test/package-config.test.ts index ef27604..cb9699d 100644 --- a/test/package-config.test.ts +++ b/test/package-config.test.ts @@ -1,11 +1,11 @@ -import test from "node:test"; import assert from "node:assert/strict"; -import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; -import { join } from "node:path"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import type { InstalledPackage, State } from "../src/types/index.js"; +import { join } from "node:path"; +import test from "node:test"; +import { type ExtensionAPI, initTheme, type Theme } from "@mariozechner/pi-coding-agent"; import { discoverPackageExtensions } from "../src/packages/extensions.js"; +import { type InstalledPackage, type State } from "../src/types/index.js"; import { applyPackageExtensionChanges, buildPackageConfigRows, @@ -13,6 +13,63 @@ import { } from "../src/ui/package-config.js"; import { createMockHarness } from "./helpers/mocks.js"; +initTheme(); + +const noop = (): undefined => undefined; + +async function captureCustomComponent( + factory: unknown, + ctxTheme: Theme, + matcher: (lines: string[]) => boolean, + onMatch: ( + component: { + render(width: number): string[]; + handleInput?(data: string): void; + dispose?(): void; + }, + lines: string[], + completion: Promise + ) => unknown +): Promise { + let resolveCompletion: (value: unknown) => void = () => undefined; + const completion = new Promise((resolve) => { + resolveCompletion = resolve; + }); + + const component = await ( + factory as ( + tui: unknown, + theme: unknown, + keybindings: unknown, + done: (result: unknown) => void + ) => + | Promise<{ + render(width: number): string[]; + handleInput?(data: string): void; + dispose?(): void; + }> + | { + render(width: number): string[]; + handleInput?(data: string): void; + dispose?(): void; + } + )({ requestRender: noop, terminal: { rows: 40, columns: 120 } }, ctxTheme, {}, resolveCompletion); + + try { + const lines = component.render(120); + if (!matcher(lines)) { + return await Promise.race([ + completion, + new Promise((resolve) => setTimeout(() => resolve(undefined), 50)), + ]); + } + + return await onMatch(component, lines, completion); + } finally { + component.dispose?.(); + } +} + function createPiRecorder() { const entries: { customType: string; data: unknown }[] = []; @@ -216,8 +273,12 @@ void test("applyPackageExtensionChanges batches multiple row changes in one save const rows = await buildPackageConfigRows(discovered); const staged = new Map(); - staged.set(rows.find((entry) => entry.extensionPath === "extensions/a.ts")!.id, "disabled"); - staged.set(rows.find((entry) => entry.extensionPath === "extensions/b.ts")!.id, "enabled"); + const firstRow = rows.find((entry) => entry.extensionPath === "extensions/a.ts"); + const secondRow = rows.find((entry) => entry.extensionPath === "extensions/b.ts"); + assert.ok(firstRow); + assert.ok(secondRow); + staged.set(firstRow.id, "disabled"); + staged.set(secondRow.id, "enabled"); const { pi } = createPiRecorder(); const result = await applyPackageExtensionChanges(rows, staged, pkg, cwd, pi); @@ -321,3 +382,60 @@ void test("configurePackageExtensions surfaces invalid settings before rendering await rm(cwd, { recursive: true, force: true }); } }); + +void test("configurePackageExtensions reloads after saving changes", async () => { + const cwd = await mkdtemp(join(tmpdir(), "pi-extmgr-package-config-")); + const pkgRoot = join(cwd, "vendor", "demo"); + + try { + await mkdir(pkgRoot, { recursive: true }); + await writeFile( + join(pkgRoot, "package.json"), + JSON.stringify({ name: "demo", pi: { extensions: ["./index.ts"] } }, null, 2), + "utf8" + ); + await writeFile(join(pkgRoot, "index.ts"), "// demo extension\n", "utf8"); + + const { pi, ctx, confirmPrompts, reloadCount } = createMockHarness({ + cwd, + hasUI: true, + hasCustomUI: true, + confirmResult: true, + }); + + (ctx.ui as { custom: (factory: unknown) => Promise }).custom = async (factory) => + captureCustomComponent( + factory, + ctx.ui.theme, + (lines) => lines.some((line) => line.includes("Configure extensions: demo")), + async (component, _lines, completion) => { + component.handleInput?.(" "); + component.handleInput?.("s"); + return await completion; + } + ); + + const result = await configurePackageExtensions( + { + source: "./vendor/demo", + name: "demo", + scope: "project", + resolvedPath: pkgRoot, + }, + ctx, + pi + ); + + assert.deepEqual(result, { changed: 1, reloaded: true }); + assert.deepEqual(confirmPrompts, ["Reload Required"]); + assert.equal(reloadCount(), 1); + + const saved = JSON.parse(await readFile(join(cwd, ".pi", "settings.json"), "utf8")) as { + packages: { source: string; extensions: string[] }[]; + }; + + assert.deepEqual(saved.packages[0]?.extensions, ["-index.ts"]); + } finally { + await rm(cwd, { recursive: true, force: true }); + } +}); diff --git a/test/package-extensions.test.ts b/test/package-extensions.test.ts index 2a28783..55cb453 100644 --- a/test/package-extensions.test.ts +++ b/test/package-extensions.test.ts @@ -1,10 +1,10 @@ -import test from "node:test"; import assert from "node:assert/strict"; -import { mkdtemp, mkdir, readFile, writeFile, rm } from "node:fs/promises"; -import { join } from "node:path"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; import { discoverPackageExtensions, setPackageExtensionState } from "../src/packages/extensions.js"; -import type { InstalledPackage } from "../src/types/index.js"; +import { type InstalledPackage } from "../src/types/index.js"; void test("discoverPackageExtensions expands manifest glob entrypoints", async () => { const cwd = await mkdtemp(join(tmpdir(), "pi-extmgr-cwd-")); diff --git a/test/remote-ui.test.ts b/test/remote-ui.test.ts index 87998e1..b75e6a6 100644 --- a/test/remote-ui.test.ts +++ b/test/remote-ui.test.ts @@ -1,7 +1,7 @@ -import test from "node:test"; import assert from "node:assert/strict"; -import { browseRemotePackages } from "../src/ui/remote.js"; +import test from "node:test"; import { clearSearchCache, setSearchCache } from "../src/packages/discovery.js"; +import { browseRemotePackages } from "../src/ui/remote.js"; import { createMockHarness } from "./helpers/mocks.js"; void test("browseRemotePackages honors an empty in-memory cache", async () => { diff --git a/test/rpc-mode.test.ts b/test/rpc-mode.test.ts index 3c26781..e3faa3f 100644 --- a/test/rpc-mode.test.ts +++ b/test/rpc-mode.test.ts @@ -1,11 +1,11 @@ -import test from "node:test"; import assert from "node:assert/strict"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import test from "node:test"; import { runResolvedCommand } from "../src/commands/registry.js"; -import { showRemote } from "../src/ui/remote.js"; import { configurePackageExtensions } from "../src/ui/package-config.js"; +import { showRemote } from "../src/ui/remote.js"; import { createMockHarness } from "./helpers/mocks.js"; import { mockPackageCatalog } from "./helpers/package-catalog.js"; diff --git a/test/search-regression.test.ts b/test/search-regression.test.ts index b07895f..a72fdc2 100644 --- a/test/search-regression.test.ts +++ b/test/search-regression.test.ts @@ -1,8 +1,8 @@ -import test from "node:test"; import assert from "node:assert/strict"; -import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; -import { join } from "node:path"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; import { initTheme, type Theme } from "@mariozechner/pi-coding-agent"; import { configurePackageExtensions } from "../src/ui/package-config.js"; import { showInteractive } from "../src/ui/unified.js"; diff --git a/test/settings-list.test.ts b/test/settings-list.test.ts index 34e0e41..1a13ecd 100644 --- a/test/settings-list.test.ts +++ b/test/settings-list.test.ts @@ -1,5 +1,5 @@ -import test from "node:test"; import assert from "node:assert/strict"; +import test from "node:test"; import { getSettingsListSelectedIndex } from "../src/utils/settings-list.js"; void test("getSettingsListSelectedIndex only accepts integer indices", () => { diff --git a/test/unified-items.test.ts b/test/unified-items.test.ts index 25ad5e3..2ae4669 100644 --- a/test/unified-items.test.ts +++ b/test/unified-items.test.ts @@ -1,9 +1,9 @@ -import test from "node:test"; import assert from "node:assert/strict"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { ExtensionEntry, InstalledPackage } from "../src/types/index.js"; +import test from "node:test"; +import { type ExtensionEntry, type InstalledPackage } from "../src/types/index.js"; import { buildUnifiedItems } from "../src/ui/unified.js"; function createPackage(source: string, name: string): InstalledPackage { @@ -108,6 +108,26 @@ void test("buildUnifiedItems keeps case-sensitive POSIX paths distinct", () => { ); }); +void test("buildUnifiedItems does not treat longer path prefixes as duplicates", () => { + const localEntries = [createLocalEntry("/tmp/vendor/demo/index.ts", "demo/index.ts")]; + const installedPackages: InstalledPackage[] = [ + { + source: "npm:demo-addon", + name: "demo-addon", + scope: "global", + resolvedPath: "/tmp/vendor/demo/index.ts-addon", + }, + ]; + + const items = buildUnifiedItems(localEntries, installedPackages, new Set()); + + assert.equal(items.length, 2); + assert.deepEqual( + items.map((item) => item.type), + ["local", "package"] + ); +}); + void test("buildUnifiedItems uses the project-winning package metadata for duplicates", () => { const installed: InstalledPackage[] = [ { source: "npm:demo@2.0.0", name: "demo", version: "2.0.0", scope: "project" },