diff --git a/.omp-plugin/marketplace.json b/.omp-plugin/marketplace.json new file mode 100644 index 0000000..63417b1 --- /dev/null +++ b/.omp-plugin/marketplace.json @@ -0,0 +1,35 @@ +{ + "name": "rblaine95", + "owner": { + "name": "rblaine95" + }, + "metadata": { + "description": "OMP plugins maintained by rblaine95" + }, + "plugins": [ + { + "name": "rules-guard", + "description": "Enforce the Claude permissions.deny policy across all omp tools (read/write/edit/find/search/bash/eval/browser).", + "version": "0.0.0", + "source": { + "source": "github", + "repo": "rblaine95/omp-plugins", + "path": "extensions/rules-guard" + }, + "repository": "https://github.com/rblaine95/omp-plugins", + "license": "MIT" + }, + { + "name": "usage-status", + "description": "Show remaining subscription usage and reset countdowns for every provider, in a color-coded row above the omp editor.", + "version": "0.0.0", + "source": { + "source": "github", + "repo": "rblaine95/omp-plugins", + "path": "extensions/usage-status" + }, + "repository": "https://github.com/rblaine95/omp-plugins", + "license": "MIT" + } + ] +} diff --git a/AGENTS.md b/AGENTS.md index 7e44cb3..44e35e3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,6 +70,7 @@ what release-please parses on `master`. ## Included extensions -| Extension | What it does | -| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `rules-guard` | Enforces the Claude `permissions` allow/deny policy across every omp tool. Read the `index.ts` docblock for the threat model before touching policy logic. | +| Extension | What it does | +| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `rules-guard` | Enforces the Claude `permissions` allow/deny policy across every omp tool. Read the `index.ts` docblock for the threat model before touching policy logic. | +| `usage-status` | Color-coded `aboveEditor` widget row showing remaining usage + reset countdowns for every provider/account `authStorage.fetchUsageReports` returns (data-driven, like `/usage`), not a fixed provider list. | diff --git a/README.md b/README.md index ab07e09..ddd0595 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,10 @@ omp plugin uninstall @rblaine95/omp-plugins ## Included extensions -| Extension | What it does | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `rules-guard` | Enforces the Claude `permissions` allow/deny policy across every omp tool (read/write/edit/find/search/bash/eval/browser), not just `bash`. | +| Extension | What it does | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `rules-guard` | Enforces the Claude `permissions` allow/deny policy across every omp tool (read/write/edit/find/search/bash/eval/browser), not just `bash`. | +| `usage-status` | Color-coded row above the editor showing remaining usage with reset countdowns for every subscription `/usage` reports (Claude, Codex, Gemini, Grok, OpenCode, Cursor, …), so you don't have to run `/usage`. | ## Local development diff --git a/biome.json b/biome.json index 7ae11ef..9510232 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.3/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.4/schema.json", "vcs": { "enabled": false, "clientKind": "git", diff --git a/bun.lock b/bun.lock index 3c913ff..e86a009 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,8 @@ "": { "name": "@rblaine95/omp-plugins", "devDependencies": { - "@biomejs/biome": "^2.5.3", - "@oh-my-pi/pi-coding-agent": "^16.4.8", + "@biomejs/biome": "^2.5.4", + "@oh-my-pi/pi-coding-agent": "^17.0.5", "@types/bun": "1.3.14", "@types/node": "^24.13.3", "sort-package-json": "^4.0.0", @@ -17,13 +17,17 @@ "name": "rules-guard", "version": "0.0.0", }, + "extensions/usage-status": { + "name": "usage-status", + "version": "0.0.0", + }, }, "packages": { - "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.25.0", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-wU1VgXNtMvdVotX49txc3WJUDV+/QbLpsgjMvFhlRmp37osdLbI7L7y+iwAlQATwfjLxcv1r1p3ZxZBcXlGhcQ=="], + "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@1.2.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-jwYUdOQR7tc+Zfch53VL4JJyUNK/46q03uUTYb+PjECsmnNl94XFXOfYLJ8RBpMNidXd1rpOAVgb0vqD98xImA=="], - "@ark/schema": ["@ark/schema@0.56.1", "", { "dependencies": { "@ark/util": "0.56.1" } }, "sha512-1Cf2g9nKD8K/3JGRu+gCCfYw5d4qR8YLLjDs5W5kpmaButCYWAPFUJqSXyBATPjglzCd4tIkp398iPYVs8MjRA=="], + "@ark/schema": ["@ark/schema@0.56.2", "", { "dependencies": { "@ark/util": "0.56.2" } }, "sha512-Qx4D2JFbBWpntiHZaTv7bGG4H/M2rigiknezKg/WVyDSaLdE4YCcWAOoFB7pjjDqHbbV2OqRfntm1nnXvwMexg=="], - "@ark/util": ["@ark/util@0.56.1", "", {}, "sha512-Tp1rTik3q5Z+jAeeDxr5JZpmVIw0miti1ykSEHyZv5Pw3TIJl2xbN6KTacOxITp0l3s9ytlrWd30Zvqcy5vzoQ=="], + "@ark/util": ["@ark/util@0.56.2", "", {}, "sha512-9kU2sUE38FZEGG7l3hamYMBieLYEJh2L1mrYD2eXpT+78EnQSV1bhjxJhnxGBMSTbtwpBSDNSK+K60WvaI/DTQ=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], @@ -33,23 +37,23 @@ "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], - "@biomejs/biome": ["@biomejs/biome@2.5.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.3", "@biomejs/cli-darwin-x64": "2.5.3", "@biomejs/cli-linux-arm64": "2.5.3", "@biomejs/cli-linux-arm64-musl": "2.5.3", "@biomejs/cli-linux-x64": "2.5.3", "@biomejs/cli-linux-x64-musl": "2.5.3", "@biomejs/cli-win32-arm64": "2.5.3", "@biomejs/cli-win32-x64": "2.5.3" }, "bin": { "biome": "bin/biome" } }, "sha512-MrJswFdei9EfDwwUy2tQrPDpK0AO+RmMFvBoaaJ6ayBc3sUbHdCE+XG5N8vp+5So41ZupZJQm0roHFFhMGVD7A=="], + "@biomejs/biome": ["@biomejs/biome@2.5.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.4", "@biomejs/cli-darwin-x64": "2.5.4", "@biomejs/cli-linux-arm64": "2.5.4", "@biomejs/cli-linux-arm64-musl": "2.5.4", "@biomejs/cli-linux-x64": "2.5.4", "@biomejs/cli-linux-x64-musl": "2.5.4", "@biomejs/cli-win32-arm64": "2.5.4", "@biomejs/cli-win32-x64": "2.5.4" }, "bin": { "biome": "bin/biome" } }, "sha512-xy5FNE5kQJKyK5MR1gJy6ztXYx4WBAbYGlK04lMEgmyPRWKybY9NFwiG9yo0XdzOU8Xvhj41u034J1ywfoWfMw=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QhYP9muVQ0nUO5zztFuPbEwi4+94sJWVjaZds9aMi1l/KNZBiUjdiSUrGHsTaMGDXrYl+r4AS2sUKfgH3w+V3g=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4o3NFRobXHynkgcFVrlZsoDAFtF2ldlEGN8sORSws5ZQqyY4PXnPUIylu4ksfyHuwkfvDREuWh3JK+niRwGq3w=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-NC1Ss13UaW7QZX+y8j44bF7AP0jSJdBl6iRhe0MAkvaSqZy+mWg3GaXsrb+eSoHoGDBtaXWEbMVV0iVN2cZ7cQ=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-D32P5HkU2Y6PySuC/WsVDTOgsDwVFmujzhhhOQjajtATpVWFDXuVd3oRbsWNSEA+aaFzyzZm22szsyydBYlSyQ=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-ksx1KWeyYW18ILL04msF/J4ZBtBDN33znYK8Z/aNv/vlBVxL9/g3mGP+omgHJKy4+KWbK87vcmmpmurfNjSgiA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-pSEfW7B8kTsXUjUxC1xVVK+y85Ht3C5XxZ9gclmC7/3Ku9Vqz8jmI7k0p/BNIjQ6t4sFERI2sFeH73ybiZl6YQ=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-fccix0w6xp6csCXgxeC0dU/3ecgRQal0y+cv2SP9ajNlhe7Yrk2Ug7UDe2j9AT9ZDYitkXpvUKgZjjuoYeP4Vg=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Rpm5/AT1m+DlJmUoYvS4/vXc+0tXJPJ2NQz25TGPyHVF5JrWy75PE0GH6kVxsKtQDuCH4OgzquZq0R4kj/wCVg=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.3", "", { "os": "linux", "cpu": "x64" }, "sha512-yMkJtilsgvILDcVkh187aVLTb64xYsrxYajx5kym+r1ULkO5HUOfu9AYKLGQbOVLwJtT2utNw7hhFNg+17mUYA=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-FNxojWJkL7EajAuzBgoLe0T2G0y112M4lBrDIFl/DomFTx8yqenYOIdsRLNXvOvBBofE8hJi85LjzLmBDpY7/Q=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.3", "", { "os": "linux", "cpu": "x64" }, "sha512-O/yU9YKRUiHhmcjF2f38PSjseVk3G4VLWYc0G2HWpzdBVREV6G8IGWIVEFf7MFPfWIzNUIvPsEjeAZQIOgnLcQ=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-aby/PohmmgbShcHqFsZVzG8H6D98+P+A6xRWRrQcLW1pCjabcov5UUlke4UqNQBYTkDQav+jB4zyyDDeKB2GaA=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-cX5z+GYwRcqEok0AH3KSfQGgqYd0Nomfp6Fbe1uiTtELE38hdH2k842wQ9wLNaF/JJ7r4rjJQ4VR+ce+fRmQbw=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-emoXexPZIPAZkz2RKmA95WJUqK3I5MJNYtwEbL5ESciRzhmFMMyekDhNG8hpeOaK+ZGRDxAU4wvGuA5IHQ0h0w=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ExSaJWi4/u6+GXCszlSKpWSjKNbDseAYqqkCznsCsZ/4uidZ/BEqsCc5/3ctlq6dfIubdIIRSVLC/PG9xPl70Q=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-U1jaluLw1qQc2Tx7/CeSoL9N5XcqIH+GWjpUAy1ouB5nVjSCMNO+NNHdY3RAs8zxNurLWAdj6pehQdCA2zyU+Q=="], "@bufbuild/protobuf": ["@bufbuild/protobuf@2.12.1", "", {}, "sha512-BvAMfS6LrgZiryOAZ4pBYucu4wG/Ei/9o9DZ9akbREnMLbPJiom2i8b9C8IsKErQoiKqVhrerzt3kOT/RrzLHg=="], @@ -133,59 +137,65 @@ "@nodable/entities": ["@nodable/entities@2.2.0", "", {}, "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg=="], - "@oh-my-pi/hashline": ["@oh-my-pi/hashline@16.4.8", "", { "dependencies": { "diff": "^9.0.0", "lru-cache": "11.5.1" } }, "sha512-aJlyuyeK2mqx210UITj9O2ZXFwfLAEHXFtvVT1R2aYaRjUR85oWqh/I/z1owQcMoVs6FuR8a1RE5o1O7rPb+GQ=="], + "@oh-my-pi/hashline": ["@oh-my-pi/hashline@17.0.5", "", { "dependencies": { "diff": "^9.0.0", "lru-cache": "11.5.2" } }, "sha512-1aFduPnzUAlnryx7JNttQi0SHstLaO4gPqFbdaSAM+UBeOXRo7W25q6h63xpKuvwBM8XabG3eZnkVLEaQ21InA=="], - "@oh-my-pi/omp-stats": ["@oh-my-pi/omp-stats@16.4.8", "", { "dependencies": { "@oh-my-pi/pi-ai": "16.4.8", "@oh-my-pi/pi-catalog": "16.4.8", "@oh-my-pi/pi-utils": "16.4.8", "@tailwindcss/node": "^4.3.0", "chart.js": "^4.5.1", "date-fns": "^4.4.0", "lucide-react": "^1.17.0", "react": "19.2.7", "react-chartjs-2": "^5.3.1", "react-dom": "19.2.7", "tailwindcss": "^4.3.0" }, "bin": { "omp-stats": "src/index.ts" } }, "sha512-oAtiBUXhz8wpNssp4J3PlDeZZCaAKbuLsHbXQv5nP8sw8xCyviDBfYrchKSi2mp4K4NURbAjIauHkbtk4CgOjQ=="], + "@oh-my-pi/omp-stats": ["@oh-my-pi/omp-stats@17.0.5", "", { "dependencies": { "@oh-my-pi/pi-ai": "17.0.5", "@oh-my-pi/pi-catalog": "17.0.5", "@oh-my-pi/pi-utils": "17.0.5", "@tailwindcss/node": "^4.3.2", "chart.js": "^4.5.1", "date-fns": "^4.4.0", "lucide-react": "^1.24.0", "react": "19.2.7", "react-chartjs-2": "^5.3.1", "react-dom": "19.2.7", "tailwindcss": "^4.3.2" }, "bin": { "omp-stats": "src/index.ts" } }, "sha512-ZHyHXC8x1In9dz/Rt4onS0mBC2fVgJLFB2+it8ry0IWX6OiGyzSpg/DinB0T+JiL0YHep1nLGC7d7BBz9vGK4g=="], - "@oh-my-pi/pi-agent-core": ["@oh-my-pi/pi-agent-core@16.4.8", "", { "dependencies": { "@oh-my-pi/pi-ai": "16.4.8", "@oh-my-pi/pi-catalog": "16.4.8", "@oh-my-pi/pi-natives": "16.4.8", "@oh-my-pi/pi-utils": "16.4.8", "@oh-my-pi/pi-wire": "16.4.8", "@oh-my-pi/snapcompact": "16.4.8", "@opentelemetry/api": "^1.9.1" } }, "sha512-PdtWBVjSKu+bYHOJAEP4n3BJtOdlu2fUntO/zJtOeegyUnPrS0ZvFLUmDTf8/O/dQhnlfVyTkd4CPuDeTflLcg=="], + "@oh-my-pi/pi-agent-core": ["@oh-my-pi/pi-agent-core@17.0.5", "", { "dependencies": { "@oh-my-pi/pi-ai": "17.0.5", "@oh-my-pi/pi-catalog": "17.0.5", "@oh-my-pi/pi-natives": "17.0.5", "@oh-my-pi/pi-utils": "17.0.5", "@oh-my-pi/pi-wire": "17.0.5", "@oh-my-pi/snapcompact": "17.0.5", "@opentelemetry/api": "^1.9.1" } }, "sha512-mnYqqr86f4UentSKXJA4nM8XS1YT7VxkG3COfLeH2kD7ri96znRFrEEpD+kjHjhaXLDj/x24jZ8Trtc6y5I1+Q=="], - "@oh-my-pi/pi-ai": ["@oh-my-pi/pi-ai@16.4.8", "", { "dependencies": { "@bufbuild/protobuf": "^2.12.0", "@oh-my-pi/pi-catalog": "16.4.8", "@oh-my-pi/pi-utils": "16.4.8", "@oh-my-pi/pi-wire": "16.4.8", "arktype": "2.2.2", "zod": "^4" } }, "sha512-kaPWaacZRDkT+veEkQ8OxL59BAJM0meLG5fwvcvywGmgUc++RVaeUaa4EVHW5VQ86w6G9GA17HJkWpvDtxMhRg=="], + "@oh-my-pi/pi-ai": ["@oh-my-pi/pi-ai@17.0.5", "", { "dependencies": { "@bufbuild/protobuf": "^2.12.1", "@oh-my-pi/pi-catalog": "17.0.5", "@oh-my-pi/pi-utils": "17.0.5", "@oh-my-pi/pi-wire": "17.0.5", "arktype": "2.2.3", "zod": "^4" } }, "sha512-6RtjXNDrz2V3IsBr3pw+FqHgBr08P61LeoW7UZE0ffMjHws9A2CYVfTXLsrMJyWOqn06AlNtxY4KORR/nTW/RA=="], - "@oh-my-pi/pi-catalog": ["@oh-my-pi/pi-catalog@16.4.8", "", { "dependencies": { "@bufbuild/protobuf": "^2.12.0", "@oh-my-pi/pi-utils": "16.4.8", "arktype": "2.2.2", "zod": "^4" } }, "sha512-4xBYzH/QxORJ/C0SK9ZROYTOtCP8iAGc0cX5wZ0LZQtYpokM9SV/0yOw2qnavNq72Rezy/flp/4bwL6pQ0PKSA=="], + "@oh-my-pi/pi-catalog": ["@oh-my-pi/pi-catalog@17.0.5", "", { "dependencies": { "@bufbuild/protobuf": "^2.12.1", "@oh-my-pi/pi-utils": "17.0.5", "arktype": "2.2.3", "zod": "^4" } }, "sha512-OY0/EypQUHuUfR+li1fMAd5mbKD2XsIM7Ewe/DGnTlmwM/TCbkSPauTJTK7ilyCCmeuDKE9Qr9cVLqkB0KlNzQ=="], - "@oh-my-pi/pi-coding-agent": ["@oh-my-pi/pi-coding-agent@16.4.8", "", { "dependencies": { "@agentclientprotocol/sdk": "0.25.0", "@babel/parser": "^7.29.7", "@mozilla/readability": "^0.6.0", "@oh-my-pi/hashline": "16.4.8", "@oh-my-pi/omp-stats": "16.4.8", "@oh-my-pi/pi-agent-core": "16.4.8", "@oh-my-pi/pi-ai": "16.4.8", "@oh-my-pi/pi-catalog": "16.4.8", "@oh-my-pi/pi-mnemopi": "16.4.8", "@oh-my-pi/pi-natives": "16.4.8", "@oh-my-pi/pi-tui": "16.4.8", "@oh-my-pi/pi-utils": "16.4.8", "@oh-my-pi/pi-wire": "16.4.8", "@oh-my-pi/snapcompact": "16.4.8", "@opentelemetry/api": "^1.9.1", "@opentelemetry/context-async-hooks": "^2.7.1", "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0", "@opentelemetry/resources": "^2.7.1", "@opentelemetry/sdk-trace-base": "^2.7.1", "@opentelemetry/sdk-trace-node": "^2.7.1", "@puppeteer/browsers": "^3.0.4", "@types/turndown": "5.0.6", "@xterm/headless": "^6.0.0", "arktype": "2.2.2", "chalk": "^5.6.2", "diff": "^9.0.0", "fast-xml-parser": "^5.9.0", "handlebars": "^4.7.9", "header-generator": "^2.1.82", "linkedom": "^0.18.12", "lru-cache": "11.5.1", "mammoth": "^1.12.0", "mupdf": "^1.27.0", "puppeteer-core": "25.3.0", "turndown": "7.2.4", "turndown-plugin-gfm": "1.0.2", "zod": "^4" }, "optionalDependencies": { "@huggingface/transformers": "^4.2.0", "sherpa-onnx-node": "1.13.2" }, "bin": { "omp": "dist/cli.js" } }, "sha512-z7sYIP1ZaDJXOmMRIDmr4wg+J14iOX0Na+rif68NAYPCZ+gglOMUHF7f+qQR7f1pyS2iQpG4pmqbyw+WE4Gsag=="], + "@oh-my-pi/pi-coding-agent": ["@oh-my-pi/pi-coding-agent@17.0.5", "", { "dependencies": { "@agentclientprotocol/sdk": "1.2.1", "@babel/parser": "^7.29.7", "@mozilla/readability": "^0.6.0", "@oh-my-pi/hashline": "17.0.5", "@oh-my-pi/omp-stats": "17.0.5", "@oh-my-pi/pi-agent-core": "17.0.5", "@oh-my-pi/pi-ai": "17.0.5", "@oh-my-pi/pi-catalog": "17.0.5", "@oh-my-pi/pi-mnemopi": "17.0.5", "@oh-my-pi/pi-natives": "17.0.5", "@oh-my-pi/pi-tui": "17.0.5", "@oh-my-pi/pi-utils": "17.0.5", "@oh-my-pi/pi-wire": "17.0.5", "@oh-my-pi/snapcompact": "17.0.5", "@opentelemetry/api": "^1.9.1", "@opentelemetry/api-logs": "^0.220.0", "@opentelemetry/context-async-hooks": "^2.9.0", "@opentelemetry/exporter-logs-otlp-proto": "^0.220.0", "@opentelemetry/exporter-metrics-otlp-proto": "^0.220.0", "@opentelemetry/exporter-trace-otlp-proto": "^0.220.0", "@opentelemetry/resources": "^2.9.0", "@opentelemetry/sdk-logs": "^0.220.0", "@opentelemetry/sdk-metrics": "^2.9.0", "@opentelemetry/sdk-trace-base": "^2.9.0", "@opentelemetry/sdk-trace-node": "^2.9.0", "@puppeteer/browsers": "^3.0.6", "@types/turndown": "5.0.6", "@xterm/headless": "^6.0.0", "arktype": "2.2.3", "chalk": "^5.6.2", "diff": "^9.0.0", "fast-xml-parser": "^5.9.3", "handlebars": "^4.7.9", "header-generator": "^2.1.82", "linkedom": "^0.18.13", "lru-cache": "11.5.2", "mammoth": "^1.12.0", "mupdf": "^1.28.0", "puppeteer-core": "25.3.0", "turndown": "7.2.4", "turndown-plugin-gfm": "1.0.2", "zod": "^4" }, "optionalDependencies": { "@huggingface/transformers": "^4.2.0", "sherpa-onnx-node": "1.13.2" }, "bin": { "omp": "dist/cli.js" } }, "sha512-SdDYSuc1+zdoufN7xV6pResZXYEN0gpt1mjz7TwQH8LZroRpkA7JiLE48fQp0t3bJYMIKpuTjAXp1bZw2Qep4A=="], - "@oh-my-pi/pi-mnemopi": ["@oh-my-pi/pi-mnemopi@16.4.8", "", { "dependencies": { "@oh-my-pi/pi-ai": "16.4.8", "@oh-my-pi/pi-catalog": "16.4.8", "@oh-my-pi/pi-utils": "16.4.8", "lru-cache": "11.5.1" }, "peerDependencies": { "fastembed": "2.1.0", "onnxruntime-node": "1.21.0" }, "optionalPeers": ["fastembed", "onnxruntime-node"], "bin": { "mnemopi": "src/cli.ts" } }, "sha512-9uJqmkeuQktR/r4l3QKQ9O3rWxeAMB14/YoS+EDi6LxhgE6UhnKK74wiXv/cdBA8R/B5XPQ/z91SqF16V5g6ug=="], + "@oh-my-pi/pi-mnemopi": ["@oh-my-pi/pi-mnemopi@17.0.5", "", { "dependencies": { "@oh-my-pi/pi-ai": "17.0.5", "@oh-my-pi/pi-catalog": "17.0.5", "@oh-my-pi/pi-utils": "17.0.5", "lru-cache": "11.5.2" }, "peerDependencies": { "fastembed": "2.1.0", "onnxruntime-node": "1.21.0" }, "optionalPeers": ["fastembed", "onnxruntime-node"], "bin": { "mnemopi": "src/cli.ts" } }, "sha512-mR0NMvMWI8x35y00cdGwfaC8LFV7W1MHjS09VclHJE/NVdGo1Cn4OxhOXHDt+ayqfHch3RqWfhPrnzV31zSMog=="], - "@oh-my-pi/pi-natives": ["@oh-my-pi/pi-natives@16.4.8", "", { "optionalDependencies": { "@oh-my-pi/pi-natives-darwin-arm64": "16.4.8", "@oh-my-pi/pi-natives-darwin-x64": "16.4.8", "@oh-my-pi/pi-natives-linux-arm64": "16.4.8", "@oh-my-pi/pi-natives-linux-x64": "16.4.8", "@oh-my-pi/pi-natives-win32-x64": "16.4.8" } }, "sha512-ee4fO/1KzbJD9pWhqzNI0/+C7jO9WcB6B/DHgcZMAZ4mBuomDuCpvD/SHspVLFgabVQZIltW1OIDsyNtyEyZYQ=="], + "@oh-my-pi/pi-natives": ["@oh-my-pi/pi-natives@17.0.5", "", { "optionalDependencies": { "@oh-my-pi/pi-natives-darwin-arm64": "17.0.5", "@oh-my-pi/pi-natives-darwin-x64": "17.0.5", "@oh-my-pi/pi-natives-linux-arm64": "17.0.5", "@oh-my-pi/pi-natives-linux-x64": "17.0.5", "@oh-my-pi/pi-natives-win32-x64": "17.0.5" } }, "sha512-nWuOVgytuiy3xFGDUK0TP0HMe+sRHOhyvXSAlc4aldrE2Z4cN3WM5CJN4OaVMxc+FR1qFBznzvvdP9K3EvHVLA=="], - "@oh-my-pi/pi-natives-darwin-arm64": ["@oh-my-pi/pi-natives-darwin-arm64@16.4.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CFmG64D7FEkwQwHq+Eb9c9HqbPQAnirCCUyA5K8215OYTQpFZ51WYcZ38php0CIzr+NGd9YFsnZHB6r3gOIhug=="], + "@oh-my-pi/pi-natives-darwin-arm64": ["@oh-my-pi/pi-natives-darwin-arm64@17.0.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FaKzy1iB2SpbqZ16elvFpctYkJiRPs/Ve0mmpjuosXLim69ir2h+OOrAP1bmXADvK9zt1mElIRr1wQsX0kVk6Q=="], - "@oh-my-pi/pi-natives-darwin-x64": ["@oh-my-pi/pi-natives-darwin-x64@16.4.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-g9TEDwi8Z9a4omBIHMY5xzMEa8qg95RvJ98EL3GmhZCeQVMtrmvIdL+D+rTMgfxQp6ELn0kbCdC9lbQmM0PkdQ=="], + "@oh-my-pi/pi-natives-darwin-x64": ["@oh-my-pi/pi-natives-darwin-x64@17.0.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-ayLls3BRHI0QmHe0pyycv8VOWSYCmm1oyDrdO9YXaSEdjS0doFB4qr+/Fz294Xpt3uSHvuBr1UvGdgvzbO3kNA=="], - "@oh-my-pi/pi-natives-linux-arm64": ["@oh-my-pi/pi-natives-linux-arm64@16.4.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WQRm3VrR3+Ol/aIfefgipkXcHZ8gitQGqMV03iF6uQs0/AnKoTiPSkzaEuLXGSNX+kM5V3Wi9MVy1RDUxYxHkQ=="], + "@oh-my-pi/pi-natives-linux-arm64": ["@oh-my-pi/pi-natives-linux-arm64@17.0.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-2G3KtSNPz8n+005UAqWk/gslDqd0oeS2unDsp5ItMJUTqGL6Yot869wHvdJRx4gVM5fgZJWG/04hTwd8G0gVxg=="], - "@oh-my-pi/pi-natives-linux-x64": ["@oh-my-pi/pi-natives-linux-x64@16.4.8", "", { "os": "linux", "cpu": "x64" }, "sha512-1pT9y3Ja+idFxLg9mK61OIIkpwxGbn0I99qXBv3iGl6Luu5K0nju2eHIWNu+1HP/S2wS/QnCN5hgniAvWELb8Q=="], + "@oh-my-pi/pi-natives-linux-x64": ["@oh-my-pi/pi-natives-linux-x64@17.0.5", "", { "os": "linux", "cpu": "x64" }, "sha512-O5HpCTj9dURsO6mhQyBikq0og6iRpCnai2nh0d3HZnD1qW/KYbKuf4Vtge09DsTEQwyvkywpnAuKP+sGYjy5XA=="], - "@oh-my-pi/pi-natives-win32-x64": ["@oh-my-pi/pi-natives-win32-x64@16.4.8", "", { "os": "win32", "cpu": "x64" }, "sha512-FAuKYiBdJf3DQd5yjQVb2PL8tD3iyAH9N2YY488CHyNKh1SgXUvZcHHLe9u3eLZy1Fp8Fzmm4pxNYSw44YKOGQ=="], + "@oh-my-pi/pi-natives-win32-x64": ["@oh-my-pi/pi-natives-win32-x64@17.0.5", "", { "os": "win32", "cpu": "x64" }, "sha512-Cev0XspoYBgJ4RbZJdavmUMDb1lJIL8xhWVaDzNMEstUaOHAzKTyYqbMLgV0c4vwSegfbRdxvInD+UOVnDDnAw=="], - "@oh-my-pi/pi-tui": ["@oh-my-pi/pi-tui@16.4.8", "", { "dependencies": { "@oh-my-pi/pi-natives": "16.4.8", "@oh-my-pi/pi-utils": "16.4.8", "lru-cache": "11.5.1", "marked": "^18.0.5" } }, "sha512-wi/SZnNbNgagUS3GZHGgg7GcDgf3dxF81cM9/jJDL3yMINPj9W6UoVi57ZjEQW7Rto0U4Zxle1iCqPYP8u8VWA=="], + "@oh-my-pi/pi-tui": ["@oh-my-pi/pi-tui@17.0.5", "", { "dependencies": { "@oh-my-pi/pi-natives": "17.0.5", "@oh-my-pi/pi-utils": "17.0.5", "lru-cache": "11.5.2", "marked": "^18.0.6" } }, "sha512-Z0U+G/SmwUHeXw94YRUmsNOGPMmtyffo/vYvm1blUe2uWBoCh7OSAgK5TcO3AxNPaK1CZAwKDzQ/8TM47is1uA=="], - "@oh-my-pi/pi-utils": ["@oh-my-pi/pi-utils@16.4.8", "", { "dependencies": { "@oh-my-pi/pi-natives": "16.4.8", "handlebars": "^4.7.9", "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0" } }, "sha512-rcHLadAf3Xc9FO6UuUWV9neIryXNSFI/DZCqhpKOo6vaHUoR7usmLIykZtxqoPjhKTpK/1xbpy7UX2LWKybI9w=="], + "@oh-my-pi/pi-utils": ["@oh-my-pi/pi-utils@17.0.5", "", { "dependencies": { "@oh-my-pi/pi-natives": "17.0.5", "handlebars": "^4.7.9", "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0" } }, "sha512-f2ZR4mP7TuNUIQTBStlbDK/abZlvojPWVHTIDj8RRwGszg8k5nojgwC+MJt0MXRBUQmevbVXei008iALGeTSmQ=="], - "@oh-my-pi/pi-wire": ["@oh-my-pi/pi-wire@16.4.8", "", {}, "sha512-ZGfl4CRKi7+EmisfpmtQewbN7SAzfaLrwqx7p+A++Q74nImjLx8d9ITlw/EDN0+mgvISATgQfE0sG0o/rKvWjQ=="], + "@oh-my-pi/pi-wire": ["@oh-my-pi/pi-wire@17.0.5", "", {}, "sha512-x6on+RyFEE74a3xQJ2JaHedJPLcvis0Kh+RVTb8kvRPhRqKMrkw9D3QkHyJZbiehk+qawxe0lMn5oahhMMwz2Q=="], - "@oh-my-pi/snapcompact": ["@oh-my-pi/snapcompact@16.4.8", "", { "dependencies": { "@oh-my-pi/pi-ai": "16.4.8", "@oh-my-pi/pi-natives": "16.4.8", "@oh-my-pi/pi-utils": "16.4.8", "@oh-my-pi/pi-wire": "16.4.8" } }, "sha512-AjZM889jsKciC6gnhtY1J4fHo1+VBMIFBQnxBta0JK3wpg/gTQkMxn772dC11gFexvtnRmIB/tsNTvmt4Dxe7g=="], + "@oh-my-pi/snapcompact": ["@oh-my-pi/snapcompact@17.0.5", "", { "dependencies": { "@oh-my-pi/pi-ai": "17.0.5", "@oh-my-pi/pi-natives": "17.0.5", "@oh-my-pi/pi-utils": "17.0.5", "@oh-my-pi/pi-wire": "17.0.5" } }, "sha512-5zqq4Ad0Wmy8S9SMuNj+9mCmpgfdNgkFxnF7xJjLeWcjxtiTKOrh0wu+Xk7LyxrVO0W/NWale2B1MAvVyKzIMA=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], - "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.218.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw=="], + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.220.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w=="], "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.9.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OQ0vzvbZBiUhjqLnUaoNfYmP8553Crr3aggB4y0ZUi815mZ7idpdJXQmoKdeBKJelYttoBlLSSHubmyw3wvX4w=="], - "@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], + "@opentelemetry/core": ["@opentelemetry/core@2.9.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw=="], + + "@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.220.0", "", { "dependencies": { "@opentelemetry/otlp-exporter-base": "0.220.0", "@opentelemetry/otlp-transformer": "0.220.0", "@opentelemetry/sdk-logs": "0.220.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-8LZAxdJ0ENDAFwr4j0oY35mHBltiSzvlhdQAPGiC7p9VnxtuSq4SW1gfBAdW6t6hiQG6OwUl8w7KHaOdJPKHWg=="], + + "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.220.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/otlp-exporter-base": "0.220.0", "@opentelemetry/otlp-transformer": "0.220.0", "@opentelemetry/resources": "2.9.0", "@opentelemetry/sdk-metrics": "2.9.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Yqt3RBw/bRVncaE9qIIhk4WfjbAQqXuP9FgAaU+IKPndnLEp/cUqZlSC324+bpmduRz7DoTjig8Ub0PeILWXUA=="], + + "@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.220.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/exporter-metrics-otlp-http": "0.220.0", "@opentelemetry/otlp-exporter-base": "0.220.0", "@opentelemetry/otlp-transformer": "0.220.0", "@opentelemetry/resources": "2.9.0", "@opentelemetry/sdk-metrics": "2.9.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lyO+IQBdSvqHN/ZOW/OzrSWemtfD+HgWngn+HBNLhjy0YrCQQTz0OE/kSekH2Pl340dn9DWzhqHdz5Eftr+HLA=="], - "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.218.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-exporter-base": "0.218.0", "@opentelemetry/otlp-transformer": "0.218.0", "@opentelemetry/resources": "2.7.1", "@opentelemetry/sdk-trace-base": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw=="], + "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.220.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/otlp-exporter-base": "0.220.0", "@opentelemetry/otlp-transformer": "0.220.0", "@opentelemetry/resources": "2.9.0", "@opentelemetry/sdk-trace": "2.9.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-voTAD8XgJxlK7zLkXh8EzMB09zrQr3tyY/BsnDTlDiQU/UdK58MZ63A3mUjdEDrxMjCVmBHU3WQJhRmQe+Dvzg=="], - "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.218.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.218.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA=="], + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.220.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/otlp-transformer": "0.220.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CXYo8UD5Mn9YbgebO2EL4wejtA+gxLmLiu6HCk2KH2BR7XhFN6/6p1UlCb23DYCjeYkndevLHuejCCN1yx4+OQ=="], - "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.218.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.218.0", "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/sdk-logs": "0.218.0", "@opentelemetry/sdk-metrics": "2.7.1", "@opentelemetry/sdk-trace-base": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ=="], + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.220.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.220.0", "@opentelemetry/core": "2.9.0", "@opentelemetry/resources": "2.9.0", "@opentelemetry/sdk-logs": "0.220.0", "@opentelemetry/sdk-metrics": "2.9.0", "@opentelemetry/sdk-trace": "2.9.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lXGrv7KXZ0gNH9SVNUaa6vv6phVYGvJxfXAlMbzbakiXru75f5MZl8Z7oqiMMQD77riVHJCFlQvbZs/VVN2/4A=="], "@opentelemetry/resources": ["@opentelemetry/resources@2.9.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg=="], - "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.218.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.218.0", "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag=="], + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.220.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.220.0", "@opentelemetry/core": "2.9.0", "@opentelemetry/resources": "2.9.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-WywcTkQtv2iNmt+6y5Kcd4rzvx9bLVsBa2Nwcmg01IUaBTkTow3W4d9KE5vNBpEDtb9tp21WcRBY/lANRrApYA=="], - "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ=="], + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.9.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/resources": "2.9.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Xx8RGS4H5XEBl01WuCreMIpiah9cCXMbSkeuIePPdD2cUpq/vUzYmj8E/MK1OsbOc93FuAD4jfn2WOacKwLn7Q=="], "@opentelemetry/sdk-trace": ["@opentelemetry/sdk-trace@2.9.0", "", { "dependencies": { "@opentelemetry/core": "2.9.0", "@opentelemetry/resources": "2.9.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw=="], @@ -283,9 +293,9 @@ "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "arkregex": ["arkregex@0.0.7", "", { "dependencies": { "@ark/util": "0.56.1" } }, "sha512-O/Ltrn9EUSn3ui0KVzfyrWGDUsHlzKxDVBtpQxL/6JmLRMAZAebfSNf/A/J5Ny5S6QIwrXX+RfXsu888HMs35A=="], + "arkregex": ["arkregex@0.0.8", "", { "dependencies": { "@ark/util": "0.56.2" } }, "sha512-PJcx6G1kQTgLKPUbeYlYecDRaKq15AMSGVajlKFYWlPeJRQL+j3dKE6tyMs40HZ99djS1l9Vhl3ezAHy9JBIqQ=="], - "arktype": ["arktype@2.2.2", "", { "dependencies": { "@ark/schema": "0.56.1", "@ark/util": "0.56.1", "arkregex": "0.0.7" } }, "sha512-YYf1xhL2dh5aPZFlsY0RAsxv5HZqfLGLptH2ZP3JidTmsGRW8VOymhPjjMTkerL12vR2YtX0SK4c1mATtae8SA=="], + "arktype": ["arktype@2.2.3", "", { "dependencies": { "@ark/schema": "0.56.2", "@ark/util": "0.56.2", "arkregex": "0.0.8" } }, "sha512-7W+0RLTUNJiBFIIZXwOQxSR8Z273IAd6IvqBeG9+gHnQKFsIx2C0iOtGTmMrPnlX4qLXyc5+ll7A0BIj9WrbTg=="], "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], @@ -295,7 +305,7 @@ "bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="], - "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + "boolbase": ["boolbase@2.0.0", "", {}, "sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA=="], "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], @@ -325,9 +335,9 @@ "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], - "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + "css-select": ["css-select@7.0.0", "", { "dependencies": { "boolbase": "^2.0.0", "css-what": "^8.0.0", "domhandler": "^6.0.1", "domutils": "^4.0.2", "nth-check": "^3.0.1" } }, "sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g=="], - "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + "css-what": ["css-what@8.0.0", "", {}, "sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw=="], "cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="], @@ -351,13 +361,13 @@ "dingbat-to-unicode": ["dingbat-to-unicode@1.0.1", "", {}, "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w=="], - "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + "dom-serializer": ["dom-serializer@3.1.1", "", { "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", "entities": "^8.0.0" } }, "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw=="], "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], - "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + "domhandler": ["domhandler@6.0.1", "", { "dependencies": { "domelementtype": "^3.0.0" } }, "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg=="], - "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + "domutils": ["domutils@4.0.2", "", { "dependencies": { "dom-serializer": "^3.0.0", "domelementtype": "^3.0.0", "domhandler": "^6.0.0" } }, "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA=="], "dot-prop": ["dot-prop@6.0.1", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA=="], @@ -473,7 +483,7 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], - "linkedom": ["linkedom@0.18.12", "", { "dependencies": { "css-select": "^5.1.0", "cssom": "^0.5.0", "html-escaper": "^3.0.3", "htmlparser2": "^10.0.0", "uhyphen": "^0.2.0" }, "peerDependencies": { "canvas": ">= 2" }, "optionalPeers": ["canvas"] }, "sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q=="], + "linkedom": ["linkedom@0.18.13", "", { "dependencies": { "css-select": "^7.0.0", "cssom": "^0.5.0", "html-escaper": "^3.0.3", "htmlparser2": "^10.1.0", "uhyphen": "^0.2.0" }, "peerDependencies": { "canvas": ">= 2" }, "optionalPeers": ["canvas"] }, "sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw=="], "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], @@ -483,15 +493,15 @@ "lop": ["lop@0.4.2", "", { "dependencies": { "duck": "^0.1.12", "option": "~0.2.1", "underscore": "^1.13.1" } }, "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw=="], - "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="], + "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], - "lucide-react": ["lucide-react@1.23.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw=="], + "lucide-react": ["lucide-react@1.25.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/mdJTRbiwcLOQ1NZZK1amZF9rIZyvO18D6r9TngE6TG1NmqHgFuT4eE7Xrkm9UsXMbBJD1NlfwHVltCDWHrOTw=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "mammoth": ["mammoth@1.12.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.6", "argparse": "~1.0.3", "base64-js": "^1.5.1", "bluebird": "~3.4.0", "dingbat-to-unicode": "^1.0.1", "jszip": "^3.7.1", "lop": "^0.4.2", "path-is-absolute": "^1.0.0", "underscore": "^1.13.1", "xmlbuilder": "^10.0.0" }, "bin": { "mammoth": "bin/mammoth" } }, "sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w=="], - "marked": ["marked@18.0.5", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w=="], + "marked": ["marked@18.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-MrV5puXBfuiy6wl6DLaq3BtIJQAJToAd5zt/ZKhRfGRAuFPALE7/4Y7jnxRQoEgK/pBgurGqLyAuRgZ2xOjr6w=="], "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], @@ -511,7 +521,7 @@ "node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="], - "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "nth-check": ["nth-check@3.0.1", "", { "dependencies": { "boolbase": "^2.0.0" } }, "sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ=="], "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], @@ -641,6 +651,8 @@ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "usage-status": ["usage-status@workspace:extensions/usage-status"], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "vali-date": ["vali-date@1.0.0", "", {}, "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg=="], @@ -671,31 +683,21 @@ "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], - - "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw=="], - - "@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], - - "@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw=="], - - "@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.9.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw=="], - - "@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], + "bun-types/@types/node": ["@types/node@24.13.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA=="], - "@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], + "chromium-bidi/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@opentelemetry/sdk-trace/@opentelemetry/core": ["@opentelemetry/core@2.9.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw=="], + "dom-serializer/domelementtype": ["domelementtype@3.0.0", "", {}, "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg=="], - "@opentelemetry/sdk-trace-base/@opentelemetry/core": ["@opentelemetry/core@2.9.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw=="], + "dom-serializer/entities": ["entities@8.0.0", "", {}, "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA=="], - "@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.9.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw=="], + "domhandler/domelementtype": ["domelementtype@3.0.0", "", {}, "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg=="], - "bun-types/@types/node": ["@types/node@24.13.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA=="], + "domutils/domelementtype": ["domelementtype@3.0.0", "", {}, "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg=="], - "chromium-bidi/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "htmlparser2/domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "htmlparser2/domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], "jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], @@ -704,5 +706,9 @@ "protobufjs/@types/node": ["@types/node@24.13.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA=="], "roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + + "htmlparser2/domutils/dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "htmlparser2/domutils/dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], } } diff --git a/extensions/usage-status/index.test.ts b/extensions/usage-status/index.test.ts new file mode 100644 index 0000000..db3fdc9 --- /dev/null +++ b/extensions/usage-status/index.test.ts @@ -0,0 +1,628 @@ +import { describe, expect, jest, test } from "bun:test"; +import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent"; +import usageStatus, { + formatReset, + formatUsageStatus, + providerLabel, + type RowStyle, + remainingPercent, + type UsageReportLike, + usageColor, + windowToken, +} from "./index"; + +const NOW = 1_700_000_000_000; +const MIN = 60_000; +const HR = 60 * MIN; +const DAY = 24 * HR; + +function limit(opts: { + windowId?: string; + windowIdField?: string; + windowLabel?: string; + durationMs?: number; + resetsAt?: number; + tier?: string; + usedFraction?: number; + remainingFraction?: number; +}): UsageReportLike["limits"][number] { + return { + scope: { windowId: opts.windowId, tier: opts.tier }, + window: { + id: opts.windowIdField, + label: opts.windowLabel, + durationMs: opts.durationMs, + resetsAt: opts.resetsAt, + }, + amount: { + usedFraction: opts.usedFraction, + remainingFraction: opts.remainingFraction, + }, + }; +} + +describe("formatReset", () => { + test("renders minutes under an hour", () => { + expect(formatReset(47 * MIN)).toBe("47m"); + }); + test("renders hours and minutes", () => { + expect(formatReset(133 * MIN)).toBe("2h13m"); + }); + test("renders days, hours, and minutes together", () => { + expect(formatReset(6 * DAY + 23 * HR + 58 * MIN)).toBe("6d23h58m"); + }); + test("rounds partial minutes up and floors at <1m", () => { + expect(formatReset(30_000)).toBe("1m"); + expect(formatReset(0)).toBe("<1m"); + expect(formatReset(-1000)).toBe("<1m"); + }); +}); + +describe("providerLabel", () => { + test("uses curated brands for known providers", () => { + expect(providerLabel("anthropic")).toBe("Claude"); + expect(providerLabel("openai-codex")).toBe("Codex"); + expect(providerLabel("xai")).toBe("Grok"); + expect(providerLabel("opencode-zen")).toBe("OpenCode Zen"); + }); + test("title-cases unknown provider ids", () => { + expect(providerLabel("some-new-provider")).toBe("Some New Provider"); + expect(providerLabel("wafer")).toBe("Wafer"); + }); +}); + +describe("windowToken", () => { + test("derives a single-unit token from duration", () => { + expect(windowToken(limit({ durationMs: 5 * HR }))).toBe("5h"); + expect(windowToken(limit({ durationMs: 7 * DAY }))).toBe("7d"); + expect(windowToken(limit({ durationMs: 30 * DAY }))).toBe("30d"); + expect(windowToken(limit({ durationMs: 45 * MIN }))).toBe("45m"); + }); + test("falls back to numeric ids and word forms without a duration", () => { + expect(windowToken(limit({ windowId: "5h" }))).toBe("5h"); + expect(windowToken(limit({ windowIdField: "rolling-5h" }))).toBe("5h"); + expect(windowToken(limit({ windowIdField: "weekly" }))).toBe("7d"); + expect(windowToken(limit({ windowIdField: "monthly" }))).toBe("30d"); + }); +}); + +describe("remainingPercent", () => { + test("prefers remainingFraction, falls back to 1 - usedFraction, clamps", () => { + expect( + remainingPercent(limit({ remainingFraction: 0.58, usedFraction: 0.1 })), + ).toBe(58); + expect(remainingPercent(limit({ usedFraction: 0.82 }))).toBe(18); + expect(remainingPercent(limit({ usedFraction: 1.5 }))).toBe(0); + expect(remainingPercent(limit({ remainingFraction: 1.2 }))).toBe(100); + expect(remainingPercent(limit({}))).toBeUndefined(); + expect( + remainingPercent({ + scope: {}, + } as unknown as UsageReportLike["limits"][number]), + ).toBeUndefined(); + }); +}); + +describe("usageColor", () => { + test("green when plenty remains, yellow when low, red when critical", () => { + expect(usageColor(100)).toBe("success"); + expect(usageColor(51)).toBe("success"); + expect(usageColor(50)).toBe("warning"); + expect(usageColor(21)).toBe("warning"); + expect(usageColor(20)).toBe("error"); + expect(usageColor(0)).toBe("error"); + }); +}); + +describe("formatUsageStatus", () => { + test("empty reports → undefined", () => { + expect(formatUsageStatus([], NOW)).toBeUndefined(); + }); + + test("renders every reported provider and its windows, sorted by label", () => { + const reports: UsageReportLike[] = [ + { + provider: "openai-codex", + limits: [ + limit({ + windowId: "5h", + usedFraction: 0.08, + resetsAt: NOW + 47 * MIN, + }), + limit({ windowId: "7d", usedFraction: 0.6, resetsAt: NOW + 3 * DAY }), + ], + }, + { + provider: "anthropic", + limits: [ + limit({ + windowId: "5h", + remainingFraction: 0.58, + resetsAt: NOW + 133 * MIN, + }), + limit({ + windowId: "7d", + remainingFraction: 0.82, + resetsAt: NOW + 5 * DAY, + }), + ], + }, + ]; + expect(formatUsageStatus(reports, NOW)).toBe( + "Claude 5h 58% (2h13m) · 7d 82% (5d) | Codex 5h 92% (47m) · 7d 40% (3d)", + ); + }); +}); + +describe("formatUsageStatus dynamic providers", () => { + test("renders arbitrary providers and window shapes dynamically", () => { + const reports: UsageReportLike[] = [ + { + provider: "xai", + limits: [ + limit({ + windowId: "5h", + remainingFraction: 0.9, + resetsAt: NOW + 1 * HR, + }), + ], + }, + { + provider: "opencode-go", + limits: [ + limit({ + windowIdField: "rolling-5h", + durationMs: 5 * HR, + remainingFraction: 0.7, + }), + limit({ + windowIdField: "weekly", + durationMs: 7 * DAY, + remainingFraction: 0.4, + }), + limit({ + windowIdField: "monthly", + durationMs: 30 * DAY, + remainingFraction: 0.2, + }), + ], + }, + ]; + expect(formatUsageStatus(reports, NOW)).toBe( + "Grok 5h 90% (1h) | OpenCode 5h 70% · 7d 40% · 30d 20%", + ); + }); +}); + +describe("formatUsageStatus styling", () => { + test("applies theme colors to label, percent, and reset", () => { + const tag: RowStyle = { + fg: (c, t) => `[${c}]${t}`, + dot: " · ", + pipe: " | ", + showReset: true, + }; + const reports: UsageReportLike[] = [ + { + provider: "anthropic", + limits: [ + limit({ + windowId: "5h", + remainingFraction: 0.1, + resetsAt: NOW + 30 * MIN, + }), + ], + }, + ]; + expect(formatUsageStatus(reports, NOW, tag)).toBe( + "[accent]Claude 5h [error]10% [dim](30m)", + ); + }); +}); + +describe("formatUsageStatus window selection", () => { + test("prefers the untiered limit over a tiered duplicate", () => { + const reports: UsageReportLike[] = [ + { + provider: "anthropic", + limits: [ + limit({ windowId: "7d", tier: "fable", remainingFraction: 0.1 }), + limit({ windowId: "7d", remainingFraction: 0.75 }), + ], + }, + ]; + expect(formatUsageStatus(reports, NOW)).toBe("Claude 7d 75%"); + }); + + test("omits reset countdown when the window has already reset", () => { + const reports: UsageReportLike[] = [ + { + provider: "anthropic", + limits: [ + limit({ + windowId: "5h", + remainingFraction: 0.3, + resetsAt: NOW - 5 * MIN, + }), + ], + }, + ]; + expect(formatUsageStatus(reports, NOW)).toBe("Claude 5h 30%"); + }); + + test("skips a report whose windows have no resolvable remaining", () => { + const reports: UsageReportLike[] = [ + { provider: "cursor", limits: [limit({ windowId: "monthly" })] }, + ]; + expect(formatUsageStatus(reports, NOW)).toBeUndefined(); + }); +}); + +describe("formatUsageStatus multi-account", () => { + test("labels each account when a provider reports more than one", () => { + const reports: UsageReportLike[] = [ + { + provider: "anthropic", + metadata: { email: "bob@work.com" }, + limits: [limit({ windowId: "5h", remainingFraction: 0.4 })], + }, + { + provider: "anthropic", + metadata: { email: "alice@home.com" }, + limits: [limit({ windowId: "5h", remainingFraction: 0.9 })], + }, + ]; + expect(formatUsageStatus(reports, NOW)).toBe( + "Claude:alice 5h 90% | Claude:bob 5h 40%", + ); + }); + + test("omits the account label for a single-account provider", () => { + const reports: UsageReportLike[] = [ + { + provider: "anthropic", + metadata: { email: "solo@x.com" }, + limits: [limit({ windowId: "5h", remainingFraction: 0.4 })], + }, + ]; + expect(formatUsageStatus(reports, NOW)).toBe("Claude 5h 40%"); + }); + + test("uses the base label when only one of a provider's accounts renders", () => { + const reports: UsageReportLike[] = [ + { + provider: "anthropic", + metadata: { email: "bob@work.com" }, + limits: [limit({ windowId: "5h", remainingFraction: 0.4 })], + }, + { + provider: "anthropic", + metadata: { email: "alice@home.com" }, + limits: [limit({ windowId: "monthly" })], // no remaining → not rendered + }, + ]; + expect(formatUsageStatus(reports, NOW)).toBe("Claude 5h 40%"); + }); +}); + +type Handler = (event: unknown, ctx: unknown) => void; +type WidgetFactory = ( + tui: unknown, + theme: unknown, +) => { render: (width: number) => readonly string[] }; + +function fakePi(handlers: Record): ExtensionAPI { + return { + on: (ev: string, fn: Handler) => { + handlers[ev] = fn; + }, + } as unknown as ExtensionAPI; +} + +async function flushMicrotasks(): Promise { + for (let i = 0; i < 5; i++) await Promise.resolve(); +} + +const FAKE_THEME = { + fg: (_c: string, t: string) => t, + sep: { dot: " · ", pipe: "|" }, +}; + +interface UiCtxOptions { + reports: UsageReportLike[]; + widgetCalls: unknown[]; + hasUI?: boolean; + onFetch?: (opts: { + baseUrlResolver: (p: string) => string | undefined; + }) => void; +} + +function uiCtx(o: UiCtxOptions): ExtensionContext { + return { + hasUI: o.hasUI ?? true, + ui: { setWidget: (...args: unknown[]) => o.widgetCalls.push(args) }, + modelRegistry: { + getProviderBaseUrl: (p: string) => `https://api/${p}`, + authStorage: { + fetchUsageReports: (opts: { + baseUrlResolver: (p: string) => string | undefined; + }) => { + o.onFetch?.(opts); + return Promise.resolve(o.reports); + }, + }, + }, + } as unknown as ExtensionContext; +} + +describe("usageStatus wiring", () => { + test("installs an aboveEditor widget that renders live usage; shutdown clears it", async () => { + const handlers: Record = {}; + const widgetCalls: unknown[] = []; + let renders = 0; + let baseUrl: string | undefined; + const reports: UsageReportLike[] = [ + { + provider: "anthropic", + limits: [ + limit({ + windowId: "5h", + remainingFraction: 0.62, + resetsAt: Date.now() + 90 * MIN, + }), + ], + }, + ]; + const ctx = uiCtx({ + reports, + widgetCalls, + onFetch: (o) => { + baseUrl = o.baseUrlResolver("anthropic"); + }, + }); + usageStatus(fakePi(handlers)); + try { + handlers["session_start"]?.({}, ctx); + expect(widgetCalls).toHaveLength(1); + const [key, factory, opts] = widgetCalls[0] as [ + string, + WidgetFactory, + unknown, + ]; + expect(key).toBe("usage-status"); + expect(opts).toEqual({ placement: "aboveEditor" }); + const component = factory({ requestRender: () => renders++ }, FAKE_THEME); + expect(component.render(200)).toEqual([]); // no data fetched yet + await flushMicrotasks(); + expect(baseUrl).toBe("https://api/anthropic"); + expect(renders).toBeGreaterThan(0); + expect(component.render(200)).toEqual(["Claude 5h 62% (1h30m)"]); + expect(component.render(13)).toEqual(["Claude 5h 62%"]); // drops reset to fit + expect(component.render(5)).toEqual([]); // hides when nothing fits + } finally { + handlers["session_shutdown"]?.({}, ctx); + } + expect(widgetCalls.at(-1)).toEqual([ + "usage-status", + undefined, + { placement: "aboveEditor" }, + ]); + }); +}); + +describe("usageStatus headless", () => { + test("headless mode (no UI) neither installs a widget nor fetches", async () => { + const handlers: Record = {}; + const widgetCalls: unknown[] = []; + let fetched = false; + const ctx = uiCtx({ + reports: [], + widgetCalls, + hasUI: false, + onFetch: () => { + fetched = true; + }, + }); + usageStatus(fakePi(handlers)); + handlers["session_start"]?.({}, ctx); + await flushMicrotasks(); + expect(fetched).toBe(false); + expect(widgetCalls).toHaveLength(0); + }); +}); + +describe("usageStatus refresh", () => { + test("fires the interval tick to re-render and reinstalls on session_switch", async () => { + jest.useFakeTimers(); + const handlers: Record = {}; + const widgetCalls: unknown[] = []; + let renders = 0; + let fetches = 0; + const reports: UsageReportLike[] = [ + { + provider: "anthropic", + limits: [limit({ windowId: "5h", remainingFraction: 0.5 })], + }, + ]; + const ctx = uiCtx({ + reports, + widgetCalls, + onFetch: () => { + fetches++; + }, + }); + try { + usageStatus(fakePi(handlers)); + handlers["session_start"]?.({}, ctx); + const factory = (widgetCalls[0] as [string, WidgetFactory, unknown])[1]; + factory({ requestRender: () => renders++ }, FAKE_THEME); + await flushMicrotasks(); + const before = renders; + jest.advanceTimersByTime(60_000); // interval → tick → redraw + expect(renders).toBeGreaterThan(before); + handlers["session_switch"]?.({}, ctx); // resets state and reinstalls + expect(widgetCalls.length).toBeGreaterThan(1); + expect(fetches).toBeGreaterThanOrEqual(2); + } finally { + handlers["session_shutdown"]?.({}, ctx); + jest.useRealTimers(); + } + }); +}); + +describe("usageStatus resilience", () => { + test("drops malformed reports and limits from the fetch payload", async () => { + const handlers: Record = {}; + const widgetCalls: unknown[] = []; + const malformed = [ + null, + 42, + { provider: 123, limits: [] }, + { provider: "x", limits: "nope" }, + { + provider: "anthropic", + limits: [ + null, + 7, + { scope: {} }, + { amount: { remainingFraction: 0.5 }, scope: { windowId: 5 } }, + { amount: { usedFraction: "high" }, scope: {} }, + { amount: {}, scope: {}, window: { durationMs: "5h" } }, + limit({ windowId: "5h", remainingFraction: 0.4 }), + ], + }, + ] as unknown as UsageReportLike[]; + const ctx = uiCtx({ reports: malformed, widgetCalls }); + usageStatus(fakePi(handlers)); + try { + handlers["session_start"]?.({}, ctx); + const c = (widgetCalls[0] as [string, WidgetFactory, unknown])[1]( + { requestRender() {} }, + FAKE_THEME, + ); + await flushMicrotasks(); + expect(c.render(200)).toEqual(["Claude 5h 40%"]); + } finally { + handlers["session_shutdown"]?.({}, ctx); + } + }); + + test("ignores a null usage payload without throwing", async () => { + const handlers: Record = {}; + const widgetCalls: unknown[] = []; + const ctx = uiCtx({ + reports: null as unknown as UsageReportLike[], + widgetCalls, + }); + usageStatus(fakePi(handlers)); + try { + handlers["session_start"]?.({}, ctx); + const c = (widgetCalls[0] as [string, WidgetFactory, unknown])[1]( + { requestRender() {} }, + FAKE_THEME, + ); + await flushMicrotasks(); + expect(c.render(200)).toEqual([]); + } finally { + handlers["session_shutdown"]?.({}, ctx); + } + }); +}); + +describe("usageStatus session switch", () => { + test("clears stale reports and renders freshly fetched usage after a switch", async () => { + const handlers: Record = {}; + const widgetCalls: unknown[] = []; + const reports: UsageReportLike[] = [ + { + provider: "anthropic", + limits: [limit({ windowId: "5h", remainingFraction: 0.8 })], + }, + ]; + const ctx = uiCtx({ reports, widgetCalls }); + usageStatus(fakePi(handlers)); + try { + handlers["session_start"]?.({}, ctx); + const first = (widgetCalls[0] as [string, WidgetFactory, unknown])[1]( + { requestRender() {} }, + FAKE_THEME, + ); + await flushMicrotasks(); + expect(first.render(200)).toEqual(["Claude 5h 80%"]); + reports[0] = { + provider: "openai-codex", + limits: [limit({ windowId: "5h", remainingFraction: 0.3 })], + }; + handlers["session_switch"]?.({}, ctx); + expect(widgetCalls[1]).toEqual([ + "usage-status", + undefined, + { placement: "aboveEditor" }, + ]); // old widget torn down before reinstall + const second = (widgetCalls[2] as [string, WidgetFactory, unknown])[1]( + { requestRender() {} }, + FAKE_THEME, + ); + expect(second).not.toBe(first); // lifecycle reset: fresh component + expect(second.render(200)).toEqual([]); // stale reports cleared before refetch + await flushMicrotasks(); + expect(second.render(200)).toEqual(["Codex 5h 30%"]); + } finally { + handlers["session_shutdown"]?.({}, ctx); + } + }); +}); + +describe("usageStatus stale fetch", () => { + test("a fetch in flight during a switch cannot overwrite the new session", async () => { + const handlers: Record = {}; + const widgetCalls: unknown[] = []; + let resolveFirst: (() => void) | undefined; + const first: UsageReportLike[] = [ + { + provider: "anthropic", + limits: [limit({ windowId: "5h", remainingFraction: 0.9 })], + }, + ]; + const second: UsageReportLike[] = [ + { + provider: "openai-codex", + limits: [limit({ windowId: "5h", remainingFraction: 0.2 })], + }, + ]; + let call = 0; + const ctx = { + hasUI: true, + ui: { setWidget: (...args: unknown[]) => widgetCalls.push(args) }, + modelRegistry: { + getProviderBaseUrl: () => undefined, + authStorage: { + fetchUsageReports: () => { + call += 1; + return call === 1 + ? new Promise((res) => { + resolveFirst = () => res(first); + }) + : Promise.resolve(second); + }, + }, + }, + } as unknown as ExtensionContext; + usageStatus(fakePi(handlers)); + try { + handlers["session_start"]?.({}, ctx); // fetch #1 starts, stays pending + handlers["session_switch"]?.({}, ctx); // teardown + fetch #2 + const comp = (widgetCalls.at(-1) as [string, WidgetFactory, unknown])[1]( + { requestRender() {} }, + FAKE_THEME, + ); + await flushMicrotasks(); + expect(comp.render(200)).toEqual(["Codex 5h 20%"]); + resolveFirst?.(); // stale fetch #1 resolves late + await flushMicrotasks(); + expect(comp.render(200)).toEqual(["Codex 5h 20%"]); // not clobbered + } finally { + handlers["session_shutdown"]?.({}, ctx); + } + }); +}); diff --git a/extensions/usage-status/index.ts b/extensions/usage-status/index.ts new file mode 100644 index 0000000..71b7572 --- /dev/null +++ b/extensions/usage-status/index.ts @@ -0,0 +1,467 @@ +/** + * usage-status — surface remaining subscription usage above the omp editor. + * + * `/usage` reports how much quota is left per subscription (rolling windows like + * 5h / 7d / weekly / monthly) plus a countdown to each reset. This extension + * renders the same numbers as a single colored row directly above the editor — + * next to the status line — so they are always visible without running `/usage`. + * + * It is fully data-driven: every provider and account that + * `ctx.modelRegistry.authStorage.fetchUsageReports(...)` returns is rendered, + * whatever it is (Claude, Codex, Gemini, Grok/xAI, OpenCode, Cursor, Copilot, + * Kimi, Z.ai, …). Providers with no usage backend simply never appear in the + * reports, so they are skipped. This is the exact call `AgentSession` makes for + * `/usage` and the built-in footer; unlike the built-in `usage` segment (active + * provider only), this shows every reported subscription. + * + * Rendering uses a component widget (`ui.setWidget(..., { placement: "aboveEditor" })`) + * rather than `ui.setStatus`, which feeds the hook-status block and renders apart + * from the main line with its own spacing. A component row renders flush above + * the editor and can carry theme colors. Network fetches are cached for + * `NETWORK_TTL_MS`; a per-minute timer requests a redraw so the countdown ticks + * locally from the cached absolute reset timestamps without refetching. The + * widget is cleared on session shutdown and stays idle where `ctx.hasUI` is false. + */ + +import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent"; + +const WIDGET_KEY = "usage-status"; +/** Refetch from the provider at most this often (matches core's footer cache). */ +const NETWORK_TTL_MS = 5 * 60_000; +/** Local redraw cadence: recomputes the reset countdown from cached data. */ +const TICK_MS = 60_000; +/** Abort a single usage fetch after this long. */ +const FETCH_TIMEOUT_MS = 10_000; + +/** Short display names for common providers; unknown ids are title-cased. */ +const KNOWN_LABELS: Record = { + anthropic: "Claude", + "openai-codex": "Codex", + "openai-codex-device": "Codex", + "github-copilot": "Copilot", + cursor: "Cursor", + "google-antigravity": "Antigravity", + "google-gemini-cli": "Gemini", + gemini: "Gemini", + "kimi-code": "Kimi", + kimi: "Kimi", + minimax: "MiniMax", + "minimax-code": "MiniMax", + "minimax-code-cn": "MiniMax", + ollama: "Ollama", + "ollama-cloud": "Ollama", + "opencode-go": "OpenCode", + "opencode-zen": "OpenCode Zen", + xai: "Grok", + "xai-oauth": "Grok", + zai: "Z.ai", + zenmux: "ZenMux", +}; + +/** Compact tokens for word-form window ids that carry no duration. */ +const WINDOW_WORDS: Record = { + hourly: "1h", + daily: "1d", + weekly: "7d", + monthly: "30d", +}; + +/** Structural subset of pi-ai's `UsageLimit` that this formatter reads. */ +export interface UsageLimitLike { + scope: { windowId?: string; tier?: string }; + window?: { + id?: string; + label?: string; + durationMs?: number; + resetsAt?: number; + }; + amount: { usedFraction?: number; remainingFraction?: number }; +} + +/** Structural subset of pi-ai's `UsageReport` that this formatter reads. */ +export interface UsageReportLike { + provider: string; + limits: UsageLimitLike[]; + metadata?: Record; +} + +/** Styling seam so the formatter stays pure and testable. `fg` wraps text in a + * theme color (identity in no-color contexts); `dot`/`pipe` are the separators. */ +export interface RowStyle { + fg: (color: string, text: string) => string; + dot: string; + pipe: string; + showReset: boolean; +} + +const plainFg = (_color: string, text: string): string => text; +const PLAIN_STYLE: RowStyle = { + fg: plainFg, + dot: " · ", + pipe: " | ", + showReset: true, +}; + +/** Friendly provider label: curated brand, else title-cased id. */ +export function providerLabel(provider: string): string { + const known = KNOWN_LABELS[provider]; + if (known) return known; + return provider + .split(/[-_]/) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +/** Round a duration to a compact single-unit token: "45m", "5h", "7d", "30d". */ +function durationToken(ms: number): string { + const minutes = Math.round(ms / 60_000); + if (minutes < 60) return `${minutes}m`; + const hours = Math.round(minutes / 60); + if (hours < 24) return `${hours}h`; + return `${Math.round(hours / 24)}d`; +} + +/** Compact window token, preferring the reported duration, then id/label. */ +export function windowToken(limit: UsageLimitLike): string { + const duration = limit.window?.durationMs; + if (typeof duration === "number" && duration > 0) + return durationToken(duration); + const id = (limit.scope.windowId ?? limit.window?.id ?? "") + .toLowerCase() + .replace(/^rolling-/, ""); + if (/^\d+(m|h|d|w|mo)$/.test(id)) return id; + return WINDOW_WORDS[id] ?? (id || limit.window?.label || "?"); +} + +/** Remaining availability as an integer percent (0-100), or undefined. */ +export function remainingPercent(limit: UsageLimitLike): number | undefined { + const amount = limit?.amount; + if (!amount) return undefined; + const fraction = + amount.remainingFraction ?? + (amount.usedFraction !== undefined ? 1 - amount.usedFraction : undefined); + if (fraction === undefined) return undefined; + return Math.round(Math.min(1, Math.max(0, fraction)) * 100); +} + +/** Theme color for a remaining-percent value: green plenty, yellow low, red critical. */ +export function usageColor(remaining: number): string { + if (remaining <= 20) return "error"; + if (remaining <= 50) return "warning"; + return "success"; +} + +/** Compact reset countdown, rounded up so it never understates: "47m", "2h13m", "6d23h58m". */ +export function formatReset(ms: number): string { + const totalMinutes = Math.ceil(Math.max(0, ms) / 60_000); + const days = Math.floor(totalMinutes / (24 * 60)); + const hours = Math.floor((totalMinutes % (24 * 60)) / 60); + const minutes = totalMinutes % 60; + if (days > 0) + return `${days}d${hours ? `${hours}h` : ""}${minutes ? `${minutes}m` : ""}`; + if (hours > 0) return `${hours}h${minutes ? `${minutes}m` : ""}`; + if (minutes > 0) return `${minutes}m`; + return "<1m"; +} + +/** A single report's windows: one per window id, preferring an untiered limit, + * dropping windows with no resolvable remaining %, sorted shortest-first. */ +function selectWindows(limits: readonly UsageLimitLike[]): UsageLimitLike[] { + const byKey = new Map(); + for (const limit of limits) { + if (remainingPercent(limit) === undefined) continue; + const key = + limit.scope.windowId ?? + limit.window?.id ?? + limit.window?.label ?? + windowToken(limit); + const existing = byKey.get(key); + if (!existing || (existing.scope.tier && !limit.scope.tier)) + byKey.set(key, limit); + } + return [...byKey.values()].sort( + (a, b) => + (a.window?.durationMs ?? Number.POSITIVE_INFINITY) - + (b.window?.durationMs ?? Number.POSITIVE_INFINITY), + ); +} + +/** Short account label from report metadata (email local-part, id, or project). */ +function accountLabel(report: UsageReportLike): string { + const meta = report.metadata ?? {}; + const email = typeof meta["email"] === "string" ? meta["email"] : ""; + if (email) return email.split("@")[0] || email; + const accountId = + typeof meta["accountId"] === "string" ? meta["accountId"] : ""; + if (accountId) return accountId; + const projectId = + typeof meta["projectId"] === "string" ? meta["projectId"] : ""; + return projectId || "acct"; +} + +/** Render one window as " %[ ()]", with color on the values. */ +function formatWindow( + limit: UsageLimitLike, + now: number, + style: RowStyle, +): string { + const remaining = remainingPercent(limit) ?? 0; + const pct = style.fg(usageColor(remaining), `${remaining}%`); + const resetsAt = limit.window?.resetsAt; + const reset = + style.showReset && typeof resetsAt === "number" && resetsAt > now + ? ` ${style.fg("dim", `(${formatReset(resetsAt - now)})`)}` + : ""; + return `${windowToken(limit)} ${pct}${reset}`; +} + +/** Absent, or a string. */ +function optionalString(value: unknown): boolean { + return value === undefined || typeof value === "string"; +} + +/** Absent, or a finite number. */ +function optionalFiniteNumber(value: unknown): boolean { + return ( + value === undefined || (typeof value === "number" && Number.isFinite(value)) + ); +} + +/** Runtime guard for a well-formed limit: network data is cast, not validated, so + * a malformed entry — including wrong-typed nested fields that would crash string + * or number ops in `windowToken`/`formatWindow` — must be dropped before render. */ +function isUsageLimit(value: unknown): value is UsageLimitLike { + if (typeof value !== "object" || value === null) return false; + const { amount, scope, window } = value as UsageLimitLike; + if (typeof amount !== "object" || amount === null) return false; + if (!optionalFiniteNumber(amount.remainingFraction)) return false; + if (!optionalFiniteNumber(amount.usedFraction)) return false; + if (typeof scope !== "object" || scope === null) return false; + if (!optionalString(scope.windowId) || !optionalString(scope.tier)) + return false; + if (window === undefined) return true; + if (typeof window !== "object" || window === null) return false; + if (!optionalString(window.id) || !optionalString(window.label)) return false; + return ( + optionalFiniteNumber(window.durationMs) && + optionalFiniteNumber(window.resetsAt) + ); +} + +/** Validate raw usage data before it is treated as `UsageReportLike[]`: keep only + * report objects with a string provider and array limits, and drop malformed + * limits, so nothing unsafe reaches `render()`. Network data is cast, not typed. */ +function sanitizeUsageReports(value: unknown): UsageReportLike[] { + if (!Array.isArray(value)) return []; + const reports: UsageReportLike[] = []; + for (const entry of value) { + if (typeof entry !== "object" || entry === null) continue; + const report = entry as UsageReportLike; + if (typeof report.provider !== "string" || !Array.isArray(report.limits)) + continue; + reports.push({ ...report, limits: report.limits.filter(isUsageLimit) }); + } + return reports; +} + +/** Build the usage row from every reported subscription, or undefined when empty. */ +export function formatUsageStatus( + reports: readonly UsageReportLike[], + now: number, + style: RowStyle = PLAIN_STYLE, +): string | undefined { + const valid = reports.filter( + (r): r is UsageReportLike => + !!r && typeof r.provider === "string" && Array.isArray(r.limits), + ); + const rendered: { report: UsageReportLike; windows: UsageLimitLike[] }[] = []; + for (const report of valid) { + const windows = selectWindows(report.limits); + if (windows.length > 0) rendered.push({ report, windows }); + } + const counts = new Map(); + for (const { report } of rendered) + counts.set(report.provider, (counts.get(report.provider) ?? 0) + 1); + + const entries = rendered.map(({ report, windows }) => { + const base = providerLabel(report.provider); + // Disambiguate with an account label only when a provider has >1 rendered account. + const label = + (counts.get(report.provider) ?? 0) > 1 + ? `${base}:${accountLabel(report)}` + : base; + return { label, windows }; + }); + entries.sort((a, b) => a.label.localeCompare(b.label)); + + const parts = entries.map( + (entry) => + `${style.fg("accent", entry.label)} ${entry.windows + .map((window) => formatWindow(window, now, style)) + .join(style.dot)}`, + ); + return parts.length ? parts.join(style.pipe) : undefined; +} + +interface ThemeLike { + fg(color: string, text: string): string; + sep: { dot: string; pipe: string }; +} + +const EMPTY_ROWS: readonly string[] = []; + +/** One-line widget component rendered above the editor. Reads `reports` live so + * a redraw recomputes the countdown from the cached absolute reset times. */ +class UsageRow { + reports: readonly UsageReportLike[] = []; + readonly #fg: (color: string, text: string) => string; + readonly #dot: string; + readonly #pipe: string; + + constructor(theme: ThemeLike) { + this.#fg = (color, text) => theme.fg(color, text); + this.#dot = theme.sep.dot; + this.#pipe = ` ${theme.sep.pipe} `; + } + + render(width: number): readonly string[] { + const now = Date.now(); + // Drop the reset countdowns before hiding entirely; measure the uncolored + // form so ANSI color codes never count against the width budget. + for (const showReset of [true, false] as const) { + const base = { dot: this.#dot, pipe: this.#pipe, showReset }; + const plain = formatUsageStatus(this.reports, now, { + ...base, + fg: plainFg, + }); + if (!plain) return EMPTY_ROWS; + if ([...plain].length <= width) { + return [ + formatUsageStatus(this.reports, now, { ...base, fg: this.#fg }) ?? + plain, + ]; + } + } + return EMPTY_ROWS; + } +} + +interface UsageState { + reports: UsageReportLike[]; + fetchedAt: number; + inFlight: boolean; + /** Bumped on every teardown; a fetch whose captured value no longer matches + * has been superseded by a session switch and must not touch state. */ + generation: number; + timer: Timer | undefined; + ctx: ExtensionContext | undefined; + component: UsageRow | undefined; + tui: { requestRender?: (force?: boolean) => void } | undefined; +} + +function redraw(state: UsageState): void { + if (state.component) state.component.reports = state.reports; + state.tui?.requestRender?.(); +} + +function installWidget(state: UsageState, ctx: ExtensionContext): void { + try { + ctx.ui.setWidget( + WIDGET_KEY, + (tui, theme) => { + state.tui = tui; + if (!state.component) state.component = new UsageRow(theme); + state.component.reports = state.reports; + return state.component; + }, + { placement: "aboveEditor" }, + ); + } catch { + // ACP/RPC hosts may not support component widgets; the row is optional. + } +} + +function clearWidget(state: UsageState): void { + const ctx = state.ctx; + if (!ctx?.hasUI) return; + try { + ctx.ui.setWidget(WIDGET_KEY, undefined, { placement: "aboveEditor" }); + } catch { + // Nothing to clear on hosts that ignored the widget. + } +} + +async function fetchReports(state: UsageState): Promise { + const registry = state.ctx?.modelRegistry; + const authStorage = registry?.authStorage; + if ( + !registry || + state.inFlight || + typeof authStorage?.fetchUsageReports !== "function" + ) + return; + const generation = state.generation; + state.inFlight = true; + try { + const result = await authStorage.fetchUsageReports({ + baseUrlResolver: (provider) => registry.getProviderBaseUrl(provider), + signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), + }); + if (generation !== state.generation) return; // superseded by a session switch + state.reports = sanitizeUsageReports(result); + state.fetchedAt = Date.now(); + } catch { + // Keep the stale cache — the countdown still ticks from cached reset times. + } finally { + if (generation === state.generation) state.inFlight = false; + } + if (generation === state.generation) redraw(state); +} + +function tick(state: UsageState): void { + if (Date.now() - state.fetchedAt >= NETWORK_TTL_MS) void fetchReports(state); + else redraw(state); +} + +function stop(state: UsageState): void { + clearInterval(state.timer); + state.timer = undefined; + clearWidget(state); + state.component = undefined; + state.tui = undefined; + // Invalidate any in-flight fetch and free the slot for the next session. + state.generation += 1; + state.inFlight = false; +} + +function start(state: UsageState, ctx: ExtensionContext): void { + state.ctx = ctx; + clearInterval(state.timer); + state.timer = undefined; + if (!ctx.hasUI) return; + installWidget(state, ctx); + void fetchReports(state); + state.timer = setInterval(() => tick(state), TICK_MS); + state.timer.unref?.(); +} + +export default function usageStatus(pi: ExtensionAPI): void { + const state: UsageState = { + reports: [], + fetchedAt: 0, + inFlight: false, + generation: 0, + timer: undefined, + ctx: undefined, + component: undefined, + tui: undefined, + }; + pi.on("session_start", (_event, ctx) => start(state, ctx)); + pi.on("session_switch", (_event, ctx) => { + stop(state); + state.reports = []; + state.fetchedAt = 0; + start(state, ctx); + }); + pi.on("session_shutdown", () => stop(state)); +} diff --git a/extensions/usage-status/package.json b/extensions/usage-status/package.json new file mode 100644 index 0000000..2505055 --- /dev/null +++ b/extensions/usage-status/package.json @@ -0,0 +1,11 @@ +{ + "name": "usage-status", + "version": "0.0.0", + "private": true, + "description": "Show remaining subscription usage and reset countdowns for every provider, in a color-coded row above the omp editor.", + "omp": { + "extensions": [ + "./index.ts" + ] + } +} diff --git a/package.json b/package.json index b77e6e1..1f3233f 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { - "@biomejs/biome": "^2.5.3", - "@oh-my-pi/pi-coding-agent": "^16.4.8", + "@biomejs/biome": "^2.5.4", + "@oh-my-pi/pi-coding-agent": "^17.0.5", "@types/bun": "1.3.14", "@types/node": "^24.13.3", "sort-package-json": "^4.0.0", @@ -23,7 +23,8 @@ "packageManager": "bun@1.3.14", "omp": { "extensions": [ - "./extensions/rules-guard/index.ts" + "./extensions/rules-guard/index.ts", + "./extensions/usage-status/index.ts" ] } }