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 @@
[](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" },