diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6de1742 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +coverage/ +.env +.env.* +*.tgz +.pi/settings.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2908799 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Mitch Fultz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f42a03 --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +# pi-cursor-sdk + +pi provider extension backed by `@cursor/sdk` local agents. + +## What this is + +This package lets pi use Cursor models through the local Cursor SDK. + +Current behavior: + +- discovers Cursor models with `Cursor.models.list()` when `CURSOR_API_KEY` is set +- registers each model's default variant as a pi model +- encodes default Cursor params into the pi model ID +- maps Cursor reasoning/thinking params into pi thinking levels when possible +- creates a fresh local Cursor agent for each pi provider call + +## Requirements + +- Node.js +- pi +- a `CURSOR_API_KEY` + +## Install + +Clone the repo, then install deps: + +```bash +npm install +``` + +## Run locally with pi + +```bash +CURSOR_API_KEY=your-key pi -e . +``` + +Pick a model with `/model`, or pass one directly: + +```bash +CURSOR_API_KEY=your-key pi -e . -p --model cursor/composer-2:fast=true "Say ok only." +``` + +## Model IDs + +When `CURSOR_API_KEY` is set, models come from `Cursor.models.list()`. +Each registered pi model uses the Cursor base model ID plus the default variant params. + +Examples: + +- `cursor/composer-2:fast=true` +- `cursor/gpt-5.5:context=1m;fast=false;reasoning=medium` +- `cursor/claude-sonnet-4-6:context=1m;effort=medium;thinking=true` + +Encoding rules: + +- no params: `gemini-3.1-pro` +- with params: `gpt-5.4:context=1m;fast=false;reasoning=medium` +- params are sorted alphabetically +- params use `name=value` pairs joined by `;` + +The extension decodes that ID back into a Cursor `ModelSelection` before calling `Agent.create()`. + +## Fallback models + +If `CURSOR_API_KEY` is missing or model discovery fails, the extension registers these fallbacks: + +| Model ID | Name | +|---|---| +| `composer-2:fast=true` | Cursor Composer 2 | +| `gpt-5.5:context=1m;reasoning=medium;fast=false` | GPT-5.5 | +| `claude-sonnet-4-6:context=1m;effort=medium;thinking=true` | Sonnet 4.6 | +| `claude-opus-4-7:context=1m;effort=xhigh;thinking=true` | Opus 4.7 | + +## Thinking support + +If a Cursor model exposes `reasoning`, `thinking`, or `effort`, the extension sets `reasoning: true` for the pi model and builds a `thinkingLevelMap`. + +Examples: + +- `reasoning=extra-high` maps to pi `xhigh` +- `effort=max` maps to pi `xhigh` +- boolean `thinking=true|false` maps to pi on/off-style thinking + +At request time, pi's selected thinking level overrides the encoded Cursor reasoning params sent to the SDK. + +## Images + +Images from the latest user message are forwarded to Cursor. +Historical images are kept out of the transcript. + +## How it works + +For each pi provider call, the extension: + +1. builds a plain-text prompt from pi conversation state +2. creates a fresh local Cursor agent for the current working directory +3. streams text and thinking deltas back into pi +4. disposes the Cursor agent + +pi remains the source of truth for conversation history. + +## Limits + +- local agents only; no Cursor cloud agent support +- Cursor tool calls are not exposed as pi tool calls +- pi tool schemas are not passed through to Cursor +- one fresh Cursor agent per provider call +- only the default Cursor variant is auto-registered +- historical images are not replayed to Cursor + +## License + +MIT. + +## Development + +Run checks: + +```bash +npm test +npm run typecheck +``` + +## Notes + +- `docs/cursor-model-ux-spec.md` is a forward-looking design spec, not a description of the exact current implementation. +- Unknown or invalid model params are passed through to Cursor and may be rejected by the Cursor SDK. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md new file mode 100644 index 0000000..9e55794 --- /dev/null +++ b/docs/cursor-model-ux-spec.md @@ -0,0 +1,649 @@ +# Cursor Model UX Spec + +## Status + +Design draft. This file describes a target UX direction, not the exact behavior of the current code in `src/`. + +Current implementation status to keep in mind while reading this spec: + +- current model IDs use `base:param=value` encoding +- default Cursor variant params are encoded directly into the pi model ID +- the `@context` model-ID shape in this doc is not implemented yet +- Cursor-only fast-mode state and footer/status behavior in this doc are not implemented yet + +## Goal + +Make Cursor models feel native in pi by leaning on pi's existing model, thinking, footer, and session behavior instead of building a parallel Cursor parameter system. + +Main outcomes: + +- `pi --list-models` shows pi-native Cursor models with accurate `contextWindow`, `maxTokens`, thinking, and image metadata. +- `shift+tab` is pi's native thinking control and drives Cursor `reasoning` or `effort`. +- Cursor context options are represented as pi-visible model variants when they change native model metadata. +- Cursor-only state, currently `fast`, is controlled by extension commands/shortcuts and shown through native status text. +- The default pi footer remains intact. +- Model capabilities are discovered from the Cursor SDK, not hardcoded per model. + +Native tradeoff: context-capable Cursor models intentionally use context-qualified pi model IDs. This gives up one completely clean row per Cursor base model, but it lets pi's native `contextWindow`, footer context usage, context overflow checks, compaction behavior, session restore, model selection, and `--list-models` metadata stay accurate. + +## Non-goals + +Not building now: + +- verbosity support +- custom UI panels +- generic pi model-parameter system for all providers +- full custom footer replacement +- independent Claude `thinking` toggle separate from pi thinking +- multi-parameter CLI suffixes such as `--model cursor/gpt-5.5:medium:272k:fast` + +## Source of Truth + +Cursor SDK is the source of truth for Cursor model IDs and Cursor-supported parameters. + +At startup, the extension calls: + +```ts +Cursor.models.list({ apiKey }); +``` + +For each model, use: + +- `model.id` +- `model.displayName` +- `model.parameters` +- `model.variants` +- default variant: `variant.isDefault === true`, else first variant + +This means new Cursor models and changed Cursor parameters are picked up after reload/restart. + +Pi model metadata is also a source of truth for pi-native behavior: + +- `ProviderModelConfig.id` +- `ProviderModelConfig.name` +- `ProviderModelConfig.reasoning` +- `ProviderModelConfig.thinkingLevelMap` +- `ProviderModelConfig.contextWindow` +- `ProviderModelConfig.maxTokens` +- `ProviderModelConfig.input` + +If a Cursor parameter changes any of those pi-native fields, model registration must expose that change to pi. + +### Refresh Current Cursor Matrix + +Run this whenever Cursor releases or changes models: + +```bash +node --input-type=module <<'EOF' +import { Cursor } from '@cursor/sdk'; + +const models = await Cursor.models.list({ apiKey: process.env.CURSOR_API_KEY }); +for (const model of models) { + const options = (model.parameters ?? []) + .map((param) => `${param.id}: ${param.values.map((value) => value.value).join(', ')}`) + .join(' | ') || 'none'; + const defaultVariant = model.variants?.find((variant) => variant.isDefault) ?? model.variants?.[0]; + const defaults = defaultVariant?.params?.map((param) => `${param.id}=${param.value}`).join('; ') || 'none'; + console.log(`${model.id}\t${model.displayName}\t${options}\t${defaults}`); +} +EOF +``` + +## Design Direction + +Use native pi abstractions wherever possible: + +| Concern | Representation | +|---|---| +| Cursor base model | pi provider model | +| Cursor `context` | pi-visible model variant because it changes `contextWindow` | +| Cursor `reasoning` | pi native thinking via `thinkingLevelMap` | +| Cursor `effort` | pi native thinking via `thinkingLevelMap` | +| Cursor `thinking=false` | pi native `off` | +| Cursor `fast` | extension state, not model identity | +| Footer | default pi footer plus optional extension status | + +Reason: + +- pi already persists model and thinking selection. +- pi already clamps unsupported thinking levels from `thinkingLevelMap`. +- pi context display, context overflow, and compaction depend on `contextWindow`. +- extension APIs can replace the whole footer but cannot partially mutate the default model text. + +## Model Registration + +Register a `cursor` provider with `pi.registerProvider()`. + +Rules: + +- Register one pi model for each Cursor base model when there is no Cursor `context` parameter. +- Register one pi model per Cursor `context` value when the model exposes a `context` parameter. +- Do not encode `reasoning`, `effort`, `thinking`, or `fast` into pi model IDs. +- Prefer stable, readable `@` suffixes that do not conflict with pi's final `:` suffix parser. +- Sort Cursor models by base ID, then context value in Cursor SDK order before calling `pi.registerProvider()`. Registration order matters for `/model` display and model cycling; `--list-models` sorts output separately. + +Recommended context-variant ID format: + +```text +cursor/gpt-5.5@1m +cursor/gpt-5.5@272k +cursor/claude-opus-4-7@1m +cursor/claude-opus-4-7@300k +cursor/composer-2 +``` + +Avoid colon-based context IDs in the first implementation unless this spec is intentionally changed: + +```text +cursor/gpt-5.5:1m +cursor/gpt-5.5:1m:medium +``` + +Those can work technically because pi parses only the final `:` suffix, but they overload pi's documented thinking shorthand. + +Avoid this old parameter encoding: + +```text +cursor/gpt-5.5:context=1m;fast=false;reasoning=medium +cursor/claude-opus-4-7:context=1m;effort=xhigh;thinking=true +``` + +Reason: + +- `@1m` keeps context visually separate from pi's native `:medium` thinking suffix. +- Context variants make `contextWindow` accurate in `--list-models`, the native footer, context overflow checks, and compaction logic. +- `fast` is intentionally not a model variant because it does not affect pi model metadata and would double list noise. + +### Metadata Per Registered Model + +Each registered model must set: + +- `id`: context-qualified pi model ID when needed. +- `name`: human-readable Cursor display name plus context when useful. +- `reasoning`: `true` only if a Cursor `reasoning`, `effort`, or `thinking` parameter can map to pi thinking. +- `thinkingLevelMap`: model-specific pi-to-Cursor mapping for pi UI, clamping, persistence, and footer display. +- `contextWindow`: parsed from context variant, else conservative fallback. +- `maxTokens`: conservative explicit value until Cursor SDK exposes output limits. +- `input`: supported input types. Use `["text", "image"]` only if the Cursor SDK path supports images for that model. +- `cost`: zeroed unless reliable Cursor costs are available. + +The extension stores runtime metadata in an internal map keyed by registered pi model ID. That map records the Cursor base model ID, selected context param, default params, and discovered capabilities. `ProviderModelConfig` has no dedicated metadata field, so do not rely on hidden custom fields for this state. + +## Dynamic Capabilities + +No per-model hardcoded control list. + +Infer behavior from discovered params: + +| Cursor param | Extension behavior | +|---|---| +| `context` with values | register pi-visible context variants | +| `reasoning` | populate `thinkingLevelMap` | +| `effort` | populate `thinkingLevelMap` | +| `thinking` with `true/false` | map `false` to pi `off`; map `true` to the enabled pi level chosen for boolean-only thinking | +| `fast` with `true/false` | enable fast extension setting | + +Unsupported Cursor-only actions are no-op plus a short notification. + +Example: + +```text +Fast mode not supported by gemini-3.1-pro +``` + +## Keybindings And Commands + +Native pi keybindings: + +| Action | Keybinding | Owner | +|---|---:|---| +| Cycle thinking / reasoning / effort | `shift+tab` | pi native `app.thinking.cycle` | +| Select model / context variant | `/model`, `ctrl+l`, scoped model cycling | pi native model selection | + +Cursor extension controls: + +| Action | Preferred control | Applies when | +|---|---:|---| +| Toggle fast | `/cursor-fast` plus optional non-reserved shortcut | model has `fast` | + +Do not register a shortcut for `shift+tab`. Pi reserves the native thinking keybinding, and the extension should only influence it through model metadata. + +Do not add a context-cycle shortcut in the first pass. Context is a pi model variant, so users should change it through native model selection/cycling. + +## Thinking / Reasoning / Effort Mapping + +Pi levels: + +```text +off, minimal, low, medium, high, xhigh +``` + +Cursor values vary by model. Build `thinkingLevelMap` from the values Cursor exposes. + +Mapping rules: + +| pi level | Cursor value preference | +|---|---| +| `off` | `none`, else `false`, else unsupported | +| `minimal` | `minimal`, else unsupported | +| `low` | `low` | +| `medium` | `medium` | +| `high` | `high`, else `true` for boolean-only thinking | +| `xhigh` | `xhigh`, else `max`, else `extra-high` | + +Important details: + +- Use `null` for unsupported pi levels so pi hides/skips/clamps them natively. +- Include `xhigh` only when Cursor exposes a real value for it. +- Prefer exact `xhigh` over `max`. Cursor currently exposes both on some Claude models, and exact `xhigh` is the closer native mapping. +- If Cursor exposes `reasoning=none`, map pi `off` to `none`. +- If Cursor exposes `thinking=false`, map pi `off` to `false`. +- `thinkingLevelMap` does not create Cursor SDK params by itself. It only controls pi-native behavior. The Cursor stream implementation must use the active pi thinking level plus the extension's discovered Cursor metadata to build `ModelSelection.params` for `Agent.create()`. + +For boolean-only `thinking`, unsupported pi levels must be explicit `null`; otherwise pi treats omitted non-`xhigh` levels as supported. Use this shape unless Cursor exposes richer values: + +```ts +{ + off: "false", + minimal: null, + low: null, + medium: null, + high: "true", + xhigh: null, +} +``` + +## Claude Behavior + +Some Claude models support both: + +```text +thinking=true|false +effort=low|medium|high|xhigh|max +``` + +Rules: + +- Pi `off` sends `thinking=false`. +- Pi enabled levels send `thinking=true` and the mapped `effort`. +- `shift+tab` changes pi thinking, which changes Cursor `effort`. +- There is no separate `thinking` toggle. + +Reason: + +- This matches pi's single thinking mental model. +- It avoids an independent Cursor `thinking` state that the native footer, CLI, and session thinking persistence cannot represent. +- Users can still disable Claude thinking with pi `off`. + +## Context Behavior + +If a Cursor model supports `context`, register one pi model variant per context value. + +Examples: + +```text +cursor/gpt-5.5@272k +cursor/gpt-5.5@1m + +cursor/claude-opus-4-7@300k +cursor/claude-opus-4-7@1m + +cursor/grok-4.3@200k +cursor/grok-4.3@1m +``` + +Each variant must: + +- have an entry in the extension metadata map that points back to the same Cursor base model ID, +- include the selected Cursor `context` param when calling `Agent.create()`, +- set pi `contextWindow` from that context value, +- share the same `thinkingLevelMap` as the base model unless Cursor reports otherwise. + +Reason: + +- pi context display and overflow logic must match the actual Cursor context. +- pi has no generic provider-parameter system that can change `contextWindow` while keeping the same model ID. + +## Fast Behavior + +If a model supports `fast`: + +```text +fast=false <-> fast=true +``` + +Rules: + +- `fast` is extension state, not pi model identity. +- Toggle with `/cursor-fast` and optional non-reserved shortcut. +- Store per-session and global per-base-model preferences. +- When calling `Agent.create()`, include the selected `fast` value in Cursor model params. +- Show `fast` through `ctx.ui.setStatus()` when enabled. +- Support a first-pass CLI flag, `--cursor-fast`, to force fast mode for one run when the selected model supports it. + +Reason: + +- `fast` does not affect pi `contextWindow`, thinking levels, or input support. +- Registering fast/non-fast variants would make `--list-models` noisy without improving native pi behavior. + +Status example: + +```text +cursor fast +``` + +## Footer Behavior + +Hard requirement: + +- Leave pi's default footer intact. +- Do not use `ctx.ui.setFooter()` for the first pass. +- Use `ctx.ui.setStatus()` only for Cursor-only state that pi cannot show natively, such as `fast`. +- Non-cursor models must have no Cursor status. + +Reason: + +- `ctx.ui.setFooter()` replaces the entire built-in footer. +- pi has no public extension API to mutate only the model text in the default footer. +- Reimplementing the default footer would create drift with pi's native footer behavior. + +Expected native footer behavior: + +- provider/model is shown by pi from the selected `cursor` model, +- thinking level is shown by pi when `reasoning` is true, +- context usage is computed from `contextWindow`, +- extension status adds only Cursor-only text such as `cursor fast`. + +`ctx.ui.setStatus()` adds an extension status line in the default footer. It does not patch the built-in model segment. The native shape is closer to: + +```text +... (cursor) gpt-5.5@1m • medium +cursor fast +``` + +not: + +```text +(cursor) gpt-5.5 • 1M • medium • fast +``` + +## State And Persistence + +Match pi's native mental model: + +### Native pi state + +Let pi persist: + +- selected model, including context variant, +- selected thinking level, +- session model restore, +- global default thinking behavior. + +### Extension state + +The extension persists only Cursor-only state: + +- `fast` per session, +- `fast` global default per Cursor base model, +- any future Cursor-only parameter that does not map to pi model metadata. + +Use: + +- `pi.appendEntry()` for session state that must survive resume/fork/reload, +- an extension-owned global config file for cross-session defaults, +- in-memory state only as a cache rebuilt from persisted state on `session_start`. + +### New Install + +Use Cursor default variants: + +```text +gpt-5.5 -> cursor/gpt-5.5@1m, thinking medium, fast=false +composer-2 -> cursor/composer-2, fast=true +``` + +### Resume Session + +Restore: + +- pi model, including context variant, +- pi thinking level, +- session Cursor-only state such as `fast`. + +### New Session + +Use: + +1. pi's selected/default model and thinking level, +2. global saved Cursor-only defaults for the selected base model, +3. else Cursor default variant params. + +## CLI / Print Mode + +Guaranteed first-pass support: + +```bash +pi --model cursor/gpt-5.5@1m --thinking medium +pi --model cursor/gpt-5.5@1m:medium +pi --model cursor/gpt-5.5@272k:xhigh +``` + +These use pi's native thinking parser. `--thinking` wins over a `:` suffix when both are present. + +Not first-pass support: + +```bash +pi --model cursor/gpt-5.5:medium:272k:fast +``` + +Reason: + +- pi supports one final `:` suffix. +- Cursor-only parameters are not generic pi CLI parameters. +- Context is already represented by the registered pi model ID. +- `fast` is controlled by saved extension defaults or the first-pass `--cursor-fast` extension flag. + +For print mode: + +- no keybindings, +- use selected context model variant, +- use `--thinking` or `:medium` for reasoning/effort, +- use saved global `fast` defaults unless `--cursor-fast` is present. + +Fast flag example: + +```bash +pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only" +``` + +## Current Discovered Model Capability Examples + +Current live Cursor data says: + +| Model | Cursor controls | Pi representation | +|---|---|---| +| `default` | none | plain model | +| `composer-2` | fast | plain model + fast extension state | +| `composer-1.5` | none | plain model | +| `gpt-5.5` | context, reasoning, fast | context variants + native thinking + fast state | +| `gpt-5.4` | context, reasoning, fast | context variants + native thinking + fast state | +| `gpt-5.4-mini` | reasoning | plain model + native thinking | +| `gpt-5.4-nano` | reasoning | plain model + native thinking | +| `gpt-5.3-codex` | reasoning, fast | plain model + native thinking + fast state | +| `gpt-5.3-codex-spark` | reasoning | plain model + native thinking | +| `gpt-5.2` | reasoning, fast | plain model + native thinking + fast state | +| `gpt-5.2-codex` | reasoning, fast | plain model + native thinking + fast state | +| `gpt-5.1-codex-max` | reasoning, fast | plain model + native thinking + fast state | +| `gpt-5.1-codex-mini` | reasoning | plain model + native thinking | +| `gpt-5.1` | reasoning | plain model + native thinking | +| `claude-opus-4-7` | thinking, context, effort | context variants + native thinking | +| `claude-opus-4-6` | thinking, context, effort, fast | context variants + native thinking + fast state | +| `claude-opus-4-5` | thinking | plain model + native thinking | +| `claude-sonnet-4-6` | thinking, context, effort | context variants + native thinking | +| `claude-sonnet-4-5` | thinking, context | context-qualified model + native thinking | +| `claude-sonnet-4` | thinking, context | context-qualified model + native thinking | +| `claude-haiku-4-5` | thinking | plain model + native thinking | +| `grok-4.3` | context | context variants | +| `grok-4-20` | thinking | plain model + native thinking | +| `gemini-3.1-pro` | none | plain model | +| `gemini-3-flash` | none | plain model | +| `gemini-2.5-flash` | none | plain model | +| `gpt-5-mini` | none | plain model | +| `kimi-k2.5` | none | plain model | + +If Cursor later adds `fast`, `context`, `reasoning`, or `effort` to a model, the extension picks it up dynamically. + +## Detailed Examples + +### `composer-2` + +Initial Cursor default: + +```text +pi model: cursor/composer-2 +Cursor params: fast=true +pi thinking: off +Cursor status: cursor fast +``` + +Toggle fast: + +```text +Cursor params: fast=false +Cursor status: cleared +``` + +`shift+tab`: no-op because the model is not reasoning-capable. + +### `gpt-5.5` + +Initial Cursor default: + +```text +pi model: cursor/gpt-5.5@1m +Cursor params: context=1m; reasoning=medium; fast=false +pi thinking: medium +Cursor status: cleared +``` + +After selecting the 272k variant: + +```text +pi model: cursor/gpt-5.5@272k +Cursor params: context=272k; reasoning=medium; fast=false +pi contextWindow: 272000 +``` + +After fast toggle: + +```text +Cursor params: context=272k; reasoning=medium; fast=true +Cursor status: cursor fast +``` + +After `shift+tab` to xhigh: + +```text +pi thinking: xhigh +Cursor params: context=272k; reasoning=extra-high; fast=true +``` + +### `gpt-5.3-codex` + +Initial Cursor default: + +```text +pi model: cursor/gpt-5.3-codex +Cursor params: reasoning=high; fast=true +pi thinking: high +Cursor status: cursor fast +``` + +After `shift+tab` to low: + +```text +pi thinking: low +Cursor params: reasoning=low; fast=true +``` + +No context variant. + +### `claude-opus-4-7` + +Initial Cursor default: + +```text +pi model: cursor/claude-opus-4-7@1m +Cursor params: thinking=true; context=1m; effort=xhigh +pi thinking: xhigh +``` + +After selecting the 300k variant: + +```text +pi model: cursor/claude-opus-4-7@300k +Cursor params: thinking=true; context=300k; effort=xhigh +pi contextWindow: 300000 +``` + +After `shift+tab` to high: + +```text +pi thinking: high +Cursor params: thinking=true; context=300k; effort=high +``` + +After `shift+tab` to off: + +```text +pi thinking: off +Cursor params: thinking=false; context=300k +``` + +### `grok-4.3` + +Supports context only. + +```text +cursor/grok-4.3@1m +cursor/grok-4.3@200k +``` + +Fast toggle: no-op. + +`shift+tab`: no-op because the model is not reasoning-capable. + +## Validation Plan + +Before calling done: + +1. Unit tests: + - context-variant model IDs + - dynamic capability discovery + - context variant registration and decoding + - fast extension state and status behavior + - `reasoning` mapping + - `effort` mapping + - boolean `thinking` maps to pi `off` / enabled levels + - pi `xhigh` preference order: `xhigh`, then `max`, then `extra-high` + - session restore for Cursor-only state + - global default state for Cursor-only state + - unsupported no-op notifications + +2. Runtime checks: + - `pi --list-models cursor` + - confirm context variants show expected `context` column + - launch interactive with Cursor + - verify default pi footer remains unchanged + - verify Cursor `fast` status appears only when enabled + - verify non-cursor footer/status unchanged + - verify `shift+tab` uses pi native thinking + - verify context changes through native model selection + - verify resume restores model, thinking, and Cursor-only state + +3. Print mode: + - `pi --model cursor/gpt-5.5@1m:medium -p "Say ok only"` + - `pi --model cursor/gpt-5.5@272k --thinking xhigh -p "Say ok only"` + - `pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only"` + - confirm requests use selected context, pi thinking, and fast flag state diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..617b5ef --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7026 @@ +{ + "name": "pi-cursor-sdk", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pi-cursor-sdk", + "version": "0.1.0", + "dependencies": { + "@cursor/sdk": "^1.0.12" + }, + "devDependencies": { + "@mariozechner/pi-ai": "^0.72.1", + "@mariozechner/pi-coding-agent": "^0.72.1", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.91.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", + "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1041.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1041.0.tgz", + "integrity": "sha512-1QehYO3jhdvNQ5mOKtwIiNV04y4aywaNZw9HzCp7SSYCX4yy+AGXc2hhYjCiMDUvQPIELuvbR8MXw81NGAj8ZQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/credential-provider-node": "^3.972.39", + "@aws-sdk/eventstream-handler-node": "^3.972.14", + "@aws-sdk/middleware-eventstream": "^3.972.10", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.38", + "@aws-sdk/middleware-websocket": "^3.972.16", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/token-providers": "3.1041.0", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.24", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/eventstream-serde-config-resolver": "^4.3.14", + "@smithy/eventstream-serde-node": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.974.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.8.tgz", + "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/xml-builder": "^3.972.22", + "@smithy/core": "^3.23.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.34.tgz", + "integrity": "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.36", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.36.tgz", + "integrity": "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-stream": "^4.5.25", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.38.tgz", + "integrity": "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/credential-provider-env": "^3.972.34", + "@aws-sdk/credential-provider-http": "^3.972.36", + "@aws-sdk/credential-provider-login": "^3.972.38", + "@aws-sdk/credential-provider-process": "^3.972.34", + "@aws-sdk/credential-provider-sso": "^3.972.38", + "@aws-sdk/credential-provider-web-identity": "^3.972.38", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.38.tgz", + "integrity": "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.39.tgz", + "integrity": "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.34", + "@aws-sdk/credential-provider-http": "^3.972.36", + "@aws-sdk/credential-provider-ini": "^3.972.38", + "@aws-sdk/credential-provider-process": "^3.972.34", + "@aws-sdk/credential-provider-sso": "^3.972.38", + "@aws-sdk/credential-provider-web-identity": "^3.972.38", + "@aws-sdk/types": "^3.973.8", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.34.tgz", + "integrity": "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.38.tgz", + "integrity": "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/token-providers": "3.1041.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.38.tgz", + "integrity": "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.14.tgz", + "integrity": "sha512-m4X56gxG76/CKfxNVbOFuYwnAZcHgS6HOH8lgp15HoGHIAVTcZfZrXvcYzJFOMLEJgVn+JHBu6EiNV+xSNXXFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.10.tgz", + "integrity": "sha512-QUqLs7Af1II9X4fCRAu+EGHG3KHyOp4RkuLhRKoA3NuFlh6TL8i+zXBl8w2LUxqm44B/Kom45hgSlwA1SpTsXQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", + "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.10.tgz", + "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.11.tgz", + "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.37.tgz", + "integrity": "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.38.tgz", + "integrity": "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@smithy/core": "^3.23.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-retry": "^4.3.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.16.tgz", + "integrity": "sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-format-url": "^3.972.10", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.6.tgz", + "integrity": "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.38", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/signature-v4-multi-region": "^3.996.25", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.24", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.13.tgz", + "integrity": "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/config-resolver": "^4.4.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.25.tgz", + "integrity": "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "^3.972.37", + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1041.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1041.0.tgz", + "integrity": "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", + "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.8.tgz", + "integrity": "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-endpoints": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.10.tgz", + "integrity": "sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.10.tgz", + "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.24.tgz", + "integrity": "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.38", + "@aws-sdk/types": "^3.973.8", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", + "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@nodable/entities": "2.1.0", + "@smithy/types": "^4.14.1", + "fast-xml-parser": "5.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", + "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@connectrpc/connect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.7.0.tgz", + "integrity": "sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^1.10.0" + } + }, + "node_modules/@connectrpc/connect-node": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-1.7.0.tgz", + "integrity": "sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A==", + "license": "Apache-2.0", + "dependencies": { + "undici": "^5.28.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^1.10.0", + "@connectrpc/connect": "1.7.0" + } + }, + "node_modules/@cursor/sdk": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk/-/sdk-1.0.12.tgz", + "integrity": "sha512-jGx0wFY1N9uIdIKr303CfM6m/dLXmRCUnU/0yNP/oiOpkBXqgqaThGbgYbcOeVrYonMZc/DZJ9EydXOEPJLcbg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@bufbuild/protobuf": "1.10.0", + "@connectrpc/connect": "^1.6.1", + "@connectrpc/connect-node": "^1.6.1", + "@statsig/js-client": "3.31.0", + "sqlite3": "^5.1.7", + "zod": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@cursor/sdk-darwin-arm64": "1.0.12", + "@cursor/sdk-darwin-x64": "1.0.12", + "@cursor/sdk-linux-arm64": "1.0.12", + "@cursor/sdk-linux-x64": "1.0.12", + "@cursor/sdk-win32-x64": "1.0.12" + } + }, + "node_modules/@cursor/sdk-darwin-arm64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-arm64/-/sdk-darwin-arm64-1.0.12.tgz", + "integrity": "sha512-AOFx+aX+4SntAeC66YncHACXk5duxp+HzDrxxF4Tl93N6nLjHaHEKSAXbt87ivL34MCHop4v/3c70QzBhamB2g==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cursor/sdk-darwin-x64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-x64/-/sdk-darwin-x64-1.0.12.tgz", + "integrity": "sha512-/ZDAYFUrnPd8hAGRky9ZGcROqZSZ2b5W+aEjTdINzLhJ8x5ZNXtjaz0ZYSHabOn2BeErjXgTcq+4bX2/To4C1A==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cursor/sdk-linux-arm64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-arm64/-/sdk-linux-arm64-1.0.12.tgz", + "integrity": "sha512-kAxNqiB3dPtlW9fVjjIZEdbIGEGLA9moOM3zYwsXh8J1Qw942nJYMGDGR4o8x0zglwZ24a1JpovvZamrCaC3Yw==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cursor/sdk-linux-x64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-x64/-/sdk-linux-x64-1.0.12.tgz", + "integrity": "sha512-RmBiBCPKMZC5McDerGk2Rk4P47xz2A+uzRoRgH6sMoOjklc33ry11iAZC0D5F5xH85chgY878086A/Q8+XrAuA==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cursor/sdk-win32-x64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-win32-x64/-/sdk-win32-x64-1.0.12.tgz", + "integrity": "sha512-uH4shdHrKOdtNLapy1uuScJ9lL2Pc8zc9I9ZKC6b6bx+0UX6xLAqjPP7dqVPfO6D9u61yLq1Hs86XOLs5ZVkPA==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@google/genai": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.51.0.tgz", + "integrity": "sha512-vTZZF3CSimN7cn2zsLpW2p5WF0eZa5Gz69ITMPCNHpPrDlAstOfGifSfi0p/s9Z9400f7xJRkgvkQNrcM7pJ6w==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mariozechner/clipboard": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.5.tgz", + "integrity": "sha512-D3F+UrU9CR7roJt0zDLp6Oc+4/KlLDIrN4frH+6V90SJNW2KKUec1oCQIPaaDjCqeOsQyX9dyqYbImIQIM45PA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.2", + "@mariozechner/clipboard-darwin-universal": "0.3.2", + "@mariozechner/clipboard-darwin-x64": "0.3.2", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-musl": "0.3.2", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" + } + }, + "node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", + "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", + "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", + "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", + "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", + "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", + "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", + "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", + "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", + "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", + "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/jiti": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz", + "integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "std-env": "^3.10.0", + "yoctocolors": "^2.1.2" + }, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@mariozechner/pi-agent-core": { + "version": "0.72.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.72.1.tgz", + "integrity": "sha512-Z5XH9mUDsBymh7Prtk5Eun4Cs+s9C3uzynug+oWAjzQESjjnCuy7KU6Ek8E9Eg+b9yroCwVw2ItLDxp2E3vYjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/pi-ai": "^0.72.1", + "typebox": "^1.1.24" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-ai": { + "version": "0.72.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.72.1.tgz", + "integrity": "sha512-mOq71Pjnu72xxzwrh52VIiNwt+/a+Wpa11k5segi01/zTZJt8eMDc5Q2z6GhczYMr5+6EpZ8T+BaeHqq0jk5ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.91.1", + "@aws-sdk/client-bedrock-runtime": "^3.1030.0", + "@google/genai": "^1.40.0", + "@mistralai/mistralai": "^2.2.0", + "chalk": "^5.6.2", + "openai": "6.26.0", + "partial-json": "^0.1.7", + "proxy-agent": "^6.5.0", + "typebox": "^1.1.24", + "undici": "^7.19.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-ai/node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/@mariozechner/pi-coding-agent": { + "version": "0.72.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.72.1.tgz", + "integrity": "sha512-gKApiLfAYpvPnWThvwXrLjZIhsziSSUKBph7DHCy/1IomuIGN1MTxS/9ZtcuFqPy3/+VfEUOKegGlY6m+CRNlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/jiti": "^2.6.2", + "@mariozechner/pi-agent-core": "^0.72.1", + "@mariozechner/pi-ai": "^0.72.1", + "@mariozechner/pi-tui": "^0.72.1", + "@silvia-odwyer/photon-node": "^0.3.4", + "chalk": "^5.5.0", + "cli-highlight": "^2.1.11", + "diff": "^8.0.2", + "extract-zip": "^2.0.1", + "file-type": "^21.1.1", + "glob": "^13.0.1", + "hosted-git-info": "^9.0.2", + "ignore": "^7.0.5", + "marked": "^15.0.12", + "minimatch": "^10.2.3", + "proper-lockfile": "^4.1.2", + "strip-ansi": "^7.1.0", + "typebox": "^1.1.24", + "undici": "^7.19.1", + "uuid": "^14.0.0", + "yaml": "^2.8.2" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=20.6.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "^0.3.5" + } + }, + "node_modules/@mariozechner/pi-coding-agent/node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/@mariozechner/pi-tui": { + "version": "0.72.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.72.1.tgz", + "integrity": "sha512-KjeqzMQp4vafomfkvI8HKv5/52PqRP5BmlowGC8WKyOaB+u0UkkTdfM5U1Ui3ty2gA7FOfglVRiyvcitiWwvMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime-types": "^2.1.4", + "chalk": "^5.5.0", + "get-east-asian-width": "^1.3.0", + "marked": "^15.0.12", + "mime-types": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "koffi": "^2.9.0" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz", + "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.25.0" + } + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", + "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.17", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.17.tgz", + "integrity": "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.17", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.17.tgz", + "integrity": "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.14.tgz", + "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.1", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.14.tgz", + "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.14.tgz", + "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.14.tgz", + "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.14.tgz", + "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", + "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", + "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", + "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.14.tgz", + "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.32", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.32.tgz", + "integrity": "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-middleware": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.7.tgz", + "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/service-error-classification": "^4.3.1", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.20.tgz", + "integrity": "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.14.tgz", + "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.14.tgz", + "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.6.1.tgz", + "integrity": "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.14.tgz", + "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.14.tgz", + "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.14.tgz", + "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.14.tgz", + "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.3.1.tgz", + "integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.9.tgz", + "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.14.tgz", + "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.13", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.13.tgz", + "integrity": "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-stream": "^4.5.25", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.14.tgz", + "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.49.tgz", + "integrity": "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.54", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.54.tgz", + "integrity": "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.17", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.2.tgz", + "integrity": "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.14.tgz", + "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.8.tgz", + "integrity": "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.3.1", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.25", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.25.tgz", + "integrity": "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@statsig/client-core": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@statsig/client-core/-/client-core-3.31.0.tgz", + "integrity": "sha512-SuxQD6TmVszPG7FoMKwTk/uyBuVFk7XnxI3T/E0uyb7PL7GNjONtfsoh+NqBBVUJVse0CUeSFfgJPoZy1ZOslQ==", + "license": "ISC" + }, + "node_modules/@statsig/js-client": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@statsig/js-client/-/js-client-3.31.0.tgz", + "integrity": "sha512-LFa5E0LjT6sTfZv3sNGoyRLSZ1078+agdgOA+Vm1ecjG+KbSOfBLTW7hMwimrJ29slRwbYDzbtKaPJo/R37N2g==", + "license": "ISC", + "dependencies": { + "@statsig/client-core": "3.31.0" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT", + "optional": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-xml-builder": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.7.tgz", + "integrity": "sha512-Yh7/7rQuMXICNr0oMYDR2yHP6oUvmQsTToFeOWj/kIDhAwQ+c4Ol/lbcwOmEM5OHYQmh6S6EQSQ1sljCKP36bQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", + "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC", + "optional": true + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/koffi": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz", + "integrity": "sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-abi": { + "version": "3.90.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.90.0.tgz", + "integrity": "sha512-pZNQT7UnYlMwMBy5N1lV5X/YLTbZM5ncytN3xL7CHEzhDN8uVe0u55yaPUJICIJjaCW8NrM5BFdqr7HLweStNA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", + "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/protobufjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", + "integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.1", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "optional": true + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", + "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typebox": { + "version": "1.1.37", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.37.tgz", + "integrity": "sha512-jb7jp6KvOvvy5sd+11AfJ0/e0F0AS9RcOXd55oGi2ZnRHIGmFvrTaNF+ZidRmGBmmNTkM5KKl0Z37KzxJ+owEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9f8a9fa --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "pi-cursor-sdk", + "version": "0.1.0", + "description": "pi provider extension backed by @cursor/sdk local agents", + "license": "MIT", + "keywords": ["pi", "cursor", "cursor-sdk", "ai", "extension"], + "files": ["src", "README.md", "docs"], + "type": "module", + "scripts": { + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@cursor/sdk": "^1.0.12" + }, + "devDependencies": { + "@mariozechner/pi-coding-agent": "^0.72.1", + "@mariozechner/pi-ai": "^0.72.1", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + }, + "pi": { + "extensions": ["./src/index.ts"] + } +} diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..aaadd06 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,86 @@ +import type { Context, Message } from "@mariozechner/pi-ai"; +import type { SDKImage } from "@cursor/sdk"; + +export interface CursorPrompt { + text: string; + images: SDKImage[]; +} + +function isTextBlock(block: { type: string }): block is { type: "text"; text: string } { + return block.type === "text"; +} + +function isImageBlock(block: { type: string }): block is { type: "image"; data: string; mimeType: string } { + return block.type === "image"; +} + +function extractLatestImages(messages: Message[]): SDKImage[] { + // Find the last user message and extract images only from it + for (let i = messages.length - 1; i >= 0; i--) { + const msg = messages[i]; + if (msg.role !== "user") continue; + if (typeof msg.content === "string") return []; + + const images: SDKImage[] = []; + for (const block of msg.content) { + if (isImageBlock(block) && block.data && block.mimeType) { + images.push({ data: block.data, mimeType: block.mimeType }); + } + } + return images; + } + return []; +} + +function formatUserContent(content: string | { type: string; text?: string; data?: string; mimeType?: string }[]): string { + if (typeof content === "string") return content; + return content + .map((block) => { + if (isTextBlock(block)) return block.text; + if (isImageBlock(block)) return "[image omitted from transcript]"; + return ""; + }) + .filter(Boolean) + .join("\n"); +} + +export function buildCursorPrompt(context: Context): CursorPrompt { + const parts: string[] = []; + + if (context.systemPrompt) { + parts.push(`System instructions from pi:\n${context.systemPrompt}`); + } + + for (const msg of context.messages) { + switch (msg.role) { + case "user": { + const text = formatUserContent(msg.content); + if (text) parts.push(`User: ${text}`); + break; + } + case "assistant": { + const blocks = typeof msg.content === "string" ? [{ type: "text" as const, text: msg.content }] : msg.content; + const textParts: string[] = []; + for (const block of blocks) { + if (isTextBlock(block)) textParts.push(block.text); + // Omit thinking content from transcript + } + if (textParts.length > 0) { + parts.push(`Assistant: ${textParts.join("\n")}`); + } + break; + } + case "toolResult": { + const text = formatUserContent(msg.content); + parts.push(`Tool result (${msg.toolName}): ${text}`); + break; + } + } + } + + parts.push("Answer the latest user request above using your capabilities. Do not assume access to pi tools."); + + const images = extractLatestImages(context.messages); + + return { text: parts.join("\n\n"), images }; +} diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts new file mode 100644 index 0000000..3985130 --- /dev/null +++ b/src/cursor-provider.ts @@ -0,0 +1,236 @@ +import { + type Api, + type AssistantMessageEventStream, + type Context, + createAssistantMessageEventStream, + type Model, + type SimpleStreamOptions, + type AssistantMessage, +} from "@mariozechner/pi-ai"; +import { Agent } from "@cursor/sdk"; +import type { InteractionUpdate, ModelSelection, SDKAgent } from "@cursor/sdk"; +import { buildCursorPrompt } from "./context.js"; +import { decodeModelSelection } from "./model-discovery.js"; + +function makeInitialMessage(model: Model): AssistantMessage { + return { + role: "assistant", + content: [], + api: model.api, + provider: model.provider, + model: model.id, + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "stop", + timestamp: Date.now(), + }; +} + +function sanitizeError(error: unknown): string { + if (error instanceof Error) return error.message; + if (typeof error === "string") return error; + return "Unknown error"; +} + +function withPiReasoningLevel( + selection: ModelSelection, + model: Model, + reasoning: SimpleStreamOptions["reasoning"], +): ModelSelection { + if (!selection.params?.length) return selection; + + const requestedLevel = reasoning ?? "off"; + const mapped = model.thinkingLevelMap?.[requestedLevel] ?? (reasoning ? reasoning : undefined); + if (mapped === undefined || mapped === null) return selection; + + const hasEffort = selection.params.some((param) => param.id === "effort"); + const params = selection.params.map((param) => { + if (param.id === "reasoning") return { ...param, value: mapped }; + if (param.id === "effort" && reasoning) return { ...param, value: mapped }; + if (param.id === "thinking") { + if (!reasoning) return { ...param, value: mapped }; + return { ...param, value: hasEffort ? "true" : mapped }; + } + return param; + }); + return { ...selection, params }; +} + +export function streamCursor( + model: Model, + context: Context, + options?: SimpleStreamOptions, +): AssistantMessageEventStream { + const stream = createAssistantMessageEventStream(); + + (async () => { + const partial = makeInitialMessage(model); + let agent: SDKAgent | null = null; + + try { + stream.push({ type: "start", partial }); + + const apiKey = options?.apiKey; + if (!apiKey) throw new Error("CURSOR_API_KEY is required"); + + const cwd = process.cwd(); + const selection = withPiReasoningLevel( + decodeModelSelection(model.id), + model, + options?.reasoning, + ); + + agent = await Agent.create({ + apiKey, + model: selection, + local: { cwd, settingSources: ["all"] }, + }); + + const prompt = buildCursorPrompt(context); + let textContentIndex = -1; + let thinkingContentIndex = -1; + let hasText = false; + + const onDelta = (args: { update: InteractionUpdate }): void => { + const update = args.update; + + if (update.type === "text-delta") { + if (textContentIndex < 0) { + textContentIndex = partial.content.length; + partial.content.push({ type: "text", text: "" }); + stream.push({ type: "text_start", contentIndex: textContentIndex, partial }); + } + const block = partial.content[textContentIndex]; + if (block.type === "text") { + block.text += update.text; + stream.push({ + type: "text_delta", + contentIndex: textContentIndex, + delta: update.text, + partial, + }); + } + hasText = true; + } else if (update.type === "thinking-delta") { + if (thinkingContentIndex < 0) { + thinkingContentIndex = partial.content.length; + partial.content.push({ type: "thinking", thinking: "" }); + stream.push({ type: "thinking_start", contentIndex: thinkingContentIndex, partial }); + } + const block = partial.content[thinkingContentIndex]; + if (block.type === "thinking") { + block.thinking += update.text; + stream.push({ + type: "thinking_delta", + contentIndex: thinkingContentIndex, + delta: update.text, + partial, + }); + } + } else if (update.type === "thinking-completed") { + if (thinkingContentIndex >= 0) { + const block = partial.content[thinkingContentIndex]; + if (block.type === "thinking") { + stream.push({ + type: "thinking_end", + contentIndex: thinkingContentIndex, + content: block.thinking, + partial, + }); + } + thinkingContentIndex = -1; + } + } + // tool-call-started, tool-call-completed, summary-*, turn-ended, + // shell-output-delta, token-delta: intentionally ignored + }; + + // Handle abort signal + let run: Awaited> | null = null; + const onAbort = () => { + if (run) { + run.cancel().catch(() => {}); + } + }; + options?.signal?.addEventListener("abort", onAbort, { once: true }); + + run = await agent.send( + { text: prompt.text, images: prompt.images.length > 0 ? prompt.images : undefined }, + { onDelta }, + ); + + const result = await run.wait(); + + options?.signal?.removeEventListener("abort", onAbort); + + // Close open text block + if (textContentIndex >= 0) { + const block = partial.content[textContentIndex]; + if (block.type === "text") { + stream.push({ + type: "text_end", + contentIndex: textContentIndex, + content: block.text, + partial, + }); + } + } + + // Close open thinking block + if (thinkingContentIndex >= 0) { + const block = partial.content[thinkingContentIndex]; + if (block.type === "thinking") { + stream.push({ + type: "thinking_end", + contentIndex: thinkingContentIndex, + content: block.thinking, + partial, + }); + } + } + + // If no text was emitted but result has text, emit it + if (!hasText && result.result) { + textContentIndex = partial.content.length; + partial.content.push({ type: "text", text: result.result }); + stream.push({ type: "text_start", contentIndex: textContentIndex, partial }); + stream.push({ + type: "text_end", + contentIndex: textContentIndex, + content: result.result, + partial, + }); + } + + if (result.status === "cancelled") { + partial.stopReason = "aborted"; + stream.push({ type: "error", reason: "aborted", error: partial }); + } else { + stream.push({ type: "done", reason: "stop", message: partial }); + } + } catch (error) { + partial.stopReason = "error"; + partial.errorMessage = sanitizeError(error); + stream.push({ type: "error", reason: "error", error: partial }); + } finally { + if (agent) { + try { + await agent[Symbol.asyncDispose](); + } catch { + // disposal failure should not mask original error + } + agent = null; + } + } + + stream.end(); + })(); + + return stream; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..05d1dc8 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,16 @@ +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { discoverModels } from "./model-discovery.js"; +import { streamCursor } from "./cursor-provider.js"; + +export default async function (pi: ExtensionAPI) { + const models = await discoverModels(); + + pi.registerProvider("cursor", { + name: "Cursor", + baseUrl: "https://cursor.com", + apiKey: "CURSOR_API_KEY", + api: "cursor-sdk", + models, + streamSimple: streamCursor, + }); +} diff --git a/src/model-discovery.ts b/src/model-discovery.ts new file mode 100644 index 0000000..4d855a5 --- /dev/null +++ b/src/model-discovery.ts @@ -0,0 +1,236 @@ +import { Cursor } from "@cursor/sdk"; +import type { + ModelListItem, + ModelParameterDefinition, + ModelParameterValue, + ModelSelection, +} from "@cursor/sdk"; +import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent"; +import type { ThinkingLevelMap } from "@mariozechner/pi-ai"; + +const FALLBACK_MODELS: ProviderModelConfig[] = [ + { + id: "composer-2:fast=true", + name: "Cursor Composer 2", + reasoning: false, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + { + id: "gpt-5.5:context=1m;reasoning=medium;fast=false", + name: "GPT-5.5", + reasoning: true, + thinkingLevelMap: { + off: "none", + minimal: null, + low: "low", + medium: "medium", + high: "high", + xhigh: "extra-high", + }, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1000000, + maxTokens: 16384, + }, + { + id: "claude-sonnet-4-6:context=1m;effort=medium;thinking=true", + name: "Sonnet 4.6", + reasoning: true, + thinkingLevelMap: { + off: "false", + minimal: null, + low: "low", + medium: "medium", + high: "high", + xhigh: "max", + }, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1000000, + maxTokens: 16384, + }, + { + id: "claude-opus-4-7:context=1m;effort=xhigh;thinking=true", + name: "Opus 4.7", + reasoning: true, + thinkingLevelMap: { + off: "false", + minimal: null, + low: "low", + medium: "medium", + high: "high", + xhigh: "max", + }, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1000000, + maxTokens: 16384, + }, +]; + +/** + * Encode a Cursor model base ID and optional params into a single pi model ID. + * + * Format: `baseId` when params are empty, otherwise `baseId:key1=val1;key2=val2`. + * The encoding is deterministic: params are sorted by id before joining. + */ +export function encodeModelId(baseId: string, params: ModelParameterValue[]): string { + if (!params.length) return baseId; + const suffix = params + .slice() + .sort((a, b) => a.id.localeCompare(b.id)) + .map((p) => `${p.id}=${p.value}`) + .join(";"); + return `${baseId}:${suffix}`; +} + +/** + * Decode a pi model ID back into a Cursor `ModelSelection`. + * + * Reverses `encodeModelId`. If the id contains no `:`, returns `{ id }` + * with no params (backward compatible with plain model ids). + */ +export function decodeModelSelection(modelId: string): ModelSelection { + const colonIndex = modelId.indexOf(":"); + if (colonIndex < 0) return { id: modelId }; + + const baseId = modelId.slice(0, colonIndex); + const paramString = modelId.slice(colonIndex + 1); + if (!paramString) return { id: baseId }; + + const params: ModelParameterValue[] = paramString.split(";").map((pair) => { + const eqIndex = pair.indexOf("="); + if (eqIndex < 0) return { id: pair, value: "" }; + return { id: pair.slice(0, eqIndex), value: pair.slice(eqIndex + 1) }; + }); + + return { id: baseId, params }; +} + +/** + * Determine if a model supports reasoning/thinking based on its parameters. + */ +function getParameter(item: ModelListItem, id: string): ModelParameterDefinition | undefined { + return item.parameters?.find((p) => p.id === id); +} + +function getReasoningParameter(item: ModelListItem): ModelParameterDefinition | undefined { + return getParameter(item, "reasoning") ?? getParameter(item, "effort") ?? getParameter(item, "thinking"); +} + +function hasReasoningParameter(item: ModelListItem): boolean { + return getReasoningParameter(item) !== undefined; +} + +function hasValue(values: Set, value: string): boolean { + return values.has(value.toLowerCase()); +} + +function mapLevel(values: Set, level: keyof ThinkingLevelMap): string | null { + if (level === "xhigh") { + if (hasValue(values, "max")) return "max"; + if (hasValue(values, "xhigh")) return "xhigh"; + if (hasValue(values, "extra-high")) return "extra-high"; + return null; + } + if (hasValue(values, level)) return level; + return null; +} + +function getParameterValues(parameter?: ModelParameterDefinition): Set { + return new Set(parameter?.values.map((v) => v.value.toLowerCase()) ?? []); +} + +function getThinkingLevelMap(item: ModelListItem): ThinkingLevelMap | undefined { + const effortParameter = getParameter(item, "effort"); + const reasoningParameter = getParameter(item, "reasoning"); + const thinkingParameter = getParameter(item, "thinking"); + const parameter = effortParameter ?? reasoningParameter ?? thinkingParameter; + if (!parameter) return undefined; + + const values = getParameterValues(parameter); + const thinkingValues = getParameterValues(thinkingParameter); + if (parameter.id === "thinking") { + return { + off: hasValue(values, "false") ? "false" : null, + minimal: null, + low: null, + medium: null, + high: hasValue(values, "true") ? "true" : null, + xhigh: null, + }; + } + + return { + off: hasValue(values, "off") + ? "off" + : hasValue(values, "none") + ? "none" + : hasValue(thinkingValues, "false") + ? "false" + : null, + minimal: mapLevel(values, "minimal"), + low: mapLevel(values, "low"), + medium: mapLevel(values, "medium"), + high: mapLevel(values, "high"), + xhigh: mapLevel(values, "xhigh"), + }; +} + +function parseContextWindow(value: string): number | undefined { + const match = /^(\d+(?:\.\d+)?)([km])$/i.exec(value.trim()); + if (!match) return undefined; + const amount = Number(match[1]); + const unit = match[2]?.toLowerCase(); + if (!Number.isFinite(amount)) return undefined; + return Math.round(amount * (unit === "m" ? 1000000 : 1000)); +} + +function getContextWindow(_item: ModelListItem, params: ModelParameterValue[]): number { + const contextParam = params.find((p) => p.id === "context"); + const parsed = contextParam ? parseContextWindow(contextParam.value) : undefined; + return parsed ?? 128000; +} + +/** + * Find the default variant's params for a model. + * Falls back to empty params if no variants or no default is marked. + */ +function getDefaultParams(item: ModelListItem): ModelParameterValue[] { + if (!item.variants?.length) return []; + const defaultVariant = item.variants.find((v) => v.isDefault); + return defaultVariant?.params ?? item.variants[0]?.params ?? []; +} + +function toModelConfig(item: ModelListItem): ProviderModelConfig { + const params = getDefaultParams(item); + const thinkingLevelMap = getThinkingLevelMap(item); + return { + id: encodeModelId(item.id, params), + name: item.displayName || item.id, + reasoning: hasReasoningParameter(item), + ...(thinkingLevelMap ? { thinkingLevelMap } : {}), + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: getContextWindow(item, params), + maxTokens: 16384, + }; +} + +export async function discoverModels(): Promise { + const apiKey = process.env.CURSOR_API_KEY?.trim(); + if (!apiKey) return FALLBACK_MODELS; + + try { + const models = await Cursor.models.list({ apiKey }); + if (models.length > 0) { + return models.map(toModelConfig); + } + return FALLBACK_MODELS; + } catch { + return FALLBACK_MODELS; + } +} diff --git a/test/context.test.ts b/test/context.test.ts new file mode 100644 index 0000000..769e489 --- /dev/null +++ b/test/context.test.ts @@ -0,0 +1,123 @@ +import { describe, it, expect } from "vitest"; +import { buildCursorPrompt } from "../src/context.js"; +import type { Context, UserMessage, AssistantMessage, ToolResultMessage } from "@mariozechner/pi-ai"; + +describe("buildCursorPrompt", () => { + it("includes system prompt", () => { + const ctx: Context = { + systemPrompt: "You are helpful.", + messages: [], + }; + const result = buildCursorPrompt(ctx); + expect(result.text).toContain("System instructions from pi:"); + expect(result.text).toContain("You are helpful."); + }); + + it("formats user and assistant messages", () => { + const ctx: Context = { + messages: [ + { role: "user", content: "Hello", timestamp: 1 } satisfies UserMessage, + { role: "assistant", content: [{ type: "text", text: "Hi there" }], api: "cursor-sdk", provider: "cursor", model: "test", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "stop", timestamp: 2 } satisfies AssistantMessage, + ], + }; + const result = buildCursorPrompt(ctx); + expect(result.text).toContain("User: Hello"); + expect(result.text).toContain("Assistant: Hi there"); + }); + + it("omits thinking content from transcript", () => { + const ctx: Context = { + messages: [ + { role: "user", content: "Think hard", timestamp: 1 } satisfies UserMessage, + { + role: "assistant", + content: [ + { type: "thinking", thinking: "internal thought" }, + { type: "text", text: "Final answer" }, + ], + api: "cursor-sdk", provider: "cursor", model: "test", + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, + stopReason: "stop", timestamp: 2, + } satisfies AssistantMessage, + ], + }; + const result = buildCursorPrompt(ctx); + expect(result.text).toContain("Final answer"); + expect(result.text).not.toContain("internal thought"); + }); + + it("formats tool results", () => { + const ctx: Context = { + messages: [ + { role: "user", content: "Run it", timestamp: 1 } satisfies UserMessage, + { + role: "toolResult", + toolCallId: "tc1", + toolName: "bash", + content: [{ type: "text", text: "output here" }], + isError: false, + timestamp: 2, + } satisfies ToolResultMessage, + ], + }; + const result = buildCursorPrompt(ctx); + expect(result.text).toContain("Tool result (bash): output here"); + }); + + it("extracts images from latest user message only", () => { + const ctx: Context = { + messages: [ + { + role: "user", + content: [ + { type: "text", text: "Look at this" }, + { type: "image", data: "oldbase64", mimeType: "image/png" }, + ], + timestamp: 1, + } satisfies UserMessage, + { + role: "user", + content: [ + { type: "text", text: "And this one" }, + { type: "image", data: "newbase64", mimeType: "image/jpeg" }, + ], + timestamp: 2, + } satisfies UserMessage, + ], + }; + const result = buildCursorPrompt(ctx); + expect(result.images).toHaveLength(1); + expect(result.images[0]).toEqual({ data: "newbase64", mimeType: "image/jpeg" }); + }); + + it("replaces historical images with placeholder text", () => { + const ctx: Context = { + messages: [ + { + role: "user", + content: [ + { type: "text", text: "First" }, + { type: "image", data: "abc", mimeType: "image/png" }, + ], + timestamp: 1, + } satisfies UserMessage, + { + role: "user", + content: "Second", + timestamp: 2, + } satisfies UserMessage, + ], + }; + const result = buildCursorPrompt(ctx); + expect(result.text).toContain("[image omitted from transcript]"); + expect(result.images).toHaveLength(0); + }); + + it("appends answer instruction", () => { + const ctx: Context = { + messages: [{ role: "user", content: "test", timestamp: 1 }], + }; + const result = buildCursorPrompt(ctx); + expect(result.text).toContain("Answer the latest user request"); + }); +}); diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts new file mode 100644 index 0000000..faeed52 --- /dev/null +++ b/test/cursor-provider.test.ts @@ -0,0 +1,461 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +// Mock @cursor/sdk before importing the module under test +vi.mock("@cursor/sdk", () => { + const mockCancel = vi.fn().mockResolvedValue(undefined); + const mockDispose = vi.fn().mockResolvedValue(undefined); + + const mockAgent = { + send: vi.fn(), + [Symbol.asyncDispose]: mockDispose, + }; + + return { + Agent: { + create: vi.fn().mockResolvedValue(mockAgent), + }, + _mockAgent: mockAgent, + _mockCancel: mockCancel, + _mockDispose: mockDispose, + }; +}); + +import { Agent } from "@cursor/sdk"; +import { streamCursor } from "../src/cursor-provider.js"; +import type { Context, Model } from "@mariozechner/pi-ai"; + +// Access the mocks via the module +const mockedCreate = vi.mocked(Agent.create); + +function makeModel(id = "test-model"): Model<"cursor-sdk"> { + return { + id, + name: "Test Model", + api: "cursor-sdk" as const, + provider: "cursor", + baseUrl: "", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }; +} + +function makeContext(): Context { + return { + systemPrompt: "Be helpful.", + messages: [{ role: "user", content: "Hello", timestamp: 1 }], + }; +} + +async function collectEvents(stream: ReturnType) { + const events: unknown[] = []; + for await (const event of stream) { + events.push(event); + } + return events; +} + +describe("streamCursor", () => { + beforeEach(() => { + vi.clearAllMocks(); + // Re-setup default mock return after clearing + mockedCreate.mockResolvedValue({ + send: vi.fn(), + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + }); + + it("emits text deltas as pi text stream events", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "text-delta", text: "Hello " } }); + opts.onDelta({ update: { type: "text-delta", text: "world" } }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + + const textDeltas = events.filter((e: any) => e.type === "text_delta"); + expect(textDeltas).toHaveLength(2); + expect(textDeltas[0].delta).toBe("Hello "); + expect(textDeltas[1].delta).toBe("world"); + + const done = events.find((e: any) => e.type === "done"); + expect(done).toBeDefined(); + }); + + it("emits thinking deltas as pi thinking stream events", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "thinking-delta", text: "hmm" } }); + opts.onDelta({ update: { type: "thinking-delta", text: " let me think" } }); + opts.onDelta({ update: { type: "thinking-completed" } }); + opts.onDelta({ update: { type: "text-delta", text: "answer" } }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + + const thinkingDeltas = events.filter((e: any) => e.type === "thinking_delta"); + expect(thinkingDeltas).toHaveLength(2); + + const thinkingEnd = events.find((e: any) => e.type === "thinking_end"); + expect(thinkingEnd).toBeDefined(); + }); + + it("does not emit pi tool call events for cursor tool deltas", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "tool-call-started", name: "read_file", callId: "c1" } }); + opts.onDelta({ update: { type: "tool-call-completed", name: "read_file", callId: "c1" } }); + opts.onDelta({ update: { type: "text-delta", text: "done" } }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + + const toolEvents = events.filter((e: any) => + ["toolcall_start", "toolcall_delta", "toolcall_end"].includes(e.type), + ); + expect(toolEvents).toHaveLength(0); + }); + + it("emits error when no API key", async () => { + const stream = streamCursor(makeModel(), makeContext(), { apiKey: undefined }); + const events = await collectEvents(stream); + + const error = events.find((e: any) => e.type === "error"); + expect(error).toBeDefined(); + expect((error as any).error.errorMessage).toContain("CURSOR_API_KEY"); + }); + + it("disposes agent on success", async () => { + const mockDispose = vi.fn().mockResolvedValue(undefined); + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: mockDispose, + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + await collectEvents(stream); + + expect(mockDispose).toHaveBeenCalledTimes(1); + }); + + it("disposes agent on error", async () => { + const mockDispose = vi.fn().mockResolvedValue(undefined); + const mockSend = vi.fn().mockRejectedValue(new Error("boom")); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: mockDispose, + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + await collectEvents(stream); + + expect(mockDispose).toHaveBeenCalledTimes(1); + }); + + it("does not leak API key in error messages", async () => { + const mockSend = vi.fn().mockRejectedValue(new Error("boom")); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "super-secret-key-12345" }); + const events = await collectEvents(stream); + + const error = events.find((e: any) => e.type === "error"); + const errorText = JSON.stringify(error); + expect(errorText).not.toContain("super-secret-key-12345"); + }); + + it("cancels run on abort signal", async () => { + const controller = new AbortController(); + const mockCancel = vi.fn().mockResolvedValue(undefined); + let resolveWait: () => void; + const waitPromise = new Promise<{ id: string; status: string }>((resolve) => { + resolveWait = () => resolve({ id: "run-1", status: "cancelled" }); + }); + const mockSend = vi.fn().mockImplementation(async () => { + return { + id: "run-1", + agentId: "agent-1", + status: "running", + wait: vi.fn().mockReturnValue(waitPromise), + cancel: mockCancel, + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { + apiKey: "test-key", + signal: controller.signal, + }); + + // Give the async IIFE time to start the run + await vi.waitFor(() => expect(mockSend).toHaveBeenCalled()); + + // Now abort + controller.abort(); + + // Let the run resolve + resolveWait!(); + + await collectEvents(stream); + + expect(mockCancel).toHaveBeenCalled(); + }); + + it("passes decoded model selection to Agent.create", async () => { + const modelWithParams = makeModel("gpt-5.4:context=1m;fast=false;reasoning=medium"); + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(modelWithParams, makeContext(), { apiKey: "test-key" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: { + id: "gpt-5.4", + params: [ + { id: "context", value: "1m" }, + { id: "fast", value: "false" }, + { id: "reasoning", value: "medium" }, + ], + }, + }), + ); + }); + + it("applies pi thinking level to Cursor reasoning parameter", async () => { + const modelWithParams = { + ...makeModel("gpt-5.4:context=1m;fast=false;reasoning=medium"), + reasoning: true, + thinkingLevelMap: { low: "low", medium: "medium", high: "high", off: null, minimal: null }, + }; + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(modelWithParams, makeContext(), { apiKey: "test-key", reasoning: "high" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: { + id: "gpt-5.4", + params: [ + { id: "context", value: "1m" }, + { id: "fast", value: "false" }, + { id: "reasoning", value: "high" }, + ], + }, + }), + ); + }); + + it("applies pi thinking level to Cursor Claude effort and thinking parameters", async () => { + const modelWithParams = { + ...makeModel("claude-opus-4-7:context=1m;effort=xhigh;thinking=true"), + reasoning: true, + thinkingLevelMap: { + off: "false", + low: "low", + medium: "medium", + high: "high", + xhigh: "max", + }, + }; + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(modelWithParams, makeContext(), { apiKey: "test-key", reasoning: "xhigh" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: { + id: "claude-opus-4-7", + params: [ + { id: "context", value: "1m" }, + { id: "effort", value: "max" }, + { id: "thinking", value: "true" }, + ], + }, + }), + ); + }); + + it("turns Cursor thinking off when pi thinking is off", async () => { + const modelWithParams = { + ...makeModel("claude-sonnet-4-6:context=1m;effort=medium;thinking=true"), + reasoning: true, + thinkingLevelMap: { off: "false", low: "low", medium: "medium", high: "high", xhigh: "max" }, + }; + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(modelWithParams, makeContext(), { apiKey: "test-key" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: { + id: "claude-sonnet-4-6", + params: [ + { id: "context", value: "1m" }, + { id: "effort", value: "medium" }, + { id: "thinking", value: "false" }, + ], + }, + }), + ); + }); + + it("passes plain model id without params to Agent.create", async () => { + const plainModel = makeModel("gemini-3.1-pro"); + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(plainModel, makeContext(), { apiKey: "test-key" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: { id: "gemini-3.1-pro" }, + }), + ); + }); + + it("emits result text when no deltas were received", async () => { + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "fallback text" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + + const textEnd = events.find((e: any) => e.type === "text_end"); + expect(textEnd).toBeDefined(); + expect((textEnd as any).content).toBe("fallback text"); + }); +}); diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 0000000..23cee90 --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +// Mock model-discovery before importing the extension +vi.mock("../src/model-discovery.js", () => ({ + discoverModels: vi.fn(), +})); + +// Mock cursor-provider before importing the extension +vi.mock("../src/cursor-provider.js", () => ({ + streamCursor: vi.fn(), +})); + +import extensionFactory from "../src/index.js"; +import { discoverModels } from "../src/model-discovery.js"; +import { streamCursor } from "../src/cursor-provider.js"; + +const mockedDiscover = vi.mocked(discoverModels); +const mockedStreamCursor = vi.mocked(streamCursor); + +function createMockPi() { + const registered: Array<{ name: string; config: Record }> = []; + return { + registerProvider: vi.fn((name: string, config: Record) => { + registered.push({ name, config }); + }), + _registered: registered, + }; +} + +describe("extension factory", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("registers one provider with correct fields", async () => { + const mockModels = [ + { + id: "composer-2:fast=true", + name: "Cursor Composer 2", + reasoning: false, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + ]; + mockedDiscover.mockResolvedValueOnce(mockModels); + + const pi = createMockPi(); + await extensionFactory(pi as any); + + expect(mockedDiscover).toHaveBeenCalledOnce(); + expect(pi.registerProvider).toHaveBeenCalledOnce(); + + const [call] = pi._registered; + expect(call.name).toBe("cursor"); + expect(call.config.name).toBe("Cursor"); + expect(call.config.apiKey).toBe("CURSOR_API_KEY"); + expect(call.config.api).toBe("cursor-sdk"); + expect(call.config.models).toBe(mockModels); + expect(call.config.streamSimple).toBe(mockedStreamCursor); + }); + + it("registers provider even with fallback models", async () => { + mockedDiscover.mockResolvedValueOnce([ + { + id: "composer-2:fast=true", + name: "Cursor Composer 2", + reasoning: false, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + { + id: "gpt-5.5:context=1m;reasoning=medium;fast=false", + name: "GPT-5.5", + reasoning: true, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + ]); + + const pi = createMockPi(); + await extensionFactory(pi as any); + + expect(pi.registerProvider).toHaveBeenCalledOnce(); + const [call] = pi._registered; + expect(call.config.models).toHaveLength(2); + }); +}); diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts new file mode 100644 index 0000000..fc44d23 --- /dev/null +++ b/test/model-discovery.test.ts @@ -0,0 +1,427 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { discoverModels, encodeModelId, decodeModelSelection } from "../src/model-discovery.js"; +import type { ModelParameterValue } from "@cursor/sdk"; + +// Mock @cursor/sdk +vi.mock("@cursor/sdk", () => ({ + Cursor: { + models: { + list: vi.fn(), + }, + }, +})); + +import { Cursor } from "@cursor/sdk"; + +const mockedList = vi.mocked(Cursor.models.list); + +describe("encodeModelId", () => { + it("returns base id when params are empty", () => { + expect(encodeModelId("gemini-3.1-pro", [])).toBe("gemini-3.1-pro"); + }); + + it("encodes single param", () => { + const params: ModelParameterValue[] = [{ id: "fast", value: "true" }]; + expect(encodeModelId("composer-2", params)).toBe("composer-2:fast=true"); + }); + + it("encodes multiple params sorted by id", () => { + const params: ModelParameterValue[] = [ + { id: "reasoning", value: "medium" }, + { id: "fast", value: "false" }, + { id: "context", value: "1m" }, + ]; + expect(encodeModelId("gpt-5.4", params)).toBe( + "gpt-5.4:context=1m;fast=false;reasoning=medium", + ); + }); + + it("handles param with empty value", () => { + const params: ModelParameterValue[] = [{ id: "mode", value: "" }]; + expect(encodeModelId("test-model", params)).toBe("test-model:mode="); + }); +}); + +describe("decodeModelSelection", () => { + it("decodes plain id with no colon", () => { + const result = decodeModelSelection("gemini-3.1-pro"); + expect(result).toEqual({ id: "gemini-3.1-pro" }); + }); + + it("decodes id with params", () => { + const result = decodeModelSelection("composer-2:fast=true"); + expect(result).toEqual({ + id: "composer-2", + params: [{ id: "fast", value: "true" }], + }); + }); + + it("decodes id with multiple params", () => { + const result = decodeModelSelection("gpt-5.4:context=1m;fast=false;reasoning=medium"); + expect(result).toEqual({ + id: "gpt-5.4", + params: [ + { id: "context", value: "1m" }, + { id: "fast", value: "false" }, + { id: "reasoning", value: "medium" }, + ], + }); + }); + + it("handles trailing colon with no params", () => { + const result = decodeModelSelection("model-id:"); + expect(result).toEqual({ id: "model-id" }); + }); + + it("round-trips through encode then decode", () => { + const params: ModelParameterValue[] = [ + { id: "reasoning", value: "high" }, + { id: "fast", value: "true" }, + ]; + const encoded = encodeModelId("gpt-5.2", params); + const decoded = decodeModelSelection(encoded); + // Params come back in sorted order from encode, decode preserves that order + expect(decoded.id).toBe("gpt-5.2"); + expect(decoded.params).toEqual([ + { id: "fast", value: "true" }, + { id: "reasoning", value: "high" }, + ]); + }); + + it("round-trips plain id with no params", () => { + const encoded = encodeModelId("gemini-3.1-pro", []); + const decoded = decodeModelSelection(encoded); + expect(decoded).toEqual({ id: "gemini-3.1-pro" }); + }); +}); + +describe("discoverModels", () => { + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv }; + }); + + afterEach(() => { + process.env = originalEnv; + vi.clearAllMocks(); + }); + + it("returns fallback models when no API key", async () => { + delete process.env.CURSOR_API_KEY; + const models = await discoverModels(); + expect(models).toHaveLength(4); + expect(models[0].id).toContain("composer-2"); + expect(models[1].id).toContain("gpt-5.5"); + expect(models[2].id).toContain("claude-sonnet"); + expect(models[3].id).toContain("claude-opus"); + expect(mockedList).not.toHaveBeenCalled(); + }); + + it("returns fallback models when API key is whitespace", async () => { + process.env.CURSOR_API_KEY = " "; + const models = await discoverModels(); + expect(models).toHaveLength(4); + expect(models[0].id).toContain("composer-2"); + }); + + it("calls Cursor.models.list with API key", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "model-a", + displayName: "Model A", + variants: [{ params: [], displayName: "Model A", isDefault: true }], + }, + { + id: "model-b", + displayName: "Model B", + variants: [{ params: [], displayName: "Model B", isDefault: true }], + }, + ]); + const models = await discoverModels(); + expect(mockedList).toHaveBeenCalledWith({ apiKey: "test-key-123" }); + expect(models).toHaveLength(2); + expect(models[0].id).toBe("model-a"); + expect(models[0].name).toBe("Model A"); + expect(models[1].id).toBe("model-b"); + }); + + it("encodes default variant params into model id", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "gpt-5.4", + displayName: "GPT-5.4", + parameters: [{ id: "reasoning", displayName: "Reasoning", values: [{ value: "medium" }] }], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "reasoning", value: "medium" }, + { id: "fast", value: "false" }, + ], + displayName: "GPT-5.4", + isDefault: true, + }, + ], + }, + ]); + const models = await discoverModels(); + expect(models[0].id).toBe("gpt-5.4:context=1m;fast=false;reasoning=medium"); + expect(models[0].name).toBe("GPT-5.4"); + }); + + it("sets reasoning true for models with thinking parameter", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "claude-opus-4-6", + displayName: "Opus 4.6", + parameters: [{ id: "thinking", displayName: "Thinking", values: [{ value: "true" }] }], + variants: [ + { + params: [{ id: "thinking", value: "true" }], + displayName: "Opus 4.6", + isDefault: true, + }, + ], + }, + ]); + const models = await discoverModels(); + expect(models[0].reasoning).toBe(true); + }); + + it("sets reasoning true for models with reasoning parameter", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "gpt-5.2", + displayName: "GPT-5.2", + parameters: [{ id: "reasoning", displayName: "Reasoning", values: [{ value: "high" }] }], + variants: [ + { + params: [{ id: "reasoning", value: "high" }], + displayName: "GPT-5.2", + isDefault: true, + }, + ], + }, + ]); + const models = await discoverModels(); + expect(models[0].reasoning).toBe(true); + }); + + it("maps Cursor reasoning values to pi thinking levels", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "gpt-5.4", + displayName: "GPT-5.4", + parameters: [ + { + id: "reasoning", + displayName: "Reasoning", + values: [{ value: "low" }, { value: "medium" }, { value: "high" }], + }, + ], + variants: [ + { + params: [{ id: "reasoning", value: "medium" }], + displayName: "GPT-5.4", + isDefault: true, + }, + ], + }, + ]); + const models = await discoverModels(); + expect(models[0].thinkingLevelMap).toEqual({ + off: null, + minimal: null, + low: "low", + medium: "medium", + high: "high", + xhigh: null, + }); + }); + + it("maps boolean Cursor thinking values to off and high", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "claude-opus-4-6", + displayName: "Opus 4.6", + parameters: [ + { + id: "thinking", + displayName: "Thinking", + values: [{ value: "false" }, { value: "true" }], + }, + ], + variants: [ + { + params: [{ id: "thinking", value: "true" }], + displayName: "Opus 4.6", + isDefault: true, + }, + ], + }, + ]); + const models = await discoverModels(); + expect(models[0].thinkingLevelMap).toMatchObject({ + off: "false", + minimal: null, + low: null, + medium: null, + high: "true", + }); + }); + + it("maps Claude effort max to pi xhigh and parses 1m context", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "claude-opus-4-7", + displayName: "Opus 4.7", + parameters: [ + { id: "thinking", displayName: "Thinking", values: [{ value: "false" }, { value: "true" }] }, + { id: "context", displayName: "Context", values: [{ value: "300k" }, { value: "1m" }] }, + { + id: "effort", + displayName: "Effort", + values: [ + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "xhigh" }, + { value: "max" }, + ], + }, + ], + variants: [ + { + params: [ + { id: "thinking", value: "true" }, + { id: "context", value: "1m" }, + { id: "effort", value: "xhigh" }, + ], + displayName: "Opus 4.7", + isDefault: true, + }, + ], + }, + ]); + const models = await discoverModels(); + expect(models[0].id).toBe("claude-opus-4-7:context=1m;effort=xhigh;thinking=true"); + expect(models[0].contextWindow).toBe(1000000); + expect(models[0].thinkingLevelMap).toEqual({ + off: "false", + minimal: null, + low: "low", + medium: "medium", + high: "high", + xhigh: "max", + }); + }); + + it("maps extra-high reasoning values to pi xhigh and parses 272k context", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "gpt-5.4", + displayName: "GPT-5.4", + parameters: [ + { id: "context", displayName: "Context", values: [{ value: "272k" }, { value: "1m" }] }, + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { value: "none" }, + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "extra-high" }, + ], + }, + ], + variants: [ + { + params: [ + { id: "context", value: "272k" }, + { id: "reasoning", value: "medium" }, + ], + displayName: "GPT-5.4", + isDefault: true, + }, + ], + }, + ]); + const models = await discoverModels(); + expect(models[0].contextWindow).toBe(272000); + expect(models[0].thinkingLevelMap?.off).toBe("none"); + expect(models[0].thinkingLevelMap?.xhigh).toBe("extra-high"); + }); + + it("sets reasoning false for models without thinking or reasoning params", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "gemini-3.1-pro", + displayName: "Gemini 3.1 Pro", + variants: [{ params: [], displayName: "Gemini 3.1 Pro", isDefault: true }], + }, + ]); + const models = await discoverModels(); + expect(models[0].reasoning).toBe(false); + }); + + it("falls back when Cursor.models.list throws", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockRejectedValueOnce(new Error("network error")); + const models = await discoverModels(); + expect(models).toHaveLength(4); + expect(models[0].id).toContain("composer-2"); + }); + + it("falls back when Cursor.models.list returns empty", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([]); + const models = await discoverModels(); + expect(models).toHaveLength(4); + expect(models[0].id).toContain("composer-2"); + }); + + it("uses id as name when displayName is missing", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { id: "raw-id", variants: [{ params: [], displayName: "raw-id", isDefault: true }] }, + ]); + const models = await discoverModels(); + expect(models[0].name).toBe("raw-id"); + }); + + it("uses first variant when no isDefault is marked", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "test-model", + displayName: "Test Model", + variants: [ + { params: [{ id: "reasoning", value: "low" }], displayName: "Test Model" }, + { params: [{ id: "reasoning", value: "high" }], displayName: "Test Model" }, + ], + }, + ]); + const models = await discoverModels(); + expect(models[0].id).toBe("test-model:reasoning=low"); + }); + + it("handles model with no variants array", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { id: "bare-model", displayName: "Bare Model" }, + ]); + const models = await discoverModels(); + expect(models[0].id).toBe("bare-model"); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..18608d3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "noEmit": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..c931b88 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + }, +}); -- 2.51.2 From 706bcb72010c1f3340c068cd2f44360c7926eab3 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 4 May 2026 12:14:50 -0600 Subject: [PATCH 002/359] Update package dependencies, enhance README, and implement fast mode for Cursor models - Upgraded TypeScript to version 6.0.3 and Vitest to version 4.1.5 in package.json and package-lock.json. - Enhanced README.md to clarify the functionality of the package, including API key setup and model ID encoding rules. - Implemented fast mode for Cursor models, allowing users to toggle fast processing and store preferences across sessions. - Updated model discovery logic to support new model parameters and context variants. - Added tests for the new fast mode functionality and ensured compatibility with existing features. --- README.md | 129 +-- docs/cursor-model-ux-spec.md | 27 +- package-lock.json | 1744 ++++++++++++++-------------------- package.json | 4 +- src/cursor-provider.ts | 36 +- src/cursor-state.ts | 154 +++ src/index.ts | 2 + src/model-discovery.ts | 533 ++++++++--- test/cursor-provider.test.ts | 185 +++- test/cursor-state.test.ts | 226 +++++ test/index.test.ts | 29 +- test/model-discovery.test.ts | 509 ++++++---- 12 files changed, 2093 insertions(+), 1485 deletions(-) create mode 100644 src/cursor-state.ts create mode 100644 test/cursor-state.test.ts diff --git a/README.md b/README.md index 1f42a03..8aed785 100644 --- a/README.md +++ b/README.md @@ -4,100 +4,127 @@ pi provider extension backed by `@cursor/sdk` local agents. ## What this is -This package lets pi use Cursor models through the local Cursor SDK. +This package lets pi use Cursor models through the local Cursor SDK while keeping model selection, thinking, context display, and the default footer native to pi. Current behavior: - discovers Cursor models with `Cursor.models.list()` when `CURSOR_API_KEY` is set -- registers each model's default variant as a pi model -- encodes default Cursor params into the pi model ID -- maps Cursor reasoning/thinking params into pi thinking levels when possible +- registers Cursor models under the `cursor` provider +- registers one pi model per Cursor `context` value, using IDs like `gpt-5.5@1m` +- maps Cursor `reasoning`, `effort`, and boolean `thinking` to pi native thinking levels +- keeps Cursor `fast` as extension state, toggled with `/cursor-fast` or forced with `--cursor-fast` +- shows Cursor fast mode through `ctx.ui.setStatus()` and leaves pi's default footer intact - creates a fresh local Cursor agent for each pi provider call ## Requirements - Node.js - pi -- a `CURSOR_API_KEY` +- a Cursor API key exposed as the `CURSOR_API_KEY` environment variable -## Install +No global `@cursor/sdk` install is required. This package depends on `@cursor/sdk`, so a normal package install brings in the SDK version the extension was built and tested against. -Clone the repo, then install deps: +## Install ```bash npm install ``` +## API key + +Set `CURSOR_API_KEY` in the environment before starting pi: + +```bash +export CURSOR_API_KEY="your-key" +``` + +Or pass it for one command: + +```bash +CURSOR_API_KEY="your-key" pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." +``` + +Do not store the API key in `~/.pi/agent/cursor-sdk.json`; that file is only for non-secret extension state such as Cursor fast defaults. `PATH` is only for executable lookup and should not contain the API key. + ## Run locally with pi ```bash -CURSOR_API_KEY=your-key pi -e . +pi -e . ``` Pick a model with `/model`, or pass one directly: ```bash -CURSOR_API_KEY=your-key pi -e . -p --model cursor/composer-2:fast=true "Say ok only." +pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." ``` ## Model IDs -When `CURSOR_API_KEY` is set, models come from `Cursor.models.list()`. -Each registered pi model uses the Cursor base model ID plus the default variant params. +Cursor-only parameters are not encoded into pi model IDs. Examples: -- `cursor/composer-2:fast=true` -- `cursor/gpt-5.5:context=1m;fast=false;reasoning=medium` -- `cursor/claude-sonnet-4-6:context=1m;effort=medium;thinking=true` +- `cursor/composer-2` +- `cursor/gpt-5.5@1m` +- `cursor/gpt-5.5@272k` +- `cursor/claude-opus-4-7@300k` -Encoding rules: +Rules: -- no params: `gemini-3.1-pro` -- with params: `gpt-5.4:context=1m;fast=false;reasoning=medium` -- params are sorted alphabetically -- params use `name=value` pairs joined by `;` +- Cursor `context` becomes a pi-visible model variant because it changes `contextWindow`. +- Cursor `reasoning`, `effort`, and `thinking` map to pi native thinking. +- Cursor `fast` is extension state, not model identity. -The extension decodes that ID back into a Cursor `ModelSelection` before calling `Agent.create()`. +## Thinking support -## Fallback models +Use pi's native thinking controls: + +```bash +pi --model cursor/gpt-5.5@1m --thinking medium -p "Say ok only" +pi --model cursor/gpt-5.5@272k:xhigh -p "Say ok only" +``` -If `CURSOR_API_KEY` is missing or model discovery fails, the extension registers these fallbacks: +The extension builds Cursor SDK params from the selected pi thinking level: -| Model ID | Name | -|---|---| -| `composer-2:fast=true` | Cursor Composer 2 | -| `gpt-5.5:context=1m;reasoning=medium;fast=false` | GPT-5.5 | -| `claude-sonnet-4-6:context=1m;effort=medium;thinking=true` | Sonnet 4.6 | -| `claude-opus-4-7:context=1m;effort=xhigh;thinking=true` | Opus 4.7 | +- `reasoning=none|low|medium|high|extra-high` +- `effort=low|medium|high|xhigh|max` +- `thinking=false|true` for boolean thinking models -## Thinking support +For Claude models with both `thinking` and `effort`, pi thinking `off` sends `thinking=false` and omits `effort`. -If a Cursor model exposes `reasoning`, `thinking`, or `effort`, the extension sets `reasoning: true` for the pi model and builds a `thinkingLevelMap`. +## Fast mode -Examples: +Use `/cursor-fast` to toggle fast mode for the selected Cursor model when supported. -- `reasoning=extra-high` maps to pi `xhigh` -- `effort=max` maps to pi `xhigh` -- boolean `thinking=true|false` maps to pi on/off-style thinking +Fast preferences are stored: -At request time, pi's selected thinking level overrides the encoded Cursor reasoning params sent to the SDK. +- in the current session with `pi.appendEntry()` +- globally per Cursor base model in `~/.pi/agent/cursor-sdk.json` -## Images +For one print-mode run: + +```bash +pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only" +``` + +When fast is enabled, the default pi footer gets an extension status line: -Images from the latest user message are forwarded to Cursor. -Historical images are kept out of the transcript. +```text +cursor fast +``` + +## Images -## How it works +Images from the latest user message are forwarded to Cursor. Historical images are kept out of the transcript. The extension advertises `text` and `image` input for Cursor models because Cursor's SDK accepts image messages and Cursor models are expected to support them. -For each pi provider call, the extension: +## Fallback models -1. builds a plain-text prompt from pi conversation state -2. creates a fresh local Cursor agent for the current working directory -3. streams text and thinking deltas back into pi -4. disposes the Cursor agent +If `CURSOR_API_KEY` is missing or model discovery fails, the extension registers conservative fallback Cursor models with the same native shape: -pi remains the source of truth for conversation history. +- `composer-2` +- `gpt-5.5@1m`, `gpt-5.5@272k` +- `claude-sonnet-4-6@1m`, `claude-sonnet-4-6@300k` +- `claude-opus-4-7@1m`, `claude-opus-4-7@300k` ## Limits @@ -105,12 +132,7 @@ pi remains the source of truth for conversation history. - Cursor tool calls are not exposed as pi tool calls - pi tool schemas are not passed through to Cursor - one fresh Cursor agent per provider call -- only the default Cursor variant is auto-registered -- historical images are not replayed to Cursor - -## License - -MIT. +- Cursor SDK model metadata does not currently expose output token limits, so the extension uses conservative token defaults ## Development @@ -121,7 +143,6 @@ npm test npm run typecheck ``` -## Notes +## License -- `docs/cursor-model-ux-spec.md` is a forward-looking design spec, not a description of the exact current implementation. -- Unknown or invalid model params are passed through to Cursor and may be rejected by the Cursor SDK. +MIT diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 9e55794..389e478 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -2,14 +2,17 @@ ## Status -Design draft. This file describes a target UX direction, not the exact behavior of the current code in `src/`. +Implemented design target. This file describes the intended Cursor model UX and should stay aligned with the current code in `src/`. -Current implementation status to keep in mind while reading this spec: +Current implementation notes: -- current model IDs use `base:param=value` encoding -- default Cursor variant params are encoded directly into the pi model ID -- the `@context` model-ID shape in this doc is not implemented yet -- Cursor-only fast-mode state and footer/status behavior in this doc are not implemented yet +- Cursor context variants use `base@context` pi model IDs. +- Cursor `reasoning`, `effort`, and `thinking` are driven by pi native thinking. +- Cursor `fast` is extension state, not model identity. +- Cursor fast status uses `ctx.ui.setStatus()`; the default pi footer remains intact. +- Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`. +- `@cursor/sdk` is a package dependency of this extension; users should not need a global SDK install. +- Cursor auth uses the `CURSOR_API_KEY` environment variable. The extension config file stores only non-secret Cursor-only state such as fast defaults. ## Goal @@ -17,10 +20,10 @@ Make Cursor models feel native in pi by leaning on pi's existing model, thinking Main outcomes: -- `pi --list-models` shows pi-native Cursor models with accurate `contextWindow`, `maxTokens`, thinking, and image metadata. +- `pi --list-models` shows pi-native Cursor models with accurate `contextWindow`, thinking metadata, and conservative defaults where the Cursor SDK does not expose limits or capabilities. - `shift+tab` is pi's native thinking control and drives Cursor `reasoning` or `effort`. - Cursor context options are represented as pi-visible model variants when they change native model metadata. -- Cursor-only state, currently `fast`, is controlled by extension commands/shortcuts and shown through native status text. +- Cursor-only state, currently `fast`, is controlled by extension commands and shown through native status text. - The default pi footer remains intact. - Model capabilities are discovered from the Cursor SDK, not hardcoded per model. @@ -164,7 +167,7 @@ Each registered model must set: - `thinkingLevelMap`: model-specific pi-to-Cursor mapping for pi UI, clamping, persistence, and footer display. - `contextWindow`: parsed from context variant, else conservative fallback. - `maxTokens`: conservative explicit value until Cursor SDK exposes output limits. -- `input`: supported input types. Use `["text", "image"]` only if the Cursor SDK path supports images for that model. +- `input`: supported input types. The installed Cursor SDK accepts `SDKUserMessage.images`, and Cursor models are expected to support image input, so advertise `["text", "image"]`. - `cost`: zeroed unless reliable Cursor costs are available. The extension stores runtime metadata in an internal map keyed by registered pi model ID. That map records the Cursor base model ID, selected context param, default params, and discovered capabilities. `ProviderModelConfig` has no dedicated metadata field, so do not rely on hidden custom fields for this state. @@ -204,7 +207,7 @@ Cursor extension controls: | Action | Preferred control | Applies when | |---|---:|---| -| Toggle fast | `/cursor-fast` plus optional non-reserved shortcut | model has `fast` | +| Toggle fast | `/cursor-fast` | model has `fast` | Do not register a shortcut for `shift+tab`. Pi reserves the native thinking keybinding, and the extension should only influence it through model metadata. @@ -224,7 +227,7 @@ Mapping rules: | pi level | Cursor value preference | |---|---| -| `off` | `none`, else `false`, else unsupported | +| `off` | `none`, else `off`, else `false`, else unsupported | | `minimal` | `minimal`, else unsupported | | `low` | `low` | | `medium` | `medium` | @@ -315,7 +318,7 @@ fast=false <-> fast=true Rules: - `fast` is extension state, not pi model identity. -- Toggle with `/cursor-fast` and optional non-reserved shortcut. +- Toggle with `/cursor-fast`. - Store per-session and global per-base-model preferences. - When calling `Agent.create()`, include the selected `fast` value in Cursor model params. - Show `fast` through `ctx.ui.setStatus()` when enabled. diff --git a/package-lock.json b/package-lock.json index 617b5ef..2250121 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,14 +7,15 @@ "": { "name": "pi-cursor-sdk", "version": "0.1.0", + "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.12" }, "devDependencies": { "@mariozechner/pi-ai": "^0.72.1", "@mariozechner/pi-coding-agent": "^0.72.1", - "typescript": "^5.8.3", - "vitest": "^3.1.1" + "typescript": "^6.0.3", + "vitest": "^4.1.5" } }, "node_modules/@anthropic-ai/sdk": { @@ -973,446 +974,38 @@ "win32" ] }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@fastify/busboy": { @@ -1813,6 +1406,25 @@ "zod-to-json-schema": "^3.25.0" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@nodable/entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", @@ -1852,6 +1464,16 @@ "node": ">=10" } }, + "node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1926,24 +1548,10 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", - "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", - "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", "cpu": [ "arm64" ], @@ -1952,12 +1560,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", - "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", "cpu": [ "arm64" ], @@ -1966,12 +1577,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", - "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", "cpu": [ "x64" ], @@ -1980,26 +1594,15 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", - "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", - "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", "cpu": [ "x64" ], @@ -2008,152 +1611,36 @@ "optional": true, "os": [ "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", - "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", - "cpu": [ - "arm" ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", - "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", "cpu": [ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", - "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", - "cpu": [ - "arm64" ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", - "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", - "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", - "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", - "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", - "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", - "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", - "cpu": [ - "riscv64" - ], - "dev": true, "libc": [ "glibc" ], @@ -2161,14 +1648,17 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", - "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", "cpu": [ - "riscv64" + "arm64" ], "dev": true, "libc": [ @@ -2178,14 +1668,17 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", - "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", "cpu": [ - "s390x" + "ppc64" ], "dev": true, "libc": [ @@ -2195,14 +1688,17 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", - "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", "cpu": [ - "x64" + "s390x" ], "dev": true, "libc": [ @@ -2212,43 +1708,55 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", - "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", "cpu": [ "x64" ], "dev": true, "libc": [ - "musl" + "glibc" ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", - "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ - "openbsd" - ] + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", - "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", "cpu": [ "arm64" ], @@ -2257,40 +1765,51 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", - "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", "cpu": [ - "arm64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", - "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", "cpu": [ - "ia32" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", - "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", "cpu": [ "x64" ], @@ -2299,21 +1818,17 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", - "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", - "cpu": [ - "x64" ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, "node_modules/@silvia-odwyer/photon-node": { "version": "0.3.4", @@ -3004,6 +2519,13 @@ "node": ">=18.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@statsig/client-core": { "version": "3.31.0", "resolved": "https://registry.npmjs.org/@statsig/client-core/-/client-core-3.31.0.tgz", @@ -3061,6 +2583,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -3122,39 +2655,40 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", "dev": true, "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "4.1.5", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -3166,42 +2700,42 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { @@ -3209,28 +2743,25 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -3492,16 +3023,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/cacache": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", @@ -3612,18 +3133,11 @@ } }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } @@ -3641,16 +3155,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -3788,6 +3292,13 @@ "license": "ISC", "optional": true }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -3831,16 +3342,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -3945,54 +3446,12 @@ "optional": true }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, - "node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4679,97 +4138,363 @@ "devOptional": true, "license": "MIT", "engines": { - "node": ">= 12" + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC", + "optional": true + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/koffi": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz", + "integrity": "sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, - "license": "MIT", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "license": "MIT", - "optional": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC", - "optional": true - }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/koffi": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz", - "integrity": "sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], "dev": true, - "hasInstallScript": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, "funding": { - "url": "https://liberapay.com/Koromix" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/long": { @@ -4779,13 +4504,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "11.3.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", @@ -5425,6 +5143,17 @@ "node": ">=0.10.0" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5601,16 +5330,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -5639,9 +5358,9 @@ } }, "node_modules/postcss": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", - "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { @@ -5938,49 +5657,38 @@ "node": "*" } }, - "node_modules/rollup": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", - "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "node_modules/rolldown": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.127.0", + "@rolldown/pluginutils": "1.0.0-rc.17" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" } }, "node_modules/safe-buffer": { @@ -6286,19 +5994,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/strnum": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", @@ -6434,11 +6129,14 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.16", @@ -6457,30 +6155,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -6540,9 +6218,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6626,18 +6304,17 @@ } }, "node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", + "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.17", + "tinyglobby": "^0.2.16" }, "bin": { "vite": "bin/vite.js" @@ -6653,9 +6330,10 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", - "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -6668,13 +6346,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { "optional": true }, - "lightningcss": { + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { @@ -6700,89 +6381,80 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { "optional": true }, "@vitest/ui": { @@ -6793,9 +6465,19 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, + "node_modules/vitest/node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", diff --git a/package.json b/package.json index 9f8a9fa..4259364 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "devDependencies": { "@mariozechner/pi-coding-agent": "^0.72.1", "@mariozechner/pi-ai": "^0.72.1", - "typescript": "^5.8.3", - "vitest": "^3.1.1" + "typescript": "^6.0.3", + "vitest": "^4.1.5" }, "pi": { "extensions": ["./src/index.ts"] diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 3985130..6fce68d 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -8,9 +8,10 @@ import { type AssistantMessage, } from "@mariozechner/pi-ai"; import { Agent } from "@cursor/sdk"; -import type { InteractionUpdate, ModelSelection, SDKAgent } from "@cursor/sdk"; +import type { InteractionUpdate, SDKAgent } from "@cursor/sdk"; import { buildCursorPrompt } from "./context.js"; -import { decodeModelSelection } from "./model-discovery.js"; +import { getEffectiveFastForModelId } from "./cursor-state.js"; +import { buildCursorModelSelection } from "./model-discovery.js"; function makeInitialMessage(model: Model): AssistantMessage { return { @@ -38,30 +39,6 @@ function sanitizeError(error: unknown): string { return "Unknown error"; } -function withPiReasoningLevel( - selection: ModelSelection, - model: Model, - reasoning: SimpleStreamOptions["reasoning"], -): ModelSelection { - if (!selection.params?.length) return selection; - - const requestedLevel = reasoning ?? "off"; - const mapped = model.thinkingLevelMap?.[requestedLevel] ?? (reasoning ? reasoning : undefined); - if (mapped === undefined || mapped === null) return selection; - - const hasEffort = selection.params.some((param) => param.id === "effort"); - const params = selection.params.map((param) => { - if (param.id === "reasoning") return { ...param, value: mapped }; - if (param.id === "effort" && reasoning) return { ...param, value: mapped }; - if (param.id === "thinking") { - if (!reasoning) return { ...param, value: mapped }; - return { ...param, value: hasEffort ? "true" : mapped }; - } - return param; - }); - return { ...selection, params }; -} - export function streamCursor( model: Model, context: Context, @@ -80,11 +57,8 @@ export function streamCursor( if (!apiKey) throw new Error("CURSOR_API_KEY is required"); const cwd = process.cwd(); - const selection = withPiReasoningLevel( - decodeModelSelection(model.id), - model, - options?.reasoning, - ); + const fastEnabled = getEffectiveFastForModelId(model.id); + const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled); agent = await Agent.create({ apiKey, diff --git a/src/cursor-state.ts b/src/cursor-state.ts new file mode 100644 index 0000000..1dec7c7 --- /dev/null +++ b/src/cursor-state.ts @@ -0,0 +1,154 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import { getAgentDir } from "@mariozechner/pi-coding-agent"; +import { getCursorModelMetadata } from "./model-discovery.js"; + +const CURSOR_PROVIDER = "cursor"; +const FAST_ENTRY_TYPE = "cursor-fast-state"; +const GLOBAL_CONFIG_FILE = "cursor-sdk.json"; + +interface CursorFastEntryData { + baseModelId: string; + fast: boolean; +} + +interface CursorGlobalConfig { + fastDefaults?: Record; +} + +const sessionFastPreferences = new Map(); +let globalFastPreferences = new Map(); +let cliForceFast = false; + +function isCursorFastEntryData(value: unknown): value is CursorFastEntryData { + if (!value || typeof value !== "object") return false; + const data = value as Record; + return typeof data.baseModelId === "string" && typeof data.fast === "boolean"; +} + +function getConfigPath(): string { + return join(getAgentDir(), GLOBAL_CONFIG_FILE); +} + +function loadGlobalFastPreferences(): Map { + const path = getConfigPath(); + if (!existsSync(path)) return new Map(); + try { + const parsed = JSON.parse(readFileSync(path, "utf-8")) as CursorGlobalConfig; + return new Map( + Object.entries(parsed.fastDefaults ?? {}).filter( + (entry): entry is [string, boolean] => typeof entry[1] === "boolean", + ), + ); + } catch { + return new Map(); + } +} + +function saveGlobalFastPreferences(): void { + const path = getConfigPath(); + mkdirSync(dirname(path), { recursive: true }); + const config: CursorGlobalConfig = { + fastDefaults: Object.fromEntries([...globalFastPreferences.entries()].sort(([a], [b]) => a.localeCompare(b))), + }; + writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 }); +} + +function restoreSessionFastPreferences(ctx: ExtensionContext): void { + sessionFastPreferences.clear(); + for (const entry of ctx.sessionManager.getBranch()) { + if (entry.type !== "custom" || entry.customType !== FAST_ENTRY_TYPE) continue; + if (isCursorFastEntryData(entry.data)) { + sessionFastPreferences.set(entry.data.baseModelId, entry.data.fast); + } + } +} + +function getEffectiveFast(baseModelId: string, modelId: string): boolean | undefined { + const metadata = getCursorModelMetadata(modelId); + if (!metadata?.supportsFast) return undefined; + if (cliForceFast) return true; + return sessionFastPreferences.get(baseModelId) ?? globalFastPreferences.get(baseModelId) ?? metadata.defaultFast; +} + +function updateCursorStatus(ctx: ExtensionContext, model = ctx.model): void { + if (model?.provider !== CURSOR_PROVIDER) { + ctx.ui.setStatus("cursor", undefined); + return; + } + const metadata = getCursorModelMetadata(model.id); + if (!metadata) { + ctx.ui.setStatus("cursor", undefined); + return; + } + const fast = getEffectiveFast(metadata.baseModelId, model.id); + ctx.ui.setStatus("cursor", fast ? "cursor fast" : undefined); +} + +function getCurrentCursorMetadata(ctx: ExtensionContext) { + const model = ctx.model; + if (model?.provider !== CURSOR_PROVIDER) return undefined; + return getCursorModelMetadata(model.id); +} + +function persistFastPreference(pi: ExtensionAPI, baseModelId: string, fast: boolean): void { + sessionFastPreferences.set(baseModelId, fast); + globalFastPreferences.set(baseModelId, fast); + pi.appendEntry(FAST_ENTRY_TYPE, { baseModelId, fast }); + saveGlobalFastPreferences(); +} + +export function getEffectiveFastForModelId(modelId: string): boolean | undefined { + const metadata = getCursorModelMetadata(modelId); + if (!metadata) return undefined; + return getEffectiveFast(metadata.baseModelId, modelId); +} + +export function registerCursorFastControls(pi: ExtensionAPI): void { + pi.registerFlag("cursor-fast", { + description: "Force Cursor fast mode for this run when the selected Cursor model supports it", + type: "boolean", + default: false, + }); + + pi.registerCommand("cursor-fast", { + description: "Toggle Cursor fast mode for the selected Cursor model", + handler: async (_args, ctx) => { + const metadata = getCurrentCursorMetadata(ctx); + if (!metadata?.supportsFast || !ctx.model) { + const modelName = ctx.model?.id ?? "current model"; + ctx.ui.notify(`Fast mode not supported by ${modelName}`, "info"); + return; + } + if (cliForceFast) { + ctx.ui.notify("Cursor fast is forced by --cursor-fast", "info"); + return; + } + + const current = getEffectiveFast(metadata.baseModelId, metadata.piModelId) ?? false; + const next = !current; + persistFastPreference(pi, metadata.baseModelId, next); + updateCursorStatus(ctx); + ctx.ui.notify(`Cursor fast ${next ? "enabled" : "disabled"}`, "info"); + }, + }); + + pi.on("session_start", async (_event, ctx) => { + globalFastPreferences = loadGlobalFastPreferences(); + cliForceFast = pi.getFlag("cursor-fast") === true; + restoreSessionFastPreferences(ctx); + updateCursorStatus(ctx); + }); + + pi.on("model_select", async (event, ctx) => { + updateCursorStatus(ctx, event.model); + }); +} + +export const __testUtils = { + FAST_ENTRY_TYPE, + getConfigPath, + loadGlobalFastPreferences, + sessionFastPreferences, +}; diff --git a/src/index.ts b/src/index.ts index 05d1dc8..3afd336 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,10 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { discoverModels } from "./model-discovery.js"; +import { registerCursorFastControls } from "./cursor-state.js"; import { streamCursor } from "./cursor-provider.js"; export default async function (pi: ExtensionAPI) { + registerCursorFastControls(pi); const models = await discoverModels(); pi.registerProvider("cursor", { diff --git a/src/model-discovery.ts b/src/model-discovery.ts index 4d855a5..eb45fb6 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -6,177 +6,243 @@ import type { ModelSelection, } from "@cursor/sdk"; import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent"; -import type { ThinkingLevelMap } from "@mariozechner/pi-ai"; +import type { ModelThinkingLevel, ThinkingLevelMap } from "@mariozechner/pi-ai"; -const FALLBACK_MODELS: ProviderModelConfig[] = [ +const FALLBACK_CONTEXT_WINDOW = 128000; +const FALLBACK_MAX_TOKENS = 16384; +const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; +const TEXT_AND_IMAGE_INPUT: ProviderModelConfig["input"] = ["text", "image"]; + +const FALLBACK_MODEL_ITEMS: ModelListItem[] = [ { - id: "composer-2:fast=true", - name: "Cursor Composer 2", - reasoning: false, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 128000, - maxTokens: 16384, + id: "composer-2", + displayName: "Cursor Composer 2", + parameters: [ + { + id: "fast", + displayName: "Fast", + values: [{ value: "false" }, { value: "true" }], + }, + ], + variants: [ + { + params: [{ id: "fast", value: "true" }], + displayName: "Cursor Composer 2", + isDefault: true, + }, + ], }, { - id: "gpt-5.5:context=1m;reasoning=medium;fast=false", - name: "GPT-5.5", - reasoning: true, - thinkingLevelMap: { - off: "none", - minimal: null, - low: "low", - medium: "medium", - high: "high", - xhigh: "extra-high", - }, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 1000000, - maxTokens: 16384, + id: "gpt-5.5", + displayName: "GPT-5.5", + parameters: [ + { + id: "context", + displayName: "Context", + values: [{ value: "1m" }, { value: "272k" }], + }, + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { value: "none" }, + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "extra-high" }, + ], + }, + { + id: "fast", + displayName: "Fast", + values: [{ value: "false" }, { value: "true" }], + }, + ], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "reasoning", value: "medium" }, + { id: "fast", value: "false" }, + ], + displayName: "GPT-5.5", + isDefault: true, + }, + ], }, { - id: "claude-sonnet-4-6:context=1m;effort=medium;thinking=true", - name: "Sonnet 4.6", - reasoning: true, - thinkingLevelMap: { - off: "false", - minimal: null, - low: "low", - medium: "medium", - high: "high", - xhigh: "max", - }, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 1000000, - maxTokens: 16384, + id: "claude-sonnet-4-6", + displayName: "Sonnet 4.6", + parameters: [ + { + id: "thinking", + displayName: "Thinking", + values: [{ value: "false" }, { value: "true" }], + }, + { + id: "context", + displayName: "Context", + values: [{ value: "1m" }, { value: "300k" }], + }, + { + id: "effort", + displayName: "Effort", + values: [ + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "xhigh" }, + { value: "max" }, + ], + }, + { + id: "fast", + displayName: "Fast", + values: [{ value: "false" }, { value: "true" }], + }, + ], + variants: [ + { + params: [ + { id: "thinking", value: "true" }, + { id: "context", value: "1m" }, + { id: "effort", value: "medium" }, + { id: "fast", value: "false" }, + ], + displayName: "Sonnet 4.6", + isDefault: true, + }, + ], }, { - id: "claude-opus-4-7:context=1m;effort=xhigh;thinking=true", - name: "Opus 4.7", - reasoning: true, - thinkingLevelMap: { - off: "false", - minimal: null, - low: "low", - medium: "medium", - high: "high", - xhigh: "max", - }, - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 1000000, - maxTokens: 16384, + id: "claude-opus-4-7", + displayName: "Opus 4.7", + parameters: [ + { + id: "thinking", + displayName: "Thinking", + values: [{ value: "false" }, { value: "true" }], + }, + { + id: "context", + displayName: "Context", + values: [{ value: "1m" }, { value: "300k" }], + }, + { + id: "effort", + displayName: "Effort", + values: [ + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "xhigh" }, + { value: "max" }, + ], + }, + ], + variants: [ + { + params: [ + { id: "thinking", value: "true" }, + { id: "context", value: "1m" }, + { id: "effort", value: "xhigh" }, + ], + displayName: "Opus 4.7", + isDefault: true, + }, + ], }, ]; -/** - * Encode a Cursor model base ID and optional params into a single pi model ID. - * - * Format: `baseId` when params are empty, otherwise `baseId:key1=val1;key2=val2`. - * The encoding is deterministic: params are sorted by id before joining. - */ -export function encodeModelId(baseId: string, params: ModelParameterValue[]): string { - if (!params.length) return baseId; - const suffix = params - .slice() - .sort((a, b) => a.id.localeCompare(b.id)) - .map((p) => `${p.id}=${p.value}`) - .join(";"); - return `${baseId}:${suffix}`; -} - -/** - * Decode a pi model ID back into a Cursor `ModelSelection`. - * - * Reverses `encodeModelId`. If the id contains no `:`, returns `{ id }` - * with no params (backward compatible with plain model ids). - */ -export function decodeModelSelection(modelId: string): ModelSelection { - const colonIndex = modelId.indexOf(":"); - if (colonIndex < 0) return { id: modelId }; - - const baseId = modelId.slice(0, colonIndex); - const paramString = modelId.slice(colonIndex + 1); - if (!paramString) return { id: baseId }; - - const params: ModelParameterValue[] = paramString.split(";").map((pair) => { - const eqIndex = pair.indexOf("="); - if (eqIndex < 0) return { id: pair, value: "" }; - return { id: pair.slice(0, eqIndex), value: pair.slice(eqIndex + 1) }; - }); - - return { id: baseId, params }; +export interface CursorModelMetadata { + piModelId: string; + baseModelId: string; + displayName: string; + defaultParams: ModelParameterValue[]; + context?: string; + contextWindow: number; + supportsFast: boolean; + defaultFast: boolean; + supportsReasoning: boolean; + thinkingLevelMap?: ThinkingLevelMap; + parameterIds: { + context: boolean; + reasoning: boolean; + effort: boolean; + thinking: boolean; + fast: boolean; + }; } -/** - * Determine if a model supports reasoning/thinking based on its parameters. - */ -function getParameter(item: ModelListItem, id: string): ModelParameterDefinition | undefined { - return item.parameters?.find((p) => p.id === id); +const metadataByPiModelId = new Map(); + +function cloneParams(params: ModelParameterValue[]): ModelParameterValue[] { + return params.map((param) => ({ ...param })); } -function getReasoningParameter(item: ModelListItem): ModelParameterDefinition | undefined { - return getParameter(item, "reasoning") ?? getParameter(item, "effort") ?? getParameter(item, "thinking"); +function getParameter(item: ModelListItem, id: string): ModelParameterDefinition | undefined { + return item.parameters?.find((parameter) => parameter.id === id); } -function hasReasoningParameter(item: ModelListItem): boolean { - return getReasoningParameter(item) !== undefined; +function hasBooleanValues(parameter: ModelParameterDefinition | undefined): boolean { + const values = new Set((parameter?.values ?? []).map((value) => value.value.toLowerCase())); + return values.has("false") && values.has("true"); } -function hasValue(values: Set, value: string): boolean { - return values.has(value.toLowerCase()); +function getParameterValue(parameter: ModelParameterDefinition | undefined, lowerValue: string): string | null { + const value = parameter?.values.find((candidate) => candidate.value.toLowerCase() === lowerValue); + return value?.value ?? null; } -function mapLevel(values: Set, level: keyof ThinkingLevelMap): string | null { - if (level === "xhigh") { - if (hasValue(values, "max")) return "max"; - if (hasValue(values, "xhigh")) return "xhigh"; - if (hasValue(values, "extra-high")) return "extra-high"; - return null; +function getPreferredParameterValue( + parameter: ModelParameterDefinition | undefined, + lowerValues: string[], +): string | null { + for (const value of lowerValues) { + const candidate = getParameterValue(parameter, value); + if (candidate) return candidate; } - if (hasValue(values, level)) return level; return null; } -function getParameterValues(parameter?: ModelParameterDefinition): Set { - return new Set(parameter?.values.map((v) => v.value.toLowerCase()) ?? []); +function mapComparableLevel( + parameter: ModelParameterDefinition | undefined, + level: Exclude, +): string | null { + if (level === "xhigh") { + return getPreferredParameterValue(parameter, ["xhigh", "max", "extra-high"]); + } + return getParameterValue(parameter, level); } function getThinkingLevelMap(item: ModelListItem): ThinkingLevelMap | undefined { - const effortParameter = getParameter(item, "effort"); const reasoningParameter = getParameter(item, "reasoning"); + const effortParameter = getParameter(item, "effort"); const thinkingParameter = getParameter(item, "thinking"); - const parameter = effortParameter ?? reasoningParameter ?? thinkingParameter; - if (!parameter) return undefined; + const valueParameter = effortParameter ?? reasoningParameter ?? thinkingParameter; + if (!valueParameter) return undefined; - const values = getParameterValues(parameter); - const thinkingValues = getParameterValues(thinkingParameter); - if (parameter.id === "thinking") { + if (valueParameter.id === "thinking" && hasBooleanValues(valueParameter)) { return { - off: hasValue(values, "false") ? "false" : null, + off: getParameterValue(valueParameter, "false"), minimal: null, low: null, medium: null, - high: hasValue(values, "true") ? "true" : null, + high: getParameterValue(valueParameter, "true"), xhigh: null, }; } return { - off: hasValue(values, "off") - ? "off" - : hasValue(values, "none") - ? "none" - : hasValue(thinkingValues, "false") - ? "false" - : null, - minimal: mapLevel(values, "minimal"), - low: mapLevel(values, "low"), - medium: mapLevel(values, "medium"), - high: mapLevel(values, "high"), - xhigh: mapLevel(values, "xhigh"), + off: + getParameterValue(reasoningParameter, "none") ?? + getParameterValue(reasoningParameter, "off") ?? + getParameterValue(thinkingParameter, "false"), + minimal: mapComparableLevel(valueParameter, "minimal"), + low: mapComparableLevel(valueParameter, "low"), + medium: mapComparableLevel(valueParameter, "medium"), + high: mapComparableLevel(valueParameter, "high"), + xhigh: mapComparableLevel(valueParameter, "xhigh"), }; } @@ -189,48 +255,203 @@ function parseContextWindow(value: string): number | undefined { return Math.round(amount * (unit === "m" ? 1000000 : 1000)); } -function getContextWindow(_item: ModelListItem, params: ModelParameterValue[]): number { - const contextParam = params.find((p) => p.id === "context"); - const parsed = contextParam ? parseContextWindow(contextParam.value) : undefined; - return parsed ?? 128000; -} - -/** - * Find the default variant's params for a model. - * Falls back to empty params if no variants or no default is marked. - */ function getDefaultParams(item: ModelListItem): ModelParameterValue[] { if (!item.variants?.length) return []; - const defaultVariant = item.variants.find((v) => v.isDefault); - return defaultVariant?.params ?? item.variants[0]?.params ?? []; + const defaultVariant = item.variants.find((variant) => variant.isDefault) ?? item.variants[0]; + return cloneParams(defaultVariant?.params ?? []); +} + +function replaceParam( + params: ModelParameterValue[], + id: string, + value: string, +): ModelParameterValue[] { + let replaced = false; + const next = params.map((param) => { + if (param.id !== id) return { ...param }; + replaced = true; + return { id, value }; + }); + if (!replaced) next.push({ id, value }); + return next; +} + +function getParamValue(params: ModelParameterValue[], id: string): string | undefined { + return params.find((param) => param.id === id)?.value; +} + +function encodePiModelId(baseModelId: string, context?: string): string { + return context ? `${baseModelId}@${context}` : baseModelId; } -function toModelConfig(item: ModelListItem): ProviderModelConfig { - const params = getDefaultParams(item); +function getModelName(item: ModelListItem, context?: string): string { + const displayName = item.displayName || item.id; + return context ? `${displayName} @ ${context}` : displayName; +} + +function toMetadata( + item: ModelListItem, + piModelId: string, + defaultParams: ModelParameterValue[], + context: string | undefined, +): CursorModelMetadata { const thinkingLevelMap = getThinkingLevelMap(item); + const fastValue = getParamValue(defaultParams, "fast")?.toLowerCase(); return { - id: encodeModelId(item.id, params), - name: item.displayName || item.id, - reasoning: hasReasoningParameter(item), + piModelId, + baseModelId: item.id, + displayName: item.displayName || item.id, + defaultParams: cloneParams(defaultParams), + ...(context ? { context } : {}), + contextWindow: context ? parseContextWindow(context) ?? FALLBACK_CONTEXT_WINDOW : FALLBACK_CONTEXT_WINDOW, + supportsFast: getParameter(item, "fast") !== undefined, + defaultFast: fastValue === "true", + supportsReasoning: thinkingLevelMap !== undefined, ...(thinkingLevelMap ? { thinkingLevelMap } : {}), - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: getContextWindow(item, params), - maxTokens: 16384, + parameterIds: { + context: getParameter(item, "context") !== undefined, + reasoning: getParameter(item, "reasoning") !== undefined, + effort: getParameter(item, "effort") !== undefined, + thinking: getParameter(item, "thinking") !== undefined, + fast: getParameter(item, "fast") !== undefined, + }, + }; +} + +function toModelConfig(metadata: CursorModelMetadata, name: string): ProviderModelConfig { + return { + id: metadata.piModelId, + name, + reasoning: metadata.supportsReasoning, + ...(metadata.thinkingLevelMap ? { thinkingLevelMap: metadata.thinkingLevelMap } : {}), + input: [...TEXT_AND_IMAGE_INPUT], + cost: { ...ZERO_COST }, + contextWindow: metadata.contextWindow, + maxTokens: FALLBACK_MAX_TOKENS, }; } +function toModelConfigs(item: ModelListItem): ProviderModelConfig[] { + const defaultParams = getDefaultParams(item); + const contextParameter = getParameter(item, "context"); + const contextValues = contextParameter?.values.map((value) => value.value) ?? []; + const contexts = contextValues.length > 0 ? contextValues : [undefined]; + + return contexts.map((context) => { + const params = context ? replaceParam(defaultParams, "context", context) : defaultParams; + const piModelId = encodePiModelId(item.id, context); + const metadata = toMetadata(item, piModelId, params, context); + metadataByPiModelId.set(piModelId, metadata); + return toModelConfig(metadata, getModelName(item, context)); + }); +} + +function sortModelsByBaseId(items: ModelListItem[]): ModelListItem[] { + return [...items].sort((a, b) => a.id.localeCompare(b.id)); +} + +function registerModelItems(items: ModelListItem[]): ProviderModelConfig[] { + metadataByPiModelId.clear(); + return sortModelsByBaseId(items).flatMap(toModelConfigs); +} + +export function getCursorModelMetadata(modelId: string): CursorModelMetadata | undefined { + return metadataByPiModelId.get(modelId); +} + +export function getCursorModelMetadataEntries(): CursorModelMetadata[] { + return [...metadataByPiModelId.values()].map((metadata) => ({ + ...metadata, + defaultParams: cloneParams(metadata.defaultParams), + ...(metadata.thinkingLevelMap ? { thinkingLevelMap: { ...metadata.thinkingLevelMap } } : {}), + parameterIds: { ...metadata.parameterIds }, + })); +} + +function setParam(params: ModelParameterValue[], id: string, value: string): void { + const existing = params.find((param) => param.id === id); + if (existing) { + existing.value = value; + } else { + params.push({ id, value }); + } +} + +function deleteParam(params: ModelParameterValue[], id: string): void { + const index = params.findIndex((param) => param.id === id); + if (index >= 0) params.splice(index, 1); +} + +function applyThinkingLevel( + metadata: CursorModelMetadata, + params: ModelParameterValue[], + level: ModelThinkingLevel, +): void { + const mapped = metadata.thinkingLevelMap?.[level]; + if (mapped === undefined || mapped === null) return; + + if (level === "off") { + if (metadata.parameterIds.thinking && mapped === "false") { + setParam(params, "thinking", mapped); + deleteParam(params, "effort"); + return; + } + if (metadata.parameterIds.reasoning) { + setParam(params, "reasoning", mapped); + } + return; + } + + if (metadata.parameterIds.effort) { + if (metadata.parameterIds.thinking) setParam(params, "thinking", "true"); + setParam(params, "effort", mapped); + return; + } + + if (metadata.parameterIds.reasoning) { + setParam(params, "reasoning", mapped); + return; + } + + if (metadata.parameterIds.thinking) { + setParam(params, "thinking", mapped); + } +} + +export function buildCursorModelSelection( + modelId: string, + thinkingLevel: ModelThinkingLevel, + fastEnabled?: boolean, +): ModelSelection { + const metadata = getCursorModelMetadata(modelId); + if (!metadata) return { id: modelId }; + + const params = cloneParams(metadata.defaultParams); + applyThinkingLevel(metadata, params, thinkingLevel); + + if (metadata.supportsFast && fastEnabled !== undefined) { + setParam(params, "fast", fastEnabled ? "true" : "false"); + } + + return params.length > 0 ? { id: metadata.baseModelId, params } : { id: metadata.baseModelId }; +} + export async function discoverModels(): Promise { const apiKey = process.env.CURSOR_API_KEY?.trim(); - if (!apiKey) return FALLBACK_MODELS; + if (!apiKey) return registerModelItems(FALLBACK_MODEL_ITEMS); try { const models = await Cursor.models.list({ apiKey }); if (models.length > 0) { - return models.map(toModelConfig); + return registerModelItems(models); } - return FALLBACK_MODELS; + return registerModelItems(FALLBACK_MODEL_ITEMS); } catch { - return FALLBACK_MODELS; + return registerModelItems(FALLBACK_MODEL_ITEMS); } } + +export const __testUtils = { + parseContextWindow, + registerModelItems, +}; diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index faeed52..22348d8 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -22,6 +22,8 @@ vi.mock("@cursor/sdk", () => { import { Agent } from "@cursor/sdk"; import { streamCursor } from "../src/cursor-provider.js"; +import { __testUtils as modelDiscoveryTestUtils } from "../src/model-discovery.js"; +import type { ModelListItem } from "@cursor/sdk"; import type { Context, Model } from "@mariozechner/pi-ai"; // Access the mocks via the module @@ -35,7 +37,7 @@ function makeModel(id = "test-model"): Model<"cursor-sdk"> { provider: "cursor", baseUrl: "", reasoning: false, - input: ["text"], + input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 16384, @@ -57,9 +59,83 @@ async function collectEvents(stream: ReturnType) { return events; } +const cursorModelItems: ModelListItem[] = [ + { + id: "gpt-5.5", + displayName: "GPT-5.5", + parameters: [ + { id: "context", displayName: "Context", values: [{ value: "1m" }, { value: "272k" }] }, + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { value: "none" }, + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "extra-high" }, + ], + }, + { id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] }, + ], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "fast", value: "false" }, + { id: "reasoning", value: "medium" }, + ], + displayName: "GPT-5.5", + isDefault: true, + }, + ], + }, + { + id: "claude-opus-4-7", + displayName: "Opus 4.7", + parameters: [ + { id: "context", displayName: "Context", values: [{ value: "1m" }] }, + { id: "effort", displayName: "Effort", values: [{ value: "low" }, { value: "medium" }, { value: "high" }, { value: "xhigh" }] }, + { id: "thinking", displayName: "Thinking", values: [{ value: "false" }, { value: "true" }] }, + ], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "effort", value: "xhigh" }, + { id: "thinking", value: "true" }, + ], + displayName: "Opus 4.7", + isDefault: true, + }, + ], + }, + { + id: "claude-sonnet-4-6", + displayName: "Sonnet 4.6", + parameters: [ + { id: "context", displayName: "Context", values: [{ value: "1m" }] }, + { id: "effort", displayName: "Effort", values: [{ value: "low" }, { value: "medium" }, { value: "high" }, { value: "xhigh" }] }, + { id: "thinking", displayName: "Thinking", values: [{ value: "false" }, { value: "true" }] }, + ], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "effort", value: "medium" }, + { id: "thinking", value: "true" }, + ], + displayName: "Sonnet 4.6", + isDefault: true, + }, + ], + }, +]; + describe("streamCursor", () => { beforeEach(() => { vi.clearAllMocks(); + modelDiscoveryTestUtils.registerModelItems(cursorModelItems); // Re-setup default mock return after clearing mockedCreate.mockResolvedValue({ send: vi.fn(), @@ -260,8 +336,47 @@ describe("streamCursor", () => { expect(mockCancel).toHaveBeenCalled(); }); - it("passes decoded model selection to Agent.create", async () => { - const modelWithParams = makeModel("gpt-5.4:context=1m;fast=false;reasoning=medium"); + it("forwards latest user images to Cursor Agent.send", async () => { + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + const context: Context = { + systemPrompt: "Be helpful.", + messages: [ + { + role: "user", + content: [ + { type: "text", text: "Describe this image" }, + { type: "image", data: "base64-image", mimeType: "image/png" }, + ], + timestamp: 1, + }, + ], + }; + + const stream = streamCursor(makeModel("gpt-5.5@1m"), context, { apiKey: "test-key" }); + await collectEvents(stream); + + expect(mockSend).toHaveBeenCalledWith( + expect.objectContaining({ + images: [{ data: "base64-image", mimeType: "image/png" }], + }), + expect.any(Object), + ); + }); + + it("passes Cursor model selection with context and pi thinking off to Agent.create", async () => { + const modelWithParams = makeModel("gpt-5.5@1m"); const mockSend = vi.fn().mockResolvedValue({ id: "run-1", agentId: "agent-1", @@ -282,22 +397,22 @@ describe("streamCursor", () => { expect(mockedCreate).toHaveBeenCalledWith( expect.objectContaining({ model: { - id: "gpt-5.4", + id: "gpt-5.5", params: [ { id: "context", value: "1m" }, { id: "fast", value: "false" }, - { id: "reasoning", value: "medium" }, + { id: "reasoning", value: "none" }, ], }, }), ); }); - it("applies pi thinking level to Cursor reasoning parameter", async () => { + it("applies pi medium thinking level to Cursor reasoning parameter", async () => { const modelWithParams = { - ...makeModel("gpt-5.4:context=1m;fast=false;reasoning=medium"), + ...makeModel("gpt-5.5@1m"), reasoning: true, - thinkingLevelMap: { low: "low", medium: "medium", high: "high", off: null, minimal: null }, + thinkingLevelMap: { low: "low", medium: "medium", high: "high", xhigh: "extra-high", off: null, minimal: null }, }; const mockSend = vi.fn().mockResolvedValue({ id: "run-1", @@ -313,17 +428,54 @@ describe("streamCursor", () => { [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), }); - const stream = streamCursor(modelWithParams, makeContext(), { apiKey: "test-key", reasoning: "high" }); + const stream = streamCursor(modelWithParams, makeContext(), { apiKey: "test-key", reasoning: "medium" }); await collectEvents(stream); expect(mockedCreate).toHaveBeenCalledWith( expect.objectContaining({ model: { - id: "gpt-5.4", + id: "gpt-5.5", params: [ { id: "context", value: "1m" }, { id: "fast", value: "false" }, - { id: "reasoning", value: "high" }, + { id: "reasoning", value: "medium" }, + ], + }, + }), + ); + }); + + it("maps pi xhigh thinking to Cursor extra-high reasoning for a sibling context", async () => { + const modelWithParams = { + ...makeModel("gpt-5.5@272k"), + reasoning: true, + thinkingLevelMap: { low: "low", medium: "medium", high: "high", xhigh: "extra-high", off: null, minimal: null }, + }; + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(modelWithParams, makeContext(), { apiKey: "test-key", reasoning: "xhigh" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: { + id: "gpt-5.5", + params: [ + { id: "context", value: "272k" }, + { id: "fast", value: "false" }, + { id: "reasoning", value: "extra-high" }, ], }, }), @@ -332,14 +484,14 @@ describe("streamCursor", () => { it("applies pi thinking level to Cursor Claude effort and thinking parameters", async () => { const modelWithParams = { - ...makeModel("claude-opus-4-7:context=1m;effort=xhigh;thinking=true"), + ...makeModel("claude-opus-4-7@1m"), reasoning: true, thinkingLevelMap: { off: "false", low: "low", medium: "medium", high: "high", - xhigh: "max", + xhigh: "xhigh", }, }; const mockSend = vi.fn().mockResolvedValue({ @@ -365,7 +517,7 @@ describe("streamCursor", () => { id: "claude-opus-4-7", params: [ { id: "context", value: "1m" }, - { id: "effort", value: "max" }, + { id: "effort", value: "xhigh" }, { id: "thinking", value: "true" }, ], }, @@ -375,9 +527,9 @@ describe("streamCursor", () => { it("turns Cursor thinking off when pi thinking is off", async () => { const modelWithParams = { - ...makeModel("claude-sonnet-4-6:context=1m;effort=medium;thinking=true"), + ...makeModel("claude-sonnet-4-6@1m"), reasoning: true, - thinkingLevelMap: { off: "false", low: "low", medium: "medium", high: "high", xhigh: "max" }, + thinkingLevelMap: { off: "false", low: "low", medium: "medium", high: "high", xhigh: "xhigh" }, }; const mockSend = vi.fn().mockResolvedValue({ id: "run-1", @@ -402,7 +554,6 @@ describe("streamCursor", () => { id: "claude-sonnet-4-6", params: [ { id: "context", value: "1m" }, - { id: "effort", value: "medium" }, { id: "thinking", value: "false" }, ], }, diff --git a/test/cursor-state.test.ts b/test/cursor-state.test.ts new file mode 100644 index 0000000..e5d393b --- /dev/null +++ b/test/cursor-state.test.ts @@ -0,0 +1,226 @@ +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { registerCursorFastControls, getEffectiveFastForModelId, __testUtils } from "../src/cursor-state.js"; +import { __testUtils as modelDiscoveryTestUtils } from "../src/model-discovery.js"; +import type { ModelListItem } from "@cursor/sdk"; + +const modelItems: ModelListItem[] = [ + { + id: "composer-2", + displayName: "Cursor Composer 2", + parameters: [{ id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] }], + variants: [ + { + params: [{ id: "fast", value: "true" }], + displayName: "Cursor Composer 2", + isDefault: true, + }, + ], + }, + { + id: "gpt-5.5", + displayName: "GPT-5.5", + parameters: [ + { id: "context", displayName: "Context", values: [{ value: "1m" }, { value: "272k" }] }, + { id: "reasoning", displayName: "Reasoning", values: [{ value: "none" }, { value: "medium" }] }, + { id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] }, + ], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "reasoning", value: "medium" }, + { id: "fast", value: "false" }, + ], + displayName: "GPT-5.5", + isDefault: true, + }, + ], + }, + { + id: "gemini-3.1-pro", + displayName: "Gemini 3.1 Pro", + variants: [{ params: [], displayName: "Gemini 3.1 Pro", isDefault: true }], + }, +]; + +function createHarness(options: { modelId?: string; provider?: string; branch?: unknown[]; cursorFastFlag?: boolean } = {}) { + const commands = new Map(); + const handlers = new Map(); + const pi = { + registerFlag: vi.fn(), + registerCommand: vi.fn((name: string, command: any) => commands.set(name, command)), + on: vi.fn((event: string, handler: any) => handlers.set(event, handler)), + getFlag: vi.fn(() => options.cursorFastFlag ?? false), + appendEntry: vi.fn(), + }; + const ctx = { + model: options.modelId + ? { + provider: options.provider ?? "cursor", + id: options.modelId, + } + : undefined, + ui: { + setStatus: vi.fn(), + notify: vi.fn(), + }, + sessionManager: { + getBranch: vi.fn(() => options.branch ?? []), + }, + }; + registerCursorFastControls(pi as any); + return { pi, ctx, commands, handlers }; +} + +describe("Cursor fast state", () => { + let tmpAgentDir: string; + const originalAgentDir = process.env.PI_CODING_AGENT_DIR; + + beforeEach(() => { + tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-cursor-state-")); + process.env.PI_CODING_AGENT_DIR = tmpAgentDir; + __testUtils.sessionFastPreferences.clear(); + modelDiscoveryTestUtils.registerModelItems(modelItems); + }); + + afterEach(() => { + if (originalAgentDir === undefined) { + delete process.env.PI_CODING_AGENT_DIR; + } else { + process.env.PI_CODING_AGENT_DIR = originalAgentDir; + } + rmSync(tmpAgentDir, { recursive: true, force: true }); + vi.clearAllMocks(); + }); + + it("toggles fast per session and writes the global default", async () => { + const { pi, ctx, commands, handlers } = createHarness({ modelId: "composer-2" }); + await handlers.get("session_start")({}, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", "cursor fast"); + + await commands.get("cursor-fast").handler("", ctx); + + expect(pi.appendEntry).toHaveBeenCalledWith(__testUtils.FAST_ENTRY_TYPE, { + baseModelId: "composer-2", + fast: false, + }); + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", undefined); + expect(getEffectiveFastForModelId("composer-2")).toBe(false); + expect(JSON.parse(readFileSync(__testUtils.getConfigPath(), "utf-8"))).toEqual({ + fastDefaults: { "composer-2": false }, + }); + }); + + it("restores fast state from the active session branch", async () => { + const { ctx, handlers } = createHarness({ + modelId: "composer-2", + branch: [ + { + type: "custom", + customType: __testUtils.FAST_ENTRY_TYPE, + data: { baseModelId: "composer-2", fast: false }, + }, + ], + }); + + await handlers.get("session_start")({}, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", undefined); + expect(getEffectiveFastForModelId("composer-2")).toBe(false); + }); + + it("uses global fast defaults for new sessions", async () => { + writeFileSync(__testUtils.getConfigPath(), JSON.stringify({ fastDefaults: { "gpt-5.5": true } })); + const { ctx, handlers } = createHarness({ modelId: "gpt-5.5@1m" }); + + await handlers.get("session_start")({}, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", "cursor fast"); + expect(getEffectiveFastForModelId("gpt-5.5@1m")).toBe(true); + }); + + it("forces fast with the CLI flag without writing session state", async () => { + const { pi, ctx, handlers } = createHarness({ modelId: "gpt-5.5@1m", cursorFastFlag: true }); + + await handlers.get("session_start")({}, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", "cursor fast"); + expect(getEffectiveFastForModelId("gpt-5.5@1m")).toBe(true); + expect(pi.appendEntry).not.toHaveBeenCalled(); + }); + + it("does not let /cursor-fast persist an opposite value when --cursor-fast is active", async () => { + const { pi, ctx, commands, handlers } = createHarness({ modelId: "gpt-5.5@1m", cursorFastFlag: true }); + await handlers.get("session_start")({}, ctx); + + await commands.get("cursor-fast").handler("", ctx); + + expect(ctx.ui.notify).toHaveBeenCalledWith("Cursor fast is forced by --cursor-fast", "info"); + expect(getEffectiveFastForModelId("gpt-5.5@1m")).toBe(true); + expect(pi.appendEntry).not.toHaveBeenCalled(); + }); + + it("notifies and no-ops when the selected model does not support fast", async () => { + const { ctx, commands, handlers } = createHarness({ modelId: "gemini-3.1-pro" }); + await handlers.get("session_start")({}, ctx); + + await commands.get("cursor-fast").handler("", ctx); + + expect(ctx.ui.notify).toHaveBeenCalledWith("Fast mode not supported by gemini-3.1-pro", "info"); + }); + + it("toggles fast by base model id so context sibling variants share the preference", async () => { + const { ctx, commands, handlers } = createHarness({ modelId: "gpt-5.5@1m" }); + await handlers.get("session_start")({}, ctx); + + await commands.get("cursor-fast").handler("", ctx); + + expect(getEffectiveFastForModelId("gpt-5.5@1m")).toBe(true); + expect(getEffectiveFastForModelId("gpt-5.5@272k")).toBe(true); + expect(JSON.parse(readFileSync(__testUtils.getConfigPath(), "utf-8"))).toEqual({ + fastDefaults: { "gpt-5.5": true }, + }); + }); + + it("clears Cursor status when model_select moves from Cursor fast model to non-cursor model", async () => { + const { ctx, handlers } = createHarness({ modelId: "composer-2" }); + await handlers.get("session_start")({}, ctx); + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", "cursor fast"); + + await handlers.get("model_select")({ model: { provider: "anthropic", id: "claude-sonnet-4-5" } }, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", undefined); + }); + + it("ignores malformed global config without throwing", async () => { + writeFileSync(__testUtils.getConfigPath(), "{not json"); + const { ctx, handlers } = createHarness({ modelId: "gpt-5.5@1m" }); + + await expect(handlers.get("session_start")({}, ctx)).resolves.toBeUndefined(); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", undefined); + expect(getEffectiveFastForModelId("gpt-5.5@1m")).toBe(false); + }); + + it("does not apply or persist --cursor-fast for unsupported Cursor models", async () => { + const { pi, ctx, handlers } = createHarness({ modelId: "gemini-3.1-pro", cursorFastFlag: true }); + + await handlers.get("session_start")({}, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", undefined); + expect(getEffectiveFastForModelId("gemini-3.1-pro")).toBeUndefined(); + expect(pi.appendEntry).not.toHaveBeenCalled(); + }); + + it("clears Cursor status for non-cursor models", async () => { + const { ctx, handlers } = createHarness({ provider: "anthropic", modelId: "claude-sonnet-4-5" }); + + await handlers.get("session_start")({}, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", undefined); + }); +}); diff --git a/test/index.test.ts b/test/index.test.ts index 23cee90..313b46a 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,11 +1,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -// Mock model-discovery before importing the extension vi.mock("../src/model-discovery.js", () => ({ discoverModels: vi.fn(), })); -// Mock cursor-provider before importing the extension vi.mock("../src/cursor-provider.js", () => ({ streamCursor: vi.fn(), })); @@ -23,6 +21,11 @@ function createMockPi() { registerProvider: vi.fn((name: string, config: Record) => { registered.push({ name, config }); }), + registerFlag: vi.fn(), + registerCommand: vi.fn(), + on: vi.fn(), + getFlag: vi.fn().mockReturnValue(false), + appendEntry: vi.fn(), _registered: registered, }; } @@ -32,10 +35,10 @@ describe("extension factory", () => { vi.clearAllMocks(); }); - it("registers one provider with correct fields", async () => { + it("registers Cursor fast controls and one provider with correct fields", async () => { const mockModels = [ { - id: "composer-2:fast=true", + id: "composer-2", name: "Cursor Composer 2", reasoning: false, input: ["text", "image"], @@ -49,6 +52,16 @@ describe("extension factory", () => { const pi = createMockPi(); await extensionFactory(pi as any); + expect(pi.registerFlag).toHaveBeenCalledWith( + "cursor-fast", + expect.objectContaining({ type: "boolean", default: false }), + ); + expect(pi.registerCommand).toHaveBeenCalledWith( + "cursor-fast", + expect.objectContaining({ description: expect.stringContaining("Toggle Cursor fast") }), + ); + expect(pi.on).toHaveBeenCalledWith("session_start", expect.any(Function)); + expect(pi.on).toHaveBeenCalledWith("model_select", expect.any(Function)); expect(mockedDiscover).toHaveBeenCalledOnce(); expect(pi.registerProvider).toHaveBeenCalledOnce(); @@ -64,7 +77,7 @@ describe("extension factory", () => { it("registers provider even with fallback models", async () => { mockedDiscover.mockResolvedValueOnce([ { - id: "composer-2:fast=true", + id: "composer-2", name: "Cursor Composer 2", reasoning: false, input: ["text", "image"], @@ -73,12 +86,12 @@ describe("extension factory", () => { maxTokens: 16384, }, { - id: "gpt-5.5:context=1m;reasoning=medium;fast=false", - name: "GPT-5.5", + id: "gpt-5.5@1m", + name: "GPT-5.5 @ 1m", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 128000, + contextWindow: 1000000, maxTokens: 16384, }, ]); diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index fc44d23..16ac6e4 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -1,8 +1,12 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { discoverModels, encodeModelId, decodeModelSelection } from "../src/model-discovery.js"; -import type { ModelParameterValue } from "@cursor/sdk"; +import { + discoverModels, + buildCursorModelSelection, + getCursorModelMetadata, + getCursorModelMetadataEntries, + __testUtils, +} from "../src/model-discovery.js"; -// Mock @cursor/sdk vi.mock("@cursor/sdk", () => ({ Cursor: { models: { @@ -12,88 +16,13 @@ vi.mock("@cursor/sdk", () => ({ })); import { Cursor } from "@cursor/sdk"; +import type { ModelListItem } from "@cursor/sdk"; const mockedList = vi.mocked(Cursor.models.list); -describe("encodeModelId", () => { - it("returns base id when params are empty", () => { - expect(encodeModelId("gemini-3.1-pro", [])).toBe("gemini-3.1-pro"); - }); - - it("encodes single param", () => { - const params: ModelParameterValue[] = [{ id: "fast", value: "true" }]; - expect(encodeModelId("composer-2", params)).toBe("composer-2:fast=true"); - }); - - it("encodes multiple params sorted by id", () => { - const params: ModelParameterValue[] = [ - { id: "reasoning", value: "medium" }, - { id: "fast", value: "false" }, - { id: "context", value: "1m" }, - ]; - expect(encodeModelId("gpt-5.4", params)).toBe( - "gpt-5.4:context=1m;fast=false;reasoning=medium", - ); - }); - - it("handles param with empty value", () => { - const params: ModelParameterValue[] = [{ id: "mode", value: "" }]; - expect(encodeModelId("test-model", params)).toBe("test-model:mode="); - }); -}); - -describe("decodeModelSelection", () => { - it("decodes plain id with no colon", () => { - const result = decodeModelSelection("gemini-3.1-pro"); - expect(result).toEqual({ id: "gemini-3.1-pro" }); - }); - - it("decodes id with params", () => { - const result = decodeModelSelection("composer-2:fast=true"); - expect(result).toEqual({ - id: "composer-2", - params: [{ id: "fast", value: "true" }], - }); - }); - - it("decodes id with multiple params", () => { - const result = decodeModelSelection("gpt-5.4:context=1m;fast=false;reasoning=medium"); - expect(result).toEqual({ - id: "gpt-5.4", - params: [ - { id: "context", value: "1m" }, - { id: "fast", value: "false" }, - { id: "reasoning", value: "medium" }, - ], - }); - }); - - it("handles trailing colon with no params", () => { - const result = decodeModelSelection("model-id:"); - expect(result).toEqual({ id: "model-id" }); - }); - - it("round-trips through encode then decode", () => { - const params: ModelParameterValue[] = [ - { id: "reasoning", value: "high" }, - { id: "fast", value: "true" }, - ]; - const encoded = encodeModelId("gpt-5.2", params); - const decoded = decodeModelSelection(encoded); - // Params come back in sorted order from encode, decode preserves that order - expect(decoded.id).toBe("gpt-5.2"); - expect(decoded.params).toEqual([ - { id: "fast", value: "true" }, - { id: "reasoning", value: "high" }, - ]); - }); - - it("round-trips plain id with no params", () => { - const encoded = encodeModelId("gemini-3.1-pro", []); - const decoded = decodeModelSelection(encoded); - expect(decoded).toEqual({ id: "gemini-3.1-pro" }); - }); -}); +function register(items: ModelListItem[]) { + return __testUtils.registerModelItems(items); +} describe("discoverModels", () => { const originalEnv = process.env; @@ -107,53 +36,87 @@ describe("discoverModels", () => { vi.clearAllMocks(); }); - it("returns fallback models when no API key", async () => { + it("returns context-qualified fallback models when no API key", async () => { delete process.env.CURSOR_API_KEY; const models = await discoverModels(); - expect(models).toHaveLength(4); - expect(models[0].id).toContain("composer-2"); - expect(models[1].id).toContain("gpt-5.5"); - expect(models[2].id).toContain("claude-sonnet"); - expect(models[3].id).toContain("claude-opus"); + expect(models.map((model) => model.id)).toEqual([ + "claude-opus-4-7@1m", + "claude-opus-4-7@300k", + "claude-sonnet-4-6@1m", + "claude-sonnet-4-6@300k", + "composer-2", + "gpt-5.5@1m", + "gpt-5.5@272k", + ]); expect(mockedList).not.toHaveBeenCalled(); }); it("returns fallback models when API key is whitespace", async () => { process.env.CURSOR_API_KEY = " "; const models = await discoverModels(); - expect(models).toHaveLength(4); - expect(models[0].id).toContain("composer-2"); + expect(models.some((model) => model.id === "gpt-5.5@1m")).toBe(true); + expect(mockedList).not.toHaveBeenCalled(); }); - it("calls Cursor.models.list with API key", async () => { + it("calls Cursor.models.list with API key and sorts by base id", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ - { - id: "model-a", - displayName: "Model A", - variants: [{ params: [], displayName: "Model A", isDefault: true }], - }, { id: "model-b", displayName: "Model B", variants: [{ params: [], displayName: "Model B", isDefault: true }], }, + { + id: "model-a", + displayName: "Model A", + variants: [{ params: [], displayName: "Model A", isDefault: true }], + }, ]); const models = await discoverModels(); expect(mockedList).toHaveBeenCalledWith({ apiKey: "test-key-123" }); - expect(models).toHaveLength(2); - expect(models[0].id).toBe("model-a"); + expect(models.map((model) => model.id)).toEqual(["model-a", "model-b"]); expect(models[0].name).toBe("Model A"); - expect(models[1].id).toBe("model-b"); }); - it("encodes default variant params into model id", async () => { + it("sorts by base id while preserving Cursor SDK context value order inside each model", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "z-model", + displayName: "Z Model", + parameters: [{ id: "context", displayName: "Context", values: [{ value: "long" }, { value: "short" }] }], + variants: [{ params: [{ id: "context", value: "short" }], displayName: "Z Model", isDefault: true }], + }, + { + id: "a-model", + displayName: "A Model", + parameters: [{ id: "context", displayName: "Context", values: [{ value: "300k" }, { value: "1m" }] }], + variants: [{ params: [{ id: "context", value: "1m" }], displayName: "A Model", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(models.map((model) => model.id)).toEqual(["a-model@300k", "a-model@1m", "z-model@long", "z-model@short"]); + expect(getCursorModelMetadata("a-model@300k")?.defaultParams).toEqual([{ id: "context", value: "300k" }]); + expect(getCursorModelMetadata("z-model@long")?.defaultParams).toEqual([{ id: "context", value: "long" }]); + }); + + it("registers one pi model per Cursor context value", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ { id: "gpt-5.4", displayName: "GPT-5.4", - parameters: [{ id: "reasoning", displayName: "Reasoning", values: [{ value: "medium" }] }], + parameters: [ + { id: "context", displayName: "Context", values: [{ value: "272k" }, { value: "1m" }] }, + { + id: "reasoning", + displayName: "Reasoning", + values: [{ value: "none" }, { value: "medium" }], + }, + { id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] }, + ], variants: [ { params: [ @@ -168,48 +131,67 @@ describe("discoverModels", () => { }, ]); const models = await discoverModels(); - expect(models[0].id).toBe("gpt-5.4:context=1m;fast=false;reasoning=medium"); - expect(models[0].name).toBe("GPT-5.4"); + expect(models.map((model) => model.id)).toEqual(["gpt-5.4@272k", "gpt-5.4@1m"]); + expect(models[0].contextWindow).toBe(272000); + expect(models[1].contextWindow).toBe(1000000); + expect(models[0].name).toBe("GPT-5.4 @ 272k"); + + const metadata = getCursorModelMetadata("gpt-5.4@272k"); + expect(metadata).toMatchObject({ + baseModelId: "gpt-5.4", + context: "272k", + supportsFast: true, + defaultFast: false, + }); + expect(metadata?.defaultParams).toEqual([ + { id: "context", value: "272k" }, + { id: "reasoning", value: "medium" }, + { id: "fast", value: "false" }, + ]); }); - it("sets reasoning true for models with thinking parameter", async () => { + it("does not encode reasoning, effort, thinking, or fast into pi model IDs", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ { - id: "claude-opus-4-6", - displayName: "Opus 4.6", - parameters: [{ id: "thinking", displayName: "Thinking", values: [{ value: "true" }] }], + id: "gpt-5.3-codex", + displayName: "GPT-5.3 Codex", + parameters: [ + { id: "reasoning", displayName: "Reasoning", values: [{ value: "high" }] }, + { id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] }, + ], variants: [ { - params: [{ id: "thinking", value: "true" }], - displayName: "Opus 4.6", + params: [ + { id: "reasoning", value: "high" }, + { id: "fast", value: "true" }, + ], + displayName: "GPT-5.3 Codex", isDefault: true, }, ], }, ]); const models = await discoverModels(); - expect(models[0].reasoning).toBe(true); + expect(models[0].id).toBe("gpt-5.3-codex"); + expect(getCursorModelMetadata("gpt-5.3-codex")?.defaultParams).toEqual([ + { id: "reasoning", value: "high" }, + { id: "fast", value: "true" }, + ]); }); - it("sets reasoning true for models with reasoning parameter", async () => { + it("sets reasoning false for models without thinking controls", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ { - id: "gpt-5.2", - displayName: "GPT-5.2", - parameters: [{ id: "reasoning", displayName: "Reasoning", values: [{ value: "high" }] }], - variants: [ - { - params: [{ id: "reasoning", value: "high" }], - displayName: "GPT-5.2", - isDefault: true, - }, - ], + id: "gemini-3.1-pro", + displayName: "Gemini 3.1 Pro", + variants: [{ params: [], displayName: "Gemini 3.1 Pro", isDefault: true }], }, ]); const models = await discoverModels(); - expect(models[0].reasoning).toBe(true); + expect(models[0].reasoning).toBe(false); + expect(models[0].thinkingLevelMap).toBeUndefined(); }); it("maps Cursor reasoning values to pi thinking levels", async () => { @@ -222,7 +204,14 @@ describe("discoverModels", () => { { id: "reasoning", displayName: "Reasoning", - values: [{ value: "low" }, { value: "medium" }, { value: "high" }], + values: [ + { value: "none" }, + { value: "minimal" }, + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "extra-high" }, + ], }, ], variants: [ @@ -236,21 +225,21 @@ describe("discoverModels", () => { ]); const models = await discoverModels(); expect(models[0].thinkingLevelMap).toEqual({ - off: null, - minimal: null, + off: "none", + minimal: "minimal", low: "low", medium: "medium", high: "high", - xhigh: null, + xhigh: "extra-high", }); }); - it("maps boolean Cursor thinking values to off and high", async () => { + it("maps boolean Cursor thinking values to off and high with explicit unsupported nulls", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ { - id: "claude-opus-4-6", - displayName: "Opus 4.6", + id: "claude-haiku-4-5", + displayName: "Haiku 4.5", parameters: [ { id: "thinking", @@ -261,23 +250,24 @@ describe("discoverModels", () => { variants: [ { params: [{ id: "thinking", value: "true" }], - displayName: "Opus 4.6", + displayName: "Haiku 4.5", isDefault: true, }, ], }, ]); const models = await discoverModels(); - expect(models[0].thinkingLevelMap).toMatchObject({ + expect(models[0].thinkingLevelMap).toEqual({ off: "false", minimal: null, low: null, medium: null, high: "true", + xhigh: null, }); }); - it("maps Claude effort max to pi xhigh and parses 1m context", async () => { + it("maps Claude effort and prefers exact xhigh over max and extra-high", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ { @@ -295,6 +285,7 @@ describe("discoverModels", () => { { value: "high" }, { value: "xhigh" }, { value: "max" }, + { value: "extra-high" }, ], }, ], @@ -312,89 +303,147 @@ describe("discoverModels", () => { }, ]); const models = await discoverModels(); - expect(models[0].id).toBe("claude-opus-4-7:context=1m;effort=xhigh;thinking=true"); - expect(models[0].contextWindow).toBe(1000000); + expect(models.map((model) => model.id)).toEqual(["claude-opus-4-7@300k", "claude-opus-4-7@1m"]); + expect(models[0].contextWindow).toBe(300000); + expect(models[1].contextWindow).toBe(1000000); expect(models[0].thinkingLevelMap).toEqual({ off: "false", minimal: null, low: "low", medium: "medium", high: "high", - xhigh: "max", + xhigh: "xhigh", }); }); - it("maps extra-high reasoning values to pi xhigh and parses 272k context", async () => { + it("registers text and image input for Cursor models", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ { - id: "gpt-5.4", - displayName: "GPT-5.4", + id: "vision-capable", + displayName: "Vision Capable", + variants: [{ params: [], displayName: "Vision Capable", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(models[0].input).toEqual(["text", "image"]); + }); + + it("maps reasoning off to unsupported null when Cursor exposes no none or off value", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "reasoning-only", + displayName: "Reasoning Only", parameters: [ - { id: "context", displayName: "Context", values: [{ value: "272k" }, { value: "1m" }] }, { id: "reasoning", displayName: "Reasoning", - values: [ - { value: "none" }, - { value: "low" }, - { value: "medium" }, - { value: "high" }, - { value: "extra-high" }, - ], + values: [{ value: "low" }, { value: "medium" }, { value: "high" }], }, ], + variants: [{ params: [{ id: "reasoning", value: "medium" }], displayName: "Reasoning Only", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(models[0].thinkingLevelMap).toEqual({ + off: null, + minimal: null, + low: "low", + medium: "medium", + high: "high", + xhigh: null, + }); + expect(buildCursorModelSelection("reasoning-only", "off")).toEqual({ + id: "reasoning-only", + params: [{ id: "reasoning", value: "medium" }], + }); + }); + + it("maps boolean thinking plus effort to thinking=true with effort and off to thinking=false without effort", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "claude-like", + displayName: "Claude Like", + parameters: [ + { id: "thinking", displayName: "Thinking", values: [{ value: "false" }, { value: "true" }] }, + { id: "effort", displayName: "Effort", values: [{ value: "low" }, { value: "medium" }, { value: "high" }] }, + ], variants: [ { params: [ - { id: "context", value: "272k" }, - { id: "reasoning", value: "medium" }, + { id: "thinking", value: "true" }, + { id: "effort", value: "medium" }, ], - displayName: "GPT-5.4", + displayName: "Claude Like", isDefault: true, }, ], }, ]); + const models = await discoverModels(); - expect(models[0].contextWindow).toBe(272000); - expect(models[0].thinkingLevelMap?.off).toBe("none"); - expect(models[0].thinkingLevelMap?.xhigh).toBe("extra-high"); + + expect(models[0].thinkingLevelMap).toEqual({ + off: "false", + minimal: null, + low: "low", + medium: "medium", + high: "high", + xhigh: null, + }); + expect(buildCursorModelSelection("claude-like", "high")).toEqual({ + id: "claude-like", + params: [ + { id: "thinking", value: "true" }, + { id: "effort", value: "high" }, + ], + }); + expect(buildCursorModelSelection("claude-like", "off")).toEqual({ + id: "claude-like", + params: [{ id: "thinking", value: "false" }], + }); }); - it("sets reasoning false for models without thinking or reasoning params", async () => { - process.env.CURSOR_API_KEY = "test-key-123"; - mockedList.mockResolvedValueOnce([ - { - id: "gemini-3.1-pro", - displayName: "Gemini 3.1 Pro", - variants: [{ params: [], displayName: "Gemini 3.1 Pro", isDefault: true }], - }, - ]); + it("keeps fallback model IDs aligned with the documented fallback list", async () => { + delete process.env.CURSOR_API_KEY; + const models = await discoverModels(); - expect(models[0].reasoning).toBe(false); + + expect(models.map((model) => model.id)).toEqual([ + "claude-opus-4-7@1m", + "claude-opus-4-7@300k", + "claude-sonnet-4-6@1m", + "claude-sonnet-4-6@300k", + "composer-2", + "gpt-5.5@1m", + "gpt-5.5@272k", + ]); }); it("falls back when Cursor.models.list throws", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockRejectedValueOnce(new Error("network error")); const models = await discoverModels(); - expect(models).toHaveLength(4); - expect(models[0].id).toContain("composer-2"); + expect(models.some((model) => model.id === "composer-2")).toBe(true); }); it("falls back when Cursor.models.list returns empty", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([]); const models = await discoverModels(); - expect(models).toHaveLength(4); - expect(models[0].id).toContain("composer-2"); + expect(models.some((model) => model.id === "claude-opus-4-7@1m")).toBe(true); }); it("uses id as name when displayName is missing", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ - { id: "raw-id", variants: [{ params: [], displayName: "raw-id", isDefault: true }] }, + { id: "raw-id", variants: [{ params: [], displayName: "raw-id", isDefault: true }] } as ModelListItem, ]); const models = await discoverModels(); expect(models[0].name).toBe("raw-id"); @@ -406,6 +455,7 @@ describe("discoverModels", () => { { id: "test-model", displayName: "Test Model", + parameters: [{ id: "reasoning", displayName: "Reasoning", values: [{ value: "low" }, { value: "high" }] }], variants: [ { params: [{ id: "reasoning", value: "low" }], displayName: "Test Model" }, { params: [{ id: "reasoning", value: "high" }], displayName: "Test Model" }, @@ -413,15 +463,126 @@ describe("discoverModels", () => { }, ]); const models = await discoverModels(); - expect(models[0].id).toBe("test-model:reasoning=low"); + expect(models[0].id).toBe("test-model"); + expect(buildCursorModelSelection("test-model", "off")).toEqual({ + id: "test-model", + params: [{ id: "reasoning", value: "low" }], + }); }); +}); - it("handles model with no variants array", async () => { - process.env.CURSOR_API_KEY = "test-key-123"; - mockedList.mockResolvedValueOnce([ - { id: "bare-model", displayName: "Bare Model" }, +describe("buildCursorModelSelection", () => { + beforeEach(() => { + register([ + { + id: "gpt-5.4", + displayName: "GPT-5.4", + parameters: [ + { id: "context", displayName: "Context", values: [{ value: "1m" }, { value: "272k" }] }, + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { value: "none" }, + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "extra-high" }, + ], + }, + { id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] }, + ], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "reasoning", value: "medium" }, + { id: "fast", value: "false" }, + ], + displayName: "GPT-5.4", + isDefault: true, + }, + ], + }, + { + id: "claude-opus-4-7", + displayName: "Opus 4.7", + parameters: [ + { id: "thinking", displayName: "Thinking", values: [{ value: "false" }, { value: "true" }] }, + { id: "context", displayName: "Context", values: [{ value: "1m" }, { value: "300k" }] }, + { + id: "effort", + displayName: "Effort", + values: [ + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "xhigh" }, + ], + }, + ], + variants: [ + { + params: [ + { id: "thinking", value: "true" }, + { id: "context", value: "1m" }, + { id: "effort", value: "xhigh" }, + ], + displayName: "Opus 4.7", + isDefault: true, + }, + ], + }, ]); - const models = await discoverModels(); - expect(models[0].id).toBe("bare-model"); + }); + + it("uses selected context, pi thinking, and fast state", () => { + expect(buildCursorModelSelection("gpt-5.4@272k", "xhigh", true)).toEqual({ + id: "gpt-5.4", + params: [ + { id: "context", value: "272k" }, + { id: "reasoning", value: "extra-high" }, + { id: "fast", value: "true" }, + ], + }); + }); + + it("turns Claude thinking off and omits effort when pi thinking is off", () => { + expect(buildCursorModelSelection("claude-opus-4-7@300k", "off")).toEqual({ + id: "claude-opus-4-7", + params: [ + { id: "thinking", value: "false" }, + { id: "context", value: "300k" }, + ], + }); + }); + + it("turns Claude thinking on and maps effort when pi thinking is enabled", () => { + expect(buildCursorModelSelection("claude-opus-4-7@1m", "high")).toEqual({ + id: "claude-opus-4-7", + params: [ + { id: "thinking", value: "true" }, + { id: "context", value: "1m" }, + { id: "effort", value: "high" }, + ], + }); + }); + + it("passes unknown model IDs through plainly", () => { + expect(buildCursorModelSelection("gemini-3.1-pro", "off")).toEqual({ id: "gemini-3.1-pro" }); + }); + + it("returns cloned metadata entries", () => { + const entries = getCursorModelMetadataEntries(); + const metadata = entries.find((entry) => entry.piModelId === "gpt-5.4@1m"); + expect(metadata?.defaultParams).toEqual([ + { id: "context", value: "1m" }, + { id: "reasoning", value: "medium" }, + { id: "fast", value: "false" }, + ]); + metadata!.defaultParams[0].value = "mutated"; + metadata!.thinkingLevelMap!.medium = "mutated"; + expect(getCursorModelMetadata("gpt-5.4@1m")?.defaultParams[0].value).toBe("1m"); + expect(getCursorModelMetadata("gpt-5.4@1m")?.thinkingLevelMap?.medium).toBe("medium"); }); }); -- 2.51.2 From 1d1633b527478e36ae11504db8c07c505ada8152 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 4 May 2026 12:22:37 -0600 Subject: [PATCH 003/359] Enhance README and UX documentation for Cursor models - Added clarification in README.md regarding why some Cursor models show `thinking=no`, explaining the distinction between pi-controllable thinking and internal reasoning. - Updated cursor-model-ux-spec.md to refine the definitions of `reasoning` and its implications for pi's thinking UI, ensuring accurate representation of model capabilities. - Emphasized the importance of not marking models as `reasoning: true` solely based on internal reasoning capabilities. --- README.md | 6 ++++++ docs/cursor-model-ux-spec.md | 11 +++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8aed785..e573e2c 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,12 @@ The extension builds Cursor SDK params from the selected pi thinking level: For Claude models with both `thinking` and `effort`, pi thinking `off` sends `thinking=false` and omits `effort`. +### Why some Cursor models show `thinking=no` + +In `pi --list-models`, the `thinking` column means pi can control the model's thinking level with `--thinking`, a final `:medium` model suffix, or shift+tab. + +Some Cursor models can still reason internally, and Cursor may still emit thinking deltas for them, even when Cursor does not expose a `reasoning`, `effort`, or `thinking` parameter for the extension to set. Those models show `thinking=no` because there is no pi-controllable Cursor SDK parameter. The extension does not disable Cursor's own default/internal reasoning behavior. + ## Fast mode Use `/cursor-fast` to toggle fast mode for the selected Cursor model when supported. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 389e478..274bd9c 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -64,7 +64,7 @@ Pi model metadata is also a source of truth for pi-native behavior: - `ProviderModelConfig.id` - `ProviderModelConfig.name` -- `ProviderModelConfig.reasoning` +- `ProviderModelConfig.reasoning`: means pi-controllable thinking, not whether a Cursor model can reason internally - `ProviderModelConfig.thinkingLevelMap` - `ProviderModelConfig.contextWindow` - `ProviderModelConfig.maxTokens` @@ -163,7 +163,7 @@ Each registered model must set: - `id`: context-qualified pi model ID when needed. - `name`: human-readable Cursor display name plus context when useful. -- `reasoning`: `true` only if a Cursor `reasoning`, `effort`, or `thinking` parameter can map to pi thinking. +- `reasoning`: `true` only if a Cursor `reasoning`, `effort`, or `thinking` parameter can map to pi thinking. This controls pi's thinking UI and `pi --list-models` `thinking` column; it must not be used to claim whether the Cursor model can reason internally. - `thinkingLevelMap`: model-specific pi-to-Cursor mapping for pi UI, clamping, persistence, and footer display. - `contextWindow`: parsed from context variant, else conservative fallback. - `maxTokens`: conservative explicit value until Cursor SDK exposes output limits. @@ -215,6 +215,13 @@ Do not add a context-cycle shortcut in the first pass. Context is a pi model var ## Thinking / Reasoning / Effort Mapping +Important distinction: + +- **Pi-controllable thinking** means Cursor exposes a `reasoning`, `effort`, or `thinking` parameter that the extension can set from pi's native thinking level. These models register `reasoning: true` and show `thinking=yes` in `pi --list-models`. +- **Cursor internal reasoning** means the model may reason by default, and Cursor may emit thinking deltas, but Cursor does not expose a user-controllable thinking parameter. These models register `reasoning: false` and show `thinking=no` in `pi --list-models` because pi cannot control a level for them. The extension still parses Cursor `thinking-delta` events if they are emitted. + +Do not mark a model `reasoning: true` only because it can reason internally. That would make pi show controls such as `--thinking`, `:medium`, and shift+tab even though the extension cannot translate them into Cursor SDK params. + Pi levels: ```text -- 2.51.2 From 182818685b328e9b7f482d80a30402b6a745ed08 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 4 May 2026 14:33:17 -0600 Subject: [PATCH 004/359] Enhance context handling and tool activity tracking in Cursor models - Updated README.md to clarify context parameter handling for Cursor models and the implications of Max Mode context windows. - Introduced a new bundled context windows file to manage default context values for models lacking a catalog context parameter. - Implemented caching for context windows, allowing user overrides and improved model discovery logic to utilize cached values. - Enhanced cursor-provider.ts to buffer and format tool call activities, ensuring they are surfaced as trace content without polluting final text outputs. - Added tests to verify the correct handling of context windows and tool activity in various scenarios. --- README.md | 8 +- docs/cursor-model-ux-spec.md | 4 + src/bundled-context-windows.ts | 27 ++++ src/context-window-cache.ts | 63 ++++++++++ src/context.ts | 28 +++-- src/cursor-provider.ts | 223 +++++++++++++++++++++++---------- src/cursor-state.ts | 26 +++- src/model-discovery.ts | 15 ++- test/context.test.ts | 71 ++++++++++- test/cursor-provider.test.ts | 212 ++++++++++++++++++++++++++++++- test/cursor-state.test.ts | 15 +++ test/model-discovery.test.ts | 51 ++++++++ 12 files changed, 657 insertions(+), 86 deletions(-) create mode 100644 src/bundled-context-windows.ts create mode 100644 src/context-window-cache.ts diff --git a/README.md b/README.md index e573e2c..5862d44 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,10 @@ pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." Cursor-only parameters are not encoded into pi model IDs. +For models where `Cursor.models.list()` exposes a `context` parameter, the extension parses that context value directly. For models where the catalog does not include a context parameter, the extension ships a bundled SDK-derived default/non-Max context-window cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. + +Max Mode has larger context windows, but `@cursor/sdk` 1.0.12 does not expose a public `ModelSelection` field that enables Max Mode for these local agent runs. The extension therefore does not advertise Max Mode windows for non-Max model IDs. Add Max-specific pi model IDs only when the SDK exposes an exact Max Mode selector and the implementation uses that selector. + Examples: - `cursor/composer-2` @@ -135,9 +139,11 @@ If `CURSOR_API_KEY` is missing or model discovery fails, the extension registers ## Limits - local agents only; no Cursor cloud agent support -- Cursor tool calls are not exposed as pi tool calls +- Cursor tool calls are not exposed as pi tool calls; Cursor-side tool activity is surfaced as compact trace text before the final answer - pi tool schemas are not passed through to Cursor +- Cursor text deltas are buffered until Cursor trace/tool activity is complete so the final answer does not appear before its trace - one fresh Cursor agent per provider call +- ambient Cursor setting/rule layers are not loaded by default because the current Cursor SDK writes setting-load logs directly to terminal output, which corrupts pi's TUI - Cursor SDK model metadata does not currently expose output token limits, so the extension uses conservative token defaults ## Development diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 274bd9c..f25d769 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -13,6 +13,10 @@ Current implementation notes: - Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`. - `@cursor/sdk` is a package dependency of this extension; users should not need a global SDK install. - Cursor auth uses the `CURSOR_API_KEY` environment variable. The extension config file stores only non-secret Cursor-only state such as fast defaults. +- Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. +- Cursor-side thinking and tool activity are surfaced as trace content before final text. Cursor text deltas are buffered until the run finishes so the saved message order is trace first, final answer second. +- For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. +- Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. ## Goal diff --git a/src/bundled-context-windows.ts b/src/bundled-context-windows.ts new file mode 100644 index 0000000..66082bf --- /dev/null +++ b/src/bundled-context-windows.ts @@ -0,0 +1,27 @@ +// Generated from Cursor SDK checkpoint tokenDetails.maxTokens on 2026-05-04. +// These are default/non-Max-mode SDK context windows for Cursor models that do not +// expose a catalog `context` parameter. Do not replace them with Max Mode values +// unless the Cursor SDK exposes an exact Max Mode model selection and the extension +// uses that selection for matching pi model IDs. +export const BUNDLED_CONTEXT_WINDOWS = { + "claude-haiku-4-5": 200000, + "claude-opus-4-5": 200000, + "composer-1.5": 200000, + "composer-2": 200000, + default: 200000, + "gemini-2.5-flash": 200000, + "gemini-3-flash": 200000, + "gemini-3.1-pro": 200000, + "gpt-5-mini": 272000, + "gpt-5.1": 272000, + "gpt-5.1-codex-max": 272000, + "gpt-5.1-codex-mini": 272000, + "gpt-5.2": 272000, + "gpt-5.2-codex": 272000, + "gpt-5.3-codex": 272000, + "gpt-5.3-codex-spark": 128000, + "gpt-5.4-mini": 272000, + "gpt-5.4-nano": 272000, + "grok-4-20": 200000, + "kimi-k2.5": 262000, +} as const satisfies Record; diff --git a/src/context-window-cache.ts b/src/context-window-cache.ts new file mode 100644 index 0000000..ee44eea --- /dev/null +++ b/src/context-window-cache.ts @@ -0,0 +1,63 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { getAgentDir } from "@mariozechner/pi-coding-agent"; +import { BUNDLED_CONTEXT_WINDOWS } from "./bundled-context-windows.js"; + +const CONTEXT_WINDOW_CACHE_FILE = "cursor-sdk-context-windows.json"; + +interface ContextWindowCacheFile { + contextWindows?: Record; +} + +function getCachePath(): string { + return join(getAgentDir(), CONTEXT_WINDOW_CACHE_FILE); +} + +function isPositiveInteger(value: unknown): value is number { + return typeof value === "number" && Number.isInteger(value) && value > 0; +} + +export function loadContextWindowCache(): Map { + const cache = new Map(Object.entries(BUNDLED_CONTEXT_WINDOWS)); + const path = getCachePath(); + if (!existsSync(path)) return cache; + try { + const parsed = JSON.parse(readFileSync(path, "utf-8")) as ContextWindowCacheFile; + for (const [modelId, contextWindow] of Object.entries(parsed.contextWindows ?? {})) { + if (isPositiveInteger(contextWindow)) cache.set(modelId, contextWindow); + } + } catch { + return cache; + } + return cache; +} + +export function getCachedContextWindow(modelId: string): number | undefined { + return loadContextWindowCache().get(modelId); +} + +export function getCheckpointContextWindow(checkpoint: unknown): number | undefined { + if (checkpoint === null || typeof checkpoint !== "object") return undefined; + const tokenDetails = (checkpoint as Record).tokenDetails; + if (tokenDetails === null || typeof tokenDetails !== "object") return undefined; + const maxTokens = (tokenDetails as Record).maxTokens; + if (!isPositiveInteger(maxTokens)) return undefined; + return maxTokens; +} + +export function saveCachedContextWindow(modelId: string, contextWindow: number): void { + if (!isPositiveInteger(contextWindow)) return; + const cache = loadContextWindowCache(); + if (cache.get(modelId) === contextWindow) return; + cache.set(modelId, contextWindow); + const path = getCachePath(); + mkdirSync(dirname(path), { recursive: true }); + const data: ContextWindowCacheFile = { + contextWindows: Object.fromEntries([...cache.entries()].sort(([a], [b]) => a.localeCompare(b))), + }; + writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 }); +} + +export const __testUtils = { + getCachePath, +}; diff --git a/src/context.ts b/src/context.ts index aaadd06..7da6f23 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,4 +1,4 @@ -import type { Context, Message } from "@mariozechner/pi-ai"; +import type { Context, Message, ToolCall } from "@mariozechner/pi-ai"; import type { SDKImage } from "@cursor/sdk"; export interface CursorPrompt { @@ -14,6 +14,10 @@ function isImageBlock(block: { type: string }): block is { type: "image"; data: return block.type === "image"; } +function isToolCallBlock(block: { type: string }): block is ToolCall { + return block.type === "toolCall"; +} + function extractLatestImages(messages: Message[]): SDKImage[] { // Find the last user message and extract images only from it for (let i = messages.length - 1; i >= 0; i--) { @@ -32,7 +36,7 @@ function extractLatestImages(messages: Message[]): SDKImage[] { return []; } -function formatUserContent(content: string | { type: string; text?: string; data?: string; mimeType?: string }[]): string { +function formatContentBlocks(content: string | { type: string; text?: string; data?: string; mimeType?: string }[]): string { if (typeof content === "string") return content; return content .map((block) => { @@ -44,6 +48,11 @@ function formatUserContent(content: string | { type: string; text?: string; data .join("\n"); } +function formatToolCall(toolCall: ToolCall): string { + const args = JSON.stringify(toolCall.arguments); + return `Tool call (${toolCall.name}, call ${toolCall.id}): ${args}`; +} + export function buildCursorPrompt(context: Context): CursorPrompt { const parts: string[] = []; @@ -54,15 +63,19 @@ export function buildCursorPrompt(context: Context): CursorPrompt { for (const msg of context.messages) { switch (msg.role) { case "user": { - const text = formatUserContent(msg.content); + const text = formatContentBlocks(msg.content); if (text) parts.push(`User: ${text}`); break; } case "assistant": { - const blocks = typeof msg.content === "string" ? [{ type: "text" as const, text: msg.content }] : msg.content; + const blocks = Array.isArray(msg.content) ? msg.content : [{ type: "text" as const, text: String(msg.content) }]; const textParts: string[] = []; for (const block of blocks) { - if (isTextBlock(block)) textParts.push(block.text); + if (isTextBlock(block)) { + textParts.push(block.text); + } else if (isToolCallBlock(block)) { + textParts.push(formatToolCall(block)); + } // Omit thinking content from transcript } if (textParts.length > 0) { @@ -71,8 +84,9 @@ export function buildCursorPrompt(context: Context): CursorPrompt { break; } case "toolResult": { - const text = formatUserContent(msg.content); - parts.push(`Tool result (${msg.toolName}): ${text}`); + const text = formatContentBlocks(msg.content); + const label = msg.isError ? "Tool error" : "Tool result"; + parts.push(`${label} (${msg.toolName}, call ${msg.toolCallId}): ${text}`); break; } } diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 6fce68d..bb59d6c 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -7,11 +7,12 @@ import { type SimpleStreamOptions, type AssistantMessage, } from "@mariozechner/pi-ai"; -import { Agent } from "@cursor/sdk"; +import { Agent, createAgentPlatform } from "@cursor/sdk"; import type { InteractionUpdate, SDKAgent } from "@cursor/sdk"; import { buildCursorPrompt } from "./context.js"; import { getEffectiveFastForModelId } from "./cursor-state.js"; import { buildCursorModelSelection } from "./model-discovery.js"; +import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js"; function makeInitialMessage(model: Model): AssistantMessage { return { @@ -33,12 +34,58 @@ function makeInitialMessage(model: Model): AssistantMessage { }; } +class CursorAbortError extends Error { + constructor() { + super("aborted"); + this.name = "CursorAbortError"; + } +} + function sanitizeError(error: unknown): string { if (error instanceof Error) return error.message; if (typeof error === "string") return error; return "Unknown error"; } +function getObjectField(value: unknown, field: string): unknown { + if (!value || typeof value !== "object") return undefined; + return (value as Record)[field]; +} + +function getCursorToolName(toolCall: unknown): string { + if (!toolCall || typeof toolCall !== "object") return "unknown"; + const data = toolCall as Record; + if (typeof data.name === "string") return data.name; + if (typeof data.type === "string") return data.type; + return "unknown"; +} + +function getCursorToolResult(toolCall: unknown): unknown { + return getObjectField(toolCall, "result"); +} + +async function cacheSdkContextWindow(agentId: string, modelId: string): Promise { + try { + const platform = await createAgentPlatform(); + const checkpoint = await platform.checkpointStore.loadLatest(agentId); + const contextWindow = getCheckpointContextWindow(checkpoint); + if (contextWindow) saveCachedContextWindow(modelId, contextWindow); + } catch { + // Context-window cache failures must not affect response streaming. + } +} + +function summarizeCursorToolResult(result: unknown): string { + if (result === undefined) return ""; + const parts: string[] = []; + const status = getObjectField(result, "status"); + if (typeof status === "string") parts.push(status); + const value = getObjectField(result, "value"); + const exitCode = getObjectField(value, "exitCode") ?? getObjectField(result, "exitCode"); + if (typeof exitCode === "number") parts.push(`exit ${exitCode}`); + return parts.length > 0 ? `: ${parts.join(", ")}` : ""; +} + export function streamCursor( model: Model, context: Context, @@ -49,9 +96,16 @@ export function streamCursor( (async () => { const partial = makeInitialMessage(model); let agent: SDKAgent | null = null; + let abortSignal: AbortSignal | undefined; + let abortListener: (() => void) | undefined; try { + const throwIfAborted = (): void => { + if (options?.signal?.aborted) throw new CursorAbortError(); + }; + stream.push({ type: "start", partial }); + throwIfAborted(); const apiKey = options?.apiKey; if (!apiKey) throw new Error("CURSOR_API_KEY is required"); @@ -63,50 +117,75 @@ export function streamCursor( agent = await Agent.create({ apiKey, model: selection, - local: { cwd, settingSources: ["all"] }, + // Do not pass settingSources here. The Cursor SDK currently writes + // setting/rule loading INFO logs directly to process output, which corrupts pi's TUI. + local: { cwd }, }); + throwIfAborted(); const prompt = buildCursorPrompt(context); let textContentIndex = -1; let thinkingContentIndex = -1; - let hasText = false; + const textDeltas: string[] = []; + + const appendBufferedTextDelta = (text: string): void => { + textDeltas.push(text); + }; + + const appendTraceDelta = (text: string): void => { + if (thinkingContentIndex < 0) { + thinkingContentIndex = partial.content.length; + partial.content.push({ type: "thinking", thinking: "" }); + stream.push({ type: "thinking_start", contentIndex: thinkingContentIndex, partial }); + } + const block = partial.content[thinkingContentIndex]; + if (block.type === "thinking") { + block.thinking += text; + stream.push({ + type: "thinking_delta", + contentIndex: thinkingContentIndex, + delta: text, + partial, + }); + } + }; + + const appendCursorToolStatus = (text: string): void => { + appendTraceDelta(`${text}\n`); + }; + + const flushBufferedText = (fallbackText?: string): void => { + const deltas = textDeltas.length > 0 ? textDeltas : fallbackText ? [fallbackText] : []; + if (deltas.length === 0) return; + textContentIndex = partial.content.length; + partial.content.push({ type: "text", text: "" }); + stream.push({ type: "text_start", contentIndex: textContentIndex, partial }); + const block = partial.content[textContentIndex]; + if (block.type !== "text") return; + for (const delta of deltas) { + block.text += delta; + stream.push({ + type: "text_delta", + contentIndex: textContentIndex, + delta, + partial, + }); + } + stream.push({ + type: "text_end", + contentIndex: textContentIndex, + content: block.text, + partial, + }); + }; const onDelta = (args: { update: InteractionUpdate }): void => { const update = args.update; if (update.type === "text-delta") { - if (textContentIndex < 0) { - textContentIndex = partial.content.length; - partial.content.push({ type: "text", text: "" }); - stream.push({ type: "text_start", contentIndex: textContentIndex, partial }); - } - const block = partial.content[textContentIndex]; - if (block.type === "text") { - block.text += update.text; - stream.push({ - type: "text_delta", - contentIndex: textContentIndex, - delta: update.text, - partial, - }); - } - hasText = true; + appendBufferedTextDelta(update.text); } else if (update.type === "thinking-delta") { - if (thinkingContentIndex < 0) { - thinkingContentIndex = partial.content.length; - partial.content.push({ type: "thinking", thinking: "" }); - stream.push({ type: "thinking_start", contentIndex: thinkingContentIndex, partial }); - } - const block = partial.content[thinkingContentIndex]; - if (block.type === "thinking") { - block.thinking += update.text; - stream.push({ - type: "thinking_delta", - contentIndex: thinkingContentIndex, - delta: update.text, - partial, - }); - } + appendTraceDelta(update.text); } else if (update.type === "thinking-completed") { if (thinkingContentIndex >= 0) { const block = partial.content[thinkingContentIndex]; @@ -120,43 +199,50 @@ export function streamCursor( } thinkingContentIndex = -1; } + } else if (update.type === "tool-call-started") { + appendCursorToolStatus(`Cursor tool started (${getCursorToolName(update.toolCall)}, call ${update.callId})`); + } else if (update.type === "tool-call-completed") { + const suffix = summarizeCursorToolResult(getCursorToolResult(update.toolCall)); + appendCursorToolStatus(`Cursor tool completed (${getCursorToolName(update.toolCall)}, call ${update.callId})${suffix}`); + } else if (update.type === "summary") { + appendCursorToolStatus(`Cursor summary: ${update.summary}`); + } else if (update.type === "turn-ended" && update.usage) { + partial.usage.input = update.usage.inputTokens; + partial.usage.output = update.usage.outputTokens; + partial.usage.cacheRead = update.usage.cacheReadTokens; + partial.usage.cacheWrite = update.usage.cacheWriteTokens; + partial.usage.totalTokens = + update.usage.inputTokens + update.usage.outputTokens + update.usage.cacheReadTokens + update.usage.cacheWriteTokens; } - // tool-call-started, tool-call-completed, summary-*, turn-ended, - // shell-output-delta, token-delta: intentionally ignored + // partial-tool-call, summary-started, summary-completed, + // shell-output-delta, token-delta, step-* are intentionally not surfaced. }; // Handle abort signal let run: Awaited> | null = null; - const onAbort = () => { + abortListener = () => { if (run) { run.cancel().catch(() => {}); } }; - options?.signal?.addEventListener("abort", onAbort, { once: true }); + abortSignal = options?.signal; + abortSignal?.addEventListener("abort", abortListener, { once: true }); + throwIfAborted(); run = await agent.send( { text: prompt.text, images: prompt.images.length > 0 ? prompt.images : undefined }, { onDelta }, ); + if (options?.signal?.aborted) { + await run.cancel().catch(() => {}); + throw new CursorAbortError(); + } const result = await run.wait(); + await cacheSdkContextWindow(agent.agentId, model.id); - options?.signal?.removeEventListener("abort", onAbort); - - // Close open text block - if (textContentIndex >= 0) { - const block = partial.content[textContentIndex]; - if (block.type === "text") { - stream.push({ - type: "text_end", - contentIndex: textContentIndex, - content: block.text, - partial, - }); - } - } - - // Close open thinking block + // Close open thinking/trace before flushing final assistant text so saved + // message content is trace first, final answer second. if (thinkingContentIndex >= 0) { const block = partial.content[thinkingContentIndex]; if (block.type === "thinking") { @@ -167,20 +253,10 @@ export function streamCursor( partial, }); } + thinkingContentIndex = -1; } - // If no text was emitted but result has text, emit it - if (!hasText && result.result) { - textContentIndex = partial.content.length; - partial.content.push({ type: "text", text: result.result }); - stream.push({ type: "text_start", contentIndex: textContentIndex, partial }); - stream.push({ - type: "text_end", - contentIndex: textContentIndex, - content: result.result, - partial, - }); - } + flushBufferedText(result.result); if (result.status === "cancelled") { partial.stopReason = "aborted"; @@ -189,10 +265,19 @@ export function streamCursor( stream.push({ type: "done", reason: "stop", message: partial }); } } catch (error) { - partial.stopReason = "error"; - partial.errorMessage = sanitizeError(error); - stream.push({ type: "error", reason: "error", error: partial }); + if (error instanceof CursorAbortError) { + partial.stopReason = "aborted"; + stream.push({ type: "error", reason: "aborted", error: partial }); + } else { + partial.stopReason = "error"; + partial.errorMessage = sanitizeError(error); + stream.push({ type: "error", reason: "error", error: partial }); + } } finally { + if (abortSignal && abortListener) { + abortSignal.removeEventListener("abort", abortListener); + } + if (agent) { try { await agent[Symbol.asyncDispose](); diff --git a/src/cursor-state.ts b/src/cursor-state.ts index 1dec7c7..98c0f9f 100644 --- a/src/cursor-state.ts +++ b/src/cursor-state.ts @@ -92,11 +92,27 @@ function getCurrentCursorMetadata(ctx: ExtensionContext) { return getCursorModelMetadata(model.id); } +function restoreMapValue(map: Map, key: string, previous: boolean | undefined): void { + if (previous === undefined) { + map.delete(key); + } else { + map.set(key, previous); + } +} + function persistFastPreference(pi: ExtensionAPI, baseModelId: string, fast: boolean): void { + const previousSession = sessionFastPreferences.get(baseModelId); + const previousGlobal = globalFastPreferences.get(baseModelId); sessionFastPreferences.set(baseModelId, fast); globalFastPreferences.set(baseModelId, fast); + try { + saveGlobalFastPreferences(); + } catch (error) { + restoreMapValue(sessionFastPreferences, baseModelId, previousSession); + restoreMapValue(globalFastPreferences, baseModelId, previousGlobal); + throw error; + } pi.appendEntry(FAST_ENTRY_TYPE, { baseModelId, fast }); - saveGlobalFastPreferences(); } export function getEffectiveFastForModelId(modelId: string): boolean | undefined { @@ -128,7 +144,13 @@ export function registerCursorFastControls(pi: ExtensionAPI): void { const current = getEffectiveFast(metadata.baseModelId, metadata.piModelId) ?? false; const next = !current; - persistFastPreference(pi, metadata.baseModelId, next); + try { + persistFastPreference(pi, metadata.baseModelId, next); + } catch (error) { + updateCursorStatus(ctx); + ctx.ui.notify(`Failed to save Cursor fast preference: ${error instanceof Error ? error.message : String(error)}`, "error"); + return; + } updateCursorStatus(ctx); ctx.ui.notify(`Cursor fast ${next ? "enabled" : "disabled"}`, "info"); }, diff --git a/src/model-discovery.ts b/src/model-discovery.ts index eb45fb6..4527652 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -7,6 +7,7 @@ import type { } from "@cursor/sdk"; import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent"; import type { ModelThinkingLevel, ThinkingLevelMap } from "@mariozechner/pi-ai"; +import { getCachedContextWindow } from "./context-window-cache.js"; const FALLBACK_CONTEXT_WINDOW = 128000; const FALLBACK_MAX_TOKENS = 16384; @@ -289,6 +290,11 @@ function getModelName(item: ModelListItem, context?: string): string { return context ? `${displayName} @ ${context}` : displayName; } +function getContextWindow(piModelId: string, context?: string): number { + if (context) return parseContextWindow(context) ?? FALLBACK_CONTEXT_WINDOW; + return getCachedContextWindow(piModelId) ?? FALLBACK_CONTEXT_WINDOW; +} + function toMetadata( item: ModelListItem, piModelId: string, @@ -303,7 +309,7 @@ function toMetadata( displayName: item.displayName || item.id, defaultParams: cloneParams(defaultParams), ...(context ? { context } : {}), - contextWindow: context ? parseContextWindow(context) ?? FALLBACK_CONTEXT_WINDOW : FALLBACK_CONTEXT_WINDOW, + contextWindow: getContextWindow(piModelId, context), supportsFast: getParameter(item, "fast") !== undefined, defaultFast: fastValue === "true", supportsReasoning: thinkingLevelMap !== undefined, @@ -331,10 +337,13 @@ function toModelConfig(metadata: CursorModelMetadata, name: string): ProviderMod }; } +function getContextValues(item: ModelListItem): string[] { + return getParameter(item, "context")?.values.map((value) => value.value) ?? []; +} + function toModelConfigs(item: ModelListItem): ProviderModelConfig[] { const defaultParams = getDefaultParams(item); - const contextParameter = getParameter(item, "context"); - const contextValues = contextParameter?.values.map((value) => value.value) ?? []; + const contextValues = getContextValues(item); const contexts = contextValues.length > 0 ? contextValues : [undefined]; return contexts.map((context) => { diff --git a/test/context.test.ts b/test/context.test.ts index 769e489..ed39b0f 100644 --- a/test/context.test.ts +++ b/test/context.test.ts @@ -25,6 +25,25 @@ describe("buildCursorPrompt", () => { expect(result.text).toContain("Assistant: Hi there"); }); + it("defensively formats assistant string content", () => { + const ctx: Context = { + messages: [ + { + role: "assistant", + content: "Legacy assistant text", + api: "cursor-sdk", + provider: "cursor", + model: "test", + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, + stopReason: "stop", + timestamp: 2, + } as unknown as AssistantMessage, + ], + }; + const result = buildCursorPrompt(ctx); + expect(result.text).toContain("Assistant: Legacy assistant text"); + }); + it("omits thinking content from transcript", () => { const ctx: Context = { messages: [ @@ -61,7 +80,57 @@ describe("buildCursorPrompt", () => { ], }; const result = buildCursorPrompt(ctx); - expect(result.text).toContain("Tool result (bash): output here"); + expect(result.text).toContain("Tool result (bash, call tc1): output here"); + }); + + it("formats tool errors", () => { + const ctx: Context = { + messages: [ + { role: "user", content: "Run it", timestamp: 1 } satisfies UserMessage, + { + role: "toolResult", + toolCallId: "tc1", + toolName: "bash", + content: [{ type: "text", text: "command failed" }], + isError: true, + timestamp: 2, + } satisfies ToolResultMessage, + ], + }; + const result = buildCursorPrompt(ctx); + expect(result.text).toContain("Tool error (bash, call tc1): command failed"); + }); + + it("formats assistant tool calls before tool results", () => { + const ctx: Context = { + messages: [ + { role: "user", content: "List files", timestamp: 1 } satisfies UserMessage, + { + role: "assistant", + content: [ + { type: "text", text: "I will inspect the directory." }, + { type: "toolCall", id: "tc1", name: "bash", arguments: { command: "ls" } }, + ], + api: "cursor-sdk", + provider: "cursor", + model: "test", + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, + stopReason: "toolUse", + timestamp: 2, + } satisfies AssistantMessage, + { + role: "toolResult", + toolCallId: "tc1", + toolName: "bash", + content: [{ type: "text", text: "README.md" }], + isError: false, + timestamp: 3, + } satisfies ToolResultMessage, + ], + }; + const result = buildCursorPrompt(ctx); + expect(result.text).toContain("Assistant: I will inspect the directory.\nTool call (bash, call tc1): {\"command\":\"ls\"}"); + expect(result.text).toContain("Tool result (bash, call tc1): README.md"); }); it("extracts images from latest user message only", () => { diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 22348d8..228b4fb 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -1,3 +1,6 @@ +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { describe, it, expect, vi, beforeEach } from "vitest"; // Mock @cursor/sdk before importing the module under test @@ -6,28 +9,38 @@ vi.mock("@cursor/sdk", () => { const mockDispose = vi.fn().mockResolvedValue(undefined); const mockAgent = { + agentId: "agent-1", send: vi.fn(), [Symbol.asyncDispose]: mockDispose, }; + const mockPlatform = { + checkpointStore: { + loadLatest: vi.fn().mockResolvedValue(undefined), + }, + }; return { Agent: { create: vi.fn().mockResolvedValue(mockAgent), }, + createAgentPlatform: vi.fn().mockResolvedValue(mockPlatform), _mockAgent: mockAgent, _mockCancel: mockCancel, _mockDispose: mockDispose, + _mockPlatform: mockPlatform, }; }); -import { Agent } from "@cursor/sdk"; +import { Agent, createAgentPlatform } from "@cursor/sdk"; import { streamCursor } from "../src/cursor-provider.js"; import { __testUtils as modelDiscoveryTestUtils } from "../src/model-discovery.js"; +import { __testUtils as contextWindowCacheTestUtils } from "../src/context-window-cache.js"; import type { ModelListItem } from "@cursor/sdk"; import type { Context, Model } from "@mariozechner/pi-ai"; // Access the mocks via the module const mockedCreate = vi.mocked(Agent.create); +const mockedCreateAgentPlatform = vi.mocked(createAgentPlatform); function makeModel(id = "test-model"): Model<"cursor-sdk"> { return { @@ -138,9 +151,15 @@ describe("streamCursor", () => { modelDiscoveryTestUtils.registerModelItems(cursorModelItems); // Re-setup default mock return after clearing mockedCreate.mockResolvedValue({ + agentId: "agent-1", send: vi.fn(), [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), }); + mockedCreateAgentPlatform.mockResolvedValue({ + checkpointStore: { + loadLatest: vi.fn().mockResolvedValue(undefined), + }, + } as any); }); it("emits text deltas as pi text stream events", async () => { @@ -207,8 +226,8 @@ describe("streamCursor", () => { it("does not emit pi tool call events for cursor tool deltas", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { - opts.onDelta({ update: { type: "tool-call-started", name: "read_file", callId: "c1" } }); - opts.onDelta({ update: { type: "tool-call-completed", name: "read_file", callId: "c1" } }); + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read_file" }, callId: "c1" } }); + opts.onDelta({ update: { type: "tool-call-completed", toolCall: { name: "read_file" }, callId: "c1" } }); opts.onDelta({ update: { type: "text-delta", text: "done" } }); return { id: "run-1", @@ -234,6 +253,130 @@ describe("streamCursor", () => { expect(toolEvents).toHaveLength(0); }); + it("surfaces cursor tool activity in the trace without polluting final text", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "list_dir" }, callId: "c1" } }); + opts.onDelta({ update: { type: "tool-call-completed", toolCall: { name: "list_dir", result: { files: ["README.md"] } }, callId: "c1" } }); + opts.onDelta({ update: { type: "summary", summary: "Inspected files" } }); + opts.onDelta({ update: { type: "text-delta", text: "done" } }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + const trace = events.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + const text = events.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); + const done = events.find((e: any) => e.type === "done") as any; + + expect(trace).toContain("Cursor tool started (list_dir, call c1)"); + expect(trace).toContain("Cursor tool completed (list_dir, call c1)"); + expect(trace).not.toContain("README.md"); + expect(trace).toContain("Cursor summary: Inspected files"); + expect(text).toBe("done"); + expect(done.message.content.map((block: any) => block.type)).toEqual(["thinking", "text"]); + }); + + it("keeps late cursor thinking before final text in the saved content order", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "text-delta", text: "Final answer" } }); + opts.onDelta({ update: { type: "thinking-delta", text: "late trace" } }); + opts.onDelta({ update: { type: "thinking-completed" } }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + const done = events.find((e: any) => e.type === "done") as any; + + expect(done.message.content).toEqual([ + { type: "thinking", thinking: "late trace" }, + { type: "text", text: "Final answer" }, + ]); + }); + + it("updates usage from cursor turn-ended events", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ + update: { + type: "turn-ended", + usage: { inputTokens: 10, outputTokens: 7, cacheReadTokens: 3, cacheWriteTokens: 2 }, + }, + }); + opts.onDelta({ update: { type: "text-delta", text: "done" } }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + const done = events.find((e: any) => e.type === "done") as any; + + expect(done.message.usage).toMatchObject({ + input: 10, + output: 7, + cacheRead: 3, + cacheWrite: 2, + totalTokens: 22, + }); + }); + + it("aborts after agent creation without sending a prompt when already cancelled", async () => { + const controller = new AbortController(); + const mockDispose = vi.fn().mockResolvedValue(undefined); + const mockSend = vi.fn(); + mockedCreate.mockImplementation(async () => { + controller.abort(); + return { + send: mockSend, + [Symbol.asyncDispose]: mockDispose, + }; + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key", signal: controller.signal }); + const events = await collectEvents(stream); + const error = events.find((e: any) => e.type === "error") as any; + + expect(error.reason).toBe("aborted"); + expect(error.error.stopReason).toBe("aborted"); + expect(mockSend).not.toHaveBeenCalled(); + expect(mockDispose).toHaveBeenCalledTimes(1); + }); + it("emits error when no API key", async () => { const stream = streamCursor(makeModel(), makeContext(), { apiKey: undefined }); const events = await collectEvents(stream); @@ -375,6 +518,69 @@ describe("streamCursor", () => { ); }); + it("caches SDK checkpoint context windows after successful runs", async () => { + const tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-cursor-provider-context-window-")); + const originalAgentDir = process.env.PI_CODING_AGENT_DIR; + process.env.PI_CODING_AGENT_DIR = tmpAgentDir; + try { + const loadLatest = vi.fn().mockResolvedValue({ tokenDetails: { usedTokens: 8435, maxTokens: 201000 } }); + mockedCreateAgentPlatform.mockResolvedValue({ checkpointStore: { loadLatest } } as any); + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "ok" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + agentId: "agent-ctx", + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel("composer-2"), makeContext(), { apiKey: "test-key" }); + await collectEvents(stream); + + expect(loadLatest).toHaveBeenCalledWith("agent-ctx"); + const cache = JSON.parse(readFileSync(contextWindowCacheTestUtils.getCachePath(), "utf-8")); + expect(cache.contextWindows["composer-2"]).toBe(201000); + } finally { + if (originalAgentDir === undefined) { + delete process.env.PI_CODING_AGENT_DIR; + } else { + process.env.PI_CODING_AGENT_DIR = originalAgentDir; + } + rmSync(tmpAgentDir, { recursive: true, force: true }); + } + }); + + it("creates local Cursor agents without ambient setting sources that write SDK logs to the terminal", async () => { + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel("composer-2"), makeContext(), { apiKey: "test-key" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + local: { cwd: process.cwd() }, + }), + ); + }); + it("passes Cursor model selection with context and pi thinking off to Agent.create", async () => { const modelWithParams = makeModel("gpt-5.5@1m"); const mockSend = vi.fn().mockResolvedValue({ diff --git a/test/cursor-state.test.ts b/test/cursor-state.test.ts index e5d393b..dae4259 100644 --- a/test/cursor-state.test.ts +++ b/test/cursor-state.test.ts @@ -115,6 +115,21 @@ describe("Cursor fast state", () => { }); }); + it("does not update fast state when the global config cannot be saved", async () => { + const blockedAgentDir = join(tmpAgentDir, "not-a-directory"); + writeFileSync(blockedAgentDir, "x"); + process.env.PI_CODING_AGENT_DIR = blockedAgentDir; + const { pi, ctx, commands, handlers } = createHarness({ modelId: "composer-2" }); + await handlers.get("session_start")({}, ctx); + + await commands.get("cursor-fast").handler("", ctx); + + expect(ctx.ui.notify).toHaveBeenCalledWith(expect.stringContaining("Failed to save Cursor fast preference"), "error"); + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", "cursor fast"); + expect(getEffectiveFastForModelId("composer-2")).toBe(true); + expect(pi.appendEntry).not.toHaveBeenCalled(); + }); + it("restores fast state from the active session branch", async () => { const { ctx, handlers } = createHarness({ modelId: "composer-2", diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 16ac6e4..4445d4c 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -1,3 +1,6 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { discoverModels, @@ -6,6 +9,7 @@ import { getCursorModelMetadataEntries, __testUtils, } from "../src/model-discovery.js"; +import { saveCachedContextWindow } from "../src/context-window-cache.js"; vi.mock("@cursor/sdk", () => ({ Cursor: { @@ -180,6 +184,53 @@ describe("discoverModels", () => { ]); }); + it("uses bundled SDK-derived context windows for models without context params", async () => { + const tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-cursor-context-window-bundled-")); + process.env.PI_CODING_AGENT_DIR = tmpAgentDir; + try { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "composer-2", + displayName: "Composer 2", + parameters: [{ id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] }], + variants: [{ params: [{ id: "fast", value: "true" }], displayName: "Composer 2", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(models[0].id).toBe("composer-2"); + expect(models[0].contextWindow).toBe(200000); + } finally { + rmSync(tmpAgentDir, { recursive: true, force: true }); + } + }); + + it("lets user cache override bundled context windows", async () => { + const tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-cursor-context-window-")); + process.env.PI_CODING_AGENT_DIR = tmpAgentDir; + try { + saveCachedContextWindow("composer-2", 201000); + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "composer-2", + displayName: "Composer 2", + parameters: [{ id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] }], + variants: [{ params: [{ id: "fast", value: "true" }], displayName: "Composer 2", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(models[0].id).toBe("composer-2"); + expect(models[0].contextWindow).toBe(201000); + } finally { + rmSync(tmpAgentDir, { recursive: true, force: true }); + } + }); + it("sets reasoning false for models without thinking controls", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ -- 2.51.2 From 8f7057ec5f25ca645a1f3f36e29e9c087c8430bf Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 4 May 2026 14:55:19 -0600 Subject: [PATCH 005/359] Refactor error handling and enhance model discovery for Cursor SDK - Introduced detailed error messages for missing or invalid API keys in cursor-provider.ts, improving user feedback during SDK runs. - Updated model discovery logic in model-discovery.ts to report fallback issues, including missing API keys and empty model lists, enhancing robustness. - Enhanced README.md with new installation instructions and usage examples for local development and package installation. - Added tests to ensure proper error handling and fallback notifications during model discovery. - Updated cursor-state.ts to support new command-line flags for controlling fast mode behavior. --- README.md | 46 ++++++++++++++++++-------- package-lock.json | 4 +++ package.json | 6 +++- src/cursor-provider.ts | 48 +++++++++++++++++++++++---- src/cursor-state.ts | 13 ++++++++ src/index.ts | 16 +++++++-- src/model-discovery.ts | 36 +++++++++++++++++--- test/cursor-provider.test.ts | 30 ++++++++++++++++- test/cursor-state.test.ts | 58 ++++++++++++++++++++++++++++++-- test/index.test.ts | 64 +++++++++++++++++++++++++++++++++++- test/model-discovery.test.ts | 39 ++++++++++++++++++---- 11 files changed, 322 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 5862d44..87f923f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Current behavior: - registers Cursor models under the `cursor` provider - registers one pi model per Cursor `context` value, using IDs like `gpt-5.5@1m` - maps Cursor `reasoning`, `effort`, and boolean `thinking` to pi native thinking levels -- keeps Cursor `fast` as extension state, toggled with `/cursor-fast` or forced with `--cursor-fast` +- keeps Cursor `fast` as extension state, toggled with `/cursor-fast` or forced for one run with `--cursor-fast` / `--cursor-no-fast` - shows Cursor fast mode through `ctx.ui.setStatus()` and leaves pi's default footer intact - creates a fresh local Cursor agent for each pi provider call @@ -26,38 +26,51 @@ No global `@cursor/sdk` install is required. This package depends on `@cursor/sd ## Install +### Local development from a checkout + ```bash npm install +pi -e . ``` -## API key +Pick a model with `/model`, or pass one directly: -Set `CURSOR_API_KEY` in the environment before starting pi: +```bash +pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." +``` + +### Pi package install from a package source + +Install a local package path: ```bash -export CURSOR_API_KEY="your-key" +pi install /absolute/path/to/pi-cursor-sdk ``` -Or pass it for one command: +Use the npm source form when installing from npm: ```bash -CURSOR_API_KEY="your-key" pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." +pi install npm:pi-cursor-sdk ``` -Do not store the API key in `~/.pi/agent/cursor-sdk.json`; that file is only for non-secret extension state such as Cursor fast defaults. `PATH` is only for executable lookup and should not contain the API key. +## API key -## Run locally with pi +Set `CURSOR_API_KEY` in the environment before starting pi: ```bash -pi -e . +export CURSOR_API_KEY="your-key" ``` -Pick a model with `/model`, or pass one directly: +Or pass it for one command: ```bash -pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." +CURSOR_API_KEY="your-key" pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." ``` +Actual Cursor runs require `CURSOR_API_KEY` or pi's `--api-key` option. If model discovery cannot authenticate or reach Cursor, pi may still list fallback Cursor models. A runtime setup/auth error means the key was missing, invalid, unauthorized, or not exported into the pi process. + +Do not store the API key in `~/.pi/agent/cursor-sdk.json`; that file is only for non-secret extension state such as Cursor fast defaults. `PATH` is only for executable lookup and should not contain the API key. + ## Model IDs Cursor-only parameters are not encoded into pi model IDs. @@ -104,19 +117,22 @@ Some Cursor models can still reason internally, and Cursor may still emit thinki ## Fast mode -Use `/cursor-fast` to toggle fast mode for the selected Cursor model when supported. +Use `/cursor-fast` to persistently toggle fast mode for the selected Cursor model when supported. Fast preferences are stored: - in the current session with `pi.appendEntry()` - globally per Cursor base model in `~/.pi/agent/cursor-sdk.json` -For one print-mode run: +For one run, force fast on or off without changing stored preferences: ```bash pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only" +pi --model cursor/composer-2 --cursor-no-fast -p "Say ok only" ``` +`composer-2` can default to fast. Use `--cursor-no-fast` for a one-shot no-fast `composer-2` run. + When fast is enabled, the default pi footer gets an extension status line: ```text @@ -129,13 +145,15 @@ Images from the latest user message are forwarded to Cursor. Historical images a ## Fallback models -If `CURSOR_API_KEY` is missing or model discovery fails, the extension registers conservative fallback Cursor models with the same native shape: +If `CURSOR_API_KEY` is missing or model discovery fails, the extension registers conservative fallback Cursor models with the same native shape and notifies interactive users when possible: - `composer-2` - `gpt-5.5@1m`, `gpt-5.5@272k` - `claude-sonnet-4-6@1m`, `claude-sonnet-4-6@300k` - `claude-opus-4-7@1m`, `claude-opus-4-7@300k` +Fallback models are only a startup model list. Actual Cursor runs still need `CURSOR_API_KEY` or `--api-key`. If a run fails with a Cursor SDK setup/auth message, verify the key is correct and exported to the same shell or process that starts pi. + ## Limits - local agents only; no Cursor cloud agent support diff --git a/package-lock.json b/package-lock.json index 2250121..4b7f79a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,10 @@ "@mariozechner/pi-coding-agent": "^0.72.1", "typescript": "^6.0.3", "vitest": "^4.1.5" + }, + "peerDependencies": { + "@mariozechner/pi-ai": "*", + "@mariozechner/pi-coding-agent": "*" } }, "node_modules/@anthropic-ai/sdk": { diff --git a/package.json b/package.json index 4259364..f8fa765 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "description": "pi provider extension backed by @cursor/sdk local agents", "license": "MIT", - "keywords": ["pi", "cursor", "cursor-sdk", "ai", "extension"], + "keywords": ["pi-package", "pi", "cursor", "cursor-sdk", "ai", "extension"], "files": ["src", "README.md", "docs"], "type": "module", "scripts": { @@ -14,6 +14,10 @@ "dependencies": { "@cursor/sdk": "^1.0.12" }, + "peerDependencies": { + "@mariozechner/pi-ai": "*", + "@mariozechner/pi-coding-agent": "*" + }, "devDependencies": { "@mariozechner/pi-coding-agent": "^0.72.1", "@mariozechner/pi-ai": "^0.72.1", diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index bb59d6c..f0b8feb 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -41,10 +41,46 @@ class CursorAbortError extends Error { } } -function sanitizeError(error: unknown): string { - if (error instanceof Error) return error.message; - if (typeof error === "string") return error; - return "Unknown error"; +const MISSING_API_KEY_MESSAGE = "CURSOR_API_KEY or --api-key is required for Cursor SDK runs."; +const GENERIC_CURSOR_SDK_ERROR_MESSAGE = + "Cursor SDK request failed. Verify CURSOR_API_KEY or pass --api-key, then retry."; +const AUTH_CURSOR_SDK_ERROR_MESSAGE = + "Cursor SDK request failed because the API key may be invalid or unauthorized. Verify CURSOR_API_KEY or pass --api-key, then retry."; + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function scrubSensitiveText(text: string, apiKey?: string): string { + let scrubbed = text; + const trimmedKey = apiKey?.trim(); + if (trimmedKey) { + scrubbed = scrubbed.replace(new RegExp(escapeRegExp(trimmedKey), "g"), "[redacted]"); + } + return scrubbed + .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]") + .replace(/(authorization\s*[:=]\s*)[^\s,;}]+/gi, "$1[redacted]") + .replace(/(api[_-]?key\s*[:=]\s*)[^\s,;}]+/gi, "$1[redacted]") + .replace(/(token\s*[:=]\s*)[^\s,;}]+/gi, "$1[redacted]") + .replace(/(cookie\s*[:=]\s*)[^\n]+/gi, "$1[redacted]") + .replace(/(session\s*[:=]\s*)[^\s,;}]+/gi, "$1[redacted]"); +} + +function isGenericErrorMessage(message: string): boolean { + const normalized = message.trim().toLowerCase(); + return normalized === "" || normalized === "error" || normalized === "unknown error"; +} + +function isLikelyAuthError(message: string): boolean { + return /\b(unauthorized|unauthorised|forbidden|invalid api key|invalid key|authentication|auth|401|403)\b/i.test(message); +} + +function sanitizeError(error: unknown, apiKey?: string): string { + const message = error instanceof Error ? error.message : typeof error === "string" ? error : ""; + const scrubbed = scrubSensitiveText(message, apiKey).trim(); + if (isGenericErrorMessage(scrubbed)) return GENERIC_CURSOR_SDK_ERROR_MESSAGE; + if (isLikelyAuthError(scrubbed)) return AUTH_CURSOR_SDK_ERROR_MESSAGE; + return scrubbed || GENERIC_CURSOR_SDK_ERROR_MESSAGE; } function getObjectField(value: unknown, field: string): unknown { @@ -108,7 +144,7 @@ export function streamCursor( throwIfAborted(); const apiKey = options?.apiKey; - if (!apiKey) throw new Error("CURSOR_API_KEY is required"); + if (!apiKey) throw new Error(MISSING_API_KEY_MESSAGE); const cwd = process.cwd(); const fastEnabled = getEffectiveFastForModelId(model.id); @@ -270,7 +306,7 @@ export function streamCursor( stream.push({ type: "error", reason: "aborted", error: partial }); } else { partial.stopReason = "error"; - partial.errorMessage = sanitizeError(error); + partial.errorMessage = sanitizeError(error, options?.apiKey); stream.push({ type: "error", reason: "error", error: partial }); } } finally { diff --git a/src/cursor-state.ts b/src/cursor-state.ts index 98c0f9f..ba83ef9 100644 --- a/src/cursor-state.ts +++ b/src/cursor-state.ts @@ -20,6 +20,7 @@ interface CursorGlobalConfig { const sessionFastPreferences = new Map(); let globalFastPreferences = new Map(); let cliForceFast = false; +let cliForceNoFast = false; function isCursorFastEntryData(value: unknown): value is CursorFastEntryData { if (!value || typeof value !== "object") return false; @@ -68,6 +69,7 @@ function restoreSessionFastPreferences(ctx: ExtensionContext): void { function getEffectiveFast(baseModelId: string, modelId: string): boolean | undefined { const metadata = getCursorModelMetadata(modelId); if (!metadata?.supportsFast) return undefined; + if (cliForceNoFast) return false; if (cliForceFast) return true; return sessionFastPreferences.get(baseModelId) ?? globalFastPreferences.get(baseModelId) ?? metadata.defaultFast; } @@ -128,6 +130,12 @@ export function registerCursorFastControls(pi: ExtensionAPI): void { default: false, }); + pi.registerFlag("cursor-no-fast", { + description: "Force Cursor fast mode off for this run when the selected Cursor model supports it", + type: "boolean", + default: false, + }); + pi.registerCommand("cursor-fast", { description: "Toggle Cursor fast mode for the selected Cursor model", handler: async (_args, ctx) => { @@ -137,6 +145,10 @@ export function registerCursorFastControls(pi: ExtensionAPI): void { ctx.ui.notify(`Fast mode not supported by ${modelName}`, "info"); return; } + if (cliForceNoFast) { + ctx.ui.notify("Cursor fast is forced off by --cursor-no-fast", "info"); + return; + } if (cliForceFast) { ctx.ui.notify("Cursor fast is forced by --cursor-fast", "info"); return; @@ -159,6 +171,7 @@ export function registerCursorFastControls(pi: ExtensionAPI): void { pi.on("session_start", async (_event, ctx) => { globalFastPreferences = loadGlobalFastPreferences(); cliForceFast = pi.getFlag("cursor-fast") === true; + cliForceNoFast = pi.getFlag("cursor-no-fast") === true; restoreSessionFastPreferences(ctx); updateCursorStatus(ctx); }); diff --git a/src/index.ts b/src/index.ts index 3afd336..68051e5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,23 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { discoverModels } from "./model-discovery.js"; +import { discoverModels, type CursorModelFallbackIssue } from "./model-discovery.js"; import { registerCursorFastControls } from "./cursor-state.js"; import { streamCursor } from "./cursor-provider.js"; export default async function (pi: ExtensionAPI) { registerCursorFastControls(pi); - const models = await discoverModels(); + let fallbackIssue: CursorModelFallbackIssue | undefined; + const models = await discoverModels({ + onFallback: (issue) => { + fallbackIssue = issue; + }, + }); + + if (fallbackIssue) { + const issue = fallbackIssue; + pi.on("session_start", async (_event, ctx) => { + if (ctx.hasUI) ctx.ui.notify(issue.message, "warning"); + }); + } pi.registerProvider("cursor", { name: "Cursor", diff --git a/src/model-discovery.ts b/src/model-discovery.ts index 4527652..79d31c6 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -155,6 +155,17 @@ const FALLBACK_MODEL_ITEMS: ModelListItem[] = [ }, ]; +export type CursorModelFallbackReason = "missing-api-key" | "discovery-failed" | "empty-model-list"; + +export interface CursorModelFallbackIssue { + reason: CursorModelFallbackReason; + message: string; +} + +export interface DiscoverModelsOptions { + onFallback?: (issue: CursorModelFallbackIssue) => void; +} + export interface CursorModelMetadata { piModelId: string; baseModelId: string; @@ -445,18 +456,35 @@ export function buildCursorModelSelection( return params.length > 0 ? { id: metadata.baseModelId, params } : { id: metadata.baseModelId }; } -export async function discoverModels(): Promise { +function useFallbackModels(options: DiscoverModelsOptions, issue: CursorModelFallbackIssue): ProviderModelConfig[] { + options.onFallback?.(issue); + return registerModelItems(FALLBACK_MODEL_ITEMS); +} + +export async function discoverModels(options: DiscoverModelsOptions = {}): Promise { const apiKey = process.env.CURSOR_API_KEY?.trim(); - if (!apiKey) return registerModelItems(FALLBACK_MODEL_ITEMS); + if (!apiKey) { + return useFallbackModels(options, { + reason: "missing-api-key", + message: + "CURSOR_API_KEY is required for Cursor model discovery; using fallback Cursor model list. Cursor runs can still use CURSOR_API_KEY or --api-key.", + }); + } try { const models = await Cursor.models.list({ apiKey }); if (models.length > 0) { return registerModelItems(models); } - return registerModelItems(FALLBACK_MODEL_ITEMS); + return useFallbackModels(options, { + reason: "empty-model-list", + message: "Cursor model discovery returned no models; using fallback Cursor model list.", + }); } catch { - return registerModelItems(FALLBACK_MODEL_ITEMS); + return useFallbackModels(options, { + reason: "discovery-failed", + message: "Cursor model discovery failed. Verify CURSOR_API_KEY or pass --api-key for runs; using fallback Cursor model list.", + }); } } diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 228b4fb..d155bf6 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -377,13 +377,41 @@ describe("streamCursor", () => { expect(mockDispose).toHaveBeenCalledTimes(1); }); - it("emits error when no API key", async () => { + it("emits actionable error when no API key", async () => { const stream = streamCursor(makeModel(), makeContext(), { apiKey: undefined }); const events = await collectEvents(stream); const error = events.find((e: any) => e.type === "error"); expect(error).toBeDefined(); expect((error as any).error.errorMessage).toContain("CURSOR_API_KEY"); + expect((error as any).error.errorMessage).toContain("--api-key"); + }); + + it("turns generic Cursor SDK failures into actionable setup errors", async () => { + mockedCreate.mockRejectedValueOnce(new Error("Error")); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + + const error = events.find((e: any) => e.type === "error"); + expect(error).toBeDefined(); + expect((error as any).error.errorMessage).toContain("Cursor SDK request failed"); + expect((error as any).error.errorMessage).toContain("CURSOR_API_KEY"); + expect((error as any).error.errorMessage).toContain("--api-key"); + expect((error as any).error.errorMessage).not.toBe("Error"); + }); + + it("labels likely auth failures without leaking the supplied API key", async () => { + mockedCreate.mockRejectedValueOnce(new Error("Unauthorized Bearer super-secret-key-12345")); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "super-secret-key-12345" }); + const events = await collectEvents(stream); + + const error = events.find((e: any) => e.type === "error"); + const message = (error as any).error.errorMessage; + expect(message).toContain("invalid or unauthorized"); + expect(message).toContain("CURSOR_API_KEY"); + expect(message).not.toContain("super-secret-key-12345"); }); it("disposes agent on success", async () => { diff --git a/test/cursor-state.test.ts b/test/cursor-state.test.ts index dae4259..c60da7b 100644 --- a/test/cursor-state.test.ts +++ b/test/cursor-state.test.ts @@ -46,14 +46,18 @@ const modelItems: ModelListItem[] = [ }, ]; -function createHarness(options: { modelId?: string; provider?: string; branch?: unknown[]; cursorFastFlag?: boolean } = {}) { +function createHarness(options: { modelId?: string; provider?: string; branch?: unknown[]; cursorFastFlag?: boolean; cursorNoFastFlag?: boolean } = {}) { const commands = new Map(); const handlers = new Map(); const pi = { registerFlag: vi.fn(), registerCommand: vi.fn((name: string, command: any) => commands.set(name, command)), on: vi.fn((event: string, handler: any) => handlers.set(event, handler)), - getFlag: vi.fn(() => options.cursorFastFlag ?? false), + getFlag: vi.fn((name: string) => { + if (name === "cursor-fast") return options.cursorFastFlag ?? false; + if (name === "cursor-no-fast") return options.cursorNoFastFlag ?? false; + return false; + }), appendEntry: vi.fn(), }; const ctx = { @@ -168,6 +172,56 @@ describe("Cursor fast state", () => { expect(pi.appendEntry).not.toHaveBeenCalled(); }); + it("forces fast off with --cursor-no-fast without writing session state", async () => { + const { pi, ctx, handlers } = createHarness({ modelId: "composer-2", cursorNoFastFlag: true }); + + await handlers.get("session_start")({}, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", undefined); + expect(getEffectiveFastForModelId("composer-2")).toBe(false); + expect(pi.appendEntry).not.toHaveBeenCalled(); + }); + + it("lets --cursor-no-fast win when both one-run force flags are set", async () => { + const { pi, ctx, handlers } = createHarness({ modelId: "composer-2", cursorFastFlag: true, cursorNoFastFlag: true }); + + await handlers.get("session_start")({}, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", undefined); + expect(getEffectiveFastForModelId("composer-2")).toBe(false); + expect(pi.appendEntry).not.toHaveBeenCalled(); + }); + + it("does not apply --cursor-no-fast to unsupported Cursor models", async () => { + const { pi, ctx, handlers } = createHarness({ modelId: "gemini-3.1-pro", cursorNoFastFlag: true }); + + await handlers.get("session_start")({}, ctx); + + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", undefined); + expect(getEffectiveFastForModelId("gemini-3.1-pro")).toBeUndefined(); + expect(pi.appendEntry).not.toHaveBeenCalled(); + }); + + it("does not let /cursor-fast persist while --cursor-no-fast is active", async () => { + const { pi, ctx, commands, handlers } = createHarness({ modelId: "composer-2", cursorNoFastFlag: true }); + await handlers.get("session_start")({}, ctx); + + await commands.get("cursor-fast").handler("", ctx); + + expect(ctx.ui.notify).toHaveBeenCalledWith("Cursor fast is forced off by --cursor-no-fast", "info"); + expect(getEffectiveFastForModelId("composer-2")).toBe(false); + expect(pi.appendEntry).not.toHaveBeenCalled(); + }); + + it("mentions --cursor-no-fast when both force flags block /cursor-fast", async () => { + const { ctx, commands, handlers } = createHarness({ modelId: "composer-2", cursorFastFlag: true, cursorNoFastFlag: true }); + await handlers.get("session_start")({}, ctx); + + await commands.get("cursor-fast").handler("", ctx); + + expect(ctx.ui.notify).toHaveBeenCalledWith("Cursor fast is forced off by --cursor-no-fast", "info"); + }); + it("does not let /cursor-fast persist an opposite value when --cursor-fast is active", async () => { const { pi, ctx, commands, handlers } = createHarness({ modelId: "gpt-5.5@1m", cursorFastFlag: true }); await handlers.get("session_start")({}, ctx); diff --git a/test/index.test.ts b/test/index.test.ts index 313b46a..d8d73f9 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -17,16 +17,20 @@ const mockedStreamCursor = vi.mocked(streamCursor); function createMockPi() { const registered: Array<{ name: string; config: Record }> = []; + const handlers = new Map Promise | void>>(); return { registerProvider: vi.fn((name: string, config: Record) => { registered.push({ name, config }); }), registerFlag: vi.fn(), registerCommand: vi.fn(), - on: vi.fn(), + on: vi.fn((event: string, handler: (event: unknown, ctx: any) => Promise | void) => { + handlers.set(event, [...(handlers.get(event) ?? []), handler]); + }), getFlag: vi.fn().mockReturnValue(false), appendEntry: vi.fn(), _registered: registered, + _handlers: handlers, }; } @@ -56,6 +60,10 @@ describe("extension factory", () => { "cursor-fast", expect.objectContaining({ type: "boolean", default: false }), ); + expect(pi.registerFlag).toHaveBeenCalledWith( + "cursor-no-fast", + expect.objectContaining({ type: "boolean", default: false }), + ); expect(pi.registerCommand).toHaveBeenCalledWith( "cursor-fast", expect.objectContaining({ description: expect.stringContaining("Toggle Cursor fast") }), @@ -103,4 +111,58 @@ describe("extension factory", () => { const [call] = pi._registered; expect(call.config.models).toHaveLength(2); }); + + it("notifies interactive users when fallback models are registered", async () => { + mockedDiscover.mockImplementationOnce(async (options: any) => { + options.onFallback({ + reason: "missing-api-key", + message: "CURSOR_API_KEY or --api-key is required; using fallback Cursor model list.", + }); + return [ + { + id: "composer-2", + name: "Cursor Composer 2", + reasoning: false, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + ]; + }); + + const pi = createMockPi(); + await extensionFactory(pi as any); + + const notify = vi.fn(); + const ctx = { + hasUI: true, + ui: { notify, setStatus: vi.fn() }, + sessionManager: { getBranch: vi.fn(() => []) }, + }; + const sessionHandlers = pi._handlers.get("session_start") ?? []; + await sessionHandlers.at(-1)!({}, ctx); + + expect(notify).toHaveBeenCalledWith( + "CURSOR_API_KEY or --api-key is required; using fallback Cursor model list.", + "warning", + ); + }); + + it("does not notify fallback discovery issues without UI", async () => { + mockedDiscover.mockImplementationOnce(async (options: any) => { + options.onFallback({ reason: "empty-model-list", message: "Cursor model discovery returned no models; using fallback Cursor model list." }); + return []; + }); + + const pi = createMockPi(); + await extensionFactory(pi as any); + + const notify = vi.fn(); + const ctx = { hasUI: false, ui: { notify, setStatus: vi.fn() }, sessionManager: { getBranch: vi.fn(() => []) } }; + const sessionHandlers = pi._handlers.get("session_start") ?? []; + await sessionHandlers.at(-1)!({}, ctx); + + expect(notify).not.toHaveBeenCalled(); + }); }); diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 4445d4c..8e418c3 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -42,7 +42,8 @@ describe("discoverModels", () => { it("returns context-qualified fallback models when no API key", async () => { delete process.env.CURSOR_API_KEY; - const models = await discoverModels(); + const issues: unknown[] = []; + const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); expect(models.map((model) => model.id)).toEqual([ "claude-opus-4-7@1m", "claude-opus-4-7@300k", @@ -52,13 +53,22 @@ describe("discoverModels", () => { "gpt-5.5@1m", "gpt-5.5@272k", ]); + expect(issues).toEqual([ + expect.objectContaining({ + reason: "missing-api-key", + message: expect.stringContaining("CURSOR_API_KEY"), + }), + ]); + expect(JSON.stringify(issues)).toContain("--api-key"); expect(mockedList).not.toHaveBeenCalled(); }); - it("returns fallback models when API key is whitespace", async () => { + it("returns fallback models and reports missing key when API key is whitespace", async () => { process.env.CURSOR_API_KEY = " "; - const models = await discoverModels(); + const issues: unknown[] = []; + const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); expect(models.some((model) => model.id === "gpt-5.5@1m")).toBe(true); + expect(issues).toEqual([expect.objectContaining({ reason: "missing-api-key" })]); expect(mockedList).not.toHaveBeenCalled(); }); @@ -477,18 +487,33 @@ describe("discoverModels", () => { ]); }); - it("falls back when Cursor.models.list throws", async () => { + it("falls back and reports discovery failure when Cursor.models.list throws", async () => { process.env.CURSOR_API_KEY = "test-key-123"; + const issues: unknown[] = []; mockedList.mockRejectedValueOnce(new Error("network error")); - const models = await discoverModels(); + const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); expect(models.some((model) => model.id === "composer-2")).toBe(true); + expect(issues).toEqual([ + expect.objectContaining({ + reason: "discovery-failed", + message: expect.stringContaining("Cursor model discovery failed"), + }), + ]); + expect(JSON.stringify(issues)).not.toContain("test-key-123"); }); - it("falls back when Cursor.models.list returns empty", async () => { + it("falls back and reports empty model list when Cursor.models.list returns empty", async () => { process.env.CURSOR_API_KEY = "test-key-123"; + const issues: unknown[] = []; mockedList.mockResolvedValueOnce([]); - const models = await discoverModels(); + const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); expect(models.some((model) => model.id === "claude-opus-4-7@1m")).toBe(true); + expect(issues).toEqual([ + expect.objectContaining({ + reason: "empty-model-list", + message: expect.stringContaining("Cursor model discovery returned no models"), + }), + ]); }); it("uses id as name when displayName is missing", async () => { -- 2.51.2 From d215ad634a9fb0f06a074afd19ad61fb2d5b0f0c Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 4 May 2026 15:28:07 -0600 Subject: [PATCH 006/359] Enhance API key handling and update README for Cursor SDK - Improved error handling in cursor-provider.ts to provide clearer messages for missing or invalid API keys. - Updated model discovery logic in model-discovery.ts to utilize CLI arguments for API key retrieval, enhancing flexibility. - Enhanced README.md with new usage examples for passing API keys and clarified the importance of using the CURSOR_API_KEY environment variable. - Added tests to verify the correct handling of API key scenarios in both cursor-provider and model-discovery modules. --- README.md | 14 +++++++++++--- src/cursor-provider.ts | 14 ++++++++++++-- src/model-discovery.ts | 25 ++++++++++++++++++++++++- test/cursor-provider.test.ts | 20 ++++++++++++++++++++ test/model-discovery.test.ts | 25 +++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 87f923f..c1f4625 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ Current behavior: ## Requirements -- Node.js +- Node.js 18+ - pi -- a Cursor API key exposed as the `CURSOR_API_KEY` environment variable +- a Cursor API key exposed as the `CURSOR_API_KEY` environment variable for full model discovery No global `@cursor/sdk` install is required. This package depends on `@cursor/sdk`, so a normal package install brings in the SDK version the extension was built and tested against. @@ -67,6 +67,14 @@ Or pass it for one command: CURSOR_API_KEY="your-key" pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." ``` +You can also pass pi's `--api-key` option for a one-shot run: + +```bash +pi -e . --api-key "your-key" --model cursor/composer-2 --cursor-no-fast -p "Say ok only." +``` + +Use `CURSOR_API_KEY` when possible. It gives the extension a key during startup model discovery. `--api-key` is also read for discovery, but shell wrappers and launchers are easier to diagnose when the key is exported as `CURSOR_API_KEY` before pi starts. + Actual Cursor runs require `CURSOR_API_KEY` or pi's `--api-key` option. If model discovery cannot authenticate or reach Cursor, pi may still list fallback Cursor models. A runtime setup/auth error means the key was missing, invalid, unauthorized, or not exported into the pi process. Do not store the API key in `~/.pi/agent/cursor-sdk.json`; that file is only for non-secret extension state such as Cursor fast defaults. `PATH` is only for executable lookup and should not contain the API key. @@ -131,7 +139,7 @@ pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only" pi --model cursor/composer-2 --cursor-no-fast -p "Say ok only" ``` -`composer-2` can default to fast. Use `--cursor-no-fast` for a one-shot no-fast `composer-2` run. +`composer-2` can default to fast. Use `--cursor-no-fast` for a one-shot no-fast `composer-2` run. In print mode (`-p`), `--cursor-no-fast` is silent and does not write `~/.pi/agent/cursor-sdk.json`; absence of the `cursor fast` status in interactive mode means fast mode is off. When fast is enabled, the default pi footer gets an extension status line: diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index f0b8feb..d53e445 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -41,9 +41,10 @@ class CursorAbortError extends Error { } } +const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY"; const MISSING_API_KEY_MESSAGE = "CURSOR_API_KEY or --api-key is required for Cursor SDK runs."; const GENERIC_CURSOR_SDK_ERROR_MESSAGE = - "Cursor SDK request failed. Verify CURSOR_API_KEY or pass --api-key, then retry."; + "Cursor SDK request failed. The API key may be missing, invalid, or unauthorized. Verify CURSOR_API_KEY or pass --api-key, then retry."; const AUTH_CURSOR_SDK_ERROR_MESSAGE = "Cursor SDK request failed because the API key may be invalid or unauthorized. Verify CURSOR_API_KEY or pass --api-key, then retry."; @@ -75,8 +76,17 @@ function isLikelyAuthError(message: string): boolean { return /\b(unauthorized|unauthorised|forbidden|invalid api key|invalid key|authentication|auth|401|403)\b/i.test(message); } +function hasEnvCursorApiKey(): boolean { + return Boolean(process.env.CURSOR_API_KEY?.trim()); +} + +function isMissingCursorApiKey(apiKey?: string): boolean { + return !apiKey || (apiKey === CURSOR_API_KEY_ENV_VAR && !hasEnvCursorApiKey()); +} + function sanitizeError(error: unknown, apiKey?: string): string { const message = error instanceof Error ? error.message : typeof error === "string" ? error : ""; + if (message === MISSING_API_KEY_MESSAGE) return MISSING_API_KEY_MESSAGE; const scrubbed = scrubSensitiveText(message, apiKey).trim(); if (isGenericErrorMessage(scrubbed)) return GENERIC_CURSOR_SDK_ERROR_MESSAGE; if (isLikelyAuthError(scrubbed)) return AUTH_CURSOR_SDK_ERROR_MESSAGE; @@ -144,7 +154,7 @@ export function streamCursor( throwIfAborted(); const apiKey = options?.apiKey; - if (!apiKey) throw new Error(MISSING_API_KEY_MESSAGE); + if (isMissingCursorApiKey(apiKey)) throw new Error(MISSING_API_KEY_MESSAGE); const cwd = process.cwd(); const fastEnabled = getEffectiveFastForModelId(model.id); diff --git a/src/model-discovery.ts b/src/model-discovery.ts index 79d31c6..4566085 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -166,6 +166,28 @@ export interface DiscoverModelsOptions { onFallback?: (issue: CursorModelFallbackIssue) => void; } +function getCliApiKeyFromArgv(argv: string[] = process.argv): string | undefined { + for (let index = 0; index < argv.length; index++) { + const arg = argv[index]; + if (arg === "--api-key") { + const value = argv[index + 1]; + if (!value || value.startsWith("--")) return undefined; + const trimmed = value.trim(); + return trimmed || undefined; + } + const prefix = "--api-key="; + if (arg.startsWith(prefix)) { + const trimmed = arg.slice(prefix.length).trim(); + return trimmed || undefined; + } + } + return undefined; +} + +function getDiscoveryApiKey(): string | undefined { + return process.env.CURSOR_API_KEY?.trim() || getCliApiKeyFromArgv(); +} + export interface CursorModelMetadata { piModelId: string; baseModelId: string; @@ -462,7 +484,7 @@ function useFallbackModels(options: DiscoverModelsOptions, issue: CursorModelFal } export async function discoverModels(options: DiscoverModelsOptions = {}): Promise { - const apiKey = process.env.CURSOR_API_KEY?.trim(); + const apiKey = getDiscoveryApiKey(); if (!apiKey) { return useFallbackModels(options, { reason: "missing-api-key", @@ -491,4 +513,5 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi export const __testUtils = { parseContextWindow, registerModelItems, + getCliApiKeyFromArgv, }; diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index d155bf6..e2b218d 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -387,6 +387,26 @@ describe("streamCursor", () => { expect((error as any).error.errorMessage).toContain("--api-key"); }); + it("treats unresolved CURSOR_API_KEY provider placeholders as a missing API key", async () => { + const originalKey = process.env.CURSOR_API_KEY; + delete process.env.CURSOR_API_KEY; + try { + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "CURSOR_API_KEY" }); + const events = await collectEvents(stream); + + const error = events.find((e: any) => e.type === "error"); + expect(error).toBeDefined(); + expect((error as any).error.errorMessage).toBe("CURSOR_API_KEY or --api-key is required for Cursor SDK runs."); + expect(mockedCreate).not.toHaveBeenCalled(); + } finally { + if (originalKey === undefined) { + delete process.env.CURSOR_API_KEY; + } else { + process.env.CURSOR_API_KEY = originalKey; + } + } + }); + it("turns generic Cursor SDK failures into actionable setup errors", async () => { mockedCreate.mockRejectedValueOnce(new Error("Error")); diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 8e418c3..1a49c1a 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -30,13 +30,16 @@ function register(items: ModelListItem[]) { describe("discoverModels", () => { const originalEnv = process.env; + const originalArgv = process.argv; beforeEach(() => { process.env = { ...originalEnv }; + process.argv = [...originalArgv]; }); afterEach(() => { process.env = originalEnv; + process.argv = originalArgv; vi.clearAllMocks(); }); @@ -72,6 +75,28 @@ describe("discoverModels", () => { expect(mockedList).not.toHaveBeenCalled(); }); + it("uses pi --api-key for model discovery when CURSOR_API_KEY is unset", async () => { + delete process.env.CURSOR_API_KEY; + process.argv = ["node", "pi", "--api-key", "cli-key-123"]; + mockedList.mockResolvedValueOnce([ + { + id: "composer-2", + displayName: "Composer 2", + variants: [{ params: [], displayName: "Composer 2", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(mockedList).toHaveBeenCalledWith({ apiKey: "cli-key-123" }); + expect(models.map((model) => model.id)).toEqual(["composer-2"]); + }); + + it("parses pi --api-key=value for model discovery", () => { + expect(__testUtils.getCliApiKeyFromArgv(["node", "pi", "--api-key=cli-key-123"])).toBe("cli-key-123"); + expect(__testUtils.getCliApiKeyFromArgv(["node", "pi", "--api-key", "--list-models"])).toBeUndefined(); + }); + it("calls Cursor.models.list with API key and sorts by base id", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ -- 2.51.2 From 38cc5995cc988251aa93fffc8f0199e96a82d363 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 4 May 2026 15:37:45 -0600 Subject: [PATCH 007/359] Update README and improve error messaging for Cursor SDK API key handling - Enhanced README.md to clarify the requirements for using CURSOR_API_KEY and the implications of missing keys during model discovery and execution. - Improved error messages in cursor-provider.ts and model-discovery.ts to provide clearer guidance on API key requirements and fallback behavior. - Updated tests to verify the accuracy of error messages and the handling of API key scenarios in the Cursor SDK. --- README.md | 12 +++++++----- docs/cursor-model-ux-spec.md | 14 ++++++++------ src/cursor-provider.ts | 3 ++- src/model-discovery.ts | 5 +++-- test/cursor-provider.test.ts | 4 +++- test/index.test.ts | 5 +++-- test/model-discovery.test.ts | 2 ++ 7 files changed, 28 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c1f4625..3d438f0 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ pi -e . --api-key "your-key" --model cursor/composer-2 --cursor-no-fast -p "Say Use `CURSOR_API_KEY` when possible. It gives the extension a key during startup model discovery. `--api-key` is also read for discovery, but shell wrappers and launchers are easier to diagnose when the key is exported as `CURSOR_API_KEY` before pi starts. -Actual Cursor runs require `CURSOR_API_KEY` or pi's `--api-key` option. If model discovery cannot authenticate or reach Cursor, pi may still list fallback Cursor models. A runtime setup/auth error means the key was missing, invalid, unauthorized, or not exported into the pi process. +Actual Cursor runs require `CURSOR_API_KEY` or pi's `--api-key` option. If model discovery cannot authenticate or reach Cursor, pi may still list fallback Cursor models for selection, but those fallback rows are not a working offline mode. In an already-started interactive session, a missing-key Cursor run will fail until pi is restarted with `CURSOR_API_KEY` exported or `--api-key` passed. A runtime setup/auth error means the key was missing, invalid, unauthorized, or not exported into the pi process. Do not store the API key in `~/.pi/agent/cursor-sdk.json`; that file is only for non-secret extension state such as Cursor fast defaults. `PATH` is only for executable lookup and should not contain the API key. @@ -102,14 +102,16 @@ Rules: ## Thinking support -Use pi's native thinking controls: +All Cursor SDK models should be treated as thinking-capable Cursor models. The `thinking` column in `pi --list-models` is narrower: it only means pi can control a Cursor SDK thinking parameter for that model. + +Use pi's native thinking controls for models where Cursor exposes `reasoning`, `effort`, or boolean `thinking` parameters: ```bash pi --model cursor/gpt-5.5@1m --thinking medium -p "Say ok only" pi --model cursor/gpt-5.5@272k:xhigh -p "Say ok only" ``` -The extension builds Cursor SDK params from the selected pi thinking level: +For those controllable models, the extension builds Cursor SDK params from the selected pi thinking level: - `reasoning=none|low|medium|high|extra-high` - `effort=low|medium|high|xhigh|max` @@ -119,9 +121,9 @@ For Claude models with both `thinking` and `effort`, pi thinking `off` sends `th ### Why some Cursor models show `thinking=no` -In `pi --list-models`, the `thinking` column means pi can control the model's thinking level with `--thinking`, a final `:medium` model suffix, or shift+tab. +In `pi --list-models`, the `thinking` column means pi can control the model's thinking level with `--thinking`, a final `:medium` model suffix, or shift+tab. It does not mean whether the Cursor model can think. -Some Cursor models can still reason internally, and Cursor may still emit thinking deltas for them, even when Cursor does not expose a `reasoning`, `effort`, or `thinking` parameter for the extension to set. Those models show `thinking=no` because there is no pi-controllable Cursor SDK parameter. The extension does not disable Cursor's own default/internal reasoning behavior. +Some Cursor SDK models do not expose a `reasoning`, `effort`, or `thinking` parameter for the extension to set. Those models show `thinking=no` because there is no pi-controllable Cursor SDK parameter. Cursor thinking is still enabled/supported by the model, and Cursor may still emit thinking deltas. The extension does not disable Cursor's own default reasoning behavior. ## Fast mode diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index f25d769..a0aa892 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -7,13 +7,14 @@ Implemented design target. This file describes the intended Cursor model UX and Current implementation notes: - Cursor context variants use `base@context` pi model IDs. -- Cursor `reasoning`, `effort`, and `thinking` are driven by pi native thinking. +- Cursor `reasoning`, `effort`, and boolean `thinking` parameters are driven by pi native thinking when the Cursor SDK exposes those controls. - Cursor `fast` is extension state, not model identity. - Cursor fast status uses `ctx.ui.setStatus()`; the default pi footer remains intact. - Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`. - `@cursor/sdk` is a package dependency of this extension; users should not need a global SDK install. - Cursor auth uses the `CURSOR_API_KEY` environment variable. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. +- Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. - Cursor-side thinking and tool activity are surfaced as trace content before final text. Cursor text deltas are buffered until the run finishes so the saved message order is trace first, final answer second. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. @@ -24,7 +25,7 @@ Make Cursor models feel native in pi by leaning on pi's existing model, thinking Main outcomes: -- `pi --list-models` shows pi-native Cursor models with accurate `contextWindow`, thinking metadata, and conservative defaults where the Cursor SDK does not expose limits or capabilities. +- `pi --list-models` shows pi-native Cursor models with accurate `contextWindow`, pi-controllable thinking metadata, and conservative defaults where the Cursor SDK does not expose limits or capabilities. - `shift+tab` is pi's native thinking control and drives Cursor `reasoning` or `effort`. - Cursor context options are represented as pi-visible model variants when they change native model metadata. - Cursor-only state, currently `fast`, is controlled by extension commands and shown through native status text. @@ -68,7 +69,7 @@ Pi model metadata is also a source of truth for pi-native behavior: - `ProviderModelConfig.id` - `ProviderModelConfig.name` -- `ProviderModelConfig.reasoning`: means pi-controllable thinking, not whether a Cursor model can reason internally +- `ProviderModelConfig.reasoning`: means pi-controllable thinking, not whether a Cursor model is thinking-capable - `ProviderModelConfig.thinkingLevelMap` - `ProviderModelConfig.contextWindow` - `ProviderModelConfig.maxTokens` @@ -167,7 +168,7 @@ Each registered model must set: - `id`: context-qualified pi model ID when needed. - `name`: human-readable Cursor display name plus context when useful. -- `reasoning`: `true` only if a Cursor `reasoning`, `effort`, or `thinking` parameter can map to pi thinking. This controls pi's thinking UI and `pi --list-models` `thinking` column; it must not be used to claim whether the Cursor model can reason internally. +- `reasoning`: `true` only if a Cursor `reasoning`, `effort`, or `thinking` parameter can map to pi thinking. This controls pi's thinking UI and `pi --list-models` `thinking` column; it must not be used to claim whether the Cursor model can think internally. Cursor SDK models are thinking-capable even when this is `false`. - `thinkingLevelMap`: model-specific pi-to-Cursor mapping for pi UI, clamping, persistence, and footer display. - `contextWindow`: parsed from context variant, else conservative fallback. - `maxTokens`: conservative explicit value until Cursor SDK exposes output limits. @@ -221,10 +222,11 @@ Do not add a context-cycle shortcut in the first pass. Context is a pi model var Important distinction: +- **Cursor thinking support** applies to all Cursor SDK models. The extension should assume Cursor models can think and may emit thinking deltas. - **Pi-controllable thinking** means Cursor exposes a `reasoning`, `effort`, or `thinking` parameter that the extension can set from pi's native thinking level. These models register `reasoning: true` and show `thinking=yes` in `pi --list-models`. -- **Cursor internal reasoning** means the model may reason by default, and Cursor may emit thinking deltas, but Cursor does not expose a user-controllable thinking parameter. These models register `reasoning: false` and show `thinking=no` in `pi --list-models` because pi cannot control a level for them. The extension still parses Cursor `thinking-delta` events if they are emitted. +- **Cursor SDK thinking-control gap** means the model can still think, but the SDK does not expose a user-controllable thinking parameter for that model. These models register `reasoning: false` and show `thinking=no` in `pi --list-models` because pi cannot control a level for them. The extension still parses Cursor `thinking-delta` events if they are emitted. -Do not mark a model `reasoning: true` only because it can reason internally. That would make pi show controls such as `--thinking`, `:medium`, and shift+tab even though the extension cannot translate them into Cursor SDK params. +Do not mark a model `reasoning: true` only because it can think. That would make pi show controls such as `--thinking`, `:medium`, and shift+tab even though the extension cannot translate them into Cursor SDK params. Pi levels: diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index d53e445..4107123 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -42,7 +42,8 @@ class CursorAbortError extends Error { } const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY"; -const MISSING_API_KEY_MESSAGE = "CURSOR_API_KEY or --api-key is required for Cursor SDK runs."; +const MISSING_API_KEY_MESSAGE = + "Cursor SDK runs require CURSOR_API_KEY or pi --api-key. Set CURSOR_API_KEY before starting pi, or restart pi with --api-key."; const GENERIC_CURSOR_SDK_ERROR_MESSAGE = "Cursor SDK request failed. The API key may be missing, invalid, or unauthorized. Verify CURSOR_API_KEY or pass --api-key, then retry."; const AUTH_CURSOR_SDK_ERROR_MESSAGE = diff --git a/src/model-discovery.ts b/src/model-discovery.ts index 4566085..9befef0 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -489,7 +489,7 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi return useFallbackModels(options, { reason: "missing-api-key", message: - "CURSOR_API_KEY is required for Cursor model discovery; using fallback Cursor model list. Cursor runs can still use CURSOR_API_KEY or --api-key.", + "CURSOR_API_KEY or --api-key is required for Cursor model discovery. Using fallback Cursor models for selection only; Cursor runs in this session will fail until pi is restarted with a key.", }); } @@ -505,7 +505,8 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi } catch { return useFallbackModels(options, { reason: "discovery-failed", - message: "Cursor model discovery failed. Verify CURSOR_API_KEY or pass --api-key for runs; using fallback Cursor model list.", + message: + "Cursor model discovery failed. Using fallback Cursor models for selection only; verify CURSOR_API_KEY or restart pi with --api-key before running Cursor models.", }); } } diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index e2b218d..1632a3f 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -396,7 +396,9 @@ describe("streamCursor", () => { const error = events.find((e: any) => e.type === "error"); expect(error).toBeDefined(); - expect((error as any).error.errorMessage).toBe("CURSOR_API_KEY or --api-key is required for Cursor SDK runs."); + expect((error as any).error.errorMessage).toBe( + "Cursor SDK runs require CURSOR_API_KEY or pi --api-key. Set CURSOR_API_KEY before starting pi, or restart pi with --api-key.", + ); expect(mockedCreate).not.toHaveBeenCalled(); } finally { if (originalKey === undefined) { diff --git a/test/index.test.ts b/test/index.test.ts index d8d73f9..6047ef5 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -116,7 +116,8 @@ describe("extension factory", () => { mockedDiscover.mockImplementationOnce(async (options: any) => { options.onFallback({ reason: "missing-api-key", - message: "CURSOR_API_KEY or --api-key is required; using fallback Cursor model list.", + message: + "CURSOR_API_KEY or --api-key is required for Cursor model discovery. Using fallback Cursor models for selection only; Cursor runs in this session will fail until pi is restarted with a key.", }); return [ { @@ -144,7 +145,7 @@ describe("extension factory", () => { await sessionHandlers.at(-1)!({}, ctx); expect(notify).toHaveBeenCalledWith( - "CURSOR_API_KEY or --api-key is required; using fallback Cursor model list.", + "CURSOR_API_KEY or --api-key is required for Cursor model discovery. Using fallback Cursor models for selection only; Cursor runs in this session will fail until pi is restarted with a key.", "warning", ); }); diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 1a49c1a..1ed9352 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -63,6 +63,8 @@ describe("discoverModels", () => { }), ]); expect(JSON.stringify(issues)).toContain("--api-key"); + expect(JSON.stringify(issues)).toContain("selection only"); + expect(JSON.stringify(issues)).toContain("will fail until pi is restarted"); expect(mockedList).not.toHaveBeenCalled(); }); -- 2.51.2 From b55ab5b235635366cdc3be7d0827b69cc8223439 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 4 May 2026 15:49:31 -0600 Subject: [PATCH 008/359] Update .gitignore, add CHANGELOG, and enhance package metadata - Updated .gitignore to exclude the entire .pi directory instead of just settings.json. - Added CHANGELOG.md to document the initial public release and features of version 0.1.0. - Enhanced package.json with author, repository, bugs, and homepage fields for better project visibility. - Specified Node.js engine requirement in package-lock.json to ensure compatibility. - Improved README.md with clearer installation instructions and usage examples for the pi-cursor-sdk extension. --- .gitignore | 2 +- CHANGELOG.md | 22 ++++ README.md | 225 ++++++++++++++++++++++++----------- docs/cursor-model-ux-spec.md | 2 + package-lock.json | 3 + package.json | 16 ++- src/context-window-cache.ts | 32 +++-- src/model-discovery.ts | 3 +- test/cursor-provider.test.ts | 2 +- test/model-discovery.test.ts | 1 + 10 files changed, 224 insertions(+), 84 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.gitignore b/.gitignore index 6de1742..c01669d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ coverage/ .env .env.* *.tgz -.pi/settings.json +.pi/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..377e0df --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +## 0.1.0 - 2026-05-04 + +Initial public release. + +### Added + +- Cursor provider registration for pi backed by local `@cursor/sdk` agents. +- Cursor model discovery with fallback startup models when discovery is unavailable. +- Context-window model variants such as `cursor/gpt-5.5@1m` and `cursor/gpt-5.5@272k`. +- Pi native thinking-level mapping for Cursor SDK `reasoning`, `effort`, and boolean `thinking` controls when exposed by the SDK. +- Cursor fast-mode controls through `/cursor-fast`, `--cursor-fast`, and `--cursor-no-fast`. +- Image forwarding from the latest user message to Cursor. +- Cursor-side trace output before final text while preserving pi's default footer. +- Local context-window override cache from successful Cursor SDK checkpoint metadata. + +### Notes + +- All Cursor SDK models are treated as thinking-capable, even when `pi --list-models` shows `thinking=no`; that column only means pi cannot control a thinking parameter for that model. +- Fallback Cursor models are selection-only. Actual Cursor runs require `CURSOR_API_KEY` or pi's `--api-key`. +- Cursor cloud agents, Cursor Max Mode selection, pi tool-schema forwarding, and ambient Cursor setting/rule loading are not supported in this release. diff --git a/README.md b/README.md index 3d438f0..acc6130 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,148 @@ # pi-cursor-sdk -pi provider extension backed by `@cursor/sdk` local agents. +A pi provider extension that lets pi use Cursor models through the local `@cursor/sdk` agent runtime. -## What this is +Use this extension if you want Cursor's model catalog inside pi while keeping pi's native model picker, thinking controls where the SDK exposes them, session restore, context display, and default footer UX. -This package lets pi use Cursor models through the local Cursor SDK while keeping model selection, thinking, context display, and the default footer native to pi. +## Quick start -Current behavior: +1. Install the package: -- discovers Cursor models with `Cursor.models.list()` when `CURSOR_API_KEY` is set -- registers Cursor models under the `cursor` provider -- registers one pi model per Cursor `context` value, using IDs like `gpt-5.5@1m` -- maps Cursor `reasoning`, `effort`, and boolean `thinking` to pi native thinking levels -- keeps Cursor `fast` as extension state, toggled with `/cursor-fast` or forced for one run with `--cursor-fast` / `--cursor-no-fast` -- shows Cursor fast mode through `ctx.ui.setStatus()` and leaves pi's default footer intact -- creates a fresh local Cursor agent for each pi provider call +```bash +pi install npm:pi-cursor-sdk +``` + +Or install from GitHub: + +```bash +pi install https://github.com/fitchmultz/pi-cursor-sdk +``` + +2. Export your Cursor API key before starting pi: + +```bash +export CURSOR_API_KEY="your-key" +``` + +3. Start pi with a Cursor model: + +```bash +pi --model cursor/composer-2 +``` + +Inside pi, use `/model` to choose another Cursor model. ## Requirements - Node.js 18+ - pi -- a Cursor API key exposed as the `CURSOR_API_KEY` environment variable for full model discovery +- a Cursor API key available as `CURSOR_API_KEY` or passed with pi's `--api-key` -No global `@cursor/sdk` install is required. This package depends on `@cursor/sdk`, so a normal package install brings in the SDK version the extension was built and tested against. +No global `@cursor/sdk` install is required. This package depends on `@cursor/sdk`, so normal package installation brings in the SDK version this extension was built and tested against. ## Install -### Local development from a checkout +### Global install ```bash -npm install -pi -e . +pi install npm:pi-cursor-sdk ``` -Pick a model with `/model`, or pass one directly: +Alternative GitHub install: ```bash -pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." +pi install https://github.com/fitchmultz/pi-cursor-sdk ``` -### Pi package install from a package source +### Project-local install -Install a local package path: +Use `-l` if you want the package recorded in the current project's `.pi/settings.json` instead of your global pi settings: ```bash -pi install /absolute/path/to/pi-cursor-sdk +pi install -l npm:pi-cursor-sdk ``` -Use the npm source form when installing from npm: +### Try from a local checkout + +For development from this repository: ```bash -pi install npm:pi-cursor-sdk +npm install +pi -e . --model cursor/composer-2 ``` -## API key +## Configure your Cursor API key -Set `CURSOR_API_KEY` in the environment before starting pi: +Preferred setup: ```bash export CURSOR_API_KEY="your-key" +pi --model cursor/composer-2 ``` -Or pass it for one command: +One-shot setup: ```bash -CURSOR_API_KEY="your-key" pi -e . --model cursor/gpt-5.5@1m -p "Say ok only." +pi --api-key "your-key" --model cursor/composer-2 --cursor-no-fast -p "Say ok only." ``` -You can also pass pi's `--api-key` option for a one-shot run: +Use `CURSOR_API_KEY` when possible. It gives the extension a key during startup model discovery. `--api-key` is also read for discovery and runs, but shell wrappers and launchers are easier to diagnose when the key is exported as `CURSOR_API_KEY` before pi starts. -```bash -pi -e . --api-key "your-key" --model cursor/composer-2 --cursor-no-fast -p "Say ok only." -``` +Important: if pi starts without a valid `CURSOR_API_KEY` or `--api-key`, model discovery may fall back to a conservative Cursor model list. That fallback list is only for selection/display. Cursor runs in that already-started session will fail until you restart pi with a valid key. -Use `CURSOR_API_KEY` when possible. It gives the extension a key during startup model discovery. `--api-key` is also read for discovery, but shell wrappers and launchers are easier to diagnose when the key is exported as `CURSOR_API_KEY` before pi starts. +Do not store the API key in `~/.pi/agent/cursor-sdk.json`. That file is only for non-secret extension state such as Cursor fast defaults. `PATH` is only for executable lookup and should not contain the API key. -Actual Cursor runs require `CURSOR_API_KEY` or pi's `--api-key` option. If model discovery cannot authenticate or reach Cursor, pi may still list fallback Cursor models for selection, but those fallback rows are not a working offline mode. In an already-started interactive session, a missing-key Cursor run will fail until pi is restarted with `CURSOR_API_KEY` exported or `--api-key` passed. A runtime setup/auth error means the key was missing, invalid, unauthorized, or not exported into the pi process. +## Verify your setup -Do not store the API key in `~/.pi/agent/cursor-sdk.json`; that file is only for non-secret extension state such as Cursor fast defaults. `PATH` is only for executable lookup and should not contain the API key. +List Cursor models: -## Model IDs +```bash +pi --list-models cursor +``` -Cursor-only parameters are not encoded into pi model IDs. +Expected behavior: -For models where `Cursor.models.list()` exposes a `context` parameter, the extension parses that context value directly. For models where the catalog does not include a context parameter, the extension ships a bundled SDK-derived default/non-Max context-window cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. +- with a valid key, Cursor models appear under the `cursor` provider +- if discovery cannot authenticate or reach Cursor, pi may still show fallback Cursor models, but those rows are selection-only and actual runs will fail until restart with a valid key -Max Mode has larger context windows, but `@cursor/sdk` 1.0.12 does not expose a public `ModelSelection` field that enables Max Mode for these local agent runs. The extension therefore does not advertise Max Mode windows for non-Max model IDs. Add Max-specific pi model IDs only when the SDK exposes an exact Max Mode selector and the implementation uses that selector. +Smoke test: -Examples: +```bash +pi --model cursor/composer-2 --cursor-no-fast -p "Reply with: ok" +``` -- `cursor/composer-2` -- `cursor/gpt-5.5@1m` -- `cursor/gpt-5.5@272k` -- `cursor/claude-opus-4-7@300k` +## Choosing a model -Rules: +Choose Cursor models interactively with `/model`, or pass a model on the command line: -- Cursor `context` becomes a pi-visible model variant because it changes `contextWindow`. -- Cursor `reasoning`, `effort`, and `thinking` map to pi native thinking. -- Cursor `fast` is extension state, not model identity. +```bash +pi --model cursor/composer-2 +pi --model cursor/gpt-5.5@1m +pi --model cursor/gpt-5.5@272k +pi --model cursor/claude-opus-4-7@300k +``` -## Thinking support +How to read model IDs: -All Cursor SDK models should be treated as thinking-capable Cursor models. The `thinking` column in `pi --list-models` is narrower: it only means pi can control a Cursor SDK thinking parameter for that model. +- `cursor/...` is the Cursor provider registered by this extension +- `@1m`, `@272k`, and `@300k` are context-window variants +- `:medium`, `:high`, and `:xhigh` are pi thinking-level suffixes for models where the Cursor SDK exposes a pi-controllable thinking parameter -Use pi's native thinking controls for models where Cursor exposes `reasoning`, `effort`, or boolean `thinking` parameters: +Examples with pi thinking controls: ```bash -pi --model cursor/gpt-5.5@1m --thinking medium -p "Say ok only" -pi --model cursor/gpt-5.5@272k:xhigh -p "Say ok only" +pi --model cursor/gpt-5.5@1m:medium +pi --model cursor/gpt-5.5@272k:xhigh +pi --model cursor/gpt-5.5@1m --thinking medium ``` -For those controllable models, the extension builds Cursor SDK params from the selected pi thinking level: +Cursor-only parameters are not encoded into pi model IDs. Cursor `context` becomes a pi-visible model variant because it changes pi's native `contextWindow`; Cursor `fast` is extension state, not model identity. + +## Thinking support + +All Cursor SDK models should be treated as thinking-capable Cursor models. The `thinking` column in `pi --list-models` is narrower: it only means pi can control a Cursor SDK thinking parameter for that model. + +For models where Cursor exposes `reasoning`, `effort`, or boolean `thinking` parameters, pi's native thinking controls map to Cursor SDK params: - `reasoning=none|low|medium|high|extra-high` - `effort=low|medium|high|xhigh|max` @@ -121,58 +152,103 @@ For Claude models with both `thinking` and `effort`, pi thinking `off` sends `th ### Why some Cursor models show `thinking=no` -In `pi --list-models`, the `thinking` column means pi can control the model's thinking level with `--thinking`, a final `:medium` model suffix, or shift+tab. It does not mean whether the Cursor model can think. +In `pi --list-models`, `thinking=no` means pi cannot control the model's thinking level with `--thinking`, a final `:medium` model suffix, or shift+tab. It does not mean the Cursor model cannot think. -Some Cursor SDK models do not expose a `reasoning`, `effort`, or `thinking` parameter for the extension to set. Those models show `thinking=no` because there is no pi-controllable Cursor SDK parameter. Cursor thinking is still enabled/supported by the model, and Cursor may still emit thinking deltas. The extension does not disable Cursor's own default reasoning behavior. +Some Cursor SDK models do not expose a `reasoning`, `effort`, or `thinking` parameter for the extension to set. Cursor thinking is still enabled/supported by the model, and Cursor may still emit thinking deltas. The extension does not disable Cursor's default reasoning behavior. ## Fast mode -Use `/cursor-fast` to persistently toggle fast mode for the selected Cursor model when supported. +Use `/cursor-fast` to persistently toggle fast mode for the selected Cursor model when the model supports Cursor's `fast` parameter. -Fast preferences are stored: +Fast preferences are remembered per Cursor base model and stored: - in the current session with `pi.appendEntry()` -- globally per Cursor base model in `~/.pi/agent/cursor-sdk.json` +- globally in `~/.pi/agent/cursor-sdk.json` -For one run, force fast on or off without changing stored preferences: +For one run, force fast on or off without changing saved defaults: ```bash pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only" pi --model cursor/composer-2 --cursor-no-fast -p "Say ok only" ``` -`composer-2` can default to fast. Use `--cursor-no-fast` for a one-shot no-fast `composer-2` run. In print mode (`-p`), `--cursor-no-fast` is silent and does not write `~/.pi/agent/cursor-sdk.json`; absence of the `cursor fast` status in interactive mode means fast mode is off. +`composer-2` can default to fast. Use `--cursor-no-fast` for a one-shot no-fast `composer-2` run. In print mode (`-p`), `--cursor-no-fast` is silent and does not write `~/.pi/agent/cursor-sdk.json`. -When fast is enabled, the default pi footer gets an extension status line: +In interactive mode, the footer only shows fast mode when fast is enabled: ```text cursor fast ``` +If you do not see `cursor fast`, fast mode is off. + ## Images Images from the latest user message are forwarded to Cursor. Historical images are kept out of the transcript. The extension advertises `text` and `image` input for Cursor models because Cursor's SDK accepts image messages and Cursor models are expected to support them. ## Fallback models -If `CURSOR_API_KEY` is missing or model discovery fails, the extension registers conservative fallback Cursor models with the same native shape and notifies interactive users when possible: +If `CURSOR_API_KEY` is missing, model discovery fails, or discovery returns no models, the extension registers conservative fallback Cursor models and notifies interactive users when possible: - `composer-2` - `gpt-5.5@1m`, `gpt-5.5@272k` - `claude-sonnet-4-6@1m`, `claude-sonnet-4-6@300k` - `claude-opus-4-7@1m`, `claude-opus-4-7@300k` -Fallback models are only a startup model list. Actual Cursor runs still need `CURSOR_API_KEY` or `--api-key`. If a run fails with a Cursor SDK setup/auth message, verify the key is correct and exported to the same shell or process that starts pi. +Fallback models are only a startup model list. Actual Cursor runs still need `CURSOR_API_KEY` or `--api-key`. ## Limits -- local agents only; no Cursor cloud agent support -- Cursor tool calls are not exposed as pi tool calls; Cursor-side tool activity is surfaced as compact trace text before the final answer -- pi tool schemas are not passed through to Cursor -- Cursor text deltas are buffered until Cursor trace/tool activity is complete so the final answer does not appear before its trace -- one fresh Cursor agent per provider call -- ambient Cursor setting/rule layers are not loaded by default because the current Cursor SDK writes setting-load logs directly to terminal output, which corrupts pi's TUI -- Cursor SDK model metadata does not currently expose output token limits, so the extension uses conservative token defaults +- **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. +- **Cursor-side tool use is not exposed as pi tool calls.** You may see compact Cursor trace text before the final answer, but you will not get normal pi tool-call rows for Cursor's internal tool activity. +- **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. +- **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. +- **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. +- **Max Mode is not exposed for these local runs.** The extension only advertises exact context windows supported by the SDK path it uses. +- **Output token limits are conservative.** Cursor SDK model metadata does not currently expose output token limits directly. + +## Troubleshooting + +### I can see Cursor models, but runs fail + +You may be seeing fallback startup models. Restart pi with a key in the same shell or launcher that starts pi: + +```bash +export CURSOR_API_KEY="your-key" +pi --model cursor/composer-2 +``` + +Or run a one-shot command: + +```bash +pi --api-key "your-key" --model cursor/composer-2 -p "Say ok only" +``` + +### `pi --list-models cursor` shows no Cursor models + +Confirm the package is installed: + +```bash +pi list +``` + +Then reinstall if needed: + +```bash +pi install npm:pi-cursor-sdk +``` + +### `pi --list-models` shows `thinking=no` + +That does not mean the model cannot think. It means the Cursor SDK does not expose a pi-controllable thinking parameter for that model. The model may still think internally and may still emit thinking deltas. + +### I do not see `cursor fast` in the footer + +Fast mode is currently off. The footer only shows `cursor fast` when fast mode is enabled. + +### My Cursor app settings or rules do not seem to apply + +They are not loaded by default in this extension. See [Limits](#limits). ## Development @@ -183,6 +259,15 @@ npm test npm run typecheck ``` +Local development run: + +```bash +npm install +CURSOR_API_KEY="your-key" pi -e . --model cursor/composer-2 +``` + +Maintainer design notes live in [`docs/cursor-model-ux-spec.md`](docs/cursor-model-ux-spec.md). + ## License MIT diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index a0aa892..04e3abb 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -1,5 +1,7 @@ # Cursor Model UX Spec +> Maintainer note: this is an internal design and behavior spec for pi-cursor-sdk. If you are trying to install or use the extension, start with the main [README](../README.md) instead. + ## Status Implemented design target. This file describes the intended Cursor model UX and should stay aligned with the current code in `src/`. diff --git a/package-lock.json b/package-lock.json index 4b7f79a..603aca3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,9 @@ "typescript": "^6.0.3", "vitest": "^4.1.5" }, + "engines": { + "node": ">=18" + }, "peerDependencies": { "@mariozechner/pi-ai": "*", "@mariozechner/pi-coding-agent": "*" diff --git a/package.json b/package.json index f8fa765..fee27dd 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,22 @@ "name": "pi-cursor-sdk", "version": "0.1.0", "description": "pi provider extension backed by @cursor/sdk local agents", + "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", - "keywords": ["pi-package", "pi", "cursor", "cursor-sdk", "ai", "extension"], - "files": ["src", "README.md", "docs"], + "keywords": ["pi-package", "pi", "pi-extension", "cursor", "cursor-sdk", "ai", "extension"], + "repository": { + "type": "git", + "url": "git+https://github.com/fitchmultz/pi-cursor-sdk.git" + }, + "bugs": { + "url": "https://github.com/fitchmultz/pi-cursor-sdk/issues" + }, + "homepage": "https://github.com/fitchmultz/pi-cursor-sdk#readme", + "files": ["src", "README.md", "docs", "LICENSE", "CHANGELOG.md"], "type": "module", + "engines": { + "node": ">=18" + }, "scripts": { "typecheck": "tsc --noEmit", "test": "vitest run", diff --git a/src/context-window-cache.ts b/src/context-window-cache.ts index ee44eea..5b2ca1a 100644 --- a/src/context-window-cache.ts +++ b/src/context-window-cache.ts @@ -17,17 +17,25 @@ function isPositiveInteger(value: unknown): value is number { return typeof value === "number" && Number.isInteger(value) && value > 0; } -export function loadContextWindowCache(): Map { - const cache = new Map(Object.entries(BUNDLED_CONTEXT_WINDOWS)); +function loadUserContextWindowOverrides(): Map { const path = getCachePath(); - if (!existsSync(path)) return cache; + const overrides = new Map(); + if (!existsSync(path)) return overrides; try { const parsed = JSON.parse(readFileSync(path, "utf-8")) as ContextWindowCacheFile; for (const [modelId, contextWindow] of Object.entries(parsed.contextWindows ?? {})) { - if (isPositiveInteger(contextWindow)) cache.set(modelId, contextWindow); + if (isPositiveInteger(contextWindow)) overrides.set(modelId, contextWindow); } } catch { - return cache; + return overrides; + } + return overrides; +} + +export function loadContextWindowCache(): Map { + const cache = new Map(Object.entries(BUNDLED_CONTEXT_WINDOWS)); + for (const [modelId, contextWindow] of loadUserContextWindowOverrides()) { + cache.set(modelId, contextWindow); } return cache; } @@ -47,13 +55,19 @@ export function getCheckpointContextWindow(checkpoint: unknown): number | undefi export function saveCachedContextWindow(modelId: string, contextWindow: number): void { if (!isPositiveInteger(contextWindow)) return; - const cache = loadContextWindowCache(); - if (cache.get(modelId) === contextWindow) return; - cache.set(modelId, contextWindow); + const overrides = loadUserContextWindowOverrides(); + const bundledContextWindow = BUNDLED_CONTEXT_WINDOWS[modelId as keyof typeof BUNDLED_CONTEXT_WINDOWS]; + if (bundledContextWindow === contextWindow) { + if (!overrides.has(modelId)) return; + overrides.delete(modelId); + } else { + if (overrides.get(modelId) === contextWindow) return; + overrides.set(modelId, contextWindow); + } const path = getCachePath(); mkdirSync(dirname(path), { recursive: true }); const data: ContextWindowCacheFile = { - contextWindows: Object.fromEntries([...cache.entries()].sort(([a], [b]) => a.localeCompare(b))), + contextWindows: Object.fromEntries([...overrides.entries()].sort(([a], [b]) => a.localeCompare(b))), }; writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 }); } diff --git a/src/model-discovery.ts b/src/model-discovery.ts index 9befef0..a455785 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -500,7 +500,8 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi } return useFallbackModels(options, { reason: "empty-model-list", - message: "Cursor model discovery returned no models; using fallback Cursor model list.", + message: + "Cursor model discovery returned no models. Using fallback Cursor models for selection only; verify CURSOR_API_KEY or restart pi with --api-key before running Cursor models.", }); } catch { return useFallbackModels(options, { diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 1632a3f..856949f 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -595,7 +595,7 @@ describe("streamCursor", () => { expect(loadLatest).toHaveBeenCalledWith("agent-ctx"); const cache = JSON.parse(readFileSync(contextWindowCacheTestUtils.getCachePath(), "utf-8")); - expect(cache.contextWindows["composer-2"]).toBe(201000); + expect(cache.contextWindows).toEqual({ "composer-2": 201000 }); } finally { if (originalAgentDir === undefined) { delete process.env.PI_CODING_AGENT_DIR; diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 1ed9352..9b13ee8 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -541,6 +541,7 @@ describe("discoverModels", () => { message: expect.stringContaining("Cursor model discovery returned no models"), }), ]); + expect(JSON.stringify(issues)).toContain("selection only"); }); it("uses id as name when displayName is missing", async () => { -- 2.51.2 From bf7ee74035b4e071f98338f5606f7439bb82fa44 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 4 May 2026 17:54:17 -0600 Subject: [PATCH 009/359] Add AGENTS.md for repository documentation and update .gitignore --- .gitignore | 1 + AGENTS.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index c01669d..4391898 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ coverage/ .env.* *.tgz .pi/ +demo-script.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4649ba8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,78 @@ +# AGENTS.md + +## Purpose + +This repository is a pi provider extension that registers Cursor SDK-backed models under the `cursor` provider. Agent work is successful when changes preserve pi-native model/thinking/session behavior, keep Cursor API keys out of repo state and logs, and pass the local validation commands below. + +## Repository map + +- `src/index.ts` registers the pi extension, provider, fallback warnings, and Cursor fast controls. +- `src/model-discovery.ts` discovers Cursor models, builds pi model metadata, stores per-model metadata, and defines fallback models. +- `src/cursor-provider.ts` streams through local `@cursor/sdk` agents and sanitizes Cursor SDK errors. +- `src/cursor-state.ts` owns `/cursor-fast`, `--cursor-fast`, `--cursor-no-fast`, session state, and global fast defaults. +- `src/context.ts`, `src/context-window-cache.ts`, and `src/bundled-context-windows.ts` handle prompt conversion and context-window caches. +- `test/**/*.test.ts` contains Vitest coverage for provider registration, discovery, state, context, and streaming behavior. +- `docs/cursor-model-ux-spec.md` is the maintainer design source of truth for Cursor model UX. Keep it aligned with behavior changes. + +## Operating rules + +- Prefer the smallest change that preserves the current pi user contract. +- Treat Cursor SDK model metadata as the source of truth for model IDs, parameters, variants, thinking controls, and context variants. Do not hardcode new model-specific behavior unless it is a documented fallback. +- Keep pi-native abstractions first: context is a model variant, thinking uses pi thinking metadata, and Cursor-only `fast` is extension state/status. +- Preserve the default pi footer; use extension status only for Cursor-only state such as `cursor fast`. +- Stop discovery once package scripts, README, config files, tests, and the relevant `src/` modules explain the task. Do not broad-search `node_modules` unless debugging a dependency API. +- Ask the user before changing public UX, published package metadata, dependency families, or behavior that requires a migration. Otherwise proceed and verify locally. + +## Setup and commands + +- Install dependencies: `npm install` +- Run tests: `npm test` +- Typecheck: `npm run typecheck` +- Watch tests while developing: `npm run test:watch` +- Local development run, requires a Cursor key: `CURSOR_API_KEY="your-key" pi -e . --model cursor/composer-2` +- List Cursor models, requires pi and usually a Cursor key: `pi --list-models cursor` + +There is no lint or format script in `package.json` at this time. + +## Coding conventions + +- TypeScript is ESM with `moduleResolution: "NodeNext"`; keep `.js` extensions on local relative imports. +- Keep strict TypeScript types. Avoid `any` except in tests or when narrowing untyped external SDK data. +- Keep provider runtime code side-effect-light. Do not write secrets, and do not let cache or discovery failures break response streaming unless the run cannot proceed safely. +- Add or update tests for behavior changes in `src/`. Prefer focused unit tests over live Cursor calls. +- If dependency versions change, update `package-lock.json` with npm. Do not manually edit generated dependency output. +- Do not commit `dist/`, `coverage/`, `.env*`, `.pi/`, or package tarballs. + +## Validation and done criteria + +Done means: + +- The intended behavior or documentation change is complete. +- `npm test` and `npm run typecheck` pass, unless the change is docs-only and the user asked for minimal validation. +- Related README/docs/tests are updated when behavior, commands, user-visible model IDs, flags, or troubleshooting change. +- No secrets, local API keys, or noisy local state are added. + +If validation fails: + +1. Triage the first failing test/type error to root cause. +2. Fix failures caused by the change. +3. If a failure is unrelated or cannot be run locally, report the command, failure, likely reason, and what still needs verification. + +## Planning and large changes + +Use a short written plan before multi-file behavior changes, SDK integration changes, or public UX changes. Use `PLANS.md` only if a task needs durable multi-session tracking; do not create one for routine edits. + +## Security and side effects + +- NEVER store Cursor API keys in repo files, `~/.pi/agent/cursor-sdk.json`, tests, logs, snapshots, or docs examples. +- Scrub Cursor SDK errors and output that may contain API keys, bearer tokens, cookies, sessions, or auth headers. +- Do not enable ambient Cursor settings/rules loading unless the SDK no longer writes terminal logs that corrupt pi's TUI and the docs/spec are updated. +- Live `pi`/Cursor smoke tests may call external services and require `CURSOR_API_KEY`; run them only when needed and report if skipped. + +## Progress updates and handoff + +For multi-step or tool-heavy work, give short progress updates after meaningful milestones: what changed, what is being checked, and any blocker. Final handoff should include changed files, validation commands/results, skipped checks with reasons, and any follow-up risks. + +## Updating this file + +Keep this file concise and repo-specific. Update it when commands, package layout, safety constraints, or validation expectations change. Put specialized subdirectory rules in a nested `AGENTS.md` only when that subtree has materially different commands or constraints. -- 2.51.2 From 5839ad8f3f9648c35d01e40510a516c6bb8be5d3 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 4 May 2026 18:18:25 -0600 Subject: [PATCH 010/359] Update .gitignore to include docs/public-demo-script.md for better file management --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4391898..d4053e7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ coverage/ *.tgz .pi/ demo-script.md +docs/public-demo-script.md -- 2.51.2 From edf8e2f04c5af8b8f431b337fa61bf8908f628ff Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Tue, 5 May 2026 12:10:53 -0600 Subject: [PATCH 011/359] Prepare 0.1.1 release --- CHANGELOG.md | 11 +++++++++++ package-lock.json | 4 ++-- package.json | 4 ++-- src/context-window-cache.ts | 6 ++++-- src/cursor-provider.ts | 10 +++++----- test/cursor-provider.test.ts | 22 +++++++++++++++++----- test/model-discovery.test.ts | 30 +++++++++++++++++++----------- 7 files changed, 60 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 377e0df..5f97a8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.1.1 - 2026-05-05 + +### Fixed + +- Use the bundled default context window for newly discovered Cursor models that do not expose a catalog `context` parameter. +- Redact more Cursor SDK error formats, including JSON-style `apiKey`, `token`, `session_id`, and multi-pair cookie values. + +### Changed + +- Keep local demo-script notes out of the published npm tarball. + ## 0.1.0 - 2026-05-04 Initial public release. diff --git a/package-lock.json b/package-lock.json index 603aca3..eaf649a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.12" diff --git a/package.json b/package.json index fee27dd..dd7ca14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.0", + "version": "0.1.1", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", @@ -13,7 +13,7 @@ "url": "https://github.com/fitchmultz/pi-cursor-sdk/issues" }, "homepage": "https://github.com/fitchmultz/pi-cursor-sdk#readme", - "files": ["src", "README.md", "docs", "LICENSE", "CHANGELOG.md"], + "files": ["src", "README.md", "docs/cursor-model-ux-spec.md", "LICENSE", "CHANGELOG.md"], "type": "module", "engines": { "node": ">=18" diff --git a/src/context-window-cache.ts b/src/context-window-cache.ts index 5b2ca1a..1441364 100644 --- a/src/context-window-cache.ts +++ b/src/context-window-cache.ts @@ -41,7 +41,8 @@ export function loadContextWindowCache(): Map { } export function getCachedContextWindow(modelId: string): number | undefined { - return loadContextWindowCache().get(modelId); + const cache = loadContextWindowCache(); + return cache.get(modelId) ?? cache.get("default"); } export function getCheckpointContextWindow(checkpoint: unknown): number | undefined { @@ -56,7 +57,8 @@ export function getCheckpointContextWindow(checkpoint: unknown): number | undefi export function saveCachedContextWindow(modelId: string, contextWindow: number): void { if (!isPositiveInteger(contextWindow)) return; const overrides = loadUserContextWindowOverrides(); - const bundledContextWindow = BUNDLED_CONTEXT_WINDOWS[modelId as keyof typeof BUNDLED_CONTEXT_WINDOWS]; + const bundledContextWindow = + BUNDLED_CONTEXT_WINDOWS[modelId as keyof typeof BUNDLED_CONTEXT_WINDOWS] ?? BUNDLED_CONTEXT_WINDOWS.default; if (bundledContextWindow === contextWindow) { if (!overrides.has(modelId)) return; overrides.delete(modelId); diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 4107123..257f952 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -61,11 +61,11 @@ function scrubSensitiveText(text: string, apiKey?: string): string { } return scrubbed .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]") - .replace(/(authorization\s*[:=]\s*)[^\s,;}]+/gi, "$1[redacted]") - .replace(/(api[_-]?key\s*[:=]\s*)[^\s,;}]+/gi, "$1[redacted]") - .replace(/(token\s*[:=]\s*)[^\s,;}]+/gi, "$1[redacted]") - .replace(/(cookie\s*[:=]\s*)[^\n]+/gi, "$1[redacted]") - .replace(/(session\s*[:=]\s*)[^\s,;}]+/gi, "$1[redacted]"); + .replace(/((?:^|[\s,{])cookie["']?\s*[:=]\s*["']?)[^\n]+/gi, "$1[redacted]") + .replace( + /((?:authorization|api[_-]?key|apiKey|token|session(?:[_-]?id)?)["']?\s*[:=]\s*["']?)[^"'\s,;}]+/gi, + "$1[redacted]", + ); } function isGenericErrorMessage(message: string): boolean { diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 856949f..3eefe50 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -472,8 +472,12 @@ describe("streamCursor", () => { expect(mockDispose).toHaveBeenCalledTimes(1); }); - it("does not leak API key in error messages", async () => { - const mockSend = vi.fn().mockRejectedValue(new Error("boom")); + it("redacts common secret-bearing fields in Cursor SDK error messages", async () => { + const mockSend = vi.fn().mockRejectedValue( + new Error( + 'request failed {"apiKey":"super-secret-key-12345","token":"token-value","session_id":"session-value"} cookie: foo=bar; baz=qux', + ), + ); mockedCreate.mockResolvedValue({ send: mockSend, [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), @@ -482,9 +486,17 @@ describe("streamCursor", () => { const stream = streamCursor(makeModel(), makeContext(), { apiKey: "super-secret-key-12345" }); const events = await collectEvents(stream); - const error = events.find((e: any) => e.type === "error"); - const errorText = JSON.stringify(error); - expect(errorText).not.toContain("super-secret-key-12345"); + const error = events.find((e: any) => e.type === "error") as any; + const message = error.error.errorMessage; + expect(message).toContain('"apiKey":"[redacted]"'); + expect(message).toContain('"token":"[redacted]"'); + expect(message).toContain('"session_id":"[redacted]"'); + expect(message).toContain("cookie: [redacted]"); + expect(message).not.toContain("super-secret-key-12345"); + expect(message).not.toContain("token-value"); + expect(message).not.toContain("session-value"); + expect(message).not.toContain("foo=bar"); + expect(message).not.toContain("baz=qux"); }); it("cancels run on abort signal", async () => { diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 9b13ee8..7c1e320 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -8,6 +8,7 @@ import { getCursorModelMetadata, getCursorModelMetadataEntries, __testUtils, + type CursorModelFallbackIssue, } from "../src/model-discovery.js"; import { saveCachedContextWindow } from "../src/context-window-cache.js"; @@ -45,7 +46,7 @@ describe("discoverModels", () => { it("returns context-qualified fallback models when no API key", async () => { delete process.env.CURSOR_API_KEY; - const issues: unknown[] = []; + const issues: CursorModelFallbackIssue[] = []; const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); expect(models.map((model) => model.id)).toEqual([ "claude-opus-4-7@1m", @@ -62,15 +63,15 @@ describe("discoverModels", () => { message: expect.stringContaining("CURSOR_API_KEY"), }), ]); - expect(JSON.stringify(issues)).toContain("--api-key"); - expect(JSON.stringify(issues)).toContain("selection only"); - expect(JSON.stringify(issues)).toContain("will fail until pi is restarted"); + expect(issues[0].message).toContain("--api-key"); + expect(issues[0].message).toContain("selection only"); + expect(issues[0].message).toContain("will fail until pi is restarted"); expect(mockedList).not.toHaveBeenCalled(); }); it("returns fallback models and reports missing key when API key is whitespace", async () => { process.env.CURSOR_API_KEY = " "; - const issues: unknown[] = []; + const issues: CursorModelFallbackIssue[] = []; const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); expect(models.some((model) => model.id === "gpt-5.5@1m")).toBe(true); expect(issues).toEqual([expect.objectContaining({ reason: "missing-api-key" })]); @@ -233,12 +234,19 @@ describe("discoverModels", () => { parameters: [{ id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] }], variants: [{ params: [{ id: "fast", value: "true" }], displayName: "Composer 2", isDefault: true }], }, + { + id: "new-sdk-model", + displayName: "New SDK Model", + variants: [{ params: [], displayName: "New SDK Model", isDefault: true }], + }, ]); const models = await discoverModels(); - expect(models[0].id).toBe("composer-2"); - expect(models[0].contextWindow).toBe(200000); + expect(models.map((model) => [model.id, model.contextWindow])).toEqual([ + ["composer-2", 200000], + ["new-sdk-model", 200000], + ]); } finally { rmSync(tmpAgentDir, { recursive: true, force: true }); } @@ -516,7 +524,7 @@ describe("discoverModels", () => { it("falls back and reports discovery failure when Cursor.models.list throws", async () => { process.env.CURSOR_API_KEY = "test-key-123"; - const issues: unknown[] = []; + const issues: CursorModelFallbackIssue[] = []; mockedList.mockRejectedValueOnce(new Error("network error")); const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); expect(models.some((model) => model.id === "composer-2")).toBe(true); @@ -526,12 +534,12 @@ describe("discoverModels", () => { message: expect.stringContaining("Cursor model discovery failed"), }), ]); - expect(JSON.stringify(issues)).not.toContain("test-key-123"); + expect(issues[0].message).not.toContain("test-key-123"); }); it("falls back and reports empty model list when Cursor.models.list returns empty", async () => { process.env.CURSOR_API_KEY = "test-key-123"; - const issues: unknown[] = []; + const issues: CursorModelFallbackIssue[] = []; mockedList.mockResolvedValueOnce([]); const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); expect(models.some((model) => model.id === "claude-opus-4-7@1m")).toBe(true); @@ -541,7 +549,7 @@ describe("discoverModels", () => { message: expect.stringContaining("Cursor model discovery returned no models"), }), ]); - expect(JSON.stringify(issues)).toContain("selection only"); + expect(issues[0].message).toContain("selection only"); }); it("uses id as name when displayName is missing", async () => { -- 2.51.2 From 754e62b40015357105df65a52ae0323f57db19bf Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 7 May 2026 15:44:27 -0600 Subject: [PATCH 012/359] Migrate pi packages to earendil works 0.74.0 --- package-lock.json | 1493 ++++++---------------------------- package.json | 34 +- src/context-window-cache.ts | 2 +- src/context.ts | 2 +- src/cursor-provider.ts | 2 +- src/cursor-state.ts | 4 +- src/index.ts | 2 +- src/model-discovery.ts | 4 +- test/context.test.ts | 2 +- test/cursor-provider.test.ts | 2 +- 10 files changed, 289 insertions(+), 1258 deletions(-) diff --git a/package-lock.json b/package-lock.json index eaf649a..6a31e4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,8 @@ "@cursor/sdk": "^1.0.12" }, "devDependencies": { - "@mariozechner/pi-ai": "^0.72.1", - "@mariozechner/pi-coding-agent": "^0.72.1", + "@earendil-works/pi-ai": "^0.74.0", + "@earendil-works/pi-coding-agent": "^0.74.0", "typescript": "^6.0.3", "vitest": "^4.1.5" }, @@ -21,8 +21,8 @@ "node": ">=18" }, "peerDependencies": { - "@mariozechner/pi-ai": "*", - "@mariozechner/pi-coding-agent": "*" + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*" } }, "node_modules/@anthropic-ai/sdk": { @@ -981,6 +981,105 @@ "win32" ] }, + "node_modules/@earendil-works/pi-agent-core": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.74.0.tgz", + "integrity": "sha512-6GMR7/wwjEJ1EsXLWEz03QOWin4AMrJ/AZoMpgm5DJ6GHsF6q6GOhQbj5Zip4dow3vo/TmBAVqM+vmGfrjGAFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-ai": "^0.74.0", + "typebox": "^1.1.24" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-ai": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.74.0.tgz", + "integrity": "sha512-7M7qcrZY/KEkH4wFkX3eqzvmKru4O88wezNKoN0KD2m4aAOmp9tdW2xCmUgSTSWlKB7b2Xw9QtAgrzHtg6t6iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.91.1", + "@aws-sdk/client-bedrock-runtime": "^3.1030.0", + "@google/genai": "^1.40.0", + "@mistralai/mistralai": "^2.2.0", + "chalk": "^5.6.2", + "openai": "6.26.0", + "partial-json": "^0.1.7", + "proxy-agent": "^6.5.0", + "typebox": "^1.1.24", + "undici": "^7.19.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.74.0.tgz", + "integrity": "sha512-Q5GikbB5vRBrsrrf/uvet53rPSQ1sn5I5mO+l7sIobdXYpS04/X2oOc2UHFm90fNdkl3yU+ANTZL0zOtHbnqRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-agent-core": "^0.74.0", + "@earendil-works/pi-ai": "^0.74.0", + "@earendil-works/pi-tui": "^0.74.0", + "@silvia-odwyer/photon-node": "^0.3.4", + "chalk": "^5.5.0", + "cli-highlight": "^2.1.11", + "diff": "^8.0.2", + "extract-zip": "^2.0.1", + "file-type": "^21.1.1", + "glob": "^13.0.1", + "hosted-git-info": "^9.0.2", + "ignore": "^7.0.5", + "jiti": "^2.7.0", + "marked": "^15.0.12", + "minimatch": "^10.2.3", + "proper-lockfile": "^4.1.2", + "strip-ansi": "^7.1.0", + "typebox": "^1.1.24", + "undici": "^7.19.1", + "uuid": "^14.0.0", + "yaml": "^2.8.2" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=20.6.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "^0.3.5" + } + }, + "node_modules/@earendil-works/pi-tui": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.74.0.tgz", + "integrity": "sha512-1aIfXZp7D/z+1VlZX8BZcs6pgO8rjmil7kwyhctNDsWvce3Yfl8GVgu4eq+I0Mjhr8Cj+ipBiv9CLIzdoyCOIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime-types": "^2.1.4", + "chalk": "^5.5.0", + "get-east-asian-width": "^1.3.0", + "marked": "^15.0.12", + "mime-types": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "koffi": "^2.9.0" + } + }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -1015,22 +1114,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "license": "MIT", - "optional": true - }, "node_modules/@google/genai": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.51.0.tgz", @@ -1056,6 +1139,18 @@ } } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1268,139 +1363,6 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/jiti": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz", - "integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "std-env": "^3.10.0", - "yoctocolors": "^2.1.2" - }, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/@mariozechner/pi-agent-core": { - "version": "0.72.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.72.1.tgz", - "integrity": "sha512-Z5XH9mUDsBymh7Prtk5Eun4Cs+s9C3uzynug+oWAjzQESjjnCuy7KU6Ek8E9Eg+b9yroCwVw2ItLDxp2E3vYjA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mariozechner/pi-ai": "^0.72.1", - "typebox": "^1.1.24" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@mariozechner/pi-ai": { - "version": "0.72.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.72.1.tgz", - "integrity": "sha512-mOq71Pjnu72xxzwrh52VIiNwt+/a+Wpa11k5segi01/zTZJt8eMDc5Q2z6GhczYMr5+6EpZ8T+BaeHqq0jk5ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@anthropic-ai/sdk": "^0.91.1", - "@aws-sdk/client-bedrock-runtime": "^3.1030.0", - "@google/genai": "^1.40.0", - "@mistralai/mistralai": "^2.2.0", - "chalk": "^5.6.2", - "openai": "6.26.0", - "partial-json": "^0.1.7", - "proxy-agent": "^6.5.0", - "typebox": "^1.1.24", - "undici": "^7.19.1", - "zod-to-json-schema": "^3.24.6" - }, - "bin": { - "pi-ai": "dist/cli.js" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/@mariozechner/pi-coding-agent": { - "version": "0.72.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.72.1.tgz", - "integrity": "sha512-gKApiLfAYpvPnWThvwXrLjZIhsziSSUKBph7DHCy/1IomuIGN1MTxS/9ZtcuFqPy3/+VfEUOKegGlY6m+CRNlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.72.1", - "@mariozechner/pi-ai": "^0.72.1", - "@mariozechner/pi-tui": "^0.72.1", - "@silvia-odwyer/photon-node": "^0.3.4", - "chalk": "^5.5.0", - "cli-highlight": "^2.1.11", - "diff": "^8.0.2", - "extract-zip": "^2.0.1", - "file-type": "^21.1.1", - "glob": "^13.0.1", - "hosted-git-info": "^9.0.2", - "ignore": "^7.0.5", - "marked": "^15.0.12", - "minimatch": "^10.2.3", - "proper-lockfile": "^4.1.2", - "strip-ansi": "^7.1.0", - "typebox": "^1.1.24", - "undici": "^7.19.1", - "uuid": "^14.0.0", - "yaml": "^2.8.2" - }, - "bin": { - "pi": "dist/cli.js" - }, - "engines": { - "node": ">=20.6.0" - }, - "optionalDependencies": { - "@mariozechner/clipboard": "^0.3.5" - } - }, - "node_modules/@mariozechner/pi-coding-agent/node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/@mariozechner/pi-tui": { - "version": "0.72.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.72.1.tgz", - "integrity": "sha512-KjeqzMQp4vafomfkvI8HKv5/52PqRP5BmlowGC8WKyOaB+u0UkkTdfM5U1Ui3ty2gA7FOfglVRiyvcitiWwvMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime-types": "^2.1.4", - "chalk": "^5.5.0", - "get-east-asian-width": "^1.3.0", - "marked": "^15.0.12", - "mime-types": "^3.0.1" - }, - "engines": { - "node": ">=20.0.0" - }, - "optionalDependencies": { - "koffi": "^2.9.0" - } - }, "node_modules/@mistralai/mistralai": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz", @@ -1445,32 +1407,6 @@ ], "license": "MIT" }, - "node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "license": "MIT", - "optional": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@oxc-project/types": { "version": "0.127.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", @@ -2573,16 +2509,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -2775,11 +2701,14 @@ } }, "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", "license": "ISC", - "optional": true + "optional": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/agent-base": { "version": "7.1.4", @@ -2791,33 +2720,6 @@ "node": ">= 14" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "optional": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -2854,28 +2756,6 @@ "dev": true, "license": "MIT" }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "license": "ISC", - "optional": true - }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -3030,115 +2910,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cacache/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "optional": true - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", - "optional": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cacache/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -3163,22 +2934,12 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "optional": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=6" + "node": ">=18" } }, "node_modules/cli-highlight": { @@ -3275,30 +3036,6 @@ "dev": true, "license": "MIT" }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "optional": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT", - "optional": true - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "license": "ISC", - "optional": true - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3320,7 +3057,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3373,13 +3110,6 @@ "node": ">= 14" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT", - "optional": true - }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -3413,19 +3143,9 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true, + "dev": true, "license": "MIT" }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -3445,13 +3165,6 @@ "node": ">=6" } }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "license": "MIT", - "optional": true - }, "node_modules/es-module-lexer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", @@ -3554,6 +3267,13 @@ "node": ">=12.0.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3634,7 +3354,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -3716,37 +3436,6 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC", - "optional": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3762,50 +3451,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/gauge/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gauge/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/gaxios": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", @@ -3969,13 +3614,6 @@ "node": ">=8" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "license": "ISC", - "optional": true - }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -3999,13 +3637,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause", - "optional": true - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -4034,29 +3665,6 @@ "node": ">= 14" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4087,45 +3695,6 @@ "node": ">= 4" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "license": "ISC", - "optional": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "optional": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4142,7 +3711,7 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -4152,25 +3721,31 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "license": "MIT", - "optional": true - }, "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC", - "optional": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } }, "node_modules/json-bigint": { "version": "1.0.0", @@ -4531,117 +4106,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "license": "ISC", - "optional": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/make-fetch-happen/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-fetch-happen/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/marked": { "version": "15.0.12", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", @@ -4709,196 +4173,35 @@ "funding": { "url": "https://github.com/sponsors/isaacs" } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "license": "MIT", - "optional": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/minipass-fetch/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", - "license": "BlueOak-1.0.0", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "dependencies": { + "minipass": "^7.1.2" }, "engines": { - "node": ">=10" + "node": ">= 18" } }, "node_modules/mkdirp-classic": { @@ -4911,7 +4214,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -4951,16 +4254,6 @@ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/netmask": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", @@ -4984,10 +4277,13 @@ } }, "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", + "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } }, "node_modules/node-domexception": { "version": "1.0.0", @@ -5030,114 +4326,44 @@ } }, "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", + "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", "license": "MIT", "optional": true, "dependencies": { "env-paths": "^2.2.0", - "glob": "^7.1.4", + "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/node-gyp/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "optional": true - }, - "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-gyp/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", - "optional": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "license": "ISC", "optional": true, "dependencies": { - "abbrev": "1" + "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": ">=6" - } - }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/object-assign": { @@ -5192,22 +4418,6 @@ } } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", @@ -5303,16 +4513,6 @@ "node": ">=14.0.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-scurry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", @@ -5355,7 +4555,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -5420,35 +4620,14 @@ "node": ">=10" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "license": "ISC", - "optional": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "license": "MIT", - "optional": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/promise-retry/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", "optional": true, "engines": { - "node": ">= 4" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/proper-lockfile": { @@ -5594,76 +4773,6 @@ "node": ">= 4" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "optional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "optional": true - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", - "optional": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/rolldown": { "version": "1.0.0-rc.17", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", @@ -5718,13 +4827,6 @@ ], "license": "MIT" }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "optional": true - }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -5737,13 +4839,6 @@ "node": ">=10" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC", - "optional": true - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -5755,7 +4850,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/simple-concat": { @@ -5807,7 +4902,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 6.0.0", @@ -5818,7 +4913,7 @@ "version": "2.8.8", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ip-address": "^10.1.1", @@ -5866,22 +4961,25 @@ } }, "node_modules/sqlite3": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", - "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-6.0.1.tgz", + "integrity": "sha512-X0czUUMG2tmSqJpEQa3tCuZSHKIx8PwM53vLZzKp/o6Rpy25fiVfjdbnZ988M8+O3ZWR1ih0K255VumCb3MAnQ==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "bindings": "^1.5.0", - "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.1", - "tar": "^6.1.11" + "node-addon-api": "^8.0.0", + "prebuild-install": "^7.1.3", + "tar": "^7.5.10" + }, + "engines": { + "node": ">=20.17.0" }, "optionalDependencies": { - "node-gyp": "8.x" + "node-gyp": "12.x" }, "peerDependencies": { - "node-gyp": "8.x" + "node-gyp": "12.x" }, "peerDependenciesMeta": { "node-gyp": { @@ -5889,32 +4987,6 @@ } } }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/ssri/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -5922,13 +4994,6 @@ "dev": true, "license": "MIT" }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5942,7 +5007,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -5957,7 +5022,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5967,7 +5032,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -6045,21 +5110,19 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "version": "7.5.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", + "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/tar-fs": { @@ -6096,15 +5159,6 @@ "node": ">=6" } }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6149,7 +5203,7 @@ "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -6252,15 +5306,12 @@ } }, "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, "engines": { - "node": ">=14.0" + "node": ">=20.18.1" } }, "node_modules/undici-types": { @@ -6270,26 +5321,6 @@ "dev": true, "license": "MIT" }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "license": "ISC", - "optional": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6496,19 +5527,19 @@ } }, "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "license": "ISC", "optional": true, "dependencies": { - "isexe": "^2.0.0" + "isexe": "^4.0.0" }, "bin": { - "node-which": "bin/node-which" + "node-which": "bin/which.js" }, "engines": { - "node": ">= 8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/why-is-node-running": { @@ -6528,16 +5559,6 @@ "node": ">=8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "license": "ISC", - "optional": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -6618,10 +5639,13 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/yaml": { "version": "2.8.4", @@ -6679,19 +5703,6 @@ "fd-slicer": "~1.1.0" } }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", diff --git a/package.json b/package.json index dd7ca14..a1223d7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,15 @@ "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", - "keywords": ["pi-package", "pi", "pi-extension", "cursor", "cursor-sdk", "ai", "extension"], + "keywords": [ + "pi-package", + "pi", + "pi-extension", + "cursor", + "cursor-sdk", + "ai", + "extension" + ], "repository": { "type": "git", "url": "git+https://github.com/fitchmultz/pi-cursor-sdk.git" @@ -13,7 +21,13 @@ "url": "https://github.com/fitchmultz/pi-cursor-sdk/issues" }, "homepage": "https://github.com/fitchmultz/pi-cursor-sdk#readme", - "files": ["src", "README.md", "docs/cursor-model-ux-spec.md", "LICENSE", "CHANGELOG.md"], + "files": [ + "src", + "README.md", + "docs/cursor-model-ux-spec.md", + "LICENSE", + "CHANGELOG.md" + ], "type": "module", "engines": { "node": ">=18" @@ -27,16 +41,22 @@ "@cursor/sdk": "^1.0.12" }, "peerDependencies": { - "@mariozechner/pi-ai": "*", - "@mariozechner/pi-coding-agent": "*" + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*" }, "devDependencies": { - "@mariozechner/pi-coding-agent": "^0.72.1", - "@mariozechner/pi-ai": "^0.72.1", + "@earendil-works/pi-ai": "^0.74.0", + "@earendil-works/pi-coding-agent": "^0.74.0", "typescript": "^6.0.3", "vitest": "^4.1.5" }, "pi": { - "extensions": ["./src/index.ts"] + "extensions": [ + "./src/index.ts" + ] + }, + "overrides": { + "undici": "7.25.0", + "sqlite3": "6.0.1" } } diff --git a/src/context-window-cache.ts b/src/context-window-cache.ts index 1441364..1c3991d 100644 --- a/src/context-window-cache.ts +++ b/src/context-window-cache.ts @@ -1,6 +1,6 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { getAgentDir } from "@mariozechner/pi-coding-agent"; +import { getAgentDir } from "@earendil-works/pi-coding-agent"; import { BUNDLED_CONTEXT_WINDOWS } from "./bundled-context-windows.js"; const CONTEXT_WINDOW_CACHE_FILE = "cursor-sdk-context-windows.json"; diff --git a/src/context.ts b/src/context.ts index 7da6f23..11fe0dd 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,4 +1,4 @@ -import type { Context, Message, ToolCall } from "@mariozechner/pi-ai"; +import type { Context, Message, ToolCall } from "@earendil-works/pi-ai"; import type { SDKImage } from "@cursor/sdk"; export interface CursorPrompt { diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 257f952..daaaed2 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -6,7 +6,7 @@ import { type Model, type SimpleStreamOptions, type AssistantMessage, -} from "@mariozechner/pi-ai"; +} from "@earendil-works/pi-ai"; import { Agent, createAgentPlatform } from "@cursor/sdk"; import type { InteractionUpdate, SDKAgent } from "@cursor/sdk"; import { buildCursorPrompt } from "./context.js"; diff --git a/src/cursor-state.ts b/src/cursor-state.ts index ba83ef9..30f5a25 100644 --- a/src/cursor-state.ts +++ b/src/cursor-state.ts @@ -1,7 +1,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; -import { getAgentDir } from "@mariozechner/pi-coding-agent"; +import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; +import { getAgentDir } from "@earendil-works/pi-coding-agent"; import { getCursorModelMetadata } from "./model-discovery.js"; const CURSOR_PROVIDER = "cursor"; diff --git a/src/index.ts b/src/index.ts index 68051e5..566d32e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { discoverModels, type CursorModelFallbackIssue } from "./model-discovery.js"; import { registerCursorFastControls } from "./cursor-state.js"; import { streamCursor } from "./cursor-provider.js"; diff --git a/src/model-discovery.ts b/src/model-discovery.ts index a455785..aeb58d5 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -5,8 +5,8 @@ import type { ModelParameterValue, ModelSelection, } from "@cursor/sdk"; -import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent"; -import type { ModelThinkingLevel, ThinkingLevelMap } from "@mariozechner/pi-ai"; +import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent"; +import type { ModelThinkingLevel, ThinkingLevelMap } from "@earendil-works/pi-ai"; import { getCachedContextWindow } from "./context-window-cache.js"; const FALLBACK_CONTEXT_WINDOW = 128000; diff --git a/test/context.test.ts b/test/context.test.ts index ed39b0f..ab36517 100644 --- a/test/context.test.ts +++ b/test/context.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest"; import { buildCursorPrompt } from "../src/context.js"; -import type { Context, UserMessage, AssistantMessage, ToolResultMessage } from "@mariozechner/pi-ai"; +import type { Context, UserMessage, AssistantMessage, ToolResultMessage } from "@earendil-works/pi-ai"; describe("buildCursorPrompt", () => { it("includes system prompt", () => { diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 3eefe50..09a7486 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -36,7 +36,7 @@ import { streamCursor } from "../src/cursor-provider.js"; import { __testUtils as modelDiscoveryTestUtils } from "../src/model-discovery.js"; import { __testUtils as contextWindowCacheTestUtils } from "../src/context-window-cache.js"; import type { ModelListItem } from "@cursor/sdk"; -import type { Context, Model } from "@mariozechner/pi-ai"; +import type { Context, Model } from "@earendil-works/pi-ai"; // Access the mocks via the module const mockedCreate = vi.mocked(Agent.create); -- 2.51.2 From 8a7b851e501f3740d34b98d61705d7d49efd6461 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 7 May 2026 17:44:48 -0600 Subject: [PATCH 013/359] Release v0.1.2 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f97a8b..ea737d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.1.2 - 2026-05-07 + +### Changed + +- Migrated the local pi development baseline and peer metadata from deprecated `@mariozechner/*` packages to maintained `@earendil-works/*` `0.74.0`. +- Regenerated the npm lockfile against the current stable dependency graph and cleared moderate audit findings with current transitive overrides. + ## 0.1.1 - 2026-05-05 ### Fixed diff --git a/package-lock.json b/package-lock.json index 6a31e4c..882afb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.1", + "version": "0.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.12" diff --git a/package.json b/package.json index a1223d7..0c750b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.1", + "version": "0.1.2", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", -- 2.51.2 From a7c0e9cb51529e01bc93f83d20ed2441a39e7788 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 7 May 2026 17:52:43 -0600 Subject: [PATCH 014/359] Release v0.1.3 --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea737d9..404db4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.1.3 - 2026-05-07 + +### Fixed + +- Bundled the resolved `@cursor/sdk` runtime dependency tree so npm consumers receive the patched `sqlite3` and `undici` dependency graph used by local verification. + ## 0.1.2 - 2026-05-07 ### Changed diff --git a/package-lock.json b/package-lock.json index 882afb5..f4bd322 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.2", + "version": "0.1.3", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.12" diff --git a/package.json b/package.json index 0c750b6..f459f2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.2", + "version": "0.1.3", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", @@ -40,6 +40,9 @@ "dependencies": { "@cursor/sdk": "^1.0.12" }, + "bundledDependencies": [ + "@cursor/sdk" + ], "peerDependencies": { "@earendil-works/pi-ai": "*", "@earendil-works/pi-coding-agent": "*" -- 2.51.2 From 5bca388e200e5b546819c7ee405018a8f0b965ef Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 7 May 2026 17:58:44 -0600 Subject: [PATCH 015/359] Release v0.1.4 --- CHANGELOG.md | 6 +++ package-lock.json | 4 +- package.json | 123 ++++++++++++++++++++++------------------------ 3 files changed, 68 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 404db4b..31feea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.1.4 - 2026-05-07 + +### Fixed + +- Restores the GitHub install path to the normal source package layout after the npm-only bundled dependency patch. + ## 0.1.3 - 2026-05-07 ### Fixed diff --git a/package-lock.json b/package-lock.json index f4bd322..bef47b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.3", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.3", + "version": "0.1.4", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.12" diff --git a/package.json b/package.json index f459f2d..81881cd 100644 --- a/package.json +++ b/package.json @@ -1,65 +1,62 @@ { - "name": "pi-cursor-sdk", - "version": "0.1.3", - "description": "pi provider extension backed by @cursor/sdk local agents", - "author": "Mitch Fultz (https://github.com/fitchmultz)", - "license": "MIT", - "keywords": [ - "pi-package", - "pi", - "pi-extension", - "cursor", - "cursor-sdk", - "ai", - "extension" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/fitchmultz/pi-cursor-sdk.git" - }, - "bugs": { - "url": "https://github.com/fitchmultz/pi-cursor-sdk/issues" - }, - "homepage": "https://github.com/fitchmultz/pi-cursor-sdk#readme", - "files": [ - "src", - "README.md", - "docs/cursor-model-ux-spec.md", - "LICENSE", - "CHANGELOG.md" - ], - "type": "module", - "engines": { - "node": ">=18" - }, - "scripts": { - "typecheck": "tsc --noEmit", - "test": "vitest run", - "test:watch": "vitest" - }, - "dependencies": { - "@cursor/sdk": "^1.0.12" - }, - "bundledDependencies": [ - "@cursor/sdk" - ], - "peerDependencies": { - "@earendil-works/pi-ai": "*", - "@earendil-works/pi-coding-agent": "*" - }, - "devDependencies": { - "@earendil-works/pi-ai": "^0.74.0", - "@earendil-works/pi-coding-agent": "^0.74.0", - "typescript": "^6.0.3", - "vitest": "^4.1.5" - }, - "pi": { - "extensions": [ - "./src/index.ts" - ] - }, - "overrides": { - "undici": "7.25.0", - "sqlite3": "6.0.1" - } + "name": "pi-cursor-sdk", + "version": "0.1.4", + "description": "pi provider extension backed by @cursor/sdk local agents", + "author": "Mitch Fultz (https://github.com/fitchmultz)", + "license": "MIT", + "keywords": [ + "pi-package", + "pi", + "pi-extension", + "cursor", + "cursor-sdk", + "ai", + "extension" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/fitchmultz/pi-cursor-sdk.git" + }, + "bugs": { + "url": "https://github.com/fitchmultz/pi-cursor-sdk/issues" + }, + "homepage": "https://github.com/fitchmultz/pi-cursor-sdk#readme", + "files": [ + "src", + "README.md", + "docs/cursor-model-ux-spec.md", + "LICENSE", + "CHANGELOG.md" + ], + "type": "module", + "engines": { + "node": ">=18" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@cursor/sdk": "^1.0.12" + }, + "peerDependencies": { + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*" + }, + "devDependencies": { + "@earendil-works/pi-ai": "^0.74.0", + "@earendil-works/pi-coding-agent": "^0.74.0", + "typescript": "^6.0.3", + "vitest": "^4.1.5" + }, + "pi": { + "extensions": [ + "./src/index.ts" + ] + }, + "overrides": { + "undici": "7.25.0", + "sqlite3": "6.0.1" + } } -- 2.51.2 From 82a2eca1db3c50699828ccffc2a5549054ec07e7 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 10:39:02 -0600 Subject: [PATCH 016/359] Add Cursor login auth support --- CHANGELOG.md | 9 ++ README.md | 47 ++++++---- docs/cursor-model-ux-spec.md | 13 ++- src/cursor-provider.ts | 160 ++++++++++++++++++++++------------- src/model-discovery.ts | 44 +++++++--- test/cursor-provider.test.ts | 122 +++++++++++++++++++++++--- test/index.test.ts | 4 +- test/model-discovery.test.ts | 102 ++++++++++++++++++++-- 8 files changed, 392 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31feea8..d0a20bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## Unreleased + +### Changed + +- Added pi-native `/login` API-key integration for the Cursor provider. Startup discovery now checks pi `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. +- Fallback Cursor models remain available when startup discovery cannot authenticate; once auth is saved, fallback model runs can use the stored key, while `/reload` or restart refreshes the full live Cursor model catalog. +- Cleaned up Cursor activity display by keeping internal tool activity in a concise bounded trace, omitting raw Cursor call IDs, and using the final Cursor run result for the visible assistant answer when available. +- Stopped copying Cursor SDK cumulative internal agent/tool/cache token usage into pi usage, preventing false context-overflow and compaction triggers after long Cursor runs. + ## 0.1.4 - 2026-05-07 ### Fixed diff --git a/README.md b/README.md index acc6130..635edd3 100644 --- a/README.md +++ b/README.md @@ -18,25 +18,21 @@ Or install from GitHub: pi install https://github.com/fitchmultz/pi-cursor-sdk ``` -2. Export your Cursor API key before starting pi: - -```bash -export CURSOR_API_KEY="your-key" -``` - -3. Start pi with a Cursor model: +2. Start pi with a Cursor model: ```bash pi --model cursor/composer-2 ``` -Inside pi, use `/model` to choose another Cursor model. +3. In pi, run `/login`, choose `Use an API key`, choose `Cursor`, and paste your Cursor API key. + +If pi started without a key, run `/reload` or restart pi after `/login` to refresh the full live Cursor model catalog. Inside pi, use `/model` to choose another Cursor model. ## Requirements - Node.js 18+ - pi -- a Cursor API key available as `CURSOR_API_KEY` or passed with pi's `--api-key` +- a Cursor API key saved through `/login`, available as `CURSOR_API_KEY`, or passed with pi's `--api-key` No global `@cursor/sdk` install is required. This package depends on `@cursor/sdk`, so normal package installation brings in the SDK version this extension was built and tested against. @@ -75,6 +71,22 @@ pi -e . --model cursor/composer-2 Preferred setup: +```bash +pi --model cursor/composer-2 +``` + +Then, inside pi: + +1. Run `/login`. +2. Select `Use an API key`. +3. Select `Cursor`. +4. Paste your Cursor API key. +5. The key is saved in pi's native `~/.pi/agent/auth.json`. + +If pi started without a key, fallback Cursor models still register so `/login` is reachable. After `/login`, fallback model runs can use the stored key, but `/reload` or a restart is needed to refresh the full live Cursor model catalog discovered from the Cursor SDK. + +Environment setup: + ```bash export CURSOR_API_KEY="your-key" pi --model cursor/composer-2 @@ -86,9 +98,7 @@ One-shot setup: pi --api-key "your-key" --model cursor/composer-2 --cursor-no-fast -p "Say ok only." ``` -Use `CURSOR_API_KEY` when possible. It gives the extension a key during startup model discovery. `--api-key` is also read for discovery and runs, but shell wrappers and launchers are easier to diagnose when the key is exported as `CURSOR_API_KEY` before pi starts. - -Important: if pi starts without a valid `CURSOR_API_KEY` or `--api-key`, model discovery may fall back to a conservative Cursor model list. That fallback list is only for selection/display. Cursor runs in that already-started session will fail until you restart pi with a valid key. +Discovery uses pi's native resolution order for this extension: `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. Do not store the API key in `~/.pi/agent/cursor-sdk.json`. That file is only for non-secret extension state such as Cursor fast defaults. `PATH` is only for executable lookup and should not contain the API key. @@ -103,7 +113,7 @@ pi --list-models cursor Expected behavior: - with a valid key, Cursor models appear under the `cursor` provider -- if discovery cannot authenticate or reach Cursor, pi may still show fallback Cursor models, but those rows are selection-only and actual runs will fail until restart with a valid key +- if discovery cannot authenticate or reach Cursor, pi may still show fallback Cursor models; after adding auth with `/login`, fallback model runs can use the saved key, and `/reload` or restart refreshes the live catalog Smoke test: @@ -188,30 +198,33 @@ Images from the latest user message are forwarded to Cursor. Historical images a ## Fallback models -If `CURSOR_API_KEY` is missing, model discovery fails, or discovery returns no models, the extension registers conservative fallback Cursor models and notifies interactive users when possible: +If no key is available from `/login`, `CURSOR_API_KEY`, or `--api-key`, model discovery fails, or discovery returns no models, the extension registers conservative fallback Cursor models and notifies interactive users when possible: - `composer-2` - `gpt-5.5@1m`, `gpt-5.5@272k` - `claude-sonnet-4-6@1m`, `claude-sonnet-4-6@300k` - `claude-opus-4-7@1m`, `claude-opus-4-7@300k` -Fallback models are only a startup model list. Actual Cursor runs still need `CURSOR_API_KEY` or `--api-key`. +Fallback models are a conservative startup model list. Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-key`. If you add auth after startup, run `/reload` or restart pi to refresh the full live Cursor model catalog. ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not exposed as pi tool calls.** You may see compact Cursor trace text before the final answer, but you will not get normal pi tool-call rows for Cursor's internal tool activity. +- **Cursor-side tool use is not exposed as pi tool calls.** You may see a concise Cursor activity trace before the final answer, but you will not get normal pi tool-call rows for Cursor's internal tool activity. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. - **Max Mode is not exposed for these local runs.** The extension only advertises exact context windows supported by the SDK path it uses. - **Output token limits are conservative.** Cursor SDK model metadata does not currently expose output token limits directly. +- **Token usage is approximate in pi.** Cursor SDK usage events include internal agent/tool/cache work, so the extension reports an approximate replayable pi prompt/output size for context display and compaction decisions. ## Troubleshooting ### I can see Cursor models, but runs fail -You may be seeing fallback startup models. Restart pi with a key in the same shell or launcher that starts pi: +You may be seeing fallback startup models or a missing/invalid key. In interactive pi, run `/login`, choose `Use an API key`, choose `Cursor`, paste the key, then run `/reload` or restart pi. + +You can also restart pi with a key in the same shell or launcher that starts pi: ```bash export CURSOR_API_KEY="your-key" diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 04e3abb..7f49cfd 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -14,10 +14,11 @@ Current implementation notes: - Cursor fast status uses `ctx.ui.setStatus()`; the default pi footer remains intact. - Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`. - `@cursor/sdk` is a package dependency of this extension; users should not need a global SDK install. -- Cursor auth uses the `CURSOR_API_KEY` environment variable. The extension config file stores only non-secret Cursor-only state such as fast defaults. +- Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking and tool activity are surfaced as trace content before final text. Cursor text deltas are buffered until the run finishes so the saved message order is trace first, final answer second. +- Cursor-side thinking and tool activity are surfaced as bounded trace content before final text. Cursor text deltas are treated as intermediate progress unless the final run result is unavailable, so the saved visible answer stays clean while message content order remains trace first, final answer second. +- Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. @@ -57,6 +58,14 @@ At startup, the extension calls: Cursor.models.list({ apiKey }); ``` +Discovery resolves `apiKey` in this order: + +1. CLI `--api-key`. +2. Stored pi auth for provider `cursor` from `AuthStorage.create().getApiKey("cursor", { includeFallback: false })`. +3. `CURSOR_API_KEY`. + +Users can persist the stored key through `/login` -> `Use an API key` -> `Cursor`. If auth is added after startup, fallback models can run once pi resolves the saved key for provider requests, but `/reload` or restart is required to refresh the full live Cursor model catalog. + For each model, use: - `model.id` diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index daaaed2..a623985 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -9,7 +9,7 @@ import { } from "@earendil-works/pi-ai"; import { Agent, createAgentPlatform } from "@cursor/sdk"; import type { InteractionUpdate, SDKAgent } from "@cursor/sdk"; -import { buildCursorPrompt } from "./context.js"; +import { buildCursorPrompt, type CursorPrompt } from "./context.js"; import { getEffectiveFastForModelId } from "./cursor-state.js"; import { buildCursorModelSelection } from "./model-discovery.js"; import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js"; @@ -43,11 +43,14 @@ class CursorAbortError extends Error { const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY"; const MISSING_API_KEY_MESSAGE = - "Cursor SDK runs require CURSOR_API_KEY or pi --api-key. Set CURSOR_API_KEY before starting pi, or restart pi with --api-key."; + "Cursor SDK runs require a Cursor API key. Run /login -> Use an API key -> Cursor, set CURSOR_API_KEY before starting pi, or restart pi with --api-key."; const GENERIC_CURSOR_SDK_ERROR_MESSAGE = - "Cursor SDK request failed. The API key may be missing, invalid, or unauthorized. Verify CURSOR_API_KEY or pass --api-key, then retry."; + "Cursor SDK request failed. The API key may be missing, invalid, or unauthorized. Run /login -> Use an API key -> Cursor, verify CURSOR_API_KEY, or pass --api-key, then retry."; const AUTH_CURSOR_SDK_ERROR_MESSAGE = - "Cursor SDK request failed because the API key may be invalid or unauthorized. Verify CURSOR_API_KEY or pass --api-key, then retry."; + "Cursor SDK request failed because the API key may be invalid or unauthorized. Run /login -> Use an API key -> Cursor, verify CURSOR_API_KEY, or pass --api-key, then retry."; +const APPROX_CHARS_PER_TOKEN = 4; +const IMAGE_TOKEN_ESTIMATE = 1200; +const CURSOR_ACTIVITY_TRACE_MAX_CHARS = 50000; function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); @@ -77,12 +80,11 @@ function isLikelyAuthError(message: string): boolean { return /\b(unauthorized|unauthorised|forbidden|invalid api key|invalid key|authentication|auth|401|403)\b/i.test(message); } -function hasEnvCursorApiKey(): boolean { - return Boolean(process.env.CURSOR_API_KEY?.trim()); -} - -function isMissingCursorApiKey(apiKey?: string): boolean { - return !apiKey || (apiKey === CURSOR_API_KEY_ENV_VAR && !hasEnvCursorApiKey()); +function resolveCursorApiKey(apiKey?: string): string | undefined { + const trimmed = apiKey?.trim(); + if (!trimmed) return undefined; + if (trimmed === CURSOR_API_KEY_ENV_VAR) return process.env.CURSOR_API_KEY?.trim(); + return trimmed; } function sanitizeError(error: unknown, apiKey?: string): string { @@ -122,15 +124,48 @@ async function cacheSdkContextWindow(agentId: string, modelId: string): Promise< } } +function estimateTextTokens(text: string): number { + return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN); +} + +function estimatePromptInputTokens(prompt: CursorPrompt): number { + return estimateTextTokens(prompt.text) + prompt.images.length * IMAGE_TOKEN_ESTIMATE; +} + +function setApproximateUsage(partial: AssistantMessage, promptInputTokens: number, outputText: string): void { + partial.usage.input = promptInputTokens; + partial.usage.output = estimateTextTokens(outputText); + partial.usage.cacheRead = 0; + partial.usage.cacheWrite = 0; + partial.usage.totalTokens = partial.usage.input + partial.usage.output; +} + +function sanitizeSingleLine(value: string): string { + return value.replace(/\s+/g, " ").trim(); +} + +function truncateSingleLine(value: string, maxLength = 240): string { + const sanitized = sanitizeSingleLine(value); + return sanitized.length > maxLength ? `${sanitized.slice(0, maxLength - 1)}…` : sanitized; +} + function summarizeCursorToolResult(result: unknown): string { if (result === undefined) return ""; const parts: string[] = []; const status = getObjectField(result, "status"); - if (typeof status === "string") parts.push(status); + if (typeof status === "string") parts.push(sanitizeSingleLine(status)); const value = getObjectField(result, "value"); const exitCode = getObjectField(value, "exitCode") ?? getObjectField(result, "exitCode"); if (typeof exitCode === "number") parts.push(`exit ${exitCode}`); - return parts.length > 0 ? `: ${parts.join(", ")}` : ""; + return parts.length > 0 ? ` (${parts.join(", ")})` : ""; +} + +function formatCursorToolName(toolCall: unknown): string { + return truncateSingleLine(getCursorToolName(toolCall), 80) || "unknown"; +} + +function hasUsableText(value: string | undefined): value is string { + return typeof value === "string" && value.trim().length > 0; } export function streamCursor( @@ -143,6 +178,7 @@ export function streamCursor( (async () => { const partial = makeInitialMessage(model); let agent: SDKAgent | null = null; + let resolvedApiKey: string | undefined; let abortSignal: AbortSignal | undefined; let abortListener: (() => void) | undefined; @@ -154,8 +190,9 @@ export function streamCursor( stream.push({ type: "start", partial }); throwIfAborted(); - const apiKey = options?.apiKey; - if (isMissingCursorApiKey(apiKey)) throw new Error(MISSING_API_KEY_MESSAGE); + const apiKey = resolveCursorApiKey(options?.apiKey); + if (!apiKey) throw new Error(MISSING_API_KEY_MESSAGE); + resolvedApiKey = apiKey; const cwd = process.cwd(); const fastEnabled = getEffectiveFastForModelId(model.id); @@ -171,8 +208,10 @@ export function streamCursor( throwIfAborted(); const prompt = buildCursorPrompt(context); - let textContentIndex = -1; + const promptInputTokens = estimatePromptInputTokens(prompt); let thinkingContentIndex = -1; + let activityTraceChars = 0; + let activityTraceTruncated = false; const textDeltas: string[] = []; const appendBufferedTextDelta = (text: string): void => { @@ -180,6 +219,16 @@ export function streamCursor( }; const appendTraceDelta = (text: string): void => { + if (activityTraceTruncated) return; + + let delta = text; + if (activityTraceChars + delta.length > CURSOR_ACTIVITY_TRACE_MAX_CHARS) { + const remainingChars = Math.max(CURSOR_ACTIVITY_TRACE_MAX_CHARS - activityTraceChars, 0); + delta = `${delta.slice(0, remainingChars)}\n[Cursor activity trace truncated]\n`; + activityTraceTruncated = true; + } + if (!delta) return; + if (thinkingContentIndex < 0) { thinkingContentIndex = partial.content.length; partial.content.push({ type: "thinking", thinking: "" }); @@ -187,28 +236,42 @@ export function streamCursor( } const block = partial.content[thinkingContentIndex]; if (block.type === "thinking") { - block.thinking += text; + block.thinking += delta; + activityTraceChars += delta.length; stream.push({ type: "thinking_delta", contentIndex: thinkingContentIndex, - delta: text, + delta, partial, }); } }; - const appendCursorToolStatus = (text: string): void => { + const appendTraceLine = (text: string): void => { appendTraceDelta(`${text}\n`); }; - const flushBufferedText = (fallbackText?: string): void => { - const deltas = textDeltas.length > 0 ? textDeltas : fallbackText ? [fallbackText] : []; - if (deltas.length === 0) return; - textContentIndex = partial.content.length; + const closeTraceBlock = (): void => { + if (thinkingContentIndex < 0) return; + const block = partial.content[thinkingContentIndex]; + if (block.type === "thinking") { + stream.push({ + type: "thinking_end", + contentIndex: thinkingContentIndex, + content: block.thinking, + partial, + }); + } + thinkingContentIndex = -1; + }; + + const flushText = (deltas: string[]): string => { + if (deltas.length === 0) return ""; + const textContentIndex = partial.content.length; partial.content.push({ type: "text", text: "" }); stream.push({ type: "text_start", contentIndex: textContentIndex, partial }); const block = partial.content[textContentIndex]; - if (block.type !== "text") return; + if (block.type !== "text") return ""; for (const delta of deltas) { block.text += delta; stream.push({ @@ -224,6 +287,7 @@ export function streamCursor( content: block.text, partial, }); + return block.text; }; const onDelta = (args: { update: InteractionUpdate }): void => { @@ -234,34 +298,18 @@ export function streamCursor( } else if (update.type === "thinking-delta") { appendTraceDelta(update.text); } else if (update.type === "thinking-completed") { - if (thinkingContentIndex >= 0) { - const block = partial.content[thinkingContentIndex]; - if (block.type === "thinking") { - stream.push({ - type: "thinking_end", - contentIndex: thinkingContentIndex, - content: block.thinking, - partial, - }); - } - thinkingContentIndex = -1; - } + closeTraceBlock(); } else if (update.type === "tool-call-started") { - appendCursorToolStatus(`Cursor tool started (${getCursorToolName(update.toolCall)}, call ${update.callId})`); + appendTraceLine(`Cursor tool: ${formatCursorToolName(update.toolCall)} started`); } else if (update.type === "tool-call-completed") { const suffix = summarizeCursorToolResult(getCursorToolResult(update.toolCall)); - appendCursorToolStatus(`Cursor tool completed (${getCursorToolName(update.toolCall)}, call ${update.callId})${suffix}`); + appendTraceLine(`Cursor tool: ${formatCursorToolName(update.toolCall)} completed${suffix}`); } else if (update.type === "summary") { - appendCursorToolStatus(`Cursor summary: ${update.summary}`); - } else if (update.type === "turn-ended" && update.usage) { - partial.usage.input = update.usage.inputTokens; - partial.usage.output = update.usage.outputTokens; - partial.usage.cacheRead = update.usage.cacheReadTokens; - partial.usage.cacheWrite = update.usage.cacheWriteTokens; - partial.usage.totalTokens = - update.usage.inputTokens + update.usage.outputTokens + update.usage.cacheReadTokens + update.usage.cacheWriteTokens; + appendTraceLine(`Cursor summary: ${truncateSingleLine(update.summary)}`); } - // partial-tool-call, summary-started, summary-completed, + // Cursor turn-ended usage is intentionally not copied into pi usage: the SDK reports + // cumulative internal agent/tool/cache tokens, not the replayable pi prompt context. + // partial-tool-call, summary-started, summary-completed, turn-ended, // shell-output-delta, token-delta, step-* are intentionally not surfaced. }; @@ -288,22 +336,12 @@ export function streamCursor( const result = await run.wait(); await cacheSdkContextWindow(agent.agentId, model.id); - // Close open thinking/trace before flushing final assistant text so saved + // Close open thinking/activity trace before flushing final assistant text so saved // message content is trace first, final answer second. - if (thinkingContentIndex >= 0) { - const block = partial.content[thinkingContentIndex]; - if (block.type === "thinking") { - stream.push({ - type: "thinking_end", - contentIndex: thinkingContentIndex, - content: block.thinking, - partial, - }); - } - thinkingContentIndex = -1; - } + closeTraceBlock(); - flushBufferedText(result.result); + const finalText = flushText(hasUsableText(result.result) ? [result.result] : textDeltas); + setApproximateUsage(partial, promptInputTokens, finalText); if (result.status === "cancelled") { partial.stopReason = "aborted"; @@ -317,7 +355,7 @@ export function streamCursor( stream.push({ type: "error", reason: "aborted", error: partial }); } else { partial.stopReason = "error"; - partial.errorMessage = sanitizeError(error, options?.apiKey); + partial.errorMessage = sanitizeError(error, resolvedApiKey ?? options?.apiKey); stream.push({ type: "error", reason: "error", error: partial }); } } finally { diff --git a/src/model-discovery.ts b/src/model-discovery.ts index aeb58d5..874d04a 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -5,14 +5,19 @@ import type { ModelParameterValue, ModelSelection, } from "@cursor/sdk"; -import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent"; +import { AuthStorage, type ProviderModelConfig } from "@earendil-works/pi-coding-agent"; import type { ModelThinkingLevel, ThinkingLevelMap } from "@earendil-works/pi-ai"; import { getCachedContextWindow } from "./context-window-cache.js"; +const CURSOR_PROVIDER_ID = "cursor"; +const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY"; const FALLBACK_CONTEXT_WINDOW = 128000; const FALLBACK_MAX_TOKENS = 16384; const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const TEXT_AND_IMAGE_INPUT: ProviderModelConfig["input"] = ["text", "image"]; +const AUTH_SETUP_HINT = "/login (Use an API key -> Cursor), CURSOR_API_KEY, or --api-key"; +const CATALOG_REFRESH_HINT = + "After adding auth to an already-started pi session, run /reload or restart pi to refresh the full live Cursor model catalog."; const FALLBACK_MODEL_ITEMS: ModelListItem[] = [ { @@ -184,8 +189,29 @@ function getCliApiKeyFromArgv(argv: string[] = process.argv): string | undefined return undefined; } -function getDiscoveryApiKey(): string | undefined { - return process.env.CURSOR_API_KEY?.trim() || getCliApiKeyFromArgv(); +function normalizeApiKey(value: string | undefined): string | undefined { + const trimmed = value?.trim(); + if (!trimmed) return undefined; + if (trimmed === CURSOR_API_KEY_ENV_VAR) return process.env.CURSOR_API_KEY?.trim() || undefined; + return trimmed; +} + +async function getStoredCursorApiKey(): Promise { + try { + return normalizeApiKey(await AuthStorage.create().getApiKey(CURSOR_PROVIDER_ID, { includeFallback: false })); + } catch { + return undefined; + } +} + +async function getDiscoveryApiKey(): Promise { + const cliApiKey = normalizeApiKey(getCliApiKeyFromArgv()); + if (cliApiKey) return cliApiKey; + + const storedApiKey = await getStoredCursorApiKey(); + if (storedApiKey) return storedApiKey; + + return normalizeApiKey(process.env.CURSOR_API_KEY); } export interface CursorModelMetadata { @@ -484,12 +510,11 @@ function useFallbackModels(options: DiscoverModelsOptions, issue: CursorModelFal } export async function discoverModels(options: DiscoverModelsOptions = {}): Promise { - const apiKey = getDiscoveryApiKey(); + const apiKey = await getDiscoveryApiKey(); if (!apiKey) { return useFallbackModels(options, { reason: "missing-api-key", - message: - "CURSOR_API_KEY or --api-key is required for Cursor model discovery. Using fallback Cursor models for selection only; Cursor runs in this session will fail until pi is restarted with a key.", + message: `Cursor model discovery needs an API key from ${AUTH_SETUP_HINT}. Using fallback Cursor models so /login and model selection still work; fallback models can run once auth exists. ${CATALOG_REFRESH_HINT}`, }); } @@ -500,14 +525,12 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi } return useFallbackModels(options, { reason: "empty-model-list", - message: - "Cursor model discovery returned no models. Using fallback Cursor models for selection only; verify CURSOR_API_KEY or restart pi with --api-key before running Cursor models.", + message: `Cursor model discovery returned no models. Using fallback Cursor models; verify ${AUTH_SETUP_HINT}. ${CATALOG_REFRESH_HINT}`, }); } catch { return useFallbackModels(options, { reason: "discovery-failed", - message: - "Cursor model discovery failed. Using fallback Cursor models for selection only; verify CURSOR_API_KEY or restart pi with --api-key before running Cursor models.", + message: `Cursor model discovery failed. Using fallback Cursor models; verify ${AUTH_SETUP_HINT}. ${CATALOG_REFRESH_HINT}`, }); } } @@ -516,4 +539,5 @@ export const __testUtils = { parseContextWindow, registerModelItems, getCliApiKeyFromArgv, + normalizeApiKey, }; diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 09a7486..ce26819 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -280,14 +280,75 @@ describe("streamCursor", () => { const text = events.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); const done = events.find((e: any) => e.type === "done") as any; - expect(trace).toContain("Cursor tool started (list_dir, call c1)"); - expect(trace).toContain("Cursor tool completed (list_dir, call c1)"); + expect(trace).toContain("Cursor tool: list_dir started"); + expect(trace).toContain("Cursor tool: list_dir completed"); + expect(trace).not.toContain("call c1"); expect(trace).not.toContain("README.md"); expect(trace).toContain("Cursor summary: Inspected files"); expect(text).toBe("done"); expect(done.message.content.map((block: any) => block.type)).toEqual(["thinking", "text"]); }); + it("prefers the final run result over intermediate cursor progress text", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "text-delta", text: "I’m checking files." } }); + opts.onDelta({ update: { type: "text-delta", text: "I found the issue." } }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "Final answer only." }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + const text = events.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); + + expect(text).toBe("Final answer only."); + expect(text).not.toContain("I’m checking files"); + }); + + it("keeps cursor tool activity one-line and omits raw call ids", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ + update: { + type: "tool-call-started", + toolCall: { name: "shell\nextra" }, + callId: "call_abc\nfc_secret", + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "done" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + const trace = events.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + + expect(trace).toContain("Cursor tool: shell extra started\n"); + expect(trace).not.toContain("call_abc"); + expect(trace).not.toContain("fc_secret"); + }); + it("keeps late cursor thinking before final text in the saved content order", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "text-delta", text: "Final answer" } }); @@ -318,12 +379,17 @@ describe("streamCursor", () => { ]); }); - it("updates usage from cursor turn-ended events", async () => { + it("uses pi prompt/output estimates instead of Cursor cumulative internal usage", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "turn-ended", - usage: { inputTokens: 10, outputTokens: 7, cacheReadTokens: 3, cacheWriteTokens: 2 }, + usage: { + inputTokens: 6746960, + outputTokens: 17701, + cacheReadTokens: 6559232, + cacheWriteTokens: 0, + }, }, }); opts.onDelta({ update: { type: "text-delta", text: "done" } }); @@ -346,13 +412,11 @@ describe("streamCursor", () => { const events = await collectEvents(stream); const done = events.find((e: any) => e.type === "done") as any; - expect(done.message.usage).toMatchObject({ - input: 10, - output: 7, - cacheRead: 3, - cacheWrite: 2, - totalTokens: 22, - }); + expect(done.message.usage.input).toBeGreaterThan(0); + expect(done.message.usage.output).toBe(1); + expect(done.message.usage.cacheRead).toBe(0); + expect(done.message.usage.cacheWrite).toBe(0); + expect(done.message.usage.totalTokens).toBeLessThan(1000); }); it("aborts after agent creation without sending a prompt when already cancelled", async () => { @@ -383,6 +447,7 @@ describe("streamCursor", () => { const error = events.find((e: any) => e.type === "error"); expect(error).toBeDefined(); + expect((error as any).error.errorMessage).toContain("/login"); expect((error as any).error.errorMessage).toContain("CURSOR_API_KEY"); expect((error as any).error.errorMessage).toContain("--api-key"); }); @@ -397,7 +462,7 @@ describe("streamCursor", () => { const error = events.find((e: any) => e.type === "error"); expect(error).toBeDefined(); expect((error as any).error.errorMessage).toBe( - "Cursor SDK runs require CURSOR_API_KEY or pi --api-key. Set CURSOR_API_KEY before starting pi, or restart pi with --api-key.", + "Cursor SDK runs require a Cursor API key. Run /login -> Use an API key -> Cursor, set CURSOR_API_KEY before starting pi, or restart pi with --api-key.", ); expect(mockedCreate).not.toHaveBeenCalled(); } finally { @@ -409,6 +474,37 @@ describe("streamCursor", () => { } }); + it("resolves CURSOR_API_KEY provider placeholders through the env var when present", async () => { + const originalKey = process.env.CURSOR_API_KEY; + process.env.CURSOR_API_KEY = "env-key-123"; + try { + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "CURSOR_API_KEY" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith(expect.objectContaining({ apiKey: "env-key-123" })); + } finally { + if (originalKey === undefined) { + delete process.env.CURSOR_API_KEY; + } else { + process.env.CURSOR_API_KEY = originalKey; + } + } + }); + it("turns generic Cursor SDK failures into actionable setup errors", async () => { mockedCreate.mockRejectedValueOnce(new Error("Error")); @@ -418,6 +514,7 @@ describe("streamCursor", () => { const error = events.find((e: any) => e.type === "error"); expect(error).toBeDefined(); expect((error as any).error.errorMessage).toContain("Cursor SDK request failed"); + expect((error as any).error.errorMessage).toContain("/login"); expect((error as any).error.errorMessage).toContain("CURSOR_API_KEY"); expect((error as any).error.errorMessage).toContain("--api-key"); expect((error as any).error.errorMessage).not.toBe("Error"); @@ -432,6 +529,7 @@ describe("streamCursor", () => { const error = events.find((e: any) => e.type === "error"); const message = (error as any).error.errorMessage; expect(message).toContain("invalid or unauthorized"); + expect(message).toContain("/login"); expect(message).toContain("CURSOR_API_KEY"); expect(message).not.toContain("super-secret-key-12345"); }); diff --git a/test/index.test.ts b/test/index.test.ts index 6047ef5..04cf225 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -117,7 +117,7 @@ describe("extension factory", () => { options.onFallback({ reason: "missing-api-key", message: - "CURSOR_API_KEY or --api-key is required for Cursor model discovery. Using fallback Cursor models for selection only; Cursor runs in this session will fail until pi is restarted with a key.", + "Cursor model discovery needs an API key from /login (Use an API key -> Cursor), CURSOR_API_KEY, or --api-key. Using fallback Cursor models so /login and model selection still work; fallback models can run once auth exists. After adding auth to an already-started pi session, run /reload or restart pi to refresh the full live Cursor model catalog.", }); return [ { @@ -145,7 +145,7 @@ describe("extension factory", () => { await sessionHandlers.at(-1)!({}, ctx); expect(notify).toHaveBeenCalledWith( - "CURSOR_API_KEY or --api-key is required for Cursor model discovery. Using fallback Cursor models for selection only; Cursor runs in this session will fail until pi is restarted with a key.", + "Cursor model discovery needs an API key from /login (Use an API key -> Cursor), CURSOR_API_KEY, or --api-key. Using fallback Cursor models so /login and model selection still work; fallback models can run once auth exists. After adding auth to an already-started pi session, run /reload or restart pi to refresh the full live Cursor model catalog.", "warning", ); }); diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 7c1e320..5bcc327 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -1,4 +1,4 @@ -import { mkdtempSync, rmSync } from "node:fs"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; @@ -29,16 +29,28 @@ function register(items: ModelListItem[]) { return __testUtils.registerModelItems(items); } +function writeStoredCursorApiKey(apiKey: string): void { + writeFileSync( + join(process.env.PI_CODING_AGENT_DIR!, "auth.json"), + JSON.stringify({ cursor: { type: "api_key", key: apiKey } }, null, 2), + ); +} + describe("discoverModels", () => { const originalEnv = process.env; const originalArgv = process.argv; + let tmpAgentDir: string; beforeEach(() => { process.env = { ...originalEnv }; - process.argv = [...originalArgv]; + delete process.env.CURSOR_API_KEY; + tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-cursor-discovery-")); + process.env.PI_CODING_AGENT_DIR = tmpAgentDir; + process.argv = ["node", "vitest"]; }); afterEach(() => { + rmSync(tmpAgentDir, { recursive: true, force: true }); process.env = originalEnv; process.argv = originalArgv; vi.clearAllMocks(); @@ -63,9 +75,11 @@ describe("discoverModels", () => { message: expect.stringContaining("CURSOR_API_KEY"), }), ]); + expect(issues[0].message).toContain("/login"); expect(issues[0].message).toContain("--api-key"); - expect(issues[0].message).toContain("selection only"); - expect(issues[0].message).toContain("will fail until pi is restarted"); + expect(issues[0].message).toContain("fallback models can run once auth exists"); + expect(issues[0].message).toContain("/reload"); + expect(issues[0].message).not.toContain("will fail until pi is restarted"); expect(mockedList).not.toHaveBeenCalled(); }); @@ -95,6 +109,82 @@ describe("discoverModels", () => { expect(models.map((model) => model.id)).toEqual(["composer-2"]); }); + it("uses stored pi auth for model discovery when env and CLI are absent", async () => { + writeStoredCursorApiKey("stored-key-123"); + mockedList.mockResolvedValueOnce([ + { + id: "composer-2", + displayName: "Composer 2", + variants: [{ params: [], displayName: "Composer 2", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(mockedList).toHaveBeenCalledWith({ apiKey: "stored-key-123" }); + expect(models.map((model) => model.id)).toEqual(["composer-2"]); + }); + + it("prefers CLI --api-key over stored pi auth for model discovery", async () => { + writeStoredCursorApiKey("stored-key-123"); + process.argv = ["node", "pi", "--api-key", "cli-key-123"]; + mockedList.mockResolvedValueOnce([ + { + id: "composer-2", + displayName: "Composer 2", + variants: [{ params: [], displayName: "Composer 2", isDefault: true }], + }, + ]); + + await discoverModels(); + + expect(mockedList).toHaveBeenCalledWith({ apiKey: "cli-key-123" }); + }); + + it("prefers stored pi auth over CURSOR_API_KEY for model discovery", async () => { + writeStoredCursorApiKey("stored-key-123"); + process.env.CURSOR_API_KEY = "env-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "composer-2", + displayName: "Composer 2", + variants: [{ params: [], displayName: "Composer 2", isDefault: true }], + }, + ]); + + await discoverModels(); + + expect(mockedList).toHaveBeenCalledWith({ apiKey: "stored-key-123" }); + }); + + it('treats unresolved stored "CURSOR_API_KEY" auth as missing when env is absent', async () => { + writeStoredCursorApiKey("CURSOR_API_KEY"); + const issues: CursorModelFallbackIssue[] = []; + + const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); + + expect(models.some((model) => model.id === "composer-2")).toBe(true); + expect(issues).toEqual([expect.objectContaining({ reason: "missing-api-key" })]); + expect(issues[0].message).toContain("/login"); + expect(mockedList).not.toHaveBeenCalled(); + }); + + it('resolves stored "CURSOR_API_KEY" auth through the env var when present', async () => { + writeStoredCursorApiKey("CURSOR_API_KEY"); + process.env.CURSOR_API_KEY = "env-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "composer-2", + displayName: "Composer 2", + variants: [{ params: [], displayName: "Composer 2", isDefault: true }], + }, + ]); + + await discoverModels(); + + expect(mockedList).toHaveBeenCalledWith({ apiKey: "env-key-123" }); + }); + it("parses pi --api-key=value for model discovery", () => { expect(__testUtils.getCliApiKeyFromArgv(["node", "pi", "--api-key=cli-key-123"])).toBe("cli-key-123"); expect(__testUtils.getCliApiKeyFromArgv(["node", "pi", "--api-key", "--list-models"])).toBeUndefined(); @@ -534,6 +624,7 @@ describe("discoverModels", () => { message: expect.stringContaining("Cursor model discovery failed"), }), ]); + expect(issues[0].message).toContain("/login"); expect(issues[0].message).not.toContain("test-key-123"); }); @@ -549,7 +640,8 @@ describe("discoverModels", () => { message: expect.stringContaining("Cursor model discovery returned no models"), }), ]); - expect(issues[0].message).toContain("selection only"); + expect(issues[0].message).toContain("/login"); + expect(issues[0].message).toContain("/reload"); }); it("uses id as name when displayName is missing", async () => { -- 2.51.2 From c583163397de58b022332a18c6598908bd7c4749 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 11:38:05 -0600 Subject: [PATCH 017/359] Improve Cursor internal tool transcript display --- CHANGELOG.md | 2 +- README.md | 2 +- docs/cursor-model-ux-spec.md | 2 +- src/cursor-provider.ts | 31 +- src/cursor-tool-transcript.ts | 466 ++++++++++++++++++++++++++++ test/cursor-provider.test.ts | 82 ++++- test/cursor-tool-transcript.test.ts | 137 ++++++++ 7 files changed, 691 insertions(+), 31 deletions(-) create mode 100644 src/cursor-tool-transcript.ts create mode 100644 test/cursor-tool-transcript.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d0a20bd..625e28a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Added pi-native `/login` API-key integration for the Cursor provider. Startup discovery now checks pi `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. - Fallback Cursor models remain available when startup discovery cannot authenticate; once auth is saved, fallback model runs can use the stored key, while `/reload` or restart refreshes the full live Cursor model catalog. -- Cleaned up Cursor activity display by keeping internal tool activity in a concise bounded trace, omitting raw Cursor call IDs, and using the final Cursor run result for the visible assistant answer when available. +- Improved Cursor activity display by preserving Cursor thinking and rendering completed Cursor internal tools as bounded pi-like transcript blocks where possible, including safe local previews for empty read results, while still omitting raw Cursor call IDs and using the final Cursor run result for the visible assistant answer when available. - Stopped copying Cursor SDK cumulative internal agent/tool/cache token usage into pi usage, preventing false context-overflow and compaction triggers after long Cursor runs. ## 0.1.4 - 2026-05-07 diff --git a/README.md b/README.md index 635edd3..fced710 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not exposed as pi tool calls.** You may see a concise Cursor activity trace before the final answer, but you will not get normal pi tool-call rows for Cursor's internal tool activity. +- **Cursor-side tool use is not exposed as pi tool calls.** Cursor thinking remains visible, and Cursor internal tools are rendered as a bounded pi-like transcript before the final answer where possible, but they are not normal pi `toolCall`/`toolResult` rows because Cursor executes them inside the SDK. This avoids duplicate reads/shell commands, but it also means Cursor runs do not get pi's native green tool-result cards or tool-call counts unless pi adds a non-executing external tool transcript API. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 7f49cfd..8f04889 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -17,7 +17,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking and tool activity are surfaced as bounded trace content before final text. Cursor text deltas are treated as intermediate progress unless the final run result is unavailable, so the saved visible answer stays clean while message content order remains trace first, final answer second. +- Cursor-side thinking remains visible. Cursor internal tool activity is surfaced as bounded pi-like transcript content before final text where possible (for example `read ` with file preview or `$ ` with output), while avoiding actual pi `toolCall` blocks so pi does not re-execute tools that Cursor already ran. This is intentionally provider-only: the TUI can show readable tool traces, but it cannot show native green tool-result cards or increment tool-call/tool-result counts without a pi core API for non-executing external tool transcripts. Cursor text deltas are treated as intermediate progress unless the final run result is unavailable, so the saved visible answer stays clean while message content order remains trace first, tool transcript, final answer. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index a623985..873e8a1 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -13,6 +13,7 @@ import { buildCursorPrompt, type CursorPrompt } from "./context.js"; import { getEffectiveFastForModelId } from "./cursor-state.js"; import { buildCursorModelSelection } from "./model-discovery.js"; import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js"; +import { formatCursorToolTranscript, mergeCursorToolCalls } from "./cursor-tool-transcript.js"; function makeInitialMessage(model: Model): AssistantMessage { return { @@ -109,10 +110,6 @@ function getCursorToolName(toolCall: unknown): string { return "unknown"; } -function getCursorToolResult(toolCall: unknown): unknown { - return getObjectField(toolCall, "result"); -} - async function cacheSdkContextWindow(agentId: string, modelId: string): Promise { try { const platform = await createAgentPlatform(); @@ -149,17 +146,6 @@ function truncateSingleLine(value: string, maxLength = 240): string { return sanitized.length > maxLength ? `${sanitized.slice(0, maxLength - 1)}…` : sanitized; } -function summarizeCursorToolResult(result: unknown): string { - if (result === undefined) return ""; - const parts: string[] = []; - const status = getObjectField(result, "status"); - if (typeof status === "string") parts.push(sanitizeSingleLine(status)); - const value = getObjectField(result, "value"); - const exitCode = getObjectField(value, "exitCode") ?? getObjectField(result, "exitCode"); - if (typeof exitCode === "number") parts.push(`exit ${exitCode}`); - return parts.length > 0 ? ` (${parts.join(", ")})` : ""; -} - function formatCursorToolName(toolCall: unknown): string { return truncateSingleLine(getCursorToolName(toolCall), 80) || "unknown"; } @@ -213,6 +199,7 @@ export function streamCursor( let activityTraceChars = 0; let activityTraceTruncated = false; const textDeltas: string[] = []; + const startedToolCalls = new Map(); const appendBufferedTextDelta = (text: string): void => { textDeltas.push(text); @@ -251,6 +238,12 @@ export function streamCursor( appendTraceDelta(`${text}\n`); }; + const appendTraceBlock = (text: string): void => { + closeTraceBlock(); + appendTraceDelta(text.endsWith("\n") ? text : `${text}\n`); + closeTraceBlock(); + }; + const closeTraceBlock = (): void => { if (thinkingContentIndex < 0) return; const block = partial.content[thinkingContentIndex]; @@ -300,10 +293,12 @@ export function streamCursor( } else if (update.type === "thinking-completed") { closeTraceBlock(); } else if (update.type === "tool-call-started") { - appendTraceLine(`Cursor tool: ${formatCursorToolName(update.toolCall)} started`); + startedToolCalls.set(update.callId, update.toolCall); } else if (update.type === "tool-call-completed") { - const suffix = summarizeCursorToolResult(getCursorToolResult(update.toolCall)); - appendTraceLine(`Cursor tool: ${formatCursorToolName(update.toolCall)} completed${suffix}`); + const mergedToolCall = mergeCursorToolCalls(startedToolCalls.get(update.callId), update.toolCall); + startedToolCalls.delete(update.callId); + const transcript = scrubSensitiveText(formatCursorToolTranscript(mergedToolCall, { cwd }), resolvedApiKey); + appendTraceBlock(transcript || `Cursor tool: ${formatCursorToolName(mergedToolCall)} completed`); } else if (update.type === "summary") { appendTraceLine(`Cursor summary: ${truncateSingleLine(update.summary)}`); } diff --git a/src/cursor-tool-transcript.ts b/src/cursor-tool-transcript.ts new file mode 100644 index 0000000..d0c0e7d --- /dev/null +++ b/src/cursor-tool-transcript.ts @@ -0,0 +1,466 @@ +import { closeSync, openSync, readSync, statSync } from "node:fs"; +import { isAbsolute, relative, resolve } from "node:path"; + +const DEFAULT_MAX_TRANSCRIPT_CHARS = 24000; +const DEFAULT_MAX_TRANSCRIPT_LINES = 800; +const DEFAULT_MAX_LIST_ITEMS = 200; +const DEFAULT_READ_TRANSCRIPT_CHARS = 4000; +const DEFAULT_READ_TRANSCRIPT_LINES = 12; + +interface TranscriptOptions { + maxChars?: number; + maxLines?: number; + maxListItems?: number; + cwd?: string; +} + +interface NormalizedResult { + status: string | undefined; + value: unknown; + error: unknown; +} + +function asRecord(value: unknown): Record | undefined { + return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : undefined; +} + +function getString(record: Record | undefined, key: string): string | undefined { + const value = record?.[key]; + return typeof value === "string" ? value : undefined; +} + +function getNumber(record: Record | undefined, key: string): number | undefined { + const value = record?.[key]; + return typeof value === "number" && Number.isFinite(value) ? value : undefined; +} + +function getBoolean(record: Record | undefined, key: string): boolean | undefined { + const value = record?.[key]; + return typeof value === "boolean" ? value : undefined; +} + +function getRecord(record: Record | undefined, key: string): Record | undefined { + return asRecord(record?.[key]); +} + +function getArray(record: Record | undefined, key: string): unknown[] | undefined { + const value = record?.[key]; + return Array.isArray(value) ? value : undefined; +} + +function getToolName(toolCall: unknown): string { + const record = asRecord(toolCall); + return getString(record, "name") ?? getString(record, "type") ?? getString(record, "toolName") ?? "unknown"; +} + +function getToolArgs(toolCall: unknown): Record { + const record = asRecord(toolCall); + return getRecord(record, "args") ?? getRecord(record, "input") ?? {}; +} + +function getToolResult(toolCall: unknown): unknown { + const record = asRecord(toolCall); + return record?.result; +} + +function normalizeToolName(name: string): string { + const normalized = name.replace(/\s+/g, " ").trim(); + switch (normalized) { + case "read_file": + return "read"; + case "list_dir": + return "ls"; + case "run_terminal_cmd": + case "terminal": + case "bash": + return "shell"; + case "grep_search": + case "search": + return "grep"; + case "file_search": + return "glob"; + default: + return normalized || "unknown"; + } +} + +function normalizeResult(result: unknown): NormalizedResult { + const record = asRecord(result); + const status = getString(record, "status"); + if (status === "success" || status === "error") { + return { status, value: record?.value, error: record?.error }; + } + return { status, value: result, error: undefined }; +} + +function stringifyUnknown(value: unknown): string { + if (value === undefined) return ""; + if (typeof value === "string") return value; + try { + return JSON.stringify(value, null, 2) ?? String(value); + } catch { + return String(value); + } +} + +function limitText(text: string, options: TranscriptOptions = {}, knownTotalLines?: number): string { + const maxChars = options.maxChars ?? DEFAULT_MAX_TRANSCRIPT_CHARS; + const maxLines = options.maxLines ?? DEFAULT_MAX_TRANSCRIPT_LINES; + const lines = text.split("\n"); + let limitedLines = lines.slice(0, maxLines); + let limited = limitedLines.join("\n"); + let truncatedLines = Math.max((knownTotalLines ?? lines.length) - limitedLines.length, 0); + let truncatedChars = 0; + + if (limited.length > maxChars) { + truncatedChars += limited.length - maxChars; + limited = limited.slice(0, maxChars); + limitedLines = limited.split("\n"); + truncatedLines = Math.max(truncatedLines, Math.max((knownTotalLines ?? lines.length) - limitedLines.length, 0)); + } + if (text.length > limited.length) { + truncatedChars += Math.max(text.length - limited.length - truncatedChars, 0); + } + + const suffixParts: string[] = []; + if (truncatedLines > 0) suffixParts.push(`${truncatedLines} more lines`); + if (truncatedChars > 0 && truncatedLines === 0) suffixParts.push(`${truncatedChars} more chars`); + return suffixParts.length > 0 ? `${limited}\n... (${suffixParts.join(", ")} truncated)` : limited; +} + +function limitItems(items: T[], options: TranscriptOptions = {}): { items: T[]; omitted: number } { + const maxListItems = options.maxListItems ?? DEFAULT_MAX_LIST_ITEMS; + return { items: items.slice(0, maxListItems), omitted: Math.max(items.length - maxListItems, 0) }; +} + +function joinSections(header: string, body?: string): string { + const trimmedBody = body?.trimEnd(); + return trimmedBody ? `${header}\n\n${trimmedBody}\n` : `${header}\n`; +} + +function formatError(error: unknown): string { + const text = stringifyUnknown(error).trim(); + return text ? `Error: ${text}` : "Error"; +} + +function formatDisplayPath(path: string, cwd = process.cwd()): string { + const trimmed = path.trim(); + if (!trimmed) return trimmed; + if (!isAbsolute(trimmed)) return trimmed; + const relativePath = relative(cwd, trimmed); + if (!relativePath || relativePath === "") return "."; + if (relativePath.startsWith("..") || isAbsolute(relativePath)) return trimmed; + return relativePath; +} + +function resolveFilePath(path: string, cwd = process.cwd()): string { + return isAbsolute(path) ? path : resolve(cwd, path); +} + +function isPathWithinCwd(filePath: string, cwd = process.cwd()): boolean { + const relativePath = relative(cwd, filePath); + return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath)); +} + +function isSensitivePreviewPath(filePath: string): boolean { + const segments = filePath.split(/[\\/]+/).map((segment) => segment.toLowerCase()); + const basename = segments.at(-1) ?? ""; + return ( + segments.includes(".ssh") || + segments.includes("secrets") || + basename === ".env" || + basename.startsWith(".env.") || + basename === ".npmrc" || + basename === ".netrc" || + basename === "credentials" || + basename === "id_rsa" || + basename === "id_ed25519" || + /\.(?:pem|key|p12|pfx)$/i.test(basename) + ); +} + +function readFilePreview(path: string, options: TranscriptOptions): string | undefined { + const cwd = options.cwd ?? process.cwd(); + const filePath = resolveFilePath(path, cwd); + if (!isPathWithinCwd(filePath, cwd) || isSensitivePreviewPath(filePath)) return undefined; + + const maxChars = options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS; + const maxBytes = Math.max(8192, maxChars * 4); + let fd: number | undefined; + try { + const stat = statSync(filePath); + if (!stat.isFile()) return undefined; + fd = openSync(filePath, "r"); + const buffer = Buffer.alloc(Math.min(stat.size, maxBytes)); + const bytesRead = readSync(fd, buffer, 0, buffer.length, 0); + const text = buffer.toString("utf8", 0, bytesRead); + if (text.includes("\0")) return undefined; + return text; + } catch { + return undefined; + } finally { + if (fd !== undefined) closeSync(fd); + } +} + +function formatPathArg(args: Record, options: TranscriptOptions, key = "path"): string | undefined { + const path = args[key]; + return typeof path === "string" && path.trim() ? formatDisplayPath(path, options.cwd) : undefined; +} + +function formatRead(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const rawPath = typeof args.path === "string" ? args.path : undefined; + const path = rawPath ? formatDisplayPath(rawPath, options.cwd) : "unknown"; + if (result.status === "error") return joinSections(`read ${path}`, formatError(result.error)); + + const value = asRecord(result.value); + const totalLines = getNumber(value, "totalLines"); + const readOptions = { + ...options, + maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS, + maxLines: options.maxLines ?? DEFAULT_READ_TRANSCRIPT_LINES, + }; + const resultContent = getString(value, "content"); + const content = resultContent && resultContent.length > 0 ? resultContent : rawPath ? readFilePreview(rawPath, readOptions) : undefined; + return joinSections(`read ${path}`, limitText(content ?? stringifyUnknown(result.value), readOptions, totalLines)); +} + +function formatShell(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const command = typeof args.command === "string" ? args.command : stringifyUnknown(args).trim(); + if (result.status === "error") return joinSections(`$ ${command || "shell"}`, formatError(result.error)); + + const value = asRecord(result.value); + const stdout = getString(value, "stdout") ?? ""; + const stderr = getString(value, "stderr") ?? ""; + const exitCode = getNumber(value, "exitCode"); + const executionTime = getNumber(value, "executionTime"); + const outputParts: string[] = []; + if (stdout) outputParts.push(stdout.trimEnd()); + if (stderr) outputParts.push(stderr.trimEnd()); + if (exitCode !== undefined && exitCode !== 0) outputParts.push(`Command exited with code ${exitCode}`); + if (executionTime !== undefined) outputParts.push(`Took ${(executionTime / 1000).toFixed(1)}s`); + return joinSections(`$ ${command || "shell"}`, limitText(outputParts.filter(Boolean).join("\n\n"), options)); +} + +function renderTreeNode(node: unknown, depth = 0, lines: string[] = []): string[] { + const record = asRecord(node); + if (!record) return lines; + const name = getString(record, "name") ?? getString(record, "path") ?? getString(record, "relativePath") ?? ""; + const indent = " ".repeat(depth); + if (name) lines.push(`${indent}${name}`); + const children = getArray(record, "children") ?? getArray(record, "entries") ?? getArray(record, "files") ?? []; + for (const child of children) renderTreeNode(child, depth + 1, lines); + return lines; +} + +function formatLs(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const path = formatPathArg(args, options) ?? "."; + if (result.status === "error") return joinSections(`ls ${path}`, formatError(result.error)); + + const value = asRecord(result.value); + const root = value?.directoryTreeRoot ?? result.value; + const treeLines = renderTreeNode(root); + const body = treeLines.length > 0 ? treeLines.join("\n") : stringifyUnknown(result.value); + return joinSections(`ls ${path}`, limitText(body, options)); +} + +function formatGlob(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const pattern = typeof args.globPattern === "string" ? args.globPattern : "*"; + const targetDirectory = typeof args.targetDirectory === "string" ? formatDisplayPath(args.targetDirectory, options.cwd) : undefined; + const header = targetDirectory ? `glob ${pattern} in ${targetDirectory}` : `glob ${pattern}`; + if (result.status === "error") return joinSections(header, formatError(result.error)); + + const value = asRecord(result.value); + const files = getArray(value, "files")?.filter((entry): entry is string => typeof entry === "string") ?? []; + if (files.length === 0) return joinSections(header, stringifyUnknown(result.value)); + const limited = limitItems(files, options); + const body = limited.omitted > 0 ? `${limited.items.join("\n")}\n... (${limited.omitted} more files truncated)` : limited.items.join("\n"); + return joinSections(header, body); +} + +function collectSearchResults(value: unknown): string[] { + const record = asRecord(value); + const outputs: unknown[] = []; + const activeEditorResult = record?.activeEditorResult; + if (activeEditorResult) outputs.push(activeEditorResult); + const workspaceResults = asRecord(record?.workspaceResults); + if (workspaceResults) outputs.push(...Object.values(workspaceResults)); + if (outputs.length === 0) outputs.push(value); + + const lines: string[] = []; + for (const outputValue of outputs) { + const outputRecord = asRecord(outputValue); + const type = getString(outputRecord, "type"); + const output = getRecord(outputRecord, "output"); + if (type === "content") { + const matches = getArray(output, "matches") ?? []; + for (const match of matches) { + const matchRecord = asRecord(match); + const file = getString(matchRecord, "file") ?? ""; + const lineNumber = getNumber(matchRecord, "lineNumber"); + const line = getString(matchRecord, "line") ?? ""; + lines.push(`${file}${lineNumber !== undefined ? `:${lineNumber}` : ""}: ${line}`.trim()); + } + } else if (type === "files") { + const files = getArray(output, "files") ?? []; + lines.push(...files.filter((entry): entry is string => typeof entry === "string")); + } else if (type === "count") { + const counts = getArray(output, "counts") ?? []; + for (const count of counts) { + const countRecord = asRecord(count); + lines.push(`${getString(countRecord, "file") ?? ""}: ${getNumber(countRecord, "count") ?? 0}`.trim()); + } + } else { + lines.push(stringifyUnknown(outputValue)); + } + } + return lines.filter(Boolean); +} + +function formatGrep(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const pattern = typeof args.pattern === "string" ? args.pattern : ""; + const path = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined; + const glob = typeof args.glob === "string" ? args.glob : undefined; + const header = ["grep", pattern && JSON.stringify(pattern), path ?? glob].filter(Boolean).join(" "); + if (result.status === "error") return joinSections(header, formatError(result.error)); + + const lines = collectSearchResults(result.value); + const limited = limitItems(lines, options); + const body = limited.omitted > 0 ? `${limited.items.join("\n")}\n... (${limited.omitted} more matches truncated)` : limited.items.join("\n"); + return joinSections(header, limitText(body || stringifyUnknown(result.value), options)); +} + +function formatWrite(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const path = formatPathArg(args, options) ?? "unknown"; + if (result.status === "error") return joinSections(`write ${path}`, formatError(result.error)); + + const value = asRecord(result.value); + const linesCreated = getNumber(value, "linesCreated"); + const fileSize = getNumber(value, "fileSize"); + const fileContentAfterWrite = getString(value, "fileContentAfterWrite"); + const parts = [ + linesCreated !== undefined ? `Created ${linesCreated} lines` : undefined, + fileSize !== undefined ? `File size: ${fileSize} bytes` : undefined, + fileContentAfterWrite ? limitText(fileContentAfterWrite, options) : undefined, + ].filter((part): part is string => Boolean(part)); + return joinSections(`write ${path}`, parts.join("\n\n") || stringifyUnknown(result.value)); +} + +function formatEdit(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const path = formatPathArg(args, options) ?? "unknown"; + if (result.status === "error") return joinSections(`edit ${path}`, formatError(result.error)); + + const value = asRecord(result.value); + const diff = getString(value, "diffString"); + const linesAdded = getNumber(value, "linesAdded"); + const linesRemoved = getNumber(value, "linesRemoved"); + const stats = [ + linesAdded !== undefined ? `+${linesAdded}` : undefined, + linesRemoved !== undefined ? `-${linesRemoved}` : undefined, + ].filter(Boolean).join(" "); + const body = [stats, diff ? limitText(diff, options) : undefined].filter((part): part is string => Boolean(part)).join("\n\n"); + return joinSections(`edit ${path}`, body || stringifyUnknown(result.value)); +} + +function formatDelete(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const path = formatPathArg(args, options) ?? "unknown"; + if (result.status === "error") return joinSections(`delete ${path}`, formatError(result.error)); + const value = asRecord(result.value); + const fileSize = getNumber(value, "fileSize"); + return joinSections(`delete ${path}`, fileSize !== undefined ? `Deleted ${fileSize} bytes` : stringifyUnknown(result.value)); +} + +function formatReadLints(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const paths = Array.isArray(args.paths) + ? args.paths.filter((entry): entry is string => typeof entry === "string").map((entry) => formatDisplayPath(entry, options.cwd)) + : []; + const header = `readLints${paths.length > 0 ? ` ${paths.join(" ")}` : ""}`; + if (result.status === "error") return joinSections(header, formatError(result.error)); + + const value = asRecord(result.value); + const files = getArray(value, "fileDiagnostics") ?? []; + const lines: string[] = []; + for (const file of files) { + const fileRecord = asRecord(file); + const pathValue = getString(fileRecord, "path"); + const path = pathValue ? formatDisplayPath(pathValue, options.cwd) : "unknown"; + const diagnostics = getArray(fileRecord, "diagnostics") ?? []; + for (const diagnostic of diagnostics) { + const diagnosticRecord = asRecord(diagnostic); + const severity = getString(diagnosticRecord, "severity") ?? "diagnostic"; + const message = getString(diagnosticRecord, "message") ?? ""; + const source = getString(diagnosticRecord, "source"); + lines.push(`${path}: ${severity}${source ? ` ${source}` : ""}: ${message}`); + } + } + return joinSections(header, limitText(lines.join("\n") || stringifyUnknown(result.value), options)); +} + +function formatMcp(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const toolName = typeof args.toolName === "string" ? args.toolName : "mcp"; + if (result.status === "error") return joinSections(toolName, formatError(result.error)); + + const value = asRecord(result.value); + const isError = getBoolean(value, "isError"); + const content = getArray(value, "content") ?? []; + const text = content + .map((entry) => getString(asRecord(entry), "text")) + .filter((entry): entry is string => Boolean(entry)) + .join("\n"); + const body = `${isError ? "[tool error]\n" : ""}${text || stringifyUnknown(result.value)}`; + return joinSections(toolName, limitText(body, options)); +} + +function formatFallback(name: string, args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const header = name === "unknown" ? "Cursor tool" : name; + if (result.status === "error") return joinSections(header, formatError(result.error)); + const argsText = Object.keys(args).length > 0 ? `${stringifyUnknown(args)}\n\n` : ""; + return joinSections(header, limitText(`${argsText}${stringifyUnknown(result.value)}`.trim(), options)); +} + +export function formatCursorToolTranscript(toolCall: unknown, options: TranscriptOptions = {}): string { + const name = normalizeToolName(getToolName(toolCall)); + const args = getToolArgs(toolCall); + const result = normalizeResult(getToolResult(toolCall)); + + switch (name) { + case "read": + return formatRead(args, result, options); + case "shell": + return formatShell(args, result, options); + case "ls": + return formatLs(args, result, options); + case "glob": + return formatGlob(args, result, options); + case "grep": + return formatGrep(args, result, options); + case "write": + return formatWrite(args, result, options); + case "edit": + return formatEdit(args, result, options); + case "delete": + return formatDelete(args, result, options); + case "readLints": + return formatReadLints(args, result, options); + case "mcp": + return formatMcp(args, result, options); + default: + return formatFallback(name, args, result, options); + } +} + +export function mergeCursorToolCalls(startedToolCall: unknown, completedToolCall: unknown): unknown { + const started = asRecord(startedToolCall); + const completed = asRecord(completedToolCall); + if (!started) return completedToolCall; + if (!completed) return startedToolCall; + return { + ...started, + ...completed, + name: completed.name ?? started.name, + type: completed.type ?? started.type, + args: completed.args ?? started.args, + input: completed.input ?? started.input, + result: completed.result ?? started.result, + }; +} diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index ce26819..05bfe33 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -253,10 +253,19 @@ describe("streamCursor", () => { expect(toolEvents).toHaveLength(0); }); - it("surfaces cursor tool activity in the trace without polluting final text", async () => { + it("surfaces cursor tool results as pi-like trace transcript without polluting final text", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { - opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "list_dir" }, callId: "c1" } }); - opts.onDelta({ update: { type: "tool-call-completed", toolCall: { name: "list_dir", result: { files: ["README.md"] } }, callId: "c1" } }); + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "README.md" } }, callId: "c1" } }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { status: "success", value: { content: "# pi-cursor-sdk\n\nReadme body", totalLines: 3, fileSize: 29 } }, + }, + callId: "c1", + }, + }); opts.onDelta({ update: { type: "summary", summary: "Inspected files" } }); opts.onDelta({ update: { type: "text-delta", text: "done" } }); return { @@ -280,13 +289,13 @@ describe("streamCursor", () => { const text = events.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); const done = events.find((e: any) => e.type === "done") as any; - expect(trace).toContain("Cursor tool: list_dir started"); - expect(trace).toContain("Cursor tool: list_dir completed"); + expect(trace).toContain("read README.md"); + expect(trace).toContain("# pi-cursor-sdk"); + expect(trace).not.toContain("Cursor tool: read started"); expect(trace).not.toContain("call c1"); - expect(trace).not.toContain("README.md"); expect(trace).toContain("Cursor summary: Inspected files"); expect(text).toBe("done"); - expect(done.message.content.map((block: any) => block.type)).toEqual(["thinking", "text"]); + expect(done.message.content.map((block: any) => block.type)).toEqual(["thinking", "thinking", "text"]); }); it("prefers the final run result over intermediate cursor progress text", async () => { @@ -316,12 +325,22 @@ describe("streamCursor", () => { expect(text).not.toContain("I’m checking files"); }); - it("keeps cursor tool activity one-line and omits raw call ids", async () => { + it("omits raw cursor call ids while rendering completed cursor tools", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "tool-call-started", - toolCall: { name: "shell\nextra" }, + toolCall: { name: "shell", args: { command: "date" } }, + callId: "call_abc\nfc_secret", + }, + }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "shell", + result: { status: "success", value: { stdout: "Sat May 9\n", stderr: "", exitCode: 0, executionTime: 12 } }, + }, callId: "call_abc\nfc_secret", }, }); @@ -344,11 +363,54 @@ describe("streamCursor", () => { const events = await collectEvents(stream); const trace = events.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); - expect(trace).toContain("Cursor tool: shell extra started\n"); + expect(trace).toContain("$ date\n"); + expect(trace).toContain("Sat May 9"); + expect(trace).toContain("Took 0.0s"); expect(trace).not.toContain("call_abc"); expect(trace).not.toContain("fc_secret"); }); + it("scrubs secrets from cursor tool transcript output", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "secrets.txt" } }, callId: "c1" } }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { + status: "success", + value: { content: "token=super-secret-key-12345\nAuthorization: Bearer bearer-token-value" }, + }, + }, + callId: "c1", + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "done" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "super-secret-key-12345" }); + const events = await collectEvents(stream); + const trace = events.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + + expect(trace).toContain("read secrets.txt"); + expect(trace).toContain("[redacted]"); + expect(trace).not.toContain("super-secret-key-12345"); + expect(trace).not.toContain("bearer-token-value"); + }); + it("keeps late cursor thinking before final text in the saved content order", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "text-delta", text: "Final answer" } }); diff --git a/test/cursor-tool-transcript.test.ts b/test/cursor-tool-transcript.test.ts new file mode 100644 index 0000000..2cc2e7a --- /dev/null +++ b/test/cursor-tool-transcript.test.ts @@ -0,0 +1,137 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { formatCursorToolTranscript, mergeCursorToolCalls } from "../src/cursor-tool-transcript.js"; + +describe("formatCursorToolTranscript", () => { + it("formats Cursor read results as a pi-like read transcript", () => { + const transcript = formatCursorToolTranscript({ + name: "read", + args: { path: "README.md" }, + result: { + status: "success", + value: { content: "# pi-cursor-sdk\n\nA pi provider extension", totalLines: 3, fileSize: 42 }, + }, + }); + + expect(transcript).toBe("read README.md\n\n# pi-cursor-sdk\n\nA pi provider extension\n"); + }); + + it("fills empty Cursor read results from a safe local file preview", () => { + const dir = mkdtempSync(join(tmpdir(), "cursor-tool-transcript-")); + try { + writeFileSync(join(dir, "README.md"), "# Local title\n\nLocal body\n"); + + const transcript = formatCursorToolTranscript( + { + name: "read", + args: { path: join(dir, "README.md") }, + result: { status: "success", value: { content: "", totalLines: 3, fileSize: 26 } }, + }, + { cwd: dir }, + ); + + expect(transcript).toContain("read README.md"); + expect(transcript).toContain("# Local title"); + expect(transcript).toContain("Local body"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("does not fill empty Cursor read results from sensitive or out-of-workspace files", () => { + const dir = mkdtempSync(join(tmpdir(), "cursor-tool-transcript-")); + const outsideDir = mkdtempSync(join(tmpdir(), "cursor-tool-transcript-outside-")); + try { + writeFileSync(join(dir, ".env"), "API_KEY=do-not-show\n"); + writeFileSync(join(outsideDir, "notes.txt"), "outside content\n"); + + const sensitiveTranscript = formatCursorToolTranscript( + { + name: "read", + args: { path: join(dir, ".env") }, + result: { status: "success", value: { content: "", totalLines: 1, fileSize: 20 } }, + }, + { cwd: dir }, + ); + const outsideTranscript = formatCursorToolTranscript( + { + name: "read", + args: { path: join(outsideDir, "notes.txt") }, + result: { status: "success", value: { content: "", totalLines: 1, fileSize: 16 } }, + }, + { cwd: dir }, + ); + + expect(sensitiveTranscript).not.toContain("do-not-show"); + expect(outsideTranscript).not.toContain("outside content"); + } finally { + rmSync(dir, { recursive: true, force: true }); + rmSync(outsideDir, { recursive: true, force: true }); + } + }); + + it("shortens absolute workspace paths to relative paths", () => { + const transcript = formatCursorToolTranscript( + { + name: "read", + args: { path: "/repo/README.md" }, + result: { status: "success", value: { content: "# Title" } }, + }, + { cwd: "/repo" }, + ); + + expect(transcript).toContain("read README.md"); + expect(transcript).not.toContain("/repo/README.md"); + }); + + it("formats Cursor shell results as a pi-like bash transcript", () => { + const transcript = formatCursorToolTranscript({ + name: "shell", + args: { command: "date" }, + result: { + status: "success", + value: { stdout: "Sat May 9 10:48:38 MDT 2026\n", stderr: "", exitCode: 0, executionTime: 12 }, + }, + }); + + expect(transcript).toContain("$ date\n\nSat May 9 10:48:38 MDT 2026"); + expect(transcript).toContain("Took 0.0s"); + }); + + it("keeps started tool args when the completed Cursor update only contains a result", () => { + const merged = mergeCursorToolCalls( + { name: "read", args: { path: "src/index.ts" } }, + { name: "read", result: { status: "success", value: { content: "export default" } } }, + ); + + expect(formatCursorToolTranscript(merged)).toContain("read src/index.ts"); + }); + + it("maps common Cursor aliases to pi-like command names", () => { + const transcript = formatCursorToolTranscript({ + name: "run_terminal_cmd", + args: { command: "pwd" }, + result: { status: "success", value: { stdout: "/tmp\n", stderr: "", exitCode: 0, executionTime: 1 } }, + }); + + expect(transcript).toContain("$ pwd"); + expect(transcript).toContain("/tmp"); + }); + + it("bounds large Cursor read output", () => { + const transcript = formatCursorToolTranscript( + { + name: "read", + args: { path: "big.txt" }, + result: { status: "success", value: { content: Array.from({ length: 20 }, (_, index) => `line ${index}`).join("\n") } }, + }, + { maxLines: 3, maxChars: 1000 }, + ); + + expect(transcript).toContain("read big.txt"); + expect(transcript).toContain("line 0\nline 1\nline 2"); + expect(transcript).toContain("17 more lines"); + }); +}); -- 2.51.2 From ce580f4ed533cfc23763caa367795d7234f3f591 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 12:06:10 -0600 Subject: [PATCH 018/359] Render Cursor tools with native pi components --- CHANGELOG.md | 2 +- README.md | 2 +- docs/cursor-model-ux-spec.md | 2 +- src/cursor-native-tool-display.ts | 79 +++++++++++++++++++++ src/cursor-provider.ts | 53 ++++++++++++-- src/cursor-tool-transcript.ts | 103 +++++++++++++++++++++++++--- src/index.ts | 2 + test/cursor-provider.test.ts | 35 ++++++++++ test/cursor-tool-transcript.test.ts | 28 +++++++- test/index.test.ts | 35 ++++++++++ 10 files changed, 325 insertions(+), 16 deletions(-) create mode 100644 src/cursor-native-tool-display.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 625e28a..ae20e1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Added pi-native `/login` API-key integration for the Cursor provider. Startup discovery now checks pi `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. - Fallback Cursor models remain available when startup discovery cannot authenticate; once auth is saved, fallback model runs can use the stored key, while `/reload` or restart refreshes the full live Cursor model catalog. -- Improved Cursor activity display by preserving Cursor thinking and rendering completed Cursor internal tools as bounded pi-like transcript blocks where possible, including safe local previews for empty read results, while still omitting raw Cursor call IDs and using the final Cursor run result for the visible assistant answer when available. +- Improved Cursor activity display by preserving Cursor thinking and replaying completed Cursor internal tools through pi's native tool rendering components where possible, including safe local previews for empty read results, while still omitting raw Cursor call IDs and using the final Cursor run result for the visible assistant answer when available. - Stopped copying Cursor SDK cumulative internal agent/tool/cache token usage into pi usage, preventing false context-overflow and compaction triggers after long Cursor runs. ## 0.1.4 - 2026-05-07 diff --git a/README.md b/README.md index fced710..c91f82f 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not exposed as pi tool calls.** Cursor thinking remains visible, and Cursor internal tools are rendered as a bounded pi-like transcript before the final answer where possible, but they are not normal pi `toolCall`/`toolResult` rows because Cursor executes them inside the SDK. This avoids duplicate reads/shell commands, but it also means Cursor runs do not get pi's native green tool-result cards or tool-call counts unless pi adds a non-executing external tool transcript API. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive pi, replays it through pi's native tool rendering components where possible (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools. Non-interactive/session consumers still get bounded scrubbed transcript data. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 8f04889..1c27c01 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -17,7 +17,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is surfaced as bounded pi-like transcript content before final text where possible (for example `read ` with file preview or `$ ` with output), while avoiding actual pi `toolCall` blocks so pi does not re-execute tools that Cursor already ran. This is intentionally provider-only: the TUI can show readable tool traces, but it cannot show native green tool-result cards or increment tool-call/tool-result counts without a pi core API for non-executing external tool transcripts. Cursor text deltas are treated as intermediate progress unless the final run result is unavailable, so the saved visible answer stays clean while message content order remains trace first, tool transcript, final answer. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events, scrubbed, and replayed through pi's native `ToolExecutionComponent` inside a custom display message where possible (for example green `read ` or `$ ` cards). These displays do not force Cursor to use pi tools and do not re-execute Cursor's reads or shell commands. Cursor text deltas are treated as intermediate progress unless the final run result is unavailable, so the saved visible answer stays clean while Cursor's own tool calls remain Cursor-owned. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts new file mode 100644 index 0000000..b29da81 --- /dev/null +++ b/src/cursor-native-tool-display.ts @@ -0,0 +1,79 @@ +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import { ToolExecutionComponent } from "@earendil-works/pi-coding-agent"; +import { Container } from "@earendil-works/pi-tui"; +import type { TUI } from "@earendil-works/pi-tui"; +import type { CursorPiToolDisplay } from "./cursor-tool-transcript.js"; + +const CURSOR_NATIVE_TOOL_MESSAGE_TYPE = "cursor-native-tool-display"; + +export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { + id: string; +} + +interface CursorNativeToolDisplayMessageDetails { + cwd: string; + tools: CursorNativeToolDisplayItem[]; +} + +let nativeToolDisplayEnabled = false; +const pendingNativeToolDisplays: CursorNativeToolDisplayItem[] = []; + +function createNoopTui(): TUI { + return { requestRender: () => {} } as TUI; +} + +export function isCursorNativeToolDisplayEnabled(): boolean { + return nativeToolDisplayEnabled; +} + +export function recordCursorNativeToolDisplay(item: CursorNativeToolDisplayItem): void { + if (!nativeToolDisplayEnabled) return; + pendingNativeToolDisplays.push(item); +} + +function drainCursorNativeToolDisplays(): CursorNativeToolDisplayItem[] { + return pendingNativeToolDisplays.splice(0); +} + +function renderNativeToolDisplay(details: CursorNativeToolDisplayMessageDetails, expanded: boolean): Container | undefined { + if (!Array.isArray(details.tools) || details.tools.length === 0) return undefined; + + const container = new Container(); + const tui = createNoopTui(); + for (const tool of details.tools) { + const component = new ToolExecutionComponent( + tool.toolName, + tool.id, + tool.args, + { showImages: true }, + undefined, + tui, + details.cwd, + ); + component.setExpanded(expanded); + component.markExecutionStarted(); + component.updateResult({ ...tool.result, isError: tool.isError }); + container.addChild(component); + } + return container; +} + +export function registerCursorNativeToolDisplay(pi: ExtensionAPI): void { + nativeToolDisplayEnabled = true; + + pi.registerMessageRenderer(CURSOR_NATIVE_TOOL_MESSAGE_TYPE, (message, options) => { + const details = message.details as CursorNativeToolDisplayMessageDetails | undefined; + return details ? renderNativeToolDisplay(details, options.expanded) : undefined; + }); + + pi.on("agent_end", () => { + const tools = drainCursorNativeToolDisplays(); + if (tools.length === 0) return; + pi.sendMessage({ + customType: CURSOR_NATIVE_TOOL_MESSAGE_TYPE, + content: "", + display: true, + details: { cwd: process.cwd(), tools } satisfies CursorNativeToolDisplayMessageDetails, + }); + }); +} diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 873e8a1..84d1c05 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -13,7 +13,8 @@ import { buildCursorPrompt, type CursorPrompt } from "./context.js"; import { getEffectiveFastForModelId } from "./cursor-state.js"; import { buildCursorModelSelection } from "./model-discovery.js"; import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js"; -import { formatCursorToolTranscript, mergeCursorToolCalls } from "./cursor-tool-transcript.js"; +import { buildCursorPiToolDisplay, formatCursorToolTranscript, mergeCursorToolCalls } from "./cursor-tool-transcript.js"; +import { isCursorNativeToolDisplayEnabled, recordCursorNativeToolDisplay } from "./cursor-native-tool-display.js"; function makeInitialMessage(model: Model): AssistantMessage { return { @@ -154,6 +155,15 @@ function hasUsableText(value: string | undefined): value is string { return typeof value === "string" && value.trim().length > 0; } +function scrubDisplayValue(value: unknown, apiKey?: string): unknown { + if (typeof value === "string") return scrubSensitiveText(value, apiKey); + if (Array.isArray(value)) return value.map((entry) => scrubDisplayValue(entry, apiKey)); + if (value && typeof value === "object") { + return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, scrubDisplayValue(entry, apiKey)])); + } + return value; +} + export function streamCursor( model: Model, context: Context, @@ -198,8 +208,10 @@ export function streamCursor( let thinkingContentIndex = -1; let activityTraceChars = 0; let activityTraceTruncated = false; + let nativeToolDisplayCounter = 0; const textDeltas: string[] = []; const startedToolCalls = new Map(); + const completedToolFingerprints = new Set(); const appendBufferedTextDelta = (text: string): void => { textDeltas.push(text); @@ -283,6 +295,33 @@ export function streamCursor( return block.text; }; + const getToolFingerprint = (toolCall: unknown): string => { + try { + return JSON.stringify(toolCall); + } catch { + return String(toolCall); + } + }; + + const handleCompletedToolCall = (toolCall: unknown): void => { + const fingerprint = getToolFingerprint(toolCall); + if (completedToolFingerprints.has(fingerprint)) return; + completedToolFingerprints.add(fingerprint); + + const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd }), resolvedApiKey); + if (isCursorNativeToolDisplayEnabled()) { + const display = buildCursorPiToolDisplay(toolCall, { cwd }); + recordCursorNativeToolDisplay({ + ...display, + id: `cursor-tool-${++nativeToolDisplayCounter}`, + args: scrubDisplayValue(display.args, resolvedApiKey) as Record, + result: scrubDisplayValue(display.result, resolvedApiKey) as typeof display.result, + }); + } else { + appendTraceBlock(transcript || `Cursor tool: ${formatCursorToolName(toolCall)} completed`); + } + }; + const onDelta = (args: { update: InteractionUpdate }): void => { const update = args.update; @@ -297,8 +336,7 @@ export function streamCursor( } else if (update.type === "tool-call-completed") { const mergedToolCall = mergeCursorToolCalls(startedToolCalls.get(update.callId), update.toolCall); startedToolCalls.delete(update.callId); - const transcript = scrubSensitiveText(formatCursorToolTranscript(mergedToolCall, { cwd }), resolvedApiKey); - appendTraceBlock(transcript || `Cursor tool: ${formatCursorToolName(mergedToolCall)} completed`); + handleCompletedToolCall(mergedToolCall); } else if (update.type === "summary") { appendTraceLine(`Cursor summary: ${truncateSingleLine(update.summary)}`); } @@ -308,6 +346,13 @@ export function streamCursor( // shell-output-delta, token-delta, step-* are intentionally not surfaced. }; + const onStep = (args: { step: unknown }): void => { + const step = getObjectField(args.step, "message") ? args.step : undefined; + if (getObjectField(args.step, "type") !== "toolCall") return; + const toolCall = getObjectField(step, "message"); + if (toolCall) handleCompletedToolCall(toolCall); + }; + // Handle abort signal let run: Awaited> | null = null; abortListener = () => { @@ -321,7 +366,7 @@ export function streamCursor( throwIfAborted(); run = await agent.send( { text: prompt.text, images: prompt.images.length > 0 ? prompt.images : undefined }, - { onDelta }, + { onDelta, onStep }, ); if (options?.signal?.aborted) { await run.cancel().catch(() => {}); diff --git a/src/cursor-tool-transcript.ts b/src/cursor-tool-transcript.ts index d0c0e7d..827d68a 100644 --- a/src/cursor-tool-transcript.ts +++ b/src/cursor-tool-transcript.ts @@ -14,6 +14,18 @@ interface TranscriptOptions { cwd?: string; } +interface PiToolDisplayResult { + content: Array<{ type: "text"; text: string }>; + details?: unknown; +} + +export interface CursorPiToolDisplay { + toolName: string; + args: Record; + result: PiToolDisplayResult; + isError: boolean; +} + interface NormalizedResult { status: string | undefined; value: unknown; @@ -208,6 +220,18 @@ function formatPathArg(args: Record, options: TranscriptOptions return typeof path === "string" && path.trim() ? formatDisplayPath(path, options.cwd) : undefined; } +function getReadContent(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const rawPath = typeof args.path === "string" ? args.path : undefined; + const readOptions = { + ...options, + maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS, + maxLines: options.maxLines ?? DEFAULT_READ_TRANSCRIPT_LINES, + }; + const value = asRecord(result.value); + const resultContent = getString(value, "content"); + return resultContent && resultContent.length > 0 ? resultContent : rawPath ? (readFilePreview(rawPath, readOptions) ?? stringifyUnknown(result.value)) : stringifyUnknown(result.value); +} + function formatRead(args: Record, result: NormalizedResult, options: TranscriptOptions): string { const rawPath = typeof args.path === "string" ? args.path : undefined; const path = rawPath ? formatDisplayPath(rawPath, options.cwd) : "unknown"; @@ -220,24 +244,28 @@ function formatRead(args: Record, result: NormalizedResult, opt maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS, maxLines: options.maxLines ?? DEFAULT_READ_TRANSCRIPT_LINES, }; - const resultContent = getString(value, "content"); - const content = resultContent && resultContent.length > 0 ? resultContent : rawPath ? readFilePreview(rawPath, readOptions) : undefined; - return joinSections(`read ${path}`, limitText(content ?? stringifyUnknown(result.value), readOptions, totalLines)); + return joinSections(`read ${path}`, limitText(getReadContent(args, result, options), readOptions, totalLines)); } -function formatShell(args: Record, result: NormalizedResult, options: TranscriptOptions): string { - const command = typeof args.command === "string" ? args.command : stringifyUnknown(args).trim(); - if (result.status === "error") return joinSections(`$ ${command || "shell"}`, formatError(result.error)); - +function getShellOutput(result: NormalizedResult): { text: string; exitCode: number | undefined } { const value = asRecord(result.value); const stdout = getString(value, "stdout") ?? ""; const stderr = getString(value, "stderr") ?? ""; const exitCode = getNumber(value, "exitCode"); - const executionTime = getNumber(value, "executionTime"); const outputParts: string[] = []; if (stdout) outputParts.push(stdout.trimEnd()); if (stderr) outputParts.push(stderr.trimEnd()); if (exitCode !== undefined && exitCode !== 0) outputParts.push(`Command exited with code ${exitCode}`); + return { text: outputParts.filter(Boolean).join("\n\n") || "(no output)", exitCode }; +} + +function formatShell(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const command = typeof args.command === "string" ? args.command : stringifyUnknown(args).trim(); + if (result.status === "error") return joinSections(`$ ${command || "shell"}`, formatError(result.error)); + + const value = asRecord(result.value); + const executionTime = getNumber(value, "executionTime"); + const outputParts = [getShellOutput(result).text]; if (executionTime !== undefined) outputParts.push(`Took ${(executionTime / 1000).toFixed(1)}s`); return joinSections(`$ ${command || "shell"}`, limitText(outputParts.filter(Boolean).join("\n\n"), options)); } @@ -449,6 +477,65 @@ export function formatCursorToolTranscript(toolCall: unknown, options: Transcrip } } +function textToolResult(text: string, details?: unknown): PiToolDisplayResult { + return { content: [{ type: "text", text }], details }; +} + +function buildGenericPiToolDisplay(name: string, args: Record, result: NormalizedResult, options: TranscriptOptions): CursorPiToolDisplay { + const isError = result.status === "error"; + return { + toolName: name, + args, + result: textToolResult(isError ? formatError(result.error) : limitText(stringifyUnknown(result.value), options)), + isError, + }; +} + +export function buildCursorPiToolDisplay(toolCall: unknown, options: TranscriptOptions = {}): CursorPiToolDisplay { + const name = normalizeToolName(getToolName(toolCall)); + const args = getToolArgs(toolCall); + const result = normalizeResult(getToolResult(toolCall)); + + if (name === "read") { + const isError = result.status === "error"; + const value = asRecord(result.value); + const totalLines = getNumber(value, "totalLines"); + const readOptions = { + ...options, + maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS, + maxLines: options.maxLines ?? DEFAULT_READ_TRANSCRIPT_LINES, + }; + return { + toolName: "read", + args, + result: textToolResult(isError ? formatError(result.error) : limitText(getReadContent(args, result, options), readOptions, totalLines)), + isError, + }; + } + + if (name === "shell") { + const shellOutput = getShellOutput(result); + const isError = result.status === "error" || (shellOutput.exitCode !== undefined && shellOutput.exitCode !== 0); + return { + toolName: "bash", + args, + result: textToolResult(result.status === "error" ? formatError(result.error) : limitText(shellOutput.text, options)), + isError, + }; + } + + if (name === "ls") { + return { + toolName: "ls", + args, + result: textToolResult(result.status === "error" ? formatError(result.error) : formatLs(args, result, options).split("\n\n").slice(1).join("\n\n").trim()), + isError: result.status === "error", + }; + } + + return buildGenericPiToolDisplay(name, args, result, options); +} + export function mergeCursorToolCalls(startedToolCall: unknown, completedToolCall: unknown): unknown { const started = asRecord(startedToolCall); const completed = asRecord(completedToolCall); diff --git a/src/index.ts b/src/index.ts index 566d32e..a6798f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,12 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { discoverModels, type CursorModelFallbackIssue } from "./model-discovery.js"; import { registerCursorFastControls } from "./cursor-state.js"; +import { registerCursorNativeToolDisplay } from "./cursor-native-tool-display.js"; import { streamCursor } from "./cursor-provider.js"; export default async function (pi: ExtensionAPI) { registerCursorFastControls(pi); + registerCursorNativeToolDisplay(pi); let fallbackIssue: CursorModelFallbackIssue | undefined; const models = await discoverModels({ onFallback: (issue) => { diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 05bfe33..185f156 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -298,6 +298,41 @@ describe("streamCursor", () => { expect(done.message.content.map((block: any) => block.type)).toEqual(["thinking", "thinking", "text"]); }); + it("uses Cursor onStep tool-call results when delta tool completion is absent", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onStep: (a: unknown) => void }) => { + opts.onStep({ + step: { + type: "toolCall", + message: { + type: "read", + args: { path: "README.md" }, + result: { status: "success", value: { content: "# pi-cursor-sdk" } }, + }, + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "done" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + const trace = events.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + + expect(trace).toContain("read README.md"); + expect(trace).toContain("# pi-cursor-sdk"); + }); + it("prefers the final run result over intermediate cursor progress text", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "text-delta", text: "I’m checking files." } }); diff --git a/test/cursor-tool-transcript.test.ts b/test/cursor-tool-transcript.test.ts index 2cc2e7a..547c131 100644 --- a/test/cursor-tool-transcript.test.ts +++ b/test/cursor-tool-transcript.test.ts @@ -2,7 +2,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { formatCursorToolTranscript, mergeCursorToolCalls } from "../src/cursor-tool-transcript.js"; +import { buildCursorPiToolDisplay, formatCursorToolTranscript, mergeCursorToolCalls } from "../src/cursor-tool-transcript.js"; describe("formatCursorToolTranscript", () => { it("formats Cursor read results as a pi-like read transcript", () => { @@ -100,6 +100,32 @@ describe("formatCursorToolTranscript", () => { expect(transcript).toContain("Took 0.0s"); }); + it("builds native pi display data for Cursor read and shell calls", () => { + const readDisplay = buildCursorPiToolDisplay({ + name: "read", + args: { path: "README.md" }, + result: { status: "success", value: { content: "# Title" } }, + }); + const shellDisplay = buildCursorPiToolDisplay({ + name: "run_terminal_cmd", + args: { command: "date" }, + result: { status: "success", value: { stdout: "Sat May 9\n", stderr: "", exitCode: 0 } }, + }); + + expect(readDisplay).toMatchObject({ + toolName: "read", + args: { path: "README.md" }, + result: { content: [{ type: "text", text: "# Title" }] }, + isError: false, + }); + expect(shellDisplay).toMatchObject({ + toolName: "bash", + args: { command: "date" }, + result: { content: [{ type: "text", text: "Sat May 9" }] }, + isError: false, + }); + }); + it("keeps started tool args when the completed Cursor update only contains a result", () => { const merged = mergeCursorToolCalls( { name: "read", args: { path: "src/index.ts" } }, diff --git a/test/index.test.ts b/test/index.test.ts index 04cf225..f9d51ce 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -11,6 +11,7 @@ vi.mock("../src/cursor-provider.js", () => ({ import extensionFactory from "../src/index.js"; import { discoverModels } from "../src/model-discovery.js"; import { streamCursor } from "../src/cursor-provider.js"; +import { recordCursorNativeToolDisplay } from "../src/cursor-native-tool-display.js"; const mockedDiscover = vi.mocked(discoverModels); const mockedStreamCursor = vi.mocked(streamCursor); @@ -24,6 +25,8 @@ function createMockPi() { }), registerFlag: vi.fn(), registerCommand: vi.fn(), + registerMessageRenderer: vi.fn(), + sendMessage: vi.fn(), on: vi.fn((event: string, handler: (event: unknown, ctx: any) => Promise | void) => { handlers.set(event, [...(handlers.get(event) ?? []), handler]); }), @@ -68,6 +71,7 @@ describe("extension factory", () => { "cursor-fast", expect.objectContaining({ description: expect.stringContaining("Toggle Cursor fast") }), ); + expect(pi.registerMessageRenderer).toHaveBeenCalledWith("cursor-native-tool-display", expect.any(Function)); expect(pi.on).toHaveBeenCalledWith("session_start", expect.any(Function)); expect(pi.on).toHaveBeenCalledWith("model_select", expect.any(Function)); expect(mockedDiscover).toHaveBeenCalledOnce(); @@ -166,4 +170,35 @@ describe("extension factory", () => { expect(notify).not.toHaveBeenCalled(); }); + + it("emits recorded Cursor tool displays as custom native-tool messages at agent end", async () => { + mockedDiscover.mockResolvedValueOnce([]); + const pi = createMockPi(); + await extensionFactory(pi as any); + + recordCursorNativeToolDisplay({ + id: "cursor-tool-1", + toolName: "read", + args: { path: "README.md" }, + result: { content: [{ type: "text", text: "# pi-cursor-sdk" }] }, + isError: false, + }); + + const agentEndHandlers = pi._handlers.get("agent_end") ?? []; + await agentEndHandlers.at(-1)!({}, {}); + + expect(pi.sendMessage).toHaveBeenCalledWith({ + customType: "cursor-native-tool-display", + content: "", + display: true, + details: expect.objectContaining({ + tools: [ + expect.objectContaining({ + toolName: "read", + args: { path: "README.md" }, + }), + ], + }), + }); + }); }); -- 2.51.2 From 9b36a2539b60a82fff6dfaa8954f30c5c15428d9 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 12:08:39 -0600 Subject: [PATCH 019/359] Update package-lock.json to reflect dependency changes and ensure compatibility with latest versions --- package-lock.json | 11448 ++++++++++++++++++++++---------------------- 1 file changed, 5724 insertions(+), 5724 deletions(-) diff --git a/package-lock.json b/package-lock.json index bef47b3..cdeac7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5726 +1,5726 @@ { - "name": "pi-cursor-sdk", - "version": "0.1.4", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "pi-cursor-sdk", - "version": "0.1.4", - "license": "MIT", - "dependencies": { - "@cursor/sdk": "^1.0.12" - }, - "devDependencies": { - "@earendil-works/pi-ai": "^0.74.0", - "@earendil-works/pi-coding-agent": "^0.74.0", - "typescript": "^6.0.3", - "vitest": "^4.1.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@earendil-works/pi-ai": "*", - "@earendil-works/pi-coding-agent": "*" - } - }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.91.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", - "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1041.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1041.0.tgz", - "integrity": "sha512-1QehYO3jhdvNQ5mOKtwIiNV04y4aywaNZw9HzCp7SSYCX4yy+AGXc2hhYjCiMDUvQPIELuvbR8MXw81NGAj8ZQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-node": "^3.972.39", - "@aws-sdk/eventstream-handler-node": "^3.972.14", - "@aws-sdk/middleware-eventstream": "^3.972.10", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/middleware-websocket": "^3.972.16", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/token-providers": "3.1041.0", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/eventstream-serde-browser": "^4.2.14", - "@smithy/eventstream-serde-config-resolver": "^4.3.14", - "@smithy/eventstream-serde-node": "^4.2.14", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.974.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.8.tgz", - "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/xml-builder": "^3.972.22", - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.34.tgz", - "integrity": "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.36.tgz", - "integrity": "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-stream": "^4.5.25", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.38.tgz", - "integrity": "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-env": "^3.972.34", - "@aws-sdk/credential-provider-http": "^3.972.36", - "@aws-sdk/credential-provider-login": "^3.972.38", - "@aws-sdk/credential-provider-process": "^3.972.34", - "@aws-sdk/credential-provider-sso": "^3.972.38", - "@aws-sdk/credential-provider-web-identity": "^3.972.38", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.38.tgz", - "integrity": "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.39.tgz", - "integrity": "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.34", - "@aws-sdk/credential-provider-http": "^3.972.36", - "@aws-sdk/credential-provider-ini": "^3.972.38", - "@aws-sdk/credential-provider-process": "^3.972.34", - "@aws-sdk/credential-provider-sso": "^3.972.38", - "@aws-sdk/credential-provider-web-identity": "^3.972.38", - "@aws-sdk/types": "^3.973.8", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.34.tgz", - "integrity": "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.38.tgz", - "integrity": "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/token-providers": "3.1041.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.38.tgz", - "integrity": "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.14.tgz", - "integrity": "sha512-m4X56gxG76/CKfxNVbOFuYwnAZcHgS6HOH8lgp15HoGHIAVTcZfZrXvcYzJFOMLEJgVn+JHBu6EiNV+xSNXXFg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/eventstream-codec": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.10.tgz", - "integrity": "sha512-QUqLs7Af1II9X4fCRAu+EGHG3KHyOp4RkuLhRKoA3NuFlh6TL8i+zXBl8w2LUxqm44B/Kom45hgSlwA1SpTsXQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", - "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.10.tgz", - "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.11.tgz", - "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.37.tgz", - "integrity": "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-arn-parser": "^3.972.3", - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.38.tgz", - "integrity": "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@smithy/core": "^3.23.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-retry": "^4.3.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.16.tgz", - "integrity": "sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-format-url": "^3.972.10", - "@smithy/eventstream-codec": "^4.2.14", - "@smithy/eventstream-serde-browser": "^4.2.14", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.6.tgz", - "integrity": "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.25", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.13.tgz", - "integrity": "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/config-resolver": "^4.4.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.25", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.25.tgz", - "integrity": "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.37", - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.1041.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1041.0.tgz", - "integrity": "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.973.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", - "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", - "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.8.tgz", - "integrity": "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-endpoints": "^3.4.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-format-url": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.10.tgz", - "integrity": "sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.10.tgz", - "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.24.tgz", - "integrity": "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/types": "^3.973.8", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.22", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", - "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@nodable/entities": "2.1.0", - "@smithy/types": "^4.14.1", - "fast-xml-parser": "5.7.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", - "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@borewit/text-codec": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", - "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@bufbuild/protobuf": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", - "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "node_modules/@connectrpc/connect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.7.0.tgz", - "integrity": "sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w==", - "license": "Apache-2.0", - "peerDependencies": { - "@bufbuild/protobuf": "^1.10.0" - } - }, - "node_modules/@connectrpc/connect-node": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-1.7.0.tgz", - "integrity": "sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A==", - "license": "Apache-2.0", - "dependencies": { - "undici": "^5.28.4" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@bufbuild/protobuf": "^1.10.0", - "@connectrpc/connect": "1.7.0" - } - }, - "node_modules/@cursor/sdk": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk/-/sdk-1.0.12.tgz", - "integrity": "sha512-jGx0wFY1N9uIdIKr303CfM6m/dLXmRCUnU/0yNP/oiOpkBXqgqaThGbgYbcOeVrYonMZc/DZJ9EydXOEPJLcbg==", - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "@bufbuild/protobuf": "1.10.0", - "@connectrpc/connect": "^1.6.1", - "@connectrpc/connect-node": "^1.6.1", - "@statsig/js-client": "3.31.0", - "sqlite3": "^5.1.7", - "zod": "^3.25.0" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@cursor/sdk-darwin-arm64": "1.0.12", - "@cursor/sdk-darwin-x64": "1.0.12", - "@cursor/sdk-linux-arm64": "1.0.12", - "@cursor/sdk-linux-x64": "1.0.12", - "@cursor/sdk-win32-x64": "1.0.12" - } - }, - "node_modules/@cursor/sdk-darwin-arm64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-arm64/-/sdk-darwin-arm64-1.0.12.tgz", - "integrity": "sha512-AOFx+aX+4SntAeC66YncHACXk5duxp+HzDrxxF4Tl93N6nLjHaHEKSAXbt87ivL34MCHop4v/3c70QzBhamB2g==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@cursor/sdk-darwin-x64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-x64/-/sdk-darwin-x64-1.0.12.tgz", - "integrity": "sha512-/ZDAYFUrnPd8hAGRky9ZGcROqZSZ2b5W+aEjTdINzLhJ8x5ZNXtjaz0ZYSHabOn2BeErjXgTcq+4bX2/To4C1A==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@cursor/sdk-linux-arm64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-arm64/-/sdk-linux-arm64-1.0.12.tgz", - "integrity": "sha512-kAxNqiB3dPtlW9fVjjIZEdbIGEGLA9moOM3zYwsXh8J1Qw942nJYMGDGR4o8x0zglwZ24a1JpovvZamrCaC3Yw==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cursor/sdk-linux-x64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-x64/-/sdk-linux-x64-1.0.12.tgz", - "integrity": "sha512-RmBiBCPKMZC5McDerGk2Rk4P47xz2A+uzRoRgH6sMoOjklc33ry11iAZC0D5F5xH85chgY878086A/Q8+XrAuA==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cursor/sdk-win32-x64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-win32-x64/-/sdk-win32-x64-1.0.12.tgz", - "integrity": "sha512-uH4shdHrKOdtNLapy1uuScJ9lL2Pc8zc9I9ZKC6b6bx+0UX6xLAqjPP7dqVPfO6D9u61yLq1Hs86XOLs5ZVkPA==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@earendil-works/pi-agent-core": { - "version": "0.74.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.74.0.tgz", - "integrity": "sha512-6GMR7/wwjEJ1EsXLWEz03QOWin4AMrJ/AZoMpgm5DJ6GHsF6q6GOhQbj5Zip4dow3vo/TmBAVqM+vmGfrjGAFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@earendil-works/pi-ai": "^0.74.0", - "typebox": "^1.1.24" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-ai": { - "version": "0.74.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.74.0.tgz", - "integrity": "sha512-7M7qcrZY/KEkH4wFkX3eqzvmKru4O88wezNKoN0KD2m4aAOmp9tdW2xCmUgSTSWlKB7b2Xw9QtAgrzHtg6t6iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@anthropic-ai/sdk": "^0.91.1", - "@aws-sdk/client-bedrock-runtime": "^3.1030.0", - "@google/genai": "^1.40.0", - "@mistralai/mistralai": "^2.2.0", - "chalk": "^5.6.2", - "openai": "6.26.0", - "partial-json": "^0.1.7", - "proxy-agent": "^6.5.0", - "typebox": "^1.1.24", - "undici": "^7.19.1", - "zod-to-json-schema": "^3.24.6" - }, - "bin": { - "pi-ai": "dist/cli.js" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.74.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.74.0.tgz", - "integrity": "sha512-Q5GikbB5vRBrsrrf/uvet53rPSQ1sn5I5mO+l7sIobdXYpS04/X2oOc2UHFm90fNdkl3yU+ANTZL0zOtHbnqRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@earendil-works/pi-agent-core": "^0.74.0", - "@earendil-works/pi-ai": "^0.74.0", - "@earendil-works/pi-tui": "^0.74.0", - "@silvia-odwyer/photon-node": "^0.3.4", - "chalk": "^5.5.0", - "cli-highlight": "^2.1.11", - "diff": "^8.0.2", - "extract-zip": "^2.0.1", - "file-type": "^21.1.1", - "glob": "^13.0.1", - "hosted-git-info": "^9.0.2", - "ignore": "^7.0.5", - "jiti": "^2.7.0", - "marked": "^15.0.12", - "minimatch": "^10.2.3", - "proper-lockfile": "^4.1.2", - "strip-ansi": "^7.1.0", - "typebox": "^1.1.24", - "undici": "^7.19.1", - "uuid": "^14.0.0", - "yaml": "^2.8.2" - }, - "bin": { - "pi": "dist/cli.js" - }, - "engines": { - "node": ">=20.6.0" - }, - "optionalDependencies": { - "@mariozechner/clipboard": "^0.3.5" - } - }, - "node_modules/@earendil-works/pi-tui": { - "version": "0.74.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.74.0.tgz", - "integrity": "sha512-1aIfXZp7D/z+1VlZX8BZcs6pgO8rjmil7kwyhctNDsWvce3Yfl8GVgu4eq+I0Mjhr8Cj+ipBiv9CLIzdoyCOIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime-types": "^2.1.4", - "chalk": "^5.5.0", - "get-east-asian-width": "^1.3.0", - "marked": "^15.0.12", - "mime-types": "^3.0.1" - }, - "engines": { - "node": ">=20.0.0" - }, - "optionalDependencies": { - "koffi": "^2.9.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@google/genai": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.51.0.tgz", - "integrity": "sha512-vTZZF3CSimN7cn2zsLpW2p5WF0eZa5Gz69ITMPCNHpPrDlAstOfGifSfi0p/s9Z9400f7xJRkgvkQNrcM7pJ6w==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@mariozechner/clipboard": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.5.tgz", - "integrity": "sha512-D3F+UrU9CR7roJt0zDLp6Oc+4/KlLDIrN4frH+6V90SJNW2KKUec1oCQIPaaDjCqeOsQyX9dyqYbImIQIM45PA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@mariozechner/clipboard-darwin-arm64": "0.3.2", - "@mariozechner/clipboard-darwin-universal": "0.3.2", - "@mariozechner/clipboard-darwin-x64": "0.3.2", - "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", - "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-musl": "0.3.2", - "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", - "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" - } - }, - "node_modules/@mariozechner/clipboard-darwin-arm64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", - "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-darwin-universal": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", - "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-darwin-x64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", - "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", - "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-arm64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", - "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", - "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-x64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", - "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-linux-x64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", - "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", - "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-win32-x64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", - "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mistralai/mistralai": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz", - "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "ws": "^8.18.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.25.0" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT" - }, - "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", - "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@silvia-odwyer/photon-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", - "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.17", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.17.tgz", - "integrity": "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.23.17", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.17.tgz", - "integrity": "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "@smithy/uuid": "^1.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.14.tgz", - "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-codec": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", - "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.1", - "@smithy/util-hex-encoding": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.14.tgz", - "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.14.tgz", - "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.14.tgz", - "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.14.tgz", - "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-codec": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.17", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", - "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", - "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", - "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", - "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.14.tgz", - "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.32", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.32.tgz", - "integrity": "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-middleware": "^4.2.14", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.7.tgz", - "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/service-error-classification": "^4.3.1", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/uuid": "^1.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.20", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.20.tgz", - "integrity": "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.14.tgz", - "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.14.tgz", - "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.6.1.tgz", - "integrity": "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.14.tgz", - "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.14.tgz", - "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.14.tgz", - "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "@smithy/util-uri-escape": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.14.tgz", - "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.3.1.tgz", - "integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.9.tgz", - "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.14.tgz", - "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-uri-escape": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.12.13", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.13.tgz", - "integrity": "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-stream": "^4.5.25", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", - "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.14.tgz", - "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", - "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", - "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", - "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", - "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", - "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.49", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.49.tgz", - "integrity": "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.54", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.54.tgz", - "integrity": "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.17", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.2.tgz", - "integrity": "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", - "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.14.tgz", - "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-retry": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.8.tgz", - "integrity": "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.3.1", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-stream": { - "version": "4.5.25", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.25.tgz", - "integrity": "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", - "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", - "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/uuid": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", - "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@statsig/client-core": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@statsig/client-core/-/client-core-3.31.0.tgz", - "integrity": "sha512-SuxQD6TmVszPG7FoMKwTk/uyBuVFk7XnxI3T/E0uyb7PL7GNjONtfsoh+NqBBVUJVse0CUeSFfgJPoZy1ZOslQ==", - "license": "ISC" - }, - "node_modules/@statsig/js-client": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@statsig/js-client/-/js-client-3.31.0.tgz", - "integrity": "sha512-LFa5E0LjT6sTfZv3sNGoyRLSZ1078+agdgOA+Vm1ecjG+KbSOfBLTW7hMwimrJ29slRwbYDzbtKaPJo/R37N2g==", - "license": "ISC", - "dependencies": { - "@statsig/client-core": "3.31.0" - } - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime-types": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", - "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.19.0" - } - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.5", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.5", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/abbrev": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", - "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", - "license": "ISC", - "optional": true, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/basic-ftp": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", - "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dev": true, - "license": "ISC", - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0", - "optional": true - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fast-xml-builder": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.7.tgz", - "integrity": "sha512-Yh7/7rQuMXICNr0oMYDR2yHP6oUvmQsTToFeOWj/kIDhAwQ+c4Ol/lbcwOmEM5OHYQmh6S6EQSQ1sljCKP36bQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.1.3" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", - "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.5", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/file-type": { - "version": "21.3.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", - "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/hosted-git-info": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "license": "BlueOak-1.0.0", - "optional": true, - "engines": { - "node": ">=20" - } - }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/koffi": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz", - "integrity": "sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "funding": { - "url": "https://liberapay.com/Koromix" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/lru-cache": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", - "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, - "node_modules/netmask": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", - "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/node-abi": { - "version": "3.90.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.90.0.tgz", - "integrity": "sha512-pZNQT7UnYlMwMBy5N1lV5X/YLTbZM5ncytN3xL7CHEzhDN8uVe0u55yaPUJICIJjaCW8NrM5BFdqr7HLweStNA==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", - "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-gyp": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", - "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", - "license": "MIT", - "optional": true, - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "tar": "^7.5.4", - "tinyglobby": "^0.2.12", - "undici": "^6.25.0", - "which": "^6.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/nopt": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", - "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "^4.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openai": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", - "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/partial-json": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", - "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/proc-log": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", - "license": "ISC", - "optional": true, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/protobufjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", - "integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==", - "dev": true, - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.1", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", - "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sqlite3": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-6.0.1.tgz", - "integrity": "sha512-X0czUUMG2tmSqJpEQa3tCuZSHKIx8PwM53vLZzKp/o6Rpy25fiVfjdbnZ988M8+O3ZWR1ih0K255VumCb3MAnQ==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^8.0.0", - "prebuild-install": "^7.1.3", - "tar": "^7.5.10" - }, - "engines": { - "node": ">=20.17.0" - }, - "optionalDependencies": { - "node-gyp": "12.x" - }, - "peerDependencies": { - "node-gyp": "12.x" - }, - "peerDependenciesMeta": { - "node-gyp": { - "optional": true - } - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strnum": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", - "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/strtok3": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", - "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar": { - "version": "7.5.15", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", - "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", - "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/typebox": { - "version": "1.1.37", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.37.tgz", - "integrity": "sha512-jb7jp6KvOvvy5sd+11AfJ0/e0F0AS9RcOXd55oGi2ZnRHIGmFvrTaNF+ZidRmGBmmNTkM5KKl0Z37KzxJ+owEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", - "dev": true, - "license": "MIT" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", - "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", - "tinyglobby": "^0.2.16" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/vitest/node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "license": "ISC", - "optional": true, - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - } - } + "name": "pi-cursor-sdk", + "version": "0.1.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pi-cursor-sdk", + "version": "0.1.4", + "license": "MIT", + "dependencies": { + "@cursor/sdk": "^1.0.12" + }, + "devDependencies": { + "@earendil-works/pi-ai": "^0.74.0", + "@earendil-works/pi-coding-agent": "^0.74.0", + "typescript": "^6.0.3", + "vitest": "^4.1.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.91.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", + "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1041.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1041.0.tgz", + "integrity": "sha512-1QehYO3jhdvNQ5mOKtwIiNV04y4aywaNZw9HzCp7SSYCX4yy+AGXc2hhYjCiMDUvQPIELuvbR8MXw81NGAj8ZQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/credential-provider-node": "^3.972.39", + "@aws-sdk/eventstream-handler-node": "^3.972.14", + "@aws-sdk/middleware-eventstream": "^3.972.10", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.38", + "@aws-sdk/middleware-websocket": "^3.972.16", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/token-providers": "3.1041.0", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.24", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/eventstream-serde-config-resolver": "^4.3.14", + "@smithy/eventstream-serde-node": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.974.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.8.tgz", + "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/xml-builder": "^3.972.22", + "@smithy/core": "^3.23.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.34.tgz", + "integrity": "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.36", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.36.tgz", + "integrity": "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-stream": "^4.5.25", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.38.tgz", + "integrity": "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/credential-provider-env": "^3.972.34", + "@aws-sdk/credential-provider-http": "^3.972.36", + "@aws-sdk/credential-provider-login": "^3.972.38", + "@aws-sdk/credential-provider-process": "^3.972.34", + "@aws-sdk/credential-provider-sso": "^3.972.38", + "@aws-sdk/credential-provider-web-identity": "^3.972.38", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.38.tgz", + "integrity": "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.39.tgz", + "integrity": "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.34", + "@aws-sdk/credential-provider-http": "^3.972.36", + "@aws-sdk/credential-provider-ini": "^3.972.38", + "@aws-sdk/credential-provider-process": "^3.972.34", + "@aws-sdk/credential-provider-sso": "^3.972.38", + "@aws-sdk/credential-provider-web-identity": "^3.972.38", + "@aws-sdk/types": "^3.973.8", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.34.tgz", + "integrity": "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.38.tgz", + "integrity": "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/token-providers": "3.1041.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.38.tgz", + "integrity": "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.14.tgz", + "integrity": "sha512-m4X56gxG76/CKfxNVbOFuYwnAZcHgS6HOH8lgp15HoGHIAVTcZfZrXvcYzJFOMLEJgVn+JHBu6EiNV+xSNXXFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.10.tgz", + "integrity": "sha512-QUqLs7Af1II9X4fCRAu+EGHG3KHyOp4RkuLhRKoA3NuFlh6TL8i+zXBl8w2LUxqm44B/Kom45hgSlwA1SpTsXQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", + "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.10.tgz", + "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.11.tgz", + "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.37.tgz", + "integrity": "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.38.tgz", + "integrity": "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@smithy/core": "^3.23.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-retry": "^4.3.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.16.tgz", + "integrity": "sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-format-url": "^3.972.10", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.6.tgz", + "integrity": "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.38", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/signature-v4-multi-region": "^3.996.25", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.24", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.13.tgz", + "integrity": "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/config-resolver": "^4.4.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.25.tgz", + "integrity": "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "^3.972.37", + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1041.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1041.0.tgz", + "integrity": "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.8", + "@aws-sdk/nested-clients": "^3.997.6", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", + "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.8.tgz", + "integrity": "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-endpoints": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.10.tgz", + "integrity": "sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.10.tgz", + "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.24.tgz", + "integrity": "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.38", + "@aws-sdk/types": "^3.973.8", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", + "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@nodable/entities": "2.1.0", + "@smithy/types": "^4.14.1", + "fast-xml-parser": "5.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", + "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@connectrpc/connect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.7.0.tgz", + "integrity": "sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^1.10.0" + } + }, + "node_modules/@connectrpc/connect-node": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-1.7.0.tgz", + "integrity": "sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A==", + "license": "Apache-2.0", + "dependencies": { + "undici": "^5.28.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^1.10.0", + "@connectrpc/connect": "1.7.0" + } + }, + "node_modules/@cursor/sdk": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk/-/sdk-1.0.12.tgz", + "integrity": "sha512-jGx0wFY1N9uIdIKr303CfM6m/dLXmRCUnU/0yNP/oiOpkBXqgqaThGbgYbcOeVrYonMZc/DZJ9EydXOEPJLcbg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@bufbuild/protobuf": "1.10.0", + "@connectrpc/connect": "^1.6.1", + "@connectrpc/connect-node": "^1.6.1", + "@statsig/js-client": "3.31.0", + "sqlite3": "^5.1.7", + "zod": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@cursor/sdk-darwin-arm64": "1.0.12", + "@cursor/sdk-darwin-x64": "1.0.12", + "@cursor/sdk-linux-arm64": "1.0.12", + "@cursor/sdk-linux-x64": "1.0.12", + "@cursor/sdk-win32-x64": "1.0.12" + } + }, + "node_modules/@cursor/sdk-darwin-arm64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-arm64/-/sdk-darwin-arm64-1.0.12.tgz", + "integrity": "sha512-AOFx+aX+4SntAeC66YncHACXk5duxp+HzDrxxF4Tl93N6nLjHaHEKSAXbt87ivL34MCHop4v/3c70QzBhamB2g==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cursor/sdk-darwin-x64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-x64/-/sdk-darwin-x64-1.0.12.tgz", + "integrity": "sha512-/ZDAYFUrnPd8hAGRky9ZGcROqZSZ2b5W+aEjTdINzLhJ8x5ZNXtjaz0ZYSHabOn2BeErjXgTcq+4bX2/To4C1A==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cursor/sdk-linux-arm64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-arm64/-/sdk-linux-arm64-1.0.12.tgz", + "integrity": "sha512-kAxNqiB3dPtlW9fVjjIZEdbIGEGLA9moOM3zYwsXh8J1Qw942nJYMGDGR4o8x0zglwZ24a1JpovvZamrCaC3Yw==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cursor/sdk-linux-x64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-x64/-/sdk-linux-x64-1.0.12.tgz", + "integrity": "sha512-RmBiBCPKMZC5McDerGk2Rk4P47xz2A+uzRoRgH6sMoOjklc33ry11iAZC0D5F5xH85chgY878086A/Q8+XrAuA==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cursor/sdk-win32-x64": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cursor/sdk-win32-x64/-/sdk-win32-x64-1.0.12.tgz", + "integrity": "sha512-uH4shdHrKOdtNLapy1uuScJ9lL2Pc8zc9I9ZKC6b6bx+0UX6xLAqjPP7dqVPfO6D9u61yLq1Hs86XOLs5ZVkPA==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@earendil-works/pi-agent-core": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.74.0.tgz", + "integrity": "sha512-6GMR7/wwjEJ1EsXLWEz03QOWin4AMrJ/AZoMpgm5DJ6GHsF6q6GOhQbj5Zip4dow3vo/TmBAVqM+vmGfrjGAFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-ai": "^0.74.0", + "typebox": "^1.1.24" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-ai": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.74.0.tgz", + "integrity": "sha512-7M7qcrZY/KEkH4wFkX3eqzvmKru4O88wezNKoN0KD2m4aAOmp9tdW2xCmUgSTSWlKB7b2Xw9QtAgrzHtg6t6iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.91.1", + "@aws-sdk/client-bedrock-runtime": "^3.1030.0", + "@google/genai": "^1.40.0", + "@mistralai/mistralai": "^2.2.0", + "chalk": "^5.6.2", + "openai": "6.26.0", + "partial-json": "^0.1.7", + "proxy-agent": "^6.5.0", + "typebox": "^1.1.24", + "undici": "^7.19.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.74.0.tgz", + "integrity": "sha512-Q5GikbB5vRBrsrrf/uvet53rPSQ1sn5I5mO+l7sIobdXYpS04/X2oOc2UHFm90fNdkl3yU+ANTZL0zOtHbnqRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-agent-core": "^0.74.0", + "@earendil-works/pi-ai": "^0.74.0", + "@earendil-works/pi-tui": "^0.74.0", + "@silvia-odwyer/photon-node": "^0.3.4", + "chalk": "^5.5.0", + "cli-highlight": "^2.1.11", + "diff": "^8.0.2", + "extract-zip": "^2.0.1", + "file-type": "^21.1.1", + "glob": "^13.0.1", + "hosted-git-info": "^9.0.2", + "ignore": "^7.0.5", + "jiti": "^2.7.0", + "marked": "^15.0.12", + "minimatch": "^10.2.3", + "proper-lockfile": "^4.1.2", + "strip-ansi": "^7.1.0", + "typebox": "^1.1.24", + "undici": "^7.19.1", + "uuid": "^14.0.0", + "yaml": "^2.8.2" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=20.6.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "^0.3.5" + } + }, + "node_modules/@earendil-works/pi-tui": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.74.0.tgz", + "integrity": "sha512-1aIfXZp7D/z+1VlZX8BZcs6pgO8rjmil7kwyhctNDsWvce3Yfl8GVgu4eq+I0Mjhr8Cj+ipBiv9CLIzdoyCOIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime-types": "^2.1.4", + "chalk": "^5.5.0", + "get-east-asian-width": "^1.3.0", + "marked": "^15.0.12", + "mime-types": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "koffi": "^2.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@google/genai": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.51.0.tgz", + "integrity": "sha512-vTZZF3CSimN7cn2zsLpW2p5WF0eZa5Gz69ITMPCNHpPrDlAstOfGifSfi0p/s9Z9400f7xJRkgvkQNrcM7pJ6w==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mariozechner/clipboard": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.5.tgz", + "integrity": "sha512-D3F+UrU9CR7roJt0zDLp6Oc+4/KlLDIrN4frH+6V90SJNW2KKUec1oCQIPaaDjCqeOsQyX9dyqYbImIQIM45PA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.2", + "@mariozechner/clipboard-darwin-universal": "0.3.2", + "@mariozechner/clipboard-darwin-x64": "0.3.2", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-musl": "0.3.2", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" + } + }, + "node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", + "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", + "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", + "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", + "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", + "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", + "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", + "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", + "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", + "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", + "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz", + "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.25.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", + "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.17", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.17.tgz", + "integrity": "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.17", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.17.tgz", + "integrity": "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.14.tgz", + "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.1", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.14.tgz", + "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.14.tgz", + "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.14.tgz", + "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.14.tgz", + "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", + "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", + "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", + "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.14.tgz", + "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.32", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.32.tgz", + "integrity": "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-middleware": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.7.tgz", + "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/service-error-classification": "^4.3.1", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.20.tgz", + "integrity": "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.14.tgz", + "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.14.tgz", + "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.6.1.tgz", + "integrity": "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.14.tgz", + "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.14.tgz", + "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.14.tgz", + "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.14.tgz", + "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.3.1.tgz", + "integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.9.tgz", + "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.14.tgz", + "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.13", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.13.tgz", + "integrity": "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.17", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-stream": "^4.5.25", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.14.tgz", + "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.49.tgz", + "integrity": "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.54", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.54.tgz", + "integrity": "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.17", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.2.tgz", + "integrity": "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.14.tgz", + "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.8.tgz", + "integrity": "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.3.1", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.25", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.25.tgz", + "integrity": "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/types": "^4.14.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@statsig/client-core": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@statsig/client-core/-/client-core-3.31.0.tgz", + "integrity": "sha512-SuxQD6TmVszPG7FoMKwTk/uyBuVFk7XnxI3T/E0uyb7PL7GNjONtfsoh+NqBBVUJVse0CUeSFfgJPoZy1ZOslQ==", + "license": "ISC" + }, + "node_modules/@statsig/js-client": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@statsig/js-client/-/js-client-3.31.0.tgz", + "integrity": "sha512-LFa5E0LjT6sTfZv3sNGoyRLSZ1078+agdgOA+Vm1ecjG+KbSOfBLTW7hMwimrJ29slRwbYDzbtKaPJo/R37N2g==", + "license": "ISC", + "dependencies": { + "@statsig/client-core": "3.31.0" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "license": "ISC", + "optional": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-xml-builder": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.7.tgz", + "integrity": "sha512-Yh7/7rQuMXICNr0oMYDR2yHP6oUvmQsTToFeOWj/kIDhAwQ+c4Ol/lbcwOmEM5OHYQmh6S6EQSQ1sljCKP36bQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", + "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/koffi": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz", + "integrity": "sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-abi": { + "version": "3.90.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.90.0.tgz", + "integrity": "sha512-pZNQT7UnYlMwMBy5N1lV5X/YLTbZM5ncytN3xL7CHEzhDN8uVe0u55yaPUJICIJjaCW8NrM5BFdqr7HLweStNA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", + "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", + "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/protobufjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", + "integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.1", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.127.0", + "@rolldown/pluginutils": "1.0.0-rc.17" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", + "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sqlite3": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-6.0.1.tgz", + "integrity": "sha512-X0czUUMG2tmSqJpEQa3tCuZSHKIx8PwM53vLZzKp/o6Rpy25fiVfjdbnZ988M8+O3ZWR1ih0K255VumCb3MAnQ==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^8.0.0", + "prebuild-install": "^7.1.3", + "tar": "^7.5.10" + }, + "engines": { + "node": ">=20.17.0" + }, + "optionalDependencies": { + "node-gyp": "12.x" + }, + "peerDependencies": { + "node-gyp": "12.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", + "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typebox": { + "version": "1.1.37", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.37.tgz", + "integrity": "sha512-jb7jp6KvOvvy5sd+11AfJ0/e0F0AS9RcOXd55oGi2ZnRHIGmFvrTaNF+ZidRmGBmmNTkM5KKl0Z37KzxJ+owEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vite": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", + "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.17", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yaml": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } } -- 2.51.2 From 395867890cf0ce1b062061d34d2d5c865207c9b1 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 12:35:31 -0600 Subject: [PATCH 020/359] Stream Cursor text and replay native tools safely --- CHANGELOG.md | 2 +- README.md | 2 +- docs/cursor-model-ux-spec.md | 2 +- src/cursor-native-tool-display.ts | 105 ++++++++++++++---------------- src/cursor-provider.ts | 84 +++++++++++++++--------- test/cursor-provider.test.ts | 16 ++--- test/index.test.ts | 33 +++++----- 7 files changed, 128 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae20e1f..7c32fdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Added pi-native `/login` API-key integration for the Cursor provider. Startup discovery now checks pi `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. - Fallback Cursor models remain available when startup discovery cannot authenticate; once auth is saved, fallback model runs can use the stored key, while `/reload` or restart refreshes the full live Cursor model catalog. -- Improved Cursor activity display by preserving Cursor thinking and replaying completed Cursor internal tools through pi's native tool rendering components where possible, including safe local previews for empty read results, while still omitting raw Cursor call IDs and using the final Cursor run result for the visible assistant answer when available. +- Improved Cursor activity display by preserving Cursor thinking, streaming Cursor text deltas live, and replaying completed Cursor internal `read`, `bash`, and `ls` activity through pi's native tool rendering path in interactive TTY sessions where possible. Non-interactive runs keep bounded scrubbed transcript output, and raw Cursor call IDs remain omitted. - Stopped copying Cursor SDK cumulative internal agent/tool/cache token usage into pi usage, preventing false context-overflow and compaction triggers after long Cursor runs. ## 0.1.4 - 2026-05-07 diff --git a/README.md b/README.md index c91f82f..7e24944 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive pi, replays it through pi's native tool rendering components where possible (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools. Non-interactive/session consumers still get bounded scrubbed transcript data. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 1c27c01..2e24aff 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -17,7 +17,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events, scrubbed, and replayed through pi's native `ToolExecutionComponent` inside a custom display message where possible (for example green `read ` or `$ ` cards). These displays do not force Cursor to use pi tools and do not re-execute Cursor's reads or shell commands. Cursor text deltas are treated as intermediate progress unless the final run result is unavailable, so the saved visible answer stays clean while Cursor's own tool calls remain Cursor-owned. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live; the final run result is used only when no text deltas arrived. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index b29da81..3513674 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -1,79 +1,72 @@ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { ToolExecutionComponent } from "@earendil-works/pi-coding-agent"; -import { Container } from "@earendil-works/pi-tui"; -import type { TUI } from "@earendil-works/pi-tui"; +import { + createBashToolDefinition, + createLsToolDefinition, + createReadToolDefinition, + type ExtensionAPI, + type ToolDefinition, +} from "@earendil-works/pi-coding-agent"; +import type { TSchema } from "typebox"; import type { CursorPiToolDisplay } from "./cursor-tool-transcript.js"; -const CURSOR_NATIVE_TOOL_MESSAGE_TYPE = "cursor-native-tool-display"; +const NATIVE_CURSOR_TOOL_NAMES = new Set(["read", "bash", "ls"]); export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { id: string; } -interface CursorNativeToolDisplayMessageDetails { - cwd: string; - tools: CursorNativeToolDisplayItem[]; -} - let nativeToolDisplayEnabled = false; -const pendingNativeToolDisplays: CursorNativeToolDisplayItem[] = []; - -function createNoopTui(): TUI { - return { requestRender: () => {} } as TUI; -} +const nativeToolResults = new Map(); export function isCursorNativeToolDisplayEnabled(): boolean { return nativeToolDisplayEnabled; } -export function recordCursorNativeToolDisplay(item: CursorNativeToolDisplayItem): void { - if (!nativeToolDisplayEnabled) return; - pendingNativeToolDisplays.push(item); +export function isCursorNativeToolDisplayRuntimeEnabled(): boolean { + if (!nativeToolDisplayEnabled) return false; + const override = process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY; + if (override === "1" || override === "true") return true; + if (override === "0" || override === "false") return false; + return process.stdout.isTTY === true; } -function drainCursorNativeToolDisplays(): CursorNativeToolDisplayItem[] { - return pendingNativeToolDisplays.splice(0); +export function canRenderCursorToolNatively(toolName: string): boolean { + return NATIVE_CURSOR_TOOL_NAMES.has(toolName); } -function renderNativeToolDisplay(details: CursorNativeToolDisplayMessageDetails, expanded: boolean): Container | undefined { - if (!Array.isArray(details.tools) || details.tools.length === 0) return undefined; +export function recordCursorNativeToolDisplay(item: CursorNativeToolDisplayItem): void { + if (!nativeToolDisplayEnabled || !canRenderCursorToolNatively(item.toolName)) return; + nativeToolResults.set(item.id, item); +} + +function consumeCursorNativeToolDisplay(id: string): CursorNativeToolDisplayItem | undefined { + const item = nativeToolResults.get(id); + if (item) nativeToolResults.delete(id); + return item; +} - const container = new Container(); - const tui = createNoopTui(); - for (const tool of details.tools) { - const component = new ToolExecutionComponent( - tool.toolName, - tool.id, - tool.args, - { showImages: true }, - undefined, - tui, - details.cwd, - ); - component.setExpanded(expanded); - component.markExecutionStarted(); - component.updateResult({ ...tool.result, isError: tool.isError }); - container.addChild(component); - } - return container; +function wrapNativeCursorTool( + definition: ToolDefinition, +): ToolDefinition { + return { + ...definition, + async execute(toolCallId, params, signal, onUpdate, ctx) { + const cursorDisplay = consumeCursorNativeToolDisplay(toolCallId); + if (cursorDisplay) { + return { + content: cursorDisplay.result.content, + details: cursorDisplay.result.details as TDetails, + terminate: true, + }; + } + return definition.execute(toolCallId, params, signal, onUpdate, ctx); + }, + }; } export function registerCursorNativeToolDisplay(pi: ExtensionAPI): void { nativeToolDisplayEnabled = true; - - pi.registerMessageRenderer(CURSOR_NATIVE_TOOL_MESSAGE_TYPE, (message, options) => { - const details = message.details as CursorNativeToolDisplayMessageDetails | undefined; - return details ? renderNativeToolDisplay(details, options.expanded) : undefined; - }); - - pi.on("agent_end", () => { - const tools = drainCursorNativeToolDisplays(); - if (tools.length === 0) return; - pi.sendMessage({ - customType: CURSOR_NATIVE_TOOL_MESSAGE_TYPE, - content: "", - display: true, - details: { cwd: process.cwd(), tools } satisfies CursorNativeToolDisplayMessageDetails, - }); - }); + const cwd = process.cwd(); + pi.registerTool(wrapNativeCursorTool(createReadToolDefinition(cwd))); + pi.registerTool(wrapNativeCursorTool(createBashToolDefinition(cwd))); + pi.registerTool(wrapNativeCursorTool(createLsToolDefinition(cwd))); } diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 84d1c05..4499d3d 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -14,7 +14,11 @@ import { getEffectiveFastForModelId } from "./cursor-state.js"; import { buildCursorModelSelection } from "./model-discovery.js"; import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js"; import { buildCursorPiToolDisplay, formatCursorToolTranscript, mergeCursorToolCalls } from "./cursor-tool-transcript.js"; -import { isCursorNativeToolDisplayEnabled, recordCursorNativeToolDisplay } from "./cursor-native-tool-display.js"; +import { + canRenderCursorToolNatively, + isCursorNativeToolDisplayRuntimeEnabled, + recordCursorNativeToolDisplay, +} from "./cursor-native-tool-display.js"; function makeInitialMessage(model: Model): AssistantMessage { return { @@ -209,12 +213,27 @@ export function streamCursor( let activityTraceChars = 0; let activityTraceTruncated = false; let nativeToolDisplayCounter = 0; + let textContentIndex = -1; const textDeltas: string[] = []; const startedToolCalls = new Map(); const completedToolFingerprints = new Set(); - const appendBufferedTextDelta = (text: string): void => { + const appendLiveTextDelta = (text: string): void => { textDeltas.push(text); + if (textContentIndex < 0) { + textContentIndex = partial.content.length; + partial.content.push({ type: "text", text: "" }); + stream.push({ type: "text_start", contentIndex: textContentIndex, partial }); + } + const block = partial.content[textContentIndex]; + if (block.type !== "text") return; + block.text += text; + stream.push({ + type: "text_delta", + contentIndex: textContentIndex, + delta: text, + partial, + }); }; const appendTraceDelta = (text: string): void => { @@ -271,21 +290,10 @@ export function streamCursor( }; const flushText = (deltas: string[]): string => { - if (deltas.length === 0) return ""; - const textContentIndex = partial.content.length; - partial.content.push({ type: "text", text: "" }); - stream.push({ type: "text_start", contentIndex: textContentIndex, partial }); + for (const delta of deltas) appendLiveTextDelta(delta); + if (textContentIndex < 0) return ""; const block = partial.content[textContentIndex]; if (block.type !== "text") return ""; - for (const delta of deltas) { - block.text += delta; - stream.push({ - type: "text_delta", - contentIndex: textContentIndex, - delta, - partial, - }); - } stream.push({ type: "text_end", contentIndex: textContentIndex, @@ -295,38 +303,52 @@ export function streamCursor( return block.text; }; - const getToolFingerprint = (toolCall: unknown): string => { + const getToolFingerprint = (value: unknown): string => { try { - return JSON.stringify(toolCall); + return JSON.stringify(value); } catch { - return String(toolCall); + return String(value); } }; const handleCompletedToolCall = (toolCall: unknown): void => { - const fingerprint = getToolFingerprint(toolCall); + const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd }), resolvedApiKey); + const display = buildCursorPiToolDisplay(toolCall, { cwd }); + const fingerprint = getToolFingerprint({ toolName: display.toolName, args: display.args, result: display.result }); if (completedToolFingerprints.has(fingerprint)) return; completedToolFingerprints.add(fingerprint); - const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd }), resolvedApiKey); - if (isCursorNativeToolDisplayEnabled()) { - const display = buildCursorPiToolDisplay(toolCall, { cwd }); + if (isCursorNativeToolDisplayRuntimeEnabled() && canRenderCursorToolNatively(display.toolName)) { + const id = `cursor-tool-${++nativeToolDisplayCounter}`; + const contentIndex = partial.content.length; + const scrubbedArgs = scrubDisplayValue(display.args, resolvedApiKey) as Record; + const scrubbedResult = scrubDisplayValue(display.result, resolvedApiKey) as typeof display.result; + partial.content.push({ + type: "toolCall", + id, + name: display.toolName, + arguments: scrubbedArgs, + }); + stream.push({ type: "toolcall_start", contentIndex, partial }); recordCursorNativeToolDisplay({ ...display, - id: `cursor-tool-${++nativeToolDisplayCounter}`, - args: scrubDisplayValue(display.args, resolvedApiKey) as Record, - result: scrubDisplayValue(display.result, resolvedApiKey) as typeof display.result, + id, + args: scrubbedArgs, + result: scrubbedResult, }); - } else { - appendTraceBlock(transcript || `Cursor tool: ${formatCursorToolName(toolCall)} completed`); + const block = partial.content[contentIndex]; + if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial }); + return; } + + appendTraceBlock(transcript || `Cursor tool: ${formatCursorToolName(toolCall)} completed`); }; const onDelta = (args: { update: InteractionUpdate }): void => { const update = args.update; if (update.type === "text-delta") { - appendBufferedTextDelta(update.text); + appendLiveTextDelta(update.text); } else if (update.type === "thinking-delta") { appendTraceDelta(update.text); } else if (update.type === "thinking-completed") { @@ -376,11 +398,11 @@ export function streamCursor( const result = await run.wait(); await cacheSdkContextWindow(agent.agentId, model.id); - // Close open thinking/activity trace before flushing final assistant text so saved - // message content is trace first, final answer second. + // Close any open thinking/activity trace, then use the final run result only when + // Cursor did not stream text deltas. closeTraceBlock(); - const finalText = flushText(hasUsableText(result.result) ? [result.result] : textDeltas); + const finalText = flushText(textDeltas.length === 0 && hasUsableText(result.result) ? [result.result] : []); setApproximateUsage(partial, promptInputTokens, finalText); if (result.status === "cancelled") { diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 185f156..8372f10 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -333,15 +333,15 @@ describe("streamCursor", () => { expect(trace).toContain("# pi-cursor-sdk"); }); - it("prefers the final run result over intermediate cursor progress text", async () => { + it("streams Cursor text deltas live and only falls back to final result when no deltas arrive", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { - opts.onDelta({ update: { type: "text-delta", text: "I’m checking files." } }); - opts.onDelta({ update: { type: "text-delta", text: "I found the issue." } }); + opts.onDelta({ update: { type: "text-delta", text: "Final " } }); + opts.onDelta({ update: { type: "text-delta", text: "answer." } }); return { id: "run-1", agentId: "agent-1", status: "finished", - wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "Final answer only." }), + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "Final answer." }), cancel: vi.fn(), supports: () => true, unsupportedReason: () => undefined, @@ -356,8 +356,8 @@ describe("streamCursor", () => { const events = await collectEvents(stream); const text = events.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); - expect(text).toBe("Final answer only."); - expect(text).not.toContain("I’m checking files"); + expect(text).toBe("Final answer."); + expect(events.filter((e: any) => e.type === "text_delta")).toHaveLength(2); }); it("omits raw cursor call ids while rendering completed cursor tools", async () => { @@ -446,7 +446,7 @@ describe("streamCursor", () => { expect(trace).not.toContain("bearer-token-value"); }); - it("keeps late cursor thinking before final text in the saved content order", async () => { + it("keeps late cursor thinking in the saved content order after live text", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "text-delta", text: "Final answer" } }); opts.onDelta({ update: { type: "thinking-delta", text: "late trace" } }); @@ -471,8 +471,8 @@ describe("streamCursor", () => { const done = events.find((e: any) => e.type === "done") as any; expect(done.message.content).toEqual([ - { type: "thinking", thinking: "late trace" }, { type: "text", text: "Final answer" }, + { type: "thinking", thinking: "late trace" }, ]); }); diff --git a/test/index.test.ts b/test/index.test.ts index f9d51ce..57c450f 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -18,6 +18,7 @@ const mockedStreamCursor = vi.mocked(streamCursor); function createMockPi() { const registered: Array<{ name: string; config: Record }> = []; + const tools: any[] = []; const handlers = new Map Promise | void>>(); return { registerProvider: vi.fn((name: string, config: Record) => { @@ -25,7 +26,9 @@ function createMockPi() { }), registerFlag: vi.fn(), registerCommand: vi.fn(), - registerMessageRenderer: vi.fn(), + registerTool: vi.fn((tool: any) => { + tools.push(tool); + }), sendMessage: vi.fn(), on: vi.fn((event: string, handler: (event: unknown, ctx: any) => Promise | void) => { handlers.set(event, [...(handlers.get(event) ?? []), handler]); @@ -33,6 +36,7 @@ function createMockPi() { getFlag: vi.fn().mockReturnValue(false), appendEntry: vi.fn(), _registered: registered, + _tools: tools, _handlers: handlers, }; } @@ -71,7 +75,8 @@ describe("extension factory", () => { "cursor-fast", expect.objectContaining({ description: expect.stringContaining("Toggle Cursor fast") }), ); - expect(pi.registerMessageRenderer).toHaveBeenCalledWith("cursor-native-tool-display", expect.any(Function)); + expect(pi.registerTool).toHaveBeenCalledTimes(3); + expect(pi._tools.map((tool) => tool.name)).toEqual(["read", "bash", "ls"]); expect(pi.on).toHaveBeenCalledWith("session_start", expect.any(Function)); expect(pi.on).toHaveBeenCalledWith("model_select", expect.any(Function)); expect(mockedDiscover).toHaveBeenCalledOnce(); @@ -171,7 +176,7 @@ describe("extension factory", () => { expect(notify).not.toHaveBeenCalled(); }); - it("emits recorded Cursor tool displays as custom native-tool messages at agent end", async () => { + it("registered native Cursor tool wrappers return recorded Cursor results without executing built-ins", async () => { mockedDiscover.mockResolvedValueOnce([]); const pi = createMockPi(); await extensionFactory(pi as any); @@ -184,21 +189,13 @@ describe("extension factory", () => { isError: false, }); - const agentEndHandlers = pi._handlers.get("agent_end") ?? []; - await agentEndHandlers.at(-1)!({}, {}); - - expect(pi.sendMessage).toHaveBeenCalledWith({ - customType: "cursor-native-tool-display", - content: "", - display: true, - details: expect.objectContaining({ - tools: [ - expect.objectContaining({ - toolName: "read", - args: { path: "README.md" }, - }), - ], - }), + const readTool = pi._tools.find((tool) => tool.name === "read"); + const result = await readTool.execute("cursor-tool-1", { path: "README.md" }, undefined, undefined, {}); + + expect(result).toEqual({ + content: [{ type: "text", text: "# pi-cursor-sdk" }], + details: undefined, + terminate: true, }); }); }); -- 2.51.2 From c4cadec2f9d94de9d8a22c81ada8c6740365eaf6 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 15:41:23 -0600 Subject: [PATCH 021/359] Match Codex ordering for Cursor native tools --- CHANGELOG.md | 2 +- README.md | 2 +- docs/cursor-model-ux-spec.md | 2 +- src/cursor-native-tool-display.ts | 10 ++- src/cursor-provider.ts | 121 +++++++++++++++++++++++++++--- test/cursor-provider.test.ts | 87 +++++++++++++++++++++ test/index.test.ts | 3 +- 7 files changed, 213 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c32fdd..a829684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Added pi-native `/login` API-key integration for the Cursor provider. Startup discovery now checks pi `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. - Fallback Cursor models remain available when startup discovery cannot authenticate; once auth is saved, fallback model runs can use the stored key, while `/reload` or restart refreshes the full live Cursor model catalog. -- Improved Cursor activity display by preserving Cursor thinking, streaming Cursor text deltas live, and replaying completed Cursor internal `read`, `bash`, and `ls` activity through pi's native tool rendering path in interactive TTY sessions where possible. Non-interactive runs keep bounded scrubbed transcript output, and raw Cursor call IDs remain omitted. +- Improved Cursor activity display by preserving Cursor thinking, streaming Cursor text deltas live when native replay is not active, and replaying completed Cursor internal `read`, `bash`, and `ls` activity through pi's native tool rendering path in interactive TTY sessions where possible. Native Cursor tool replay now follows Codex-style ordering: assistant tool-use turn, recorded pi tool results, then replayed final assistant answer. Non-interactive runs keep bounded scrubbed transcript output, and raw Cursor call IDs remain omitted. - Stopped copying Cursor SDK cumulative internal agent/tool/cache token usage into pi usage, preventing false context-overflow and compaction triggers after long Cursor runs. ## 0.1.4 - 2026-05-07 diff --git a/README.md b/README.md index 7e24944..6fc7a5b 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. When final text is available, the extension mirrors native Codex ordering by ending the tool turn first, letting pi render the recorded tool results, then replaying Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 2e24aff..cf44101 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -17,7 +17,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live; the final run result is used only when no text deltas arrived. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. When these native cards are emitted and Cursor has final text, the provider mirrors Codex's two-turn shape: first assistant `toolUse`, then pi `toolResult`s, then a replayed final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index 3513674..eb9bf3a 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -12,6 +12,7 @@ const NATIVE_CURSOR_TOOL_NAMES = new Set(["read", "bash", "ls"]); export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { id: string; + terminate?: boolean; } let nativeToolDisplayEnabled = false; @@ -44,6 +45,13 @@ function consumeCursorNativeToolDisplay(id: string): CursorNativeToolDisplayItem return item; } +export const __testUtils = { + reset(): void { + nativeToolDisplayEnabled = false; + nativeToolResults.clear(); + }, +}; + function wrapNativeCursorTool( definition: ToolDefinition, ): ToolDefinition { @@ -55,7 +63,7 @@ function wrapNativeCursorTool( return { content: cursorDisplay.result.content, details: cursorDisplay.result.details as TDetails, - terminate: true, + terminate: cursorDisplay.terminate ?? true, }; } return definition.execute(toolCallId, params, signal, onUpdate, ctx); diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 4499d3d..60e90d6 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -18,6 +18,7 @@ import { canRenderCursorToolNatively, isCursorNativeToolDisplayRuntimeEnabled, recordCursorNativeToolDisplay, + type CursorNativeToolDisplayItem, } from "./cursor-native-tool-display.js"; function makeInitialMessage(model: Model): AssistantMessage { @@ -57,6 +58,15 @@ const AUTH_CURSOR_SDK_ERROR_MESSAGE = const APPROX_CHARS_PER_TOKEN = 4; const IMAGE_TOKEN_ESTIMATE = 1200; const CURSOR_ACTIVITY_TRACE_MAX_CHARS = 50000; +const CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN = /^(cursor-replay-\d+-\d+)-tool-\d+$/; + +interface PendingCursorNativeReply { + textDeltas: string[]; + promptInputTokens: number; +} + +let cursorNativeReplayCounter = 0; +const pendingCursorNativeReplies = new Map(); function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); @@ -168,6 +178,77 @@ function scrubDisplayValue(value: unknown, apiKey?: string): unknown { return value; } +function createCursorNativeReplayId(): string { + cursorNativeReplayCounter += 1; + return `cursor-replay-${Date.now()}-${cursorNativeReplayCounter}`; +} + +function getCursorNativeReplayIdFromToolCallId(toolCallId: string): string | undefined { + return CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN.exec(toolCallId)?.[1]; +} + +function getPendingCursorNativeReplayId(context: Context): string | undefined { + for (let index = context.messages.length - 1; index >= 0; index -= 1) { + const message = context.messages[index]; + if (message.role !== "toolResult") break; + const replayId = getCursorNativeReplayIdFromToolCallId(message.toolCallId); + if (replayId && pendingCursorNativeReplies.has(replayId)) return replayId; + } + return undefined; +} + +function splitTextIntoReplayDeltas(text: string): string[] { + const deltas: string[] = []; + let remaining = text; + while (remaining.length > 0) { + if (remaining.length <= 96) { + deltas.push(remaining); + break; + } + const boundary = Math.max(48, remaining.lastIndexOf(" ", 96)); + deltas.push(remaining.slice(0, boundary)); + remaining = remaining.slice(boundary); + } + return deltas; +} + +async function emitTextDeltas( + stream: AssistantMessageEventStream, + partial: AssistantMessage, + deltas: string[], +): Promise { + if (deltas.length === 0) return ""; + const contentIndex = partial.content.length; + partial.content.push({ type: "text", text: "" }); + stream.push({ type: "text_start", contentIndex, partial }); + const block = partial.content[contentIndex]; + if (block.type !== "text") return ""; + for (const delta of deltas) { + block.text += delta; + stream.push({ type: "text_delta", contentIndex, delta, partial }); + await Promise.resolve(); + } + stream.push({ type: "text_end", contentIndex, content: block.text, partial }); + return block.text; +} + +async function replayPendingCursorNativeReply( + stream: AssistantMessageEventStream, + partial: AssistantMessage, + context: Context, +): Promise { + const replayId = getPendingCursorNativeReplayId(context); + if (!replayId) return false; + const pending = pendingCursorNativeReplies.get(replayId); + if (!pending) return false; + pendingCursorNativeReplies.delete(replayId); + const finalText = await emitTextDeltas(stream, partial, pending.textDeltas); + setApproximateUsage(partial, pending.promptInputTokens, finalText); + partial.stopReason = "stop"; + stream.push({ type: "done", reason: "stop", message: partial }); + return true; +} + export function streamCursor( model: Model, context: Context, @@ -190,6 +271,11 @@ export function streamCursor( stream.push({ type: "start", partial }); throwIfAborted(); + if (await replayPendingCursorNativeReply(stream, partial, context)) { + stream.end(); + return; + } + const apiKey = resolveCursorApiKey(options?.apiKey); if (!apiKey) throw new Error(MISSING_API_KEY_MESSAGE); resolvedApiKey = apiKey; @@ -214,12 +300,14 @@ export function streamCursor( let activityTraceTruncated = false; let nativeToolDisplayCounter = 0; let textContentIndex = -1; + const useNativeToolReplay = isCursorNativeToolDisplayRuntimeEnabled(); + const nativeReplayId = createCursorNativeReplayId(); const textDeltas: string[] = []; + const nativeToolDisplays: CursorNativeToolDisplayItem[] = []; const startedToolCalls = new Map(); const completedToolFingerprints = new Set(); const appendLiveTextDelta = (text: string): void => { - textDeltas.push(text); if (textContentIndex < 0) { textContentIndex = partial.content.length; partial.content.push({ type: "text", text: "" }); @@ -318,8 +406,8 @@ export function streamCursor( if (completedToolFingerprints.has(fingerprint)) return; completedToolFingerprints.add(fingerprint); - if (isCursorNativeToolDisplayRuntimeEnabled() && canRenderCursorToolNatively(display.toolName)) { - const id = `cursor-tool-${++nativeToolDisplayCounter}`; + if (useNativeToolReplay && canRenderCursorToolNatively(display.toolName)) { + const id = `${nativeReplayId}-tool-${++nativeToolDisplayCounter}`; const contentIndex = partial.content.length; const scrubbedArgs = scrubDisplayValue(display.args, resolvedApiKey) as Record; const scrubbedResult = scrubDisplayValue(display.result, resolvedApiKey) as typeof display.result; @@ -330,7 +418,8 @@ export function streamCursor( arguments: scrubbedArgs, }); stream.push({ type: "toolcall_start", contentIndex, partial }); - recordCursorNativeToolDisplay({ + stream.push({ type: "toolcall_delta", contentIndex, delta: JSON.stringify(scrubbedArgs), partial }); + nativeToolDisplays.push({ ...display, id, args: scrubbedArgs, @@ -348,7 +437,8 @@ export function streamCursor( const update = args.update; if (update.type === "text-delta") { - appendLiveTextDelta(update.text); + textDeltas.push(update.text); + if (!useNativeToolReplay) appendLiveTextDelta(update.text); } else if (update.type === "thinking-delta") { appendTraceDelta(update.text); } else if (update.type === "thinking-completed") { @@ -398,17 +488,30 @@ export function streamCursor( const result = await run.wait(); await cacheSdkContextWindow(agent.agentId, model.id); - // Close any open thinking/activity trace, then use the final run result only when - // Cursor did not stream text deltas. + // Close any open thinking/activity trace. For native tool replay, mirror Codex's + // shape: emit a toolUse turn first, let pi render recorded tool results, then + // replay Cursor's final answer as the next assistant turn. closeTraceBlock(); - const finalText = flushText(textDeltas.length === 0 && hasUsableText(result.result) ? [result.result] : []); - setApproximateUsage(partial, promptInputTokens, finalText); + const replayTextDeltas = hasUsableText(result.result) ? splitTextIntoReplayDeltas(result.result) : textDeltas; if (result.status === "cancelled") { partial.stopReason = "aborted"; stream.push({ type: "error", reason: "aborted", error: partial }); + } else if (useNativeToolReplay && nativeToolDisplays.length > 0) { + const shouldReplayFinalText = replayTextDeltas.some((delta) => delta.trim().length > 0); + if (shouldReplayFinalText) { + pendingCursorNativeReplies.set(nativeReplayId, { textDeltas: replayTextDeltas, promptInputTokens }); + } + for (const display of nativeToolDisplays) { + recordCursorNativeToolDisplay({ ...display, terminate: !shouldReplayFinalText }); + } + setApproximateUsage(partial, promptInputTokens, ""); + partial.stopReason = "toolUse"; + stream.push({ type: "done", reason: "toolUse", message: partial }); } else { + const finalText = flushText(useNativeToolReplay ? replayTextDeltas : textDeltas.length === 0 && hasUsableText(result.result) ? replayTextDeltas : []); + setApproximateUsage(partial, promptInputTokens, finalText); stream.push({ type: "done", reason: "stop", message: partial }); } } catch (error) { diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 8372f10..2ba6349 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -35,6 +35,7 @@ import { Agent, createAgentPlatform } from "@cursor/sdk"; import { streamCursor } from "../src/cursor-provider.js"; import { __testUtils as modelDiscoveryTestUtils } from "../src/model-discovery.js"; import { __testUtils as contextWindowCacheTestUtils } from "../src/context-window-cache.js"; +import { __testUtils as nativeToolDisplayTestUtils, registerCursorNativeToolDisplay } from "../src/cursor-native-tool-display.js"; import type { ModelListItem } from "@cursor/sdk"; import type { Context, Model } from "@earendil-works/pi-ai"; @@ -148,6 +149,8 @@ const cursorModelItems: ModelListItem[] = [ describe("streamCursor", () => { beforeEach(() => { vi.clearAllMocks(); + delete process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY; + nativeToolDisplayTestUtils.reset(); modelDiscoveryTestUtils.registerModelItems(cursorModelItems); // Re-setup default mock return after clearing mockedCreate.mockResolvedValue({ @@ -333,6 +336,90 @@ describe("streamCursor", () => { expect(trace).toContain("# pi-cursor-sdk"); }); + it("replays native Cursor tools as a toolUse turn before final text", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + const registeredTools: any[] = []; + registerCursorNativeToolDisplay({ + registerTool: vi.fn((tool: any) => { + registeredTools.push(tool); + }), + } as any); + + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "text-delta", text: "I am checking files." } }); + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "README.md" } }, callId: "c1" } }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { status: "success", value: { content: "# pi-cursor-sdk" } }, + }, + callId: "c1", + }, + }); + opts.onDelta({ update: { type: "text-delta", text: "Final-ish streamed text." } }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "Final answer only." }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + agentId: "agent-1", + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const firstEvents = await collectEvents(streamCursor(makeModel(), makeContext(), { apiKey: "test-key" })); + const firstDone = firstEvents.find((e: any) => e.type === "done") as any; + const firstText = firstEvents.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); + const toolCall = firstDone.message.content.find((block: any) => block.type === "toolCall"); + + expect(firstText).toBe(""); + expect(firstDone.reason).toBe("toolUse"); + expect(firstDone.message.stopReason).toBe("toolUse"); + expect(firstDone.message.content.map((block: any) => block.type)).toEqual(["toolCall"]); + expect(toolCall.name).toBe("read"); + expect(firstEvents.some((event: any) => event.type === "toolcall_delta")).toBe(true); + + const readTool = registeredTools.find((tool) => tool.name === "read"); + const toolResult = await readTool.execute(toolCall.id, toolCall.arguments, undefined, undefined, {}); + expect(toolResult).toEqual({ + content: [{ type: "text", text: "# pi-cursor-sdk" }], + details: undefined, + terminate: false, + }); + + const replayContext = makeContext(); + replayContext.messages = [ + ...replayContext.messages, + firstDone.message, + { + role: "toolResult", + toolCallId: toolCall.id, + toolName: "read", + content: toolResult.content, + details: toolResult.details, + isError: false, + timestamp: 2, + }, + ]; + + const replayEvents = await collectEvents(streamCursor(makeModel(), replayContext, { apiKey: "test-key" })); + const replayText = replayEvents.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); + const replayDone = replayEvents.find((e: any) => e.type === "done") as any; + + expect(mockedCreate).toHaveBeenCalledTimes(1); + expect(replayText).toBe("Final answer only."); + expect(replayDone.reason).toBe("stop"); + expect(replayDone.message.content).toEqual([{ type: "text", text: "Final answer only." }]); + }); + it("streams Cursor text deltas live and only falls back to final result when no deltas arrive", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "text-delta", text: "Final " } }); diff --git a/test/index.test.ts b/test/index.test.ts index 57c450f..399f77c 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -11,7 +11,7 @@ vi.mock("../src/cursor-provider.js", () => ({ import extensionFactory from "../src/index.js"; import { discoverModels } from "../src/model-discovery.js"; import { streamCursor } from "../src/cursor-provider.js"; -import { recordCursorNativeToolDisplay } from "../src/cursor-native-tool-display.js"; +import { __testUtils as nativeToolDisplayTestUtils, recordCursorNativeToolDisplay } from "../src/cursor-native-tool-display.js"; const mockedDiscover = vi.mocked(discoverModels); const mockedStreamCursor = vi.mocked(streamCursor); @@ -44,6 +44,7 @@ function createMockPi() { describe("extension factory", () => { beforeEach(() => { vi.clearAllMocks(); + nativeToolDisplayTestUtils.reset(); }); it("registers Cursor fast controls and one provider with correct fields", async () => { -- 2.51.2 From 676a344886ea8aabb2d1f0bc4bb6695627baf334 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 15:57:04 -0600 Subject: [PATCH 022/359] Stream Cursor native tool results live --- CHANGELOG.md | 2 +- README.md | 2 +- docs/cursor-model-ux-spec.md | 2 +- src/cursor-provider.ts | 240 +++++++++++++++++++++++++++-------- test/cursor-provider.test.ts | 14 +- 5 files changed, 204 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a829684..d94b373 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Added pi-native `/login` API-key integration for the Cursor provider. Startup discovery now checks pi `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. - Fallback Cursor models remain available when startup discovery cannot authenticate; once auth is saved, fallback model runs can use the stored key, while `/reload` or restart refreshes the full live Cursor model catalog. -- Improved Cursor activity display by preserving Cursor thinking, streaming Cursor text deltas live when native replay is not active, and replaying completed Cursor internal `read`, `bash`, and `ls` activity through pi's native tool rendering path in interactive TTY sessions where possible. Native Cursor tool replay now follows Codex-style ordering: assistant tool-use turn, recorded pi tool results, then replayed final assistant answer. Non-interactive runs keep bounded scrubbed transcript output, and raw Cursor call IDs remain omitted. +- Improved Cursor activity display by preserving Cursor thinking, streaming Cursor text deltas live when native replay is not active, and replaying completed Cursor internal `read`, `bash`, and `ls` activity through pi's native tool rendering path in interactive TTY sessions where possible. Native Cursor tool replay now follows Codex-style ordering as Cursor SDK tool completions arrive: assistant tool-use turn, recorded pi tool results, any later Cursor tool batches, then replayed final assistant answer. Non-interactive runs keep bounded scrubbed transcript output, and raw Cursor call IDs remain omitted. - Stopped copying Cursor SDK cumulative internal agent/tool/cache token usage into pi usage, preventing false context-overflow and compaction triggers after long Cursor runs. ## 0.1.4 - 2026-05-07 diff --git a/README.md b/README.md index 6fc7a5b..692afc7 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. When final text is available, the extension mirrors native Codex ordering by ending the tool turn first, letting pi render the recorded tool results, then replaying Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with any later Cursor tool batches or replaying Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index cf44101..28a32d2 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -17,7 +17,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. When these native cards are emitted and Cursor has final text, the provider mirrors Codex's two-turn shape: first assistant `toolUse`, then pi `toolResult`s, then a replayed final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, any later Cursor tool batches as further `toolUse` turns, then a replayed final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 60e90d6..cd113be 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -60,13 +60,22 @@ const IMAGE_TOKEN_ESTIMATE = 1200; const CURSOR_ACTIVITY_TRACE_MAX_CHARS = 50000; const CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN = /^(cursor-replay-\d+-\d+)-tool-\d+$/; -interface PendingCursorNativeReply { - textDeltas: string[]; +interface CursorNativeLiveRun { + id: string; + agent: SDKAgent; promptInputTokens: number; + pendingTools: CursorNativeToolDisplayItem[]; + pendingThinkingDeltas: string[]; + textDeltas: string[]; + finalText?: string; + done: boolean; + cancelled: boolean; + errorMessage?: string; + waiters: Set<() => void>; } let cursorNativeReplayCounter = 0; -const pendingCursorNativeReplies = new Map(); +const pendingCursorNativeRuns = new Map(); function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); @@ -192,7 +201,7 @@ function getPendingCursorNativeReplayId(context: Context): string | undefined { const message = context.messages[index]; if (message.role !== "toolResult") break; const replayId = getCursorNativeReplayIdFromToolCallId(message.toolCallId); - if (replayId && pendingCursorNativeReplies.has(replayId)) return replayId; + if (replayId && pendingCursorNativeRuns.has(replayId)) return replayId; } return undefined; } @@ -232,20 +241,128 @@ async function emitTextDeltas( return block.text; } -async function replayPendingCursorNativeReply( +function notifyCursorNativeRun(run: CursorNativeLiveRun): void { + for (const waiter of run.waiters) waiter(); + run.waiters.clear(); +} + +function isCursorNativeRunReady(run: CursorNativeLiveRun): boolean { + return run.pendingTools.length > 0 || run.done || run.cancelled || run.errorMessage !== undefined; +} + +async function waitForCursorNativeRunProgress(run: CursorNativeLiveRun, signal?: AbortSignal): Promise { + if (isCursorNativeRunReady(run)) return; + await new Promise((resolve, reject) => { + const onAbort = (): void => { + run.waiters.delete(resolve); + reject(new CursorAbortError()); + }; + run.waiters.add(resolve); + signal?.addEventListener("abort", onAbort, { once: true }); + }).finally(() => { + // The abort listener is one-shot; removing with an unknown function is not possible here. + }); +} + +async function settleCursorNativeToolBatch(run: CursorNativeLiveRun): Promise { + if (run.pendingTools.length === 0) return; + await new Promise((resolve) => setTimeout(resolve, 75)); +} + +function flushStoredThinkingDeltas(stream: AssistantMessageEventStream, partial: AssistantMessage, run: CursorNativeLiveRun): void { + if (run.pendingThinkingDeltas.length === 0) return; + const contentIndex = partial.content.length; + partial.content.push({ type: "thinking", thinking: "" }); + stream.push({ type: "thinking_start", contentIndex, partial }); + const block = partial.content[contentIndex]; + if (block.type !== "thinking") return; + for (const delta of run.pendingThinkingDeltas.splice(0)) { + block.thinking += delta; + stream.push({ type: "thinking_delta", contentIndex, delta, partial }); + } + stream.push({ type: "thinking_end", contentIndex, content: block.thinking, partial }); +} + +function emitCursorNativeToolUseTurn( + stream: AssistantMessageEventStream, + partial: AssistantMessage, + run: CursorNativeLiveRun, +): void { + flushStoredThinkingDeltas(stream, partial, run); + const tools = run.pendingTools.splice(0); + const shouldTerminate = run.done && !run.finalText?.trim() && run.pendingTools.length === 0; + for (const tool of tools) { + const contentIndex = partial.content.length; + partial.content.push({ + type: "toolCall", + id: tool.id, + name: tool.toolName, + arguments: tool.args, + }); + stream.push({ type: "toolcall_start", contentIndex, partial }); + stream.push({ type: "toolcall_delta", contentIndex, delta: JSON.stringify(tool.args), partial }); + const block = partial.content[contentIndex]; + if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial }); + recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate }); + } + setApproximateUsage(partial, run.promptInputTokens, ""); + partial.stopReason = "toolUse"; + stream.push({ type: "done", reason: "toolUse", message: partial }); +} + +async function disposeCursorNativeRun(run: CursorNativeLiveRun): Promise { + pendingCursorNativeRuns.delete(run.id); + try { + await run.agent[Symbol.asyncDispose](); + } catch { + // disposal failure should not mask the provider result + } +} + +async function emitCursorNativeRunNextTurn( + stream: AssistantMessageEventStream, + partial: AssistantMessage, + run: CursorNativeLiveRun, + signal?: AbortSignal, +): Promise { + await waitForCursorNativeRunProgress(run, signal); + await settleCursorNativeToolBatch(run); + if (run.pendingTools.length > 0) { + emitCursorNativeToolUseTurn(stream, partial, run); + return; + } + if (run.cancelled) { + partial.stopReason = "aborted"; + stream.push({ type: "error", reason: "aborted", error: partial }); + await disposeCursorNativeRun(run); + return; + } + if (run.errorMessage) { + partial.stopReason = "error"; + partial.errorMessage = run.errorMessage; + stream.push({ type: "error", reason: "error", error: partial }); + await disposeCursorNativeRun(run); + return; + } + flushStoredThinkingDeltas(stream, partial, run); + const finalText = await emitTextDeltas(stream, partial, splitTextIntoReplayDeltas(run.finalText ?? run.textDeltas.join(""))); + setApproximateUsage(partial, run.promptInputTokens, finalText); + partial.stopReason = "stop"; + stream.push({ type: "done", reason: "stop", message: partial }); + await disposeCursorNativeRun(run); +} + +async function replayPendingCursorNativeRun( stream: AssistantMessageEventStream, partial: AssistantMessage, context: Context, + signal?: AbortSignal, ): Promise { const replayId = getPendingCursorNativeReplayId(context); if (!replayId) return false; - const pending = pendingCursorNativeReplies.get(replayId); - if (!pending) return false; - pendingCursorNativeReplies.delete(replayId); - const finalText = await emitTextDeltas(stream, partial, pending.textDeltas); - setApproximateUsage(partial, pending.promptInputTokens, finalText); - partial.stopReason = "stop"; - stream.push({ type: "done", reason: "stop", message: partial }); + const run = pendingCursorNativeRuns.get(replayId); + if (!run) return false; + await emitCursorNativeRunNextTurn(stream, partial, run, signal); return true; } @@ -271,7 +388,7 @@ export function streamCursor( stream.push({ type: "start", partial }); throwIfAborted(); - if (await replayPendingCursorNativeReply(stream, partial, context)) { + if (await replayPendingCursorNativeRun(stream, partial, context, options?.signal)) { stream.end(); return; } @@ -303,7 +420,21 @@ export function streamCursor( const useNativeToolReplay = isCursorNativeToolDisplayRuntimeEnabled(); const nativeReplayId = createCursorNativeReplayId(); const textDeltas: string[] = []; - const nativeToolDisplays: CursorNativeToolDisplayItem[] = []; + let liveStreamClosed = false; + const liveRun: CursorNativeLiveRun | undefined = useNativeToolReplay + ? { + id: nativeReplayId, + agent, + promptInputTokens, + pendingTools: [], + pendingThinkingDeltas: [], + textDeltas, + done: false, + cancelled: false, + waiters: new Set(), + } + : undefined; + if (liveRun) pendingCursorNativeRuns.set(liveRun.id, liveRun); const startedToolCalls = new Map(); const completedToolFingerprints = new Set(); @@ -406,27 +537,15 @@ export function streamCursor( if (completedToolFingerprints.has(fingerprint)) return; completedToolFingerprints.add(fingerprint); - if (useNativeToolReplay && canRenderCursorToolNatively(display.toolName)) { + if (useNativeToolReplay && canRenderCursorToolNatively(display.toolName) && liveRun) { const id = `${nativeReplayId}-tool-${++nativeToolDisplayCounter}`; - const contentIndex = partial.content.length; - const scrubbedArgs = scrubDisplayValue(display.args, resolvedApiKey) as Record; - const scrubbedResult = scrubDisplayValue(display.result, resolvedApiKey) as typeof display.result; - partial.content.push({ - type: "toolCall", - id, - name: display.toolName, - arguments: scrubbedArgs, - }); - stream.push({ type: "toolcall_start", contentIndex, partial }); - stream.push({ type: "toolcall_delta", contentIndex, delta: JSON.stringify(scrubbedArgs), partial }); - nativeToolDisplays.push({ + liveRun.pendingTools.push({ ...display, id, - args: scrubbedArgs, - result: scrubbedResult, + args: scrubDisplayValue(display.args, resolvedApiKey) as Record, + result: scrubDisplayValue(display.result, resolvedApiKey) as typeof display.result, }); - const block = partial.content[contentIndex]; - if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial }); + notifyCursorNativeRun(liveRun); return; } @@ -440,9 +559,13 @@ export function streamCursor( textDeltas.push(update.text); if (!useNativeToolReplay) appendLiveTextDelta(update.text); } else if (update.type === "thinking-delta") { - appendTraceDelta(update.text); + if (liveRun && liveStreamClosed) { + liveRun.pendingThinkingDeltas.push(update.text); + } else { + appendTraceDelta(update.text); + } } else if (update.type === "thinking-completed") { - closeTraceBlock(); + if (!liveStreamClosed) closeTraceBlock(); } else if (update.type === "tool-call-started") { startedToolCalls.set(update.callId, update.toolCall); } else if (update.type === "tool-call-completed") { @@ -450,7 +573,12 @@ export function streamCursor( startedToolCalls.delete(update.callId); handleCompletedToolCall(mergedToolCall); } else if (update.type === "summary") { - appendTraceLine(`Cursor summary: ${truncateSingleLine(update.summary)}`); + const summary = `Cursor summary: ${truncateSingleLine(update.summary)}\n`; + if (liveRun && liveStreamClosed) { + liveRun.pendingThinkingDeltas.push(summary); + } else { + appendTraceDelta(summary); + } } // Cursor turn-ended usage is intentionally not copied into pi usage: the SDK reports // cumulative internal agent/tool/cache tokens, not the replayable pi prompt context. @@ -485,32 +613,42 @@ export function streamCursor( throw new CursorAbortError(); } + if (useNativeToolReplay && liveRun) { + void run + .wait() + .then(async (result) => { + await cacheSdkContextWindow(liveRun.agent.agentId, model.id); + liveRun.cancelled = result.status === "cancelled"; + liveRun.finalText = hasUsableText(result.result) ? result.result : liveRun.textDeltas.join(""); + liveRun.done = true; + notifyCursorNativeRun(liveRun); + }) + .catch((error: unknown) => { + liveRun.errorMessage = sanitizeError(error, resolvedApiKey ?? options?.apiKey); + notifyCursorNativeRun(liveRun); + }); + + await waitForCursorNativeRunProgress(liveRun, options?.signal); + await settleCursorNativeToolBatch(liveRun); + closeTraceBlock(); + liveStreamClosed = true; + await emitCursorNativeRunNextTurn(stream, partial, liveRun, options?.signal); + agent = null; + return; + } + const result = await run.wait(); await cacheSdkContextWindow(agent.agentId, model.id); - // Close any open thinking/activity trace. For native tool replay, mirror Codex's - // shape: emit a toolUse turn first, let pi render recorded tool results, then - // replay Cursor's final answer as the next assistant turn. + // Close any open thinking/activity trace, then use the final run result only when + // Cursor did not stream text deltas. closeTraceBlock(); - const replayTextDeltas = hasUsableText(result.result) ? splitTextIntoReplayDeltas(result.result) : textDeltas; - if (result.status === "cancelled") { partial.stopReason = "aborted"; stream.push({ type: "error", reason: "aborted", error: partial }); - } else if (useNativeToolReplay && nativeToolDisplays.length > 0) { - const shouldReplayFinalText = replayTextDeltas.some((delta) => delta.trim().length > 0); - if (shouldReplayFinalText) { - pendingCursorNativeReplies.set(nativeReplayId, { textDeltas: replayTextDeltas, promptInputTokens }); - } - for (const display of nativeToolDisplays) { - recordCursorNativeToolDisplay({ ...display, terminate: !shouldReplayFinalText }); - } - setApproximateUsage(partial, promptInputTokens, ""); - partial.stopReason = "toolUse"; - stream.push({ type: "done", reason: "toolUse", message: partial }); } else { - const finalText = flushText(useNativeToolReplay ? replayTextDeltas : textDeltas.length === 0 && hasUsableText(result.result) ? replayTextDeltas : []); + const finalText = flushText(textDeltas.length === 0 && hasUsableText(result.result) ? [result.result] : []); setApproximateUsage(partial, promptInputTokens, finalText); stream.push({ type: "done", reason: "stop", message: partial }); } diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 2ba6349..aa0d9a3 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -345,6 +345,13 @@ describe("streamCursor", () => { }), } as any); + let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; + const runWait = vi.fn( + () => + new Promise<{ id: string; status: "finished"; result: string }>((resolve) => { + resolveRun = resolve; + }), + ); const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "text-delta", text: "I am checking files." } }); opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "README.md" } }, callId: "c1" } }); @@ -362,8 +369,8 @@ describe("streamCursor", () => { return { id: "run-1", agentId: "agent-1", - status: "finished", - wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "Final answer only." }), + status: "running", + wait: runWait, cancel: vi.fn(), supports: () => true, unsupportedReason: () => undefined, @@ -376,6 +383,7 @@ describe("streamCursor", () => { }); const firstEvents = await collectEvents(streamCursor(makeModel(), makeContext(), { apiKey: "test-key" })); + expect(runWait).toHaveBeenCalledTimes(1); const firstDone = firstEvents.find((e: any) => e.type === "done") as any; const firstText = firstEvents.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); const toolCall = firstDone.message.content.find((block: any) => block.type === "toolCall"); @@ -395,6 +403,8 @@ describe("streamCursor", () => { terminate: false, }); + resolveRun({ id: "run-1", status: "finished", result: "Final answer only." }); + const replayContext = makeContext(); replayContext.messages = [ ...replayContext.messages, -- 2.51.2 From 0efedae64e550e5c4fbc16d5d1f537a2e094d3be Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 16:15:20 -0600 Subject: [PATCH 023/359] Stream Cursor replay text after tools --- CHANGELOG.md | 2 +- README.md | 2 +- docs/cursor-model-ux-spec.md | 2 +- src/cursor-provider.ts | 251 +++++++++++++++++++++++++++-------- test/cursor-provider.test.ts | 103 ++++++++++++++ 5 files changed, 299 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d94b373..0fbb311 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Added pi-native `/login` API-key integration for the Cursor provider. Startup discovery now checks pi `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. - Fallback Cursor models remain available when startup discovery cannot authenticate; once auth is saved, fallback model runs can use the stored key, while `/reload` or restart refreshes the full live Cursor model catalog. -- Improved Cursor activity display by preserving Cursor thinking, streaming Cursor text deltas live when native replay is not active, and replaying completed Cursor internal `read`, `bash`, and `ls` activity through pi's native tool rendering path in interactive TTY sessions where possible. Native Cursor tool replay now follows Codex-style ordering as Cursor SDK tool completions arrive: assistant tool-use turn, recorded pi tool results, any later Cursor tool batches, then replayed final assistant answer. Non-interactive runs keep bounded scrubbed transcript output, and raw Cursor call IDs remain omitted. +- Improved Cursor activity display by preserving Cursor thinking, streaming Cursor text deltas live when native replay is not active, and replaying completed Cursor internal `read`, `bash`, and `ls` activity through pi's native tool rendering path in interactive TTY sessions where possible. Native Cursor tool replay now follows Codex-style ordering as Cursor SDK tool completions arrive: assistant tool-use turn, recorded pi tool results, live post-tool Cursor thinking/text, any later Cursor tool batches, then final assistant answer. Non-interactive runs keep bounded scrubbed transcript output, and raw Cursor call IDs remain omitted. - Stopped copying Cursor SDK cumulative internal agent/tool/cache token usage into pi usage, preventing false context-overflow and compaction triggers after long Cursor runs. ## 0.1.4 - 2026-05-07 diff --git a/README.md b/README.md index 692afc7..f7eb191 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with any later Cursor tool batches or replaying Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 28a32d2..eacfbcd 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -17,7 +17,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, any later Cursor tool batches as further `toolUse` turns, then a replayed final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index cd113be..249df44 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -60,20 +60,33 @@ const IMAGE_TOKEN_ESTIMATE = 1200; const CURSOR_ACTIVITY_TRACE_MAX_CHARS = 50000; const CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN = /^(cursor-replay-\d+-\d+)-tool-\d+$/; +type CursorNativeQueuedEvent = + | { type: "thinking-delta"; text: string } + | { type: "thinking-completed" } + | { type: "text-delta"; text: string } + | { type: "tool"; tool: CursorNativeToolDisplayItem }; + interface CursorNativeLiveRun { id: string; agent: SDKAgent; promptInputTokens: number; - pendingTools: CursorNativeToolDisplayItem[]; - pendingThinkingDeltas: string[]; + pendingEvents: CursorNativeQueuedEvent[]; textDeltas: string[]; finalText?: string; + streamedText: string; done: boolean; cancelled: boolean; errorMessage?: string; waiters: Set<() => void>; } +interface CursorNativeTurnState { + stream: AssistantMessageEventStream; + partial: AssistantMessage; + thinkingContentIndex: number; + textContentIndex: number; +} + let cursorNativeReplayCounter = 0; const pendingCursorNativeRuns = new Map(); @@ -246,51 +259,132 @@ function notifyCursorNativeRun(run: CursorNativeLiveRun): void { run.waiters.clear(); } +function queueCursorNativeEvent(run: CursorNativeLiveRun, event: CursorNativeQueuedEvent): void { + run.pendingEvents.push(event); + notifyCursorNativeRun(run); +} + function isCursorNativeRunReady(run: CursorNativeLiveRun): boolean { - return run.pendingTools.length > 0 || run.done || run.cancelled || run.errorMessage !== undefined; + return run.pendingEvents.length > 0 || run.done || run.cancelled || run.errorMessage !== undefined; } async function waitForCursorNativeRunProgress(run: CursorNativeLiveRun, signal?: AbortSignal): Promise { if (isCursorNativeRunReady(run)) return; await new Promise((resolve, reject) => { + let waiter: (() => void) | undefined; + const cleanup = (): void => { + if (waiter) run.waiters.delete(waiter); + signal?.removeEventListener("abort", onAbort); + }; const onAbort = (): void => { - run.waiters.delete(resolve); + cleanup(); reject(new CursorAbortError()); }; - run.waiters.add(resolve); + waiter = (): void => { + cleanup(); + resolve(); + }; + run.waiters.add(waiter); signal?.addEventListener("abort", onAbort, { once: true }); - }).finally(() => { - // The abort listener is one-shot; removing with an unknown function is not possible here. }); } async function settleCursorNativeToolBatch(run: CursorNativeLiveRun): Promise { - if (run.pendingTools.length === 0) return; + if (run.pendingEvents[0]?.type !== "tool") return; await new Promise((resolve) => setTimeout(resolve, 75)); } -function flushStoredThinkingDeltas(stream: AssistantMessageEventStream, partial: AssistantMessage, run: CursorNativeLiveRun): void { - if (run.pendingThinkingDeltas.length === 0) return; - const contentIndex = partial.content.length; - partial.content.push({ type: "thinking", thinking: "" }); - stream.push({ type: "thinking_start", contentIndex, partial }); - const block = partial.content[contentIndex]; +function closeCursorNativeThinkingBlock(turn: CursorNativeTurnState): void { + if (turn.thinkingContentIndex < 0) return; + const block = turn.partial.content[turn.thinkingContentIndex]; + if (block.type === "thinking") { + turn.stream.push({ + type: "thinking_end", + contentIndex: turn.thinkingContentIndex, + content: block.thinking, + partial: turn.partial, + }); + } + turn.thinkingContentIndex = -1; +} + +function closeCursorNativeTextBlock(turn: CursorNativeTurnState): string { + if (turn.textContentIndex < 0) return ""; + const block = turn.partial.content[turn.textContentIndex]; + turn.textContentIndex = -1; + if (block.type !== "text") return ""; + turn.stream.push({ + type: "text_end", + contentIndex: turn.partial.content.indexOf(block), + content: block.text, + partial: turn.partial, + }); + return block.text; +} + +function closeCursorNativeTurnBlocks(turn: CursorNativeTurnState): string { + closeCursorNativeThinkingBlock(turn); + return closeCursorNativeTextBlock(turn); +} + +function emitCursorNativeThinkingDelta(turn: CursorNativeTurnState, delta: string): void { + closeCursorNativeTextBlock(turn); + if (turn.thinkingContentIndex < 0) { + turn.thinkingContentIndex = turn.partial.content.length; + turn.partial.content.push({ type: "thinking", thinking: "" }); + turn.stream.push({ type: "thinking_start", contentIndex: turn.thinkingContentIndex, partial: turn.partial }); + } + const block = turn.partial.content[turn.thinkingContentIndex]; if (block.type !== "thinking") return; - for (const delta of run.pendingThinkingDeltas.splice(0)) { - block.thinking += delta; - stream.push({ type: "thinking_delta", contentIndex, delta, partial }); + block.thinking += delta; + turn.stream.push({ type: "thinking_delta", contentIndex: turn.thinkingContentIndex, delta, partial: turn.partial }); +} + +function emitCursorNativeTextDelta(turn: CursorNativeTurnState, run: CursorNativeLiveRun, delta: string): void { + closeCursorNativeThinkingBlock(turn); + if (turn.textContentIndex < 0) { + turn.textContentIndex = turn.partial.content.length; + turn.partial.content.push({ type: "text", text: "" }); + turn.stream.push({ type: "text_start", contentIndex: turn.textContentIndex, partial: turn.partial }); + } + const block = turn.partial.content[turn.textContentIndex]; + if (block.type !== "text") return; + block.text += delta; + run.streamedText += delta; + turn.stream.push({ type: "text_delta", contentIndex: turn.textContentIndex, delta, partial: turn.partial }); +} + +function emitCursorNativeQueuedEvent( + turn: CursorNativeTurnState, + run: CursorNativeLiveRun, + event: Exclude, +): void { + if (event.type === "thinking-delta") { + emitCursorNativeThinkingDelta(turn, event.text); + } else if (event.type === "thinking-completed") { + closeCursorNativeThinkingBlock(turn); + } else if (event.type === "text-delta") { + emitCursorNativeTextDelta(turn, run, event.text); + } +} + +function collectCursorNativeToolBatch(run: CursorNativeLiveRun): CursorNativeToolDisplayItem[] { + const tools: CursorNativeToolDisplayItem[] = []; + while (run.pendingEvents[0]?.type === "tool") { + const event = run.pendingEvents.shift(); + if (event?.type === "tool") tools.push(event.tool); } - stream.push({ type: "thinking_end", contentIndex, content: block.thinking, partial }); + return tools; } function emitCursorNativeToolUseTurn( stream: AssistantMessageEventStream, partial: AssistantMessage, run: CursorNativeLiveRun, + tools: CursorNativeToolDisplayItem[], + outputText: string, ): void { - flushStoredThinkingDeltas(stream, partial, run); - const tools = run.pendingTools.splice(0); - const shouldTerminate = run.done && !run.finalText?.trim() && run.pendingTools.length === 0; + const shouldTerminate = run.done && !run.finalText?.trim() && run.pendingEvents.length === 0; for (const tool of tools) { const contentIndex = partial.content.length; partial.content.push({ @@ -305,7 +399,7 @@ function emitCursorNativeToolUseTurn( if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial }); recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate }); } - setApproximateUsage(partial, run.promptInputTokens, ""); + setApproximateUsage(partial, run.promptInputTokens, outputText); partial.stopReason = "toolUse"; stream.push({ type: "done", reason: "toolUse", message: partial }); } @@ -325,31 +419,62 @@ async function emitCursorNativeRunNextTurn( run: CursorNativeLiveRun, signal?: AbortSignal, ): Promise { - await waitForCursorNativeRunProgress(run, signal); - await settleCursorNativeToolBatch(run); - if (run.pendingTools.length > 0) { - emitCursorNativeToolUseTurn(stream, partial, run); - return; - } - if (run.cancelled) { - partial.stopReason = "aborted"; - stream.push({ type: "error", reason: "aborted", error: partial }); - await disposeCursorNativeRun(run); - return; - } - if (run.errorMessage) { - partial.stopReason = "error"; - partial.errorMessage = run.errorMessage; - stream.push({ type: "error", reason: "error", error: partial }); - await disposeCursorNativeRun(run); - return; + const turn: CursorNativeTurnState = { + stream, + partial, + thinkingContentIndex: -1, + textContentIndex: -1, + }; + + while (true) { + while (run.pendingEvents.length > 0) { + const event = run.pendingEvents[0]; + if (event.type === "tool") { + await settleCursorNativeToolBatch(run); + const outputText = closeCursorNativeTurnBlocks(turn); + const tools = collectCursorNativeToolBatch(run); + emitCursorNativeToolUseTurn(stream, partial, run, tools, outputText); + return; + } + run.pendingEvents.shift(); + emitCursorNativeQueuedEvent(turn, run, event); + } + + if (run.cancelled) { + partial.stopReason = "aborted"; + stream.push({ type: "error", reason: "aborted", error: partial }); + await disposeCursorNativeRun(run); + return; + } + if (run.errorMessage) { + partial.stopReason = "error"; + partial.errorMessage = run.errorMessage; + stream.push({ type: "error", reason: "error", error: partial }); + await disposeCursorNativeRun(run); + return; + } + if (run.done) { + let outputText = closeCursorNativeTurnBlocks(turn); + const finalText = run.finalText ?? run.textDeltas.join(""); + const replayTextSource = run.streamedText + ? finalText.startsWith(run.streamedText) + ? finalText.slice(run.streamedText.length) + : finalText === run.streamedText + ? "" + : finalText + : finalText; + const replayText = await emitTextDeltas(stream, partial, splitTextIntoReplayDeltas(replayTextSource)); + run.streamedText += replayText; + outputText += replayText; + setApproximateUsage(partial, run.promptInputTokens, outputText); + partial.stopReason = "stop"; + stream.push({ type: "done", reason: "stop", message: partial }); + await disposeCursorNativeRun(run); + return; + } + + await waitForCursorNativeRunProgress(run, signal); } - flushStoredThinkingDeltas(stream, partial, run); - const finalText = await emitTextDeltas(stream, partial, splitTextIntoReplayDeltas(run.finalText ?? run.textDeltas.join(""))); - setApproximateUsage(partial, run.promptInputTokens, finalText); - partial.stopReason = "stop"; - stream.push({ type: "done", reason: "stop", message: partial }); - await disposeCursorNativeRun(run); } async function replayPendingCursorNativeRun( @@ -426,9 +551,9 @@ export function streamCursor( id: nativeReplayId, agent, promptInputTokens, - pendingTools: [], - pendingThinkingDeltas: [], + pendingEvents: [], textDeltas, + streamedText: "", done: false, cancelled: false, waiters: new Set(), @@ -539,13 +664,15 @@ export function streamCursor( if (useNativeToolReplay && canRenderCursorToolNatively(display.toolName) && liveRun) { const id = `${nativeReplayId}-tool-${++nativeToolDisplayCounter}`; - liveRun.pendingTools.push({ - ...display, - id, - args: scrubDisplayValue(display.args, resolvedApiKey) as Record, - result: scrubDisplayValue(display.result, resolvedApiKey) as typeof display.result, + queueCursorNativeEvent(liveRun, { + type: "tool", + tool: { + ...display, + id, + args: scrubDisplayValue(display.args, resolvedApiKey) as Record, + result: scrubDisplayValue(display.result, resolvedApiKey) as typeof display.result, + }, }); - notifyCursorNativeRun(liveRun); return; } @@ -557,15 +684,23 @@ export function streamCursor( if (update.type === "text-delta") { textDeltas.push(update.text); - if (!useNativeToolReplay) appendLiveTextDelta(update.text); + if (liveRun && liveStreamClosed) { + queueCursorNativeEvent(liveRun, { type: "text-delta", text: update.text }); + } else if (!useNativeToolReplay) { + appendLiveTextDelta(update.text); + } } else if (update.type === "thinking-delta") { if (liveRun && liveStreamClosed) { - liveRun.pendingThinkingDeltas.push(update.text); + queueCursorNativeEvent(liveRun, { type: "thinking-delta", text: update.text }); } else { appendTraceDelta(update.text); } } else if (update.type === "thinking-completed") { - if (!liveStreamClosed) closeTraceBlock(); + if (liveRun && liveStreamClosed) { + queueCursorNativeEvent(liveRun, { type: "thinking-completed" }); + } else { + closeTraceBlock(); + } } else if (update.type === "tool-call-started") { startedToolCalls.set(update.callId, update.toolCall); } else if (update.type === "tool-call-completed") { @@ -575,7 +710,7 @@ export function streamCursor( } else if (update.type === "summary") { const summary = `Cursor summary: ${truncateSingleLine(update.summary)}\n`; if (liveRun && liveStreamClosed) { - liveRun.pendingThinkingDeltas.push(summary); + queueCursorNativeEvent(liveRun, { type: "thinking-delta", text: summary }); } else { appendTraceDelta(summary); } diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index aa0d9a3..df718c6 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -430,6 +430,109 @@ describe("streamCursor", () => { expect(replayDone.message.content).toEqual([{ type: "text", text: "Final answer only." }]); }); + it("streams post-tool Cursor thinking and text while a native replay run is still active", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + const registeredTools: any[] = []; + registerCursorNativeToolDisplay({ + registerTool: vi.fn((tool: any) => { + registeredTools.push(tool); + }), + } as any); + + let onDelta: ((args: { update: any }) => void) | undefined; + let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; + const runWait = vi.fn( + () => + new Promise<{ id: string; status: "finished"; result: string }>((resolve) => { + resolveRun = resolve; + }), + ); + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + onDelta = opts.onDelta as (args: { update: any }) => void; + onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "README.md" } }, callId: "c1" } }); + onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { status: "success", value: { content: "# pi-cursor-sdk" } }, + }, + callId: "c1", + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "running", + wait: runWait, + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + agentId: "agent-1", + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const firstEvents = await collectEvents(streamCursor(makeModel(), makeContext(), { apiKey: "test-key" })); + const firstDone = firstEvents.find((e: any) => e.type === "done") as any; + const toolCall = firstDone.message.content.find((block: any) => block.type === "toolCall"); + const readTool = registeredTools.find((tool) => tool.name === "read"); + const toolResult = await readTool.execute(toolCall.id, toolCall.arguments, undefined, undefined, {}); + + const replayContext = makeContext(); + replayContext.messages = [ + ...replayContext.messages, + firstDone.message, + { + role: "toolResult", + toolCallId: toolCall.id, + toolName: "read", + content: toolResult.content, + details: toolResult.details, + isError: false, + timestamp: 2, + }, + ]; + + const replayStream = streamCursor(makeModel(), replayContext, { apiKey: "test-key" }); + const replayEvents: any[] = []; + let sawLiveText: () => void = () => {}; + const liveTextSeen = new Promise((resolve) => { + sawLiveText = resolve; + }); + const replayDone = (async () => { + for await (const event of replayStream) { + replayEvents.push(event); + if (event.type === "text_delta" && event.delta === "Final ") sawLiveText(); + } + })(); + + await Promise.resolve(); + onDelta?.({ update: { type: "thinking-delta", text: "Streaming thought." } }); + onDelta?.({ update: { type: "thinking-completed" } }); + onDelta?.({ update: { type: "text-delta", text: "Final " } }); + await Promise.race([ + liveTextSeen, + new Promise((_, reject) => setTimeout(() => reject(new Error("timed out waiting for live Cursor text")), 500)), + ]); + onDelta?.({ update: { type: "text-delta", text: "answer." } }); + resolveRun({ id: "run-1", status: "finished", result: "Final answer." }); + await replayDone; + + const replayText = replayEvents.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); + const replayThinking = replayEvents.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + const finalDone = replayEvents.find((e: any) => e.type === "done") as any; + + expect(runWait).toHaveBeenCalledTimes(1); + expect(replayThinking).toBe("Streaming thought."); + expect(replayText).toBe("Final answer."); + expect(finalDone.reason).toBe("stop"); + expect(finalDone.message.content.map((block: any) => block.type)).toEqual(["thinking", "text"]); + }); + it("streams Cursor text deltas live and only falls back to final result when no deltas arrive", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "text-delta", text: "Final " } }); -- 2.51.2 From c55b0912a4a28679479ec3fd21839c1575be4bee Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 16:20:10 -0600 Subject: [PATCH 024/359] Avoid duplicate Cursor final replay text --- src/cursor-provider.ts | 24 ++------ test/cursor-provider.test.ts | 106 +++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 18 deletions(-) diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 249df44..b8e4466 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -73,7 +73,6 @@ interface CursorNativeLiveRun { pendingEvents: CursorNativeQueuedEvent[]; textDeltas: string[]; finalText?: string; - streamedText: string; done: boolean; cancelled: boolean; errorMessage?: string; @@ -340,7 +339,7 @@ function emitCursorNativeThinkingDelta(turn: CursorNativeTurnState, delta: strin turn.stream.push({ type: "thinking_delta", contentIndex: turn.thinkingContentIndex, delta, partial: turn.partial }); } -function emitCursorNativeTextDelta(turn: CursorNativeTurnState, run: CursorNativeLiveRun, delta: string): void { +function emitCursorNativeTextDelta(turn: CursorNativeTurnState, delta: string): void { closeCursorNativeThinkingBlock(turn); if (turn.textContentIndex < 0) { turn.textContentIndex = turn.partial.content.length; @@ -350,13 +349,11 @@ function emitCursorNativeTextDelta(turn: CursorNativeTurnState, run: CursorNativ const block = turn.partial.content[turn.textContentIndex]; if (block.type !== "text") return; block.text += delta; - run.streamedText += delta; turn.stream.push({ type: "text_delta", contentIndex: turn.textContentIndex, delta, partial: turn.partial }); } function emitCursorNativeQueuedEvent( turn: CursorNativeTurnState, - run: CursorNativeLiveRun, event: Exclude, ): void { if (event.type === "thinking-delta") { @@ -364,7 +361,7 @@ function emitCursorNativeQueuedEvent( } else if (event.type === "thinking-completed") { closeCursorNativeThinkingBlock(turn); } else if (event.type === "text-delta") { - emitCursorNativeTextDelta(turn, run, event.text); + emitCursorNativeTextDelta(turn, event.text); } } @@ -437,7 +434,7 @@ async function emitCursorNativeRunNextTurn( return; } run.pendingEvents.shift(); - emitCursorNativeQueuedEvent(turn, run, event); + emitCursorNativeQueuedEvent(turn, event); } if (run.cancelled) { @@ -455,17 +452,9 @@ async function emitCursorNativeRunNextTurn( } if (run.done) { let outputText = closeCursorNativeTurnBlocks(turn); - const finalText = run.finalText ?? run.textDeltas.join(""); - const replayTextSource = run.streamedText - ? finalText.startsWith(run.streamedText) - ? finalText.slice(run.streamedText.length) - : finalText === run.streamedText - ? "" - : finalText - : finalText; - const replayText = await emitTextDeltas(stream, partial, splitTextIntoReplayDeltas(replayTextSource)); - run.streamedText += replayText; - outputText += replayText; + if (!outputText) { + outputText += await emitTextDeltas(stream, partial, splitTextIntoReplayDeltas(run.finalText ?? run.textDeltas.join(""))); + } setApproximateUsage(partial, run.promptInputTokens, outputText); partial.stopReason = "stop"; stream.push({ type: "done", reason: "stop", message: partial }); @@ -553,7 +542,6 @@ export function streamCursor( promptInputTokens, pendingEvents: [], textDeltas, - streamedText: "", done: false, cancelled: false, waiters: new Set(), diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index df718c6..9f55583 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -533,6 +533,112 @@ describe("streamCursor", () => { expect(finalDone.message.content.map((block: any) => block.type)).toEqual(["thinking", "text"]); }); + it("does not duplicate final result after an earlier post-tool text turn", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + const registeredTools: any[] = []; + registerCursorNativeToolDisplay({ + registerTool: vi.fn((tool: any) => { + registeredTools.push(tool); + }), + } as any); + + let onDelta: ((args: { update: any }) => void) | undefined; + let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; + const runWait = vi.fn( + () => + new Promise<{ id: string; status: "finished"; result: string }>((resolve) => { + resolveRun = resolve; + }), + ); + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + onDelta = opts.onDelta as (args: { update: any }) => void; + onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "README.md" } }, callId: "c1" } }); + onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { status: "success", value: { content: "# pi-cursor-sdk" } }, + }, + callId: "c1", + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "running", + wait: runWait, + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + agentId: "agent-1", + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + const readTool = registeredTools.find((tool) => tool.name === "read"); + + const context = makeContext(); + const firstEvents = await collectEvents(streamCursor(makeModel(), context, { apiKey: "test-key" })); + const firstDone = firstEvents.find((e: any) => e.type === "done") as any; + const firstToolCall = firstDone.message.content.find((block: any) => block.type === "toolCall"); + const firstToolResult = await readTool.execute(firstToolCall.id, firstToolCall.arguments, undefined, undefined, {}); + context.messages.push(firstDone.message, { + role: "toolResult", + toolCallId: firstToolCall.id, + toolName: "read", + content: firstToolResult.content, + details: firstToolResult.details, + isError: false, + timestamp: 2, + }); + + const secondStream = streamCursor(makeModel(), context, { apiKey: "test-key" }); + const secondDonePromise = collectEvents(secondStream); + await Promise.resolve(); + onDelta?.({ update: { type: "text-delta", text: "I am checking helpers." } }); + onDelta?.({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "src/index.ts" } }, callId: "c2" } }); + onDelta?.({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { status: "success", value: { content: "import type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";" } }, + }, + callId: "c2", + }, + }); + const secondEvents = await secondDonePromise; + const secondDone = secondEvents.find((e: any) => e.type === "done") as any; + const secondToolCall = secondDone.message.content.find((block: any) => block.type === "toolCall"); + const secondToolResult = await readTool.execute(secondToolCall.id, secondToolCall.arguments, undefined, undefined, {}); + context.messages.push(secondDone.message, { + role: "toolResult", + toolCallId: secondToolCall.id, + toolName: "read", + content: secondToolResult.content, + details: secondToolResult.details, + isError: false, + timestamp: 3, + }); + + const finalStream = streamCursor(makeModel(), context, { apiKey: "test-key" }); + const finalEventsPromise = collectEvents(finalStream); + await Promise.resolve(); + onDelta?.({ update: { type: "text-delta", text: "Final answer." } }); + resolveRun({ id: "run-1", status: "finished", result: "Final answer." }); + const finalEvents = await finalEventsPromise; + const finalDone = finalEvents.find((e: any) => e.type === "done") as any; + const finalText = finalEvents.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); + + expect(runWait).toHaveBeenCalledTimes(1); + expect(secondDone.message.content.map((block: any) => block.type)).toEqual(["text", "toolCall"]); + expect(finalText).toBe("Final answer."); + expect(finalDone.message.content).toEqual([{ type: "text", text: "Final answer." }]); + }); + it("streams Cursor text deltas live and only falls back to final result when no deltas arrive", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "text-delta", text: "Final " } }); -- 2.51.2 From df5431fab874312bdffd11ca6a64f531b6519de8 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sat, 9 May 2026 16:29:21 -0600 Subject: [PATCH 025/359] Release 0.1.5 --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fbb311..0a02dca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.1.5 - 2026-05-09 + ### Changed - Added pi-native `/login` API-key integration for the Cursor provider. Startup discovery now checks pi `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. @@ -9,6 +11,10 @@ - Improved Cursor activity display by preserving Cursor thinking, streaming Cursor text deltas live when native replay is not active, and replaying completed Cursor internal `read`, `bash`, and `ls` activity through pi's native tool rendering path in interactive TTY sessions where possible. Native Cursor tool replay now follows Codex-style ordering as Cursor SDK tool completions arrive: assistant tool-use turn, recorded pi tool results, live post-tool Cursor thinking/text, any later Cursor tool batches, then final assistant answer. Non-interactive runs keep bounded scrubbed transcript output, and raw Cursor call IDs remain omitted. - Stopped copying Cursor SDK cumulative internal agent/tool/cache token usage into pi usage, preventing false context-overflow and compaction triggers after long Cursor runs. +### Fixed + +- Avoid duplicate final answer text after Cursor streams post-tool text before a later native replayed tool batch. + ## 0.1.4 - 2026-05-07 ### Fixed diff --git a/package-lock.json b/package-lock.json index cdeac7a..1c395ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.4", + "version": "0.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.4", + "version": "0.1.5", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.12" diff --git a/package.json b/package.json index 81881cd..e523481 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.4", + "version": "0.1.5", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", -- 2.51.2 From d672ff2cd4a0bf77b5d45451bae3d45c19eab761 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sun, 10 May 2026 09:39:27 -0600 Subject: [PATCH 026/359] Avoid native tool replay conflicts --- CHANGELOG.md | 5 ++ README.md | 12 +++- docs/cursor-model-ux-spec.md | 2 +- src/cursor-native-tool-display.ts | 91 ++++++++++++++++++++++++++----- test/cursor-provider.test.ts | 48 +++++++++++----- test/index.test.ts | 83 +++++++++++++++++++++++++++- 6 files changed, 209 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a02dca..89b6a0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Fixed + +- Avoid loading failures when another extension already owns `read`, `bash`, or `ls`; Cursor native replay now registers only non-conflicting wrappers and falls back to scrubbed activity transcripts for skipped tools. +- `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` now skips Cursor native replay tool registration instead of only disabling replay at runtime. + ## 0.1.5 - 2026-05-09 ### Changed diff --git a/README.md b/README.md index f7eb191..58bf530 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. @@ -263,6 +263,16 @@ Fast mode is currently off. The footer only shows `cursor fast` when fast mode i They are not loaded by default in this extension. See [Limits](#limits). +### Cursor native tool cards conflict with another extension + +Cursor native replay is a UI enhancement for interactive TTY sessions. If another extension already owns `read`, `bash`, or `ls`, this extension skips only the conflicting native replay wrapper and uses the scrubbed Cursor activity transcript for that tool instead. To disable Cursor native replay registration entirely, start pi with: + +```bash +PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2 +``` + +`PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is also accepted as a registration-only opt-out. + ## Development Run checks: diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index eacfbcd..8177ba4 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -17,7 +17,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index eb9bf3a..f5a3efe 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -3,12 +3,16 @@ import { createLsToolDefinition, createReadToolDefinition, type ExtensionAPI, + type ExtensionContext, type ToolDefinition, } from "@earendil-works/pi-coding-agent"; import type { TSchema } from "typebox"; import type { CursorPiToolDisplay } from "./cursor-tool-transcript.js"; -const NATIVE_CURSOR_TOOL_NAMES = new Set(["read", "bash", "ls"]); +const NATIVE_CURSOR_TOOL_NAMES = ["read", "bash", "ls"] as const; +type NativeCursorToolName = (typeof NATIVE_CURSOR_TOOL_NAMES)[number]; +const NATIVE_CURSOR_TOOL_DISPLAY_ENV = "PI_CURSOR_NATIVE_TOOL_DISPLAY"; +const NATIVE_CURSOR_TOOL_REGISTRATION_ENV = "PI_CURSOR_REGISTER_NATIVE_TOOLS"; export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { id: string; @@ -16,26 +20,41 @@ export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { } let nativeToolDisplayEnabled = false; +const registeredNativeToolNames = new Set(); const nativeToolResults = new Map(); +function readBooleanEnv(name: string): boolean | undefined { + const value = process.env[name]?.trim().toLowerCase(); + if (value === "1" || value === "true" || value === "yes" || value === "on") return true; + if (value === "0" || value === "false" || value === "no" || value === "off") return false; + return undefined; +} + +function isCursorNativeToolDisplayRequested(): boolean { + const override = readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV); + if (override !== undefined) return override; + return process.stdout.isTTY === true; +} + +function isCursorNativeToolRegistrationRequested(): boolean { + if (readBooleanEnv(NATIVE_CURSOR_TOOL_REGISTRATION_ENV) === false) return false; + return isCursorNativeToolDisplayRequested(); +} + export function isCursorNativeToolDisplayEnabled(): boolean { return nativeToolDisplayEnabled; } export function isCursorNativeToolDisplayRuntimeEnabled(): boolean { - if (!nativeToolDisplayEnabled) return false; - const override = process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY; - if (override === "1" || override === "true") return true; - if (override === "0" || override === "false") return false; - return process.stdout.isTTY === true; + return isCursorNativeToolDisplayRequested() && registeredNativeToolNames.size > 0; } export function canRenderCursorToolNatively(toolName: string): boolean { - return NATIVE_CURSOR_TOOL_NAMES.has(toolName); + return registeredNativeToolNames.has(toolName); } export function recordCursorNativeToolDisplay(item: CursorNativeToolDisplayItem): void { - if (!nativeToolDisplayEnabled || !canRenderCursorToolNatively(item.toolName)) return; + if (!canRenderCursorToolNatively(item.toolName)) return; nativeToolResults.set(item.id, item); } @@ -48,6 +67,7 @@ function consumeCursorNativeToolDisplay(id: string): CursorNativeToolDisplayItem export const __testUtils = { reset(): void { nativeToolDisplayEnabled = false; + registeredNativeToolNames.clear(); nativeToolResults.clear(); }, }; @@ -71,10 +91,55 @@ function wrapNativeCursorTool( }; } -export function registerCursorNativeToolDisplay(pi: ExtensionAPI): void { - nativeToolDisplayEnabled = true; - const cwd = process.cwd(); - pi.registerTool(wrapNativeCursorTool(createReadToolDefinition(cwd))); - pi.registerTool(wrapNativeCursorTool(createBashToolDefinition(cwd))); +function registerNativeCursorTool(pi: ExtensionAPI, toolName: NativeCursorToolName, cwd: string): void { + if (toolName === "read") { + pi.registerTool(wrapNativeCursorTool(createReadToolDefinition(cwd))); + return; + } + if (toolName === "bash") { + pi.registerTool(wrapNativeCursorTool(createBashToolDefinition(cwd))); + return; + } pi.registerTool(wrapNativeCursorTool(createLsToolDefinition(cwd))); } + +function getExistingToolOwner(pi: ExtensionAPI, toolName: NativeCursorToolName): string | undefined { + const existingTool = pi.getAllTools().find((tool) => tool.name === toolName); + if (!existingTool || existingTool.sourceInfo.source === "builtin") return undefined; + return existingTool.sourceInfo.path ?? existingTool.sourceInfo.source; +} + +function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionContext): void { + if (!isCursorNativeToolRegistrationRequested()) { + nativeToolDisplayEnabled = false; + registeredNativeToolNames.clear(); + return; + } + + const cwd = process.cwd(); + const skippedToolNames: string[] = []; + for (const toolName of NATIVE_CURSOR_TOOL_NAMES) { + if (registeredNativeToolNames.has(toolName)) continue; + const existingOwner = getExistingToolOwner(pi, toolName); + if (existingOwner) { + skippedToolNames.push(toolName); + continue; + } + registerNativeCursorTool(pi, toolName, cwd); + registeredNativeToolNames.add(toolName); + } + + nativeToolDisplayEnabled = registeredNativeToolNames.size > 0; + if (skippedToolNames.length > 0 && readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV) === true && ctx.hasUI) { + ctx.ui.notify( + `Cursor native tool replay skipped for ${skippedToolNames.join(", ")} because another extension already provides ${skippedToolNames.length === 1 ? "that tool" : "those tools"}. Cursor will use scrubbed activity transcripts for skipped tools.`, + "warning", + ); + } +} + +export function registerCursorNativeToolDisplay(pi: ExtensionAPI): void { + pi.on("session_start", async (_event, ctx) => { + registerAvailableNativeCursorTools(pi, ctx); + }); +} diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 9f55583..8a1a889 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -73,6 +73,36 @@ async function collectEvents(stream: ReturnType) { return events; } +async function registerNativeToolDisplayForTest(registeredTools: any[]): Promise { + const handlers: Array<(event: unknown, ctx: any) => Promise | void> = []; + registerCursorNativeToolDisplay({ + on: vi.fn((event: string, handler: (event: unknown, ctx: any) => Promise | void) => { + if (event === "session_start") handlers.push(handler); + }), + registerTool: vi.fn((tool: any) => { + registeredTools.push(tool); + }), + getAllTools: vi.fn(() => { + const toolsByName = new Map(); + for (const name of ["read", "bash", "ls"]) { + toolsByName.set(name, { name, description: "", parameters: {}, sourceInfo: { source: "builtin", path: `` } }); + } + for (const tool of registeredTools) { + toolsByName.set(tool.name, { + name: tool.name, + description: tool.description, + parameters: tool.parameters, + sourceInfo: { source: "test", path: "cursor-native-tool-display-test" }, + }); + } + return [...toolsByName.values()]; + }), + } as any); + for (const handler of handlers) { + await handler({ reason: "startup" }, { hasUI: false, ui: { notify: vi.fn() } }); + } +} + const cursorModelItems: ModelListItem[] = [ { id: "gpt-5.5", @@ -339,11 +369,7 @@ describe("streamCursor", () => { it("replays native Cursor tools as a toolUse turn before final text", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; const registeredTools: any[] = []; - registerCursorNativeToolDisplay({ - registerTool: vi.fn((tool: any) => { - registeredTools.push(tool); - }), - } as any); + await registerNativeToolDisplayForTest(registeredTools); let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; const runWait = vi.fn( @@ -433,11 +459,7 @@ describe("streamCursor", () => { it("streams post-tool Cursor thinking and text while a native replay run is still active", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; const registeredTools: any[] = []; - registerCursorNativeToolDisplay({ - registerTool: vi.fn((tool: any) => { - registeredTools.push(tool); - }), - } as any); + await registerNativeToolDisplayForTest(registeredTools); let onDelta: ((args: { update: any }) => void) | undefined; let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; @@ -536,11 +558,7 @@ describe("streamCursor", () => { it("does not duplicate final result after an earlier post-tool text turn", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; const registeredTools: any[] = []; - registerCursorNativeToolDisplay({ - registerTool: vi.fn((tool: any) => { - registeredTools.push(tool); - }), - } as any); + await registerNativeToolDisplayForTest(registeredTools); let onDelta: ((args: { update: any }) => void) | undefined; let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; diff --git a/test/index.test.ts b/test/index.test.ts index 399f77c..20ffee7 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -11,15 +11,42 @@ vi.mock("../src/cursor-provider.js", () => ({ import extensionFactory from "../src/index.js"; import { discoverModels } from "../src/model-discovery.js"; import { streamCursor } from "../src/cursor-provider.js"; -import { __testUtils as nativeToolDisplayTestUtils, recordCursorNativeToolDisplay } from "../src/cursor-native-tool-display.js"; +import { + __testUtils as nativeToolDisplayTestUtils, + canRenderCursorToolNatively, + recordCursorNativeToolDisplay, +} from "../src/cursor-native-tool-display.js"; const mockedDiscover = vi.mocked(discoverModels); const mockedStreamCursor = vi.mocked(streamCursor); -function createMockPi() { +function createBuiltinToolInfo(name: string) { + return { + name, + description: "", + parameters: {}, + sourceInfo: { source: "builtin", path: `` }, + }; +} + +async function runSessionStartHandlers(pi: ReturnType, ctxOverrides: Record = {}): Promise { + const notify = vi.fn(); + const ctx = { + hasUI: true, + ui: { notify, setStatus: vi.fn() }, + sessionManager: { getBranch: vi.fn(() => []) }, + ...ctxOverrides, + }; + for (const handler of pi._handlers.get("session_start") ?? []) { + await handler({ reason: "startup" }, ctx); + } +} + +function createMockPi(existingTools?: any[]) { const registered: Array<{ name: string; config: Record }> = []; const tools: any[] = []; const handlers = new Map Promise | void>>(); + const initialTools = existingTools ?? ["read", "bash", "ls"].map(createBuiltinToolInfo); return { registerProvider: vi.fn((name: string, config: Record) => { registered.push({ name, config }); @@ -29,6 +56,19 @@ function createMockPi() { registerTool: vi.fn((tool: any) => { tools.push(tool); }), + getAllTools: vi.fn(() => { + const toolsByName = new Map(); + for (const tool of initialTools) toolsByName.set(tool.name, tool); + for (const tool of tools) { + toolsByName.set(tool.name, { + name: tool.name, + description: tool.description, + parameters: tool.parameters, + sourceInfo: { source: "test", path: "pi-cursor-sdk-test" }, + }); + } + return [...toolsByName.values()]; + }), sendMessage: vi.fn(), on: vi.fn((event: string, handler: (event: unknown, ctx: any) => Promise | void) => { handlers.set(event, [...(handlers.get(event) ?? []), handler]); @@ -44,6 +84,8 @@ function createMockPi() { describe("extension factory", () => { beforeEach(() => { vi.clearAllMocks(); + delete process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY; + delete process.env.PI_CURSOR_REGISTER_NATIVE_TOOLS; nativeToolDisplayTestUtils.reset(); }); @@ -61,8 +103,10 @@ describe("extension factory", () => { ]; mockedDiscover.mockResolvedValueOnce(mockModels); + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; const pi = createMockPi(); await extensionFactory(pi as any); + await runSessionStartHandlers(pi); expect(pi.registerFlag).toHaveBeenCalledWith( "cursor-fast", @@ -178,9 +222,11 @@ describe("extension factory", () => { }); it("registered native Cursor tool wrappers return recorded Cursor results without executing built-ins", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; mockedDiscover.mockResolvedValueOnce([]); const pi = createMockPi(); await extensionFactory(pi as any); + await runSessionStartHandlers(pi); recordCursorNativeToolDisplay({ id: "cursor-tool-1", @@ -199,4 +245,37 @@ describe("extension factory", () => { terminate: true, }); }); + + it("does not register native Cursor tool wrappers when native display is disabled", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "0"; + mockedDiscover.mockResolvedValueOnce([]); + const pi = createMockPi(); + await extensionFactory(pi as any); + await runSessionStartHandlers(pi); + + expect(pi.registerTool).not.toHaveBeenCalled(); + expect(canRenderCursorToolNatively("read")).toBe(false); + }); + + it("skips only native Cursor tool wrappers owned by another extension", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + mockedDiscover.mockResolvedValueOnce([]); + const pi = createMockPi([ + { + name: "read", + description: "hashline read", + parameters: {}, + sourceInfo: { source: "package", path: "/opt/homebrew/lib/node_modules/pi-hashline-edit/index.ts" }, + }, + createBuiltinToolInfo("bash"), + createBuiltinToolInfo("ls"), + ]); + await extensionFactory(pi as any); + await runSessionStartHandlers(pi); + + expect(pi._tools.map((tool) => tool.name)).toEqual(["bash", "ls"]); + expect(canRenderCursorToolNatively("read")).toBe(false); + expect(canRenderCursorToolNatively("bash")).toBe(true); + expect(canRenderCursorToolNatively("ls")).toBe(true); + }); }); -- 2.51.2 From ff6485426204b857064fb3b66d829dad12c44fd2 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sun, 10 May 2026 09:41:28 -0600 Subject: [PATCH 027/359] Release 0.1.6 --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89b6a0f..a4c943f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.1.6 - 2026-05-10 + ### Fixed - Avoid loading failures when another extension already owns `read`, `bash`, or `ls`; Cursor native replay now registers only non-conflicting wrappers and falls back to scrubbed activity transcripts for skipped tools. diff --git a/package-lock.json b/package-lock.json index 1c395ca..b34c47a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.5", + "version": "0.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.5", + "version": "0.1.6", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.12" diff --git a/package.json b/package.json index e523481..0af0f30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.5", + "version": "0.1.6", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", -- 2.51.2 From 026438864229d4d8aeadff8c3919fea158702d3f Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sun, 10 May 2026 10:26:19 -0600 Subject: [PATCH 028/359] Fix Cursor native replay continuation --- docs/cursor-model-ux-spec.md | 4 +- src/cursor-native-tool-display.ts | 28 +++--- src/cursor-provider.ts | 25 ++++-- test/cursor-provider.test.ts | 139 +++++++++++++++++++++++++++--- test/index.test.ts | 77 +++++++++++------ 5 files changed, 211 insertions(+), 62 deletions(-) diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 8177ba4..6382351 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -17,8 +17,8 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. -- Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. +- Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. When native replay splits one Cursor SDK run into multiple pi turns, prompt input is counted once for the run; later synthetic replay turns report `input: 0` and only their own output estimate. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index f5a3efe..d9a1df3 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -12,6 +12,7 @@ import type { CursorPiToolDisplay } from "./cursor-tool-transcript.js"; const NATIVE_CURSOR_TOOL_NAMES = ["read", "bash", "ls"] as const; type NativeCursorToolName = (typeof NATIVE_CURSOR_TOOL_NAMES)[number]; const NATIVE_CURSOR_TOOL_DISPLAY_ENV = "PI_CURSOR_NATIVE_TOOL_DISPLAY"; +// Registration-only kill switch for users who want transcript fallback without shadowing read/bash/ls. const NATIVE_CURSOR_TOOL_REGISTRATION_ENV = "PI_CURSOR_REGISTER_NATIVE_TOOLS"; export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { @@ -19,8 +20,7 @@ export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { terminate?: boolean; } -let nativeToolDisplayEnabled = false; -const registeredNativeToolNames = new Set(); +const registeredNativeToolNames = new Set(); const nativeToolResults = new Map(); function readBooleanEnv(name: string): boolean | undefined { @@ -36,13 +36,16 @@ function isCursorNativeToolDisplayRequested(): boolean { return process.stdout.isTTY === true; } +function isNativeCursorToolName(toolName: string): toolName is NativeCursorToolName { + return NATIVE_CURSOR_TOOL_NAMES.some((nativeToolName) => nativeToolName === toolName); +} + function isCursorNativeToolRegistrationRequested(): boolean { - if (readBooleanEnv(NATIVE_CURSOR_TOOL_REGISTRATION_ENV) === false) return false; - return isCursorNativeToolDisplayRequested(); + return readBooleanEnv(NATIVE_CURSOR_TOOL_REGISTRATION_ENV) !== false && isCursorNativeToolDisplayRequested(); } export function isCursorNativeToolDisplayEnabled(): boolean { - return nativeToolDisplayEnabled; + return registeredNativeToolNames.size > 0; } export function isCursorNativeToolDisplayRuntimeEnabled(): boolean { @@ -50,7 +53,7 @@ export function isCursorNativeToolDisplayRuntimeEnabled(): boolean { } export function canRenderCursorToolNatively(toolName: string): boolean { - return registeredNativeToolNames.has(toolName); + return isNativeCursorToolName(toolName) && registeredNativeToolNames.has(toolName); } export function recordCursorNativeToolDisplay(item: CursorNativeToolDisplayItem): void { @@ -66,7 +69,6 @@ function consumeCursorNativeToolDisplay(id: string): CursorNativeToolDisplayItem export const __testUtils = { reset(): void { - nativeToolDisplayEnabled = false; registeredNativeToolNames.clear(); nativeToolResults.clear(); }, @@ -103,15 +105,13 @@ function registerNativeCursorTool(pi: ExtensionAPI, toolName: NativeCursorToolNa pi.registerTool(wrapNativeCursorTool(createLsToolDefinition(cwd))); } -function getExistingToolOwner(pi: ExtensionAPI, toolName: NativeCursorToolName): string | undefined { +function hasNonBuiltinTool(pi: ExtensionAPI, toolName: NativeCursorToolName): boolean { const existingTool = pi.getAllTools().find((tool) => tool.name === toolName); - if (!existingTool || existingTool.sourceInfo.source === "builtin") return undefined; - return existingTool.sourceInfo.path ?? existingTool.sourceInfo.source; + return existingTool !== undefined && existingTool.sourceInfo.source !== "builtin"; } function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionContext): void { if (!isCursorNativeToolRegistrationRequested()) { - nativeToolDisplayEnabled = false; registeredNativeToolNames.clear(); return; } @@ -120,8 +120,7 @@ function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionCont const skippedToolNames: string[] = []; for (const toolName of NATIVE_CURSOR_TOOL_NAMES) { if (registeredNativeToolNames.has(toolName)) continue; - const existingOwner = getExistingToolOwner(pi, toolName); - if (existingOwner) { + if (hasNonBuiltinTool(pi, toolName)) { skippedToolNames.push(toolName); continue; } @@ -129,7 +128,6 @@ function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionCont registeredNativeToolNames.add(toolName); } - nativeToolDisplayEnabled = registeredNativeToolNames.size > 0; if (skippedToolNames.length > 0 && readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV) === true && ctx.hasUI) { ctx.ui.notify( `Cursor native tool replay skipped for ${skippedToolNames.join(", ")} because another extension already provides ${skippedToolNames.length === 1 ? "that tool" : "those tools"}. Cursor will use scrubbed activity transcripts for skipped tools.`, @@ -139,7 +137,7 @@ function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionCont } export function registerCursorNativeToolDisplay(pi: ExtensionAPI): void { - pi.on("session_start", async (_event, ctx) => { + pi.on("session_start", (_event, ctx) => { registerAvailableNativeCursorTools(pi, ctx); }); } diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index b8e4466..b9fb9c6 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -70,6 +70,7 @@ interface CursorNativeLiveRun { id: string; agent: SDKAgent; promptInputTokens: number; + promptInputTokensReported: boolean; pendingEvents: CursorNativeQueuedEvent[]; textDeltas: string[]; finalText?: string; @@ -374,6 +375,13 @@ function collectCursorNativeToolBatch(run: CursorNativeLiveRun): CursorNativeToo return tools; } +function takeCursorNativePromptInputTokens(run: CursorNativeLiveRun): number { + // Native replay can split one Cursor run into multiple pi turns; count prompt input once. + if (run.promptInputTokensReported) return 0; + run.promptInputTokensReported = true; + return run.promptInputTokens; +} + function emitCursorNativeToolUseTurn( stream: AssistantMessageEventStream, partial: AssistantMessage, @@ -396,7 +404,7 @@ function emitCursorNativeToolUseTurn( if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial }); recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate }); } - setApproximateUsage(partial, run.promptInputTokens, outputText); + setApproximateUsage(partial, takeCursorNativePromptInputTokens(run), outputText); partial.stopReason = "toolUse"; stream.push({ type: "done", reason: "toolUse", message: partial }); } @@ -455,7 +463,7 @@ async function emitCursorNativeRunNextTurn( if (!outputText) { outputText += await emitTextDeltas(stream, partial, splitTextIntoReplayDeltas(run.finalText ?? run.textDeltas.join(""))); } - setApproximateUsage(partial, run.promptInputTokens, outputText); + setApproximateUsage(partial, takeCursorNativePromptInputTokens(run), outputText); partial.stopReason = "stop"; stream.push({ type: "done", reason: "stop", message: partial }); await disposeCursorNativeRun(run); @@ -534,12 +542,13 @@ export function streamCursor( const useNativeToolReplay = isCursorNativeToolDisplayRuntimeEnabled(); const nativeReplayId = createCursorNativeReplayId(); const textDeltas: string[] = []; - let liveStreamClosed = false; + let nativeToolReplayStarted = false; const liveRun: CursorNativeLiveRun | undefined = useNativeToolReplay ? { id: nativeReplayId, agent, promptInputTokens, + promptInputTokensReported: false, pendingEvents: [], textDeltas, done: false, @@ -651,6 +660,7 @@ export function streamCursor( completedToolFingerprints.add(fingerprint); if (useNativeToolReplay && canRenderCursorToolNatively(display.toolName) && liveRun) { + nativeToolReplayStarted = true; const id = `${nativeReplayId}-tool-${++nativeToolDisplayCounter}`; queueCursorNativeEvent(liveRun, { type: "tool", @@ -672,19 +682,19 @@ export function streamCursor( if (update.type === "text-delta") { textDeltas.push(update.text); - if (liveRun && liveStreamClosed) { + if (liveRun && nativeToolReplayStarted) { queueCursorNativeEvent(liveRun, { type: "text-delta", text: update.text }); } else if (!useNativeToolReplay) { appendLiveTextDelta(update.text); } } else if (update.type === "thinking-delta") { - if (liveRun && liveStreamClosed) { + if (liveRun && nativeToolReplayStarted) { queueCursorNativeEvent(liveRun, { type: "thinking-delta", text: update.text }); } else { appendTraceDelta(update.text); } } else if (update.type === "thinking-completed") { - if (liveRun && liveStreamClosed) { + if (liveRun && nativeToolReplayStarted) { queueCursorNativeEvent(liveRun, { type: "thinking-completed" }); } else { closeTraceBlock(); @@ -697,7 +707,7 @@ export function streamCursor( handleCompletedToolCall(mergedToolCall); } else if (update.type === "summary") { const summary = `Cursor summary: ${truncateSingleLine(update.summary)}\n`; - if (liveRun && liveStreamClosed) { + if (liveRun && nativeToolReplayStarted) { queueCursorNativeEvent(liveRun, { type: "thinking-delta", text: summary }); } else { appendTraceDelta(summary); @@ -754,7 +764,6 @@ export function streamCursor( await waitForCursorNativeRunProgress(liveRun, options?.signal); await settleCursorNativeToolBatch(liveRun); closeTraceBlock(); - liveStreamClosed = true; await emitCursorNativeRunNextTurn(stream, partial, liveRun, options?.signal); agent = null; return; diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 8a1a889..bce91d8 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -38,11 +38,26 @@ import { __testUtils as contextWindowCacheTestUtils } from "../src/context-windo import { __testUtils as nativeToolDisplayTestUtils, registerCursorNativeToolDisplay } from "../src/cursor-native-tool-display.js"; import type { ModelListItem } from "@cursor/sdk"; import type { Context, Model } from "@earendil-works/pi-ai"; +import type { ExtensionAPI, ExtensionContext, ToolDefinition, ToolInfo } from "@earendil-works/pi-coding-agent"; +import { Type, type TSchema } from "typebox"; // Access the mocks via the module const mockedCreate = vi.mocked(Agent.create); const mockedCreateAgentPlatform = vi.mocked(createAgentPlatform); +type RegisteredTool = ToolDefinition; +type TestExtensionContext = Pick & { ui: Pick }; +type TestEventHandler = (event: unknown, ctx: TestExtensionContext) => Promise | void; + +function createBuiltinToolInfo(name: string): ToolInfo { + return { + name, + description: "", + parameters: Type.Object({}), + sourceInfo: { source: "builtin", path: ``, scope: "temporary", origin: "top-level" }, + }; +} + function makeModel(id = "test-model"): Model<"cursor-sdk"> { return { id, @@ -73,31 +88,32 @@ async function collectEvents(stream: ReturnType) { return events; } -async function registerNativeToolDisplayForTest(registeredTools: any[]): Promise { - const handlers: Array<(event: unknown, ctx: any) => Promise | void> = []; +async function registerNativeToolDisplayForTest(registeredTools: RegisteredTool[]): Promise { + const handlers: TestEventHandler[] = []; registerCursorNativeToolDisplay({ - on: vi.fn((event: string, handler: (event: unknown, ctx: any) => Promise | void) => { + on: vi.fn((event: string, handler: TestEventHandler) => { if (event === "session_start") handlers.push(handler); }), - registerTool: vi.fn((tool: any) => { + registerTool: vi.fn((tool: RegisteredTool) => { registeredTools.push(tool); }), getAllTools: vi.fn(() => { - const toolsByName = new Map(); + const toolsByName = new Map(); for (const name of ["read", "bash", "ls"]) { - toolsByName.set(name, { name, description: "", parameters: {}, sourceInfo: { source: "builtin", path: `` } }); + const tool = createBuiltinToolInfo(name); + toolsByName.set(tool.name, tool); } for (const tool of registeredTools) { toolsByName.set(tool.name, { name: tool.name, description: tool.description, parameters: tool.parameters, - sourceInfo: { source: "test", path: "cursor-native-tool-display-test" }, + sourceInfo: { source: "test", path: "cursor-native-tool-display-test", scope: "temporary", origin: "top-level" }, }); } return [...toolsByName.values()]; }), - } as any); + } as unknown as ExtensionAPI); for (const handler of handlers) { await handler({ reason: "startup" }, { hasUI: false, ui: { notify: vi.fn() } }); } @@ -180,6 +196,7 @@ describe("streamCursor", () => { beforeEach(() => { vi.clearAllMocks(); delete process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY; + delete process.env.PI_CURSOR_REGISTER_NATIVE_TOOLS; nativeToolDisplayTestUtils.reset(); modelDiscoveryTestUtils.registerModelItems(cursorModelItems); // Re-setup default mock return after clearing @@ -368,7 +385,7 @@ describe("streamCursor", () => { it("replays native Cursor tools as a toolUse turn before final text", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; - const registeredTools: any[] = []; + const registeredTools: RegisteredTool[] = []; await registerNativeToolDisplayForTest(registeredTools); let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; @@ -391,7 +408,6 @@ describe("streamCursor", () => { callId: "c1", }, }); - opts.onDelta({ update: { type: "text-delta", text: "Final-ish streamed text." } }); return { id: "run-1", agentId: "agent-1", @@ -458,7 +474,7 @@ describe("streamCursor", () => { it("streams post-tool Cursor thinking and text while a native replay run is still active", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; - const registeredTools: any[] = []; + const registeredTools: RegisteredTool[] = []; await registerNativeToolDisplayForTest(registeredTools); let onDelta: ((args: { update: any }) => void) | undefined; @@ -555,9 +571,105 @@ describe("streamCursor", () => { expect(finalDone.message.content.map((block: any) => block.type)).toEqual(["thinking", "text"]); }); + it("queues post-tool thinking and text that arrive before the native tool-use turn closes", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + const registeredTools: RegisteredTool[] = []; + await registerNativeToolDisplayForTest(registeredTools); + + let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; + const runWait = vi.fn( + () => + new Promise<{ id: string; status: "finished"; result: string }>((resolve) => { + resolveRun = resolve; + }), + ); + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "README.md" } }, callId: "c1" } }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { status: "success", value: { content: "# pi-cursor-sdk" } }, + }, + callId: "c1", + }, + }); + opts.onDelta({ update: { type: "thinking-delta", text: "Post-tool thought." } }); + opts.onDelta({ update: { type: "thinking-completed" } }); + opts.onDelta({ update: { type: "text-delta", text: "Final " } }); + opts.onDelta({ update: { type: "text-delta", text: "answer." } }); + return { + id: "run-1", + agentId: "agent-1", + status: "running", + wait: runWait, + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + agentId: "agent-1", + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const firstEvents = await collectEvents(streamCursor(makeModel(), makeContext(), { apiKey: "test-key" })); + const firstDone = firstEvents.find((e: any) => e.type === "done") as any; + const toolCall = firstDone.message.content.find((block: any) => block.type === "toolCall"); + const readTool = registeredTools.find((tool) => tool.name === "read"); + const toolResult = await readTool.execute(toolCall.id, toolCall.arguments, undefined, undefined, {}); + + expect(firstDone.message.content.map((block: any) => block.type)).toEqual(["toolCall"]); + + const replayContext = makeContext(); + replayContext.messages = [ + ...replayContext.messages, + firstDone.message, + { + role: "toolResult", + toolCallId: toolCall.id, + toolName: "read", + content: toolResult.content, + details: toolResult.details, + isError: false, + timestamp: 2, + }, + ]; + + const replayStream = streamCursor(makeModel(), replayContext, { apiKey: "test-key" }); + const replayEvents: any[] = []; + let sawLiveText: () => void = () => {}; + const liveTextSeen = new Promise((resolve) => { + sawLiveText = resolve; + }); + const replayDone = (async () => { + for await (const event of replayStream) { + replayEvents.push(event); + if (event.type === "text_delta" && event.delta === "Final ") sawLiveText(); + } + })(); + + await Promise.race([ + liveTextSeen, + new Promise((_, reject) => setTimeout(() => reject(new Error("timed out waiting for queued Cursor text")), 500)), + ]); + resolveRun({ id: "run-1", status: "finished", result: "Final answer." }); + await replayDone; + + const replayText = replayEvents.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); + const replayThinking = replayEvents.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + const finalDone = replayEvents.find((e: any) => e.type === "done") as any; + + expect(replayThinking).toBe("Post-tool thought."); + expect(replayText).toBe("Final answer."); + expect(finalDone.message.content.map((block: any) => block.type)).toEqual(["thinking", "text"]); + }); + it("does not duplicate final result after an earlier post-tool text turn", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; - const registeredTools: any[] = []; + const registeredTools: RegisteredTool[] = []; await registerNativeToolDisplayForTest(registeredTools); let onDelta: ((args: { update: any }) => void) | undefined; @@ -652,6 +764,9 @@ describe("streamCursor", () => { const finalText = finalEvents.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); expect(runWait).toHaveBeenCalledTimes(1); + expect(firstDone.message.usage.input).toBeGreaterThan(0); + expect(secondDone.message.usage.input).toBe(0); + expect(finalDone.message.usage.input).toBe(0); expect(secondDone.message.content.map((block: any) => block.type)).toEqual(["text", "toolCall"]); expect(finalText).toBe("Final answer."); expect(finalDone.message.content).toEqual([{ type: "text", text: "Final answer." }]); diff --git a/test/index.test.ts b/test/index.test.ts index 20ffee7..29a23b1 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,4 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; +import type { ExtensionAPI, ExtensionContext, ProviderConfig, ToolDefinition, ToolInfo } from "@earendil-works/pi-coding-agent"; +import { Type, type TSchema } from "typebox"; vi.mock("../src/model-discovery.js", () => ({ discoverModels: vi.fn(), @@ -20,18 +22,26 @@ import { const mockedDiscover = vi.mocked(discoverModels); const mockedStreamCursor = vi.mocked(streamCursor); -function createBuiltinToolInfo(name: string) { +type DiscoverOptions = Parameters[0]; +type RegisteredTool = ToolDefinition; +type TestExtensionContext = Pick & { + ui: Pick; + sessionManager: Pick; +}; +type TestEventHandler = (event: unknown, ctx: TestExtensionContext) => Promise | void; + +function createBuiltinToolInfo(name: string): ToolInfo { return { name, description: "", - parameters: {}, - sourceInfo: { source: "builtin", path: `` }, + parameters: Type.Object({}), + sourceInfo: { source: "builtin", path: ``, scope: "temporary", origin: "top-level" }, }; } -async function runSessionStartHandlers(pi: ReturnType, ctxOverrides: Record = {}): Promise { +async function runSessionStartHandlers(pi: ReturnType, ctxOverrides: Partial = {}): Promise { const notify = vi.fn(); - const ctx = { + const ctx: TestExtensionContext = { hasUI: true, ui: { notify, setStatus: vi.fn() }, sessionManager: { getBranch: vi.fn(() => []) }, @@ -42,35 +52,35 @@ async function runSessionStartHandlers(pi: ReturnType, ctxO } } -function createMockPi(existingTools?: any[]) { - const registered: Array<{ name: string; config: Record }> = []; - const tools: any[] = []; - const handlers = new Map Promise | void>>(); +function createMockPi(existingTools?: ToolInfo[]) { + const registered: Array<{ name: string; config: ProviderConfig }> = []; + const tools: RegisteredTool[] = []; + const handlers = new Map(); const initialTools = existingTools ?? ["read", "bash", "ls"].map(createBuiltinToolInfo); return { - registerProvider: vi.fn((name: string, config: Record) => { + registerProvider: vi.fn((name: string, config: ProviderConfig) => { registered.push({ name, config }); }), registerFlag: vi.fn(), registerCommand: vi.fn(), - registerTool: vi.fn((tool: any) => { + registerTool: vi.fn((tool: RegisteredTool) => { tools.push(tool); }), getAllTools: vi.fn(() => { - const toolsByName = new Map(); + const toolsByName = new Map(); for (const tool of initialTools) toolsByName.set(tool.name, tool); for (const tool of tools) { toolsByName.set(tool.name, { name: tool.name, description: tool.description, parameters: tool.parameters, - sourceInfo: { source: "test", path: "pi-cursor-sdk-test" }, + sourceInfo: { source: "test", path: "pi-cursor-sdk-test", scope: "temporary", origin: "top-level" }, }); } return [...toolsByName.values()]; }), sendMessage: vi.fn(), - on: vi.fn((event: string, handler: (event: unknown, ctx: any) => Promise | void) => { + on: vi.fn((event: string, handler: TestEventHandler) => { handlers.set(event, [...(handlers.get(event) ?? []), handler]); }), getFlag: vi.fn().mockReturnValue(false), @@ -105,7 +115,7 @@ describe("extension factory", () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; const pi = createMockPi(); - await extensionFactory(pi as any); + await extensionFactory(pi as unknown as ExtensionAPI); await runSessionStartHandlers(pi); expect(pi.registerFlag).toHaveBeenCalledWith( @@ -159,7 +169,7 @@ describe("extension factory", () => { ]); const pi = createMockPi(); - await extensionFactory(pi as any); + await extensionFactory(pi as unknown as ExtensionAPI); expect(pi.registerProvider).toHaveBeenCalledOnce(); const [call] = pi._registered; @@ -167,7 +177,7 @@ describe("extension factory", () => { }); it("notifies interactive users when fallback models are registered", async () => { - mockedDiscover.mockImplementationOnce(async (options: any) => { + mockedDiscover.mockImplementationOnce(async (options: DiscoverOptions) => { options.onFallback({ reason: "missing-api-key", message: @@ -187,7 +197,7 @@ describe("extension factory", () => { }); const pi = createMockPi(); - await extensionFactory(pi as any); + await extensionFactory(pi as unknown as ExtensionAPI); const notify = vi.fn(); const ctx = { @@ -205,13 +215,13 @@ describe("extension factory", () => { }); it("does not notify fallback discovery issues without UI", async () => { - mockedDiscover.mockImplementationOnce(async (options: any) => { + mockedDiscover.mockImplementationOnce(async (options: DiscoverOptions) => { options.onFallback({ reason: "empty-model-list", message: "Cursor model discovery returned no models; using fallback Cursor model list." }); return []; }); const pi = createMockPi(); - await extensionFactory(pi as any); + await extensionFactory(pi as unknown as ExtensionAPI); const notify = vi.fn(); const ctx = { hasUI: false, ui: { notify, setStatus: vi.fn() }, sessionManager: { getBranch: vi.fn(() => []) } }; @@ -225,7 +235,7 @@ describe("extension factory", () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; mockedDiscover.mockResolvedValueOnce([]); const pi = createMockPi(); - await extensionFactory(pi as any); + await extensionFactory(pi as unknown as ExtensionAPI); await runSessionStartHandlers(pi); recordCursorNativeToolDisplay({ @@ -250,7 +260,19 @@ describe("extension factory", () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "0"; mockedDiscover.mockResolvedValueOnce([]); const pi = createMockPi(); - await extensionFactory(pi as any); + await extensionFactory(pi as unknown as ExtensionAPI); + await runSessionStartHandlers(pi); + + expect(pi.registerTool).not.toHaveBeenCalled(); + expect(canRenderCursorToolNatively("read")).toBe(false); + }); + + it("does not register native Cursor tool wrappers when native tool registration is disabled", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + process.env.PI_CURSOR_REGISTER_NATIVE_TOOLS = "0"; + mockedDiscover.mockResolvedValueOnce([]); + const pi = createMockPi(); + await extensionFactory(pi as unknown as ExtensionAPI); await runSessionStartHandlers(pi); expect(pi.registerTool).not.toHaveBeenCalled(); @@ -264,13 +286,18 @@ describe("extension factory", () => { { name: "read", description: "hashline read", - parameters: {}, - sourceInfo: { source: "package", path: "/opt/homebrew/lib/node_modules/pi-hashline-edit/index.ts" }, + parameters: Type.Object({}), + sourceInfo: { + source: "package", + path: "/opt/homebrew/lib/node_modules/pi-hashline-edit/index.ts", + scope: "user", + origin: "package", + }, }, createBuiltinToolInfo("bash"), createBuiltinToolInfo("ls"), ]); - await extensionFactory(pi as any); + await extensionFactory(pi as unknown as ExtensionAPI); await runSessionStartHandlers(pi); expect(pi._tools.map((tool) => tool.name)).toEqual(["bash", "ls"]); -- 2.51.2 From 4f98a8ef9e0c70fb67702804d88a9d2f50dc05fd Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Sun, 10 May 2026 10:26:19 -0600 Subject: [PATCH 029/359] Release 0.1.7 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4c943f..400ca21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 0.1.7 - 2026-05-10 + +### Fixed + +- Preserve Cursor post-tool thinking and text that arrive before a native replay tool-use turn closes. +- Count prompt input only once when one Cursor SDK run is split across multiple native replay turns. +- Tighten native replay registration tests and documentation around registration opt-out behavior. + ## 0.1.6 - 2026-05-10 ### Fixed diff --git a/package-lock.json b/package-lock.json index b34c47a..7e6a831 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.6", + "version": "0.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.6", + "version": "0.1.7", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.12" diff --git a/package.json b/package.json index 0af0f30..4777507 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.6", + "version": "0.1.7", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", -- 2.51.2 From fb0301e9ec546e915b2db817a9546b1bf652d776 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 14 May 2026 15:26:37 -0600 Subject: [PATCH 030/359] Update dependencies to `@cursor/sdk` 1.0.13 and `vitest` 4.1.6; add support for latest-style Cursor model aliases in pi model registration; enhance context window caching; improve documentation on Max Mode behavior and model ID registration. --- .gitignore | 1 + CHANGELOG.md | 6 + README.md | 5 +- docs/cursor-model-ux-spec.md | 12 +- package-lock.json | 298 +++++++++++++++++------------------ package.json | 4 +- src/context-window-cache.ts | 4 + src/model-discovery.ts | 64 +++++--- test/cursor-provider.test.ts | 52 ++++++ test/model-discovery.test.ts | 83 ++++++++++ 10 files changed, 353 insertions(+), 176 deletions(-) diff --git a/.gitignore b/.gitignore index d4053e7..0b2dbd0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ coverage/ .env.* *.tgz .pi/ +recording/ demo-script.md docs/public-demo-script.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 400ca21..72ca05f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +### Changed + +- Update the verified dependency baseline to `@cursor/sdk` 1.0.13 and Vitest 4.1.6. +- Register latest-style Cursor SDK model aliases returned by `Cursor.models.list()` as pi-selectable Cursor model IDs, including context-qualified alias variants where applicable. +- Clarify Max Mode behavior against current Cursor SDK docs: Cursor may enable required Max Mode automatically, but the extension still only advertises catalog-exposed context variants. + ## 0.1.7 - 2026-05-10 ### Fixed diff --git a/README.md b/README.md index 58bf530..2047268 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ How to read model IDs: - `cursor/...` is the Cursor provider registered by this extension - `@1m`, `@272k`, and `@300k` are context-window variants - `:medium`, `:high`, and `:xhigh` are pi thinking-level suffixes for models where the Cursor SDK exposes a pi-controllable thinking parameter +- latest-style Cursor aliases returned by `Cursor.models.list()` are registered too, using the same context suffixes when the target model has context variants Examples with pi thinking controls: @@ -146,7 +147,7 @@ pi --model cursor/gpt-5.5@272k:xhigh pi --model cursor/gpt-5.5@1m --thinking medium ``` -Cursor-only parameters are not encoded into pi model IDs. Cursor `context` becomes a pi-visible model variant because it changes pi's native `contextWindow`; Cursor `fast` is extension state, not model identity. +Cursor-only parameters are not encoded into pi model IDs. Cursor `context` becomes a pi-visible model variant because it changes pi's native `contextWindow`; Cursor `fast` is extension state, not model identity. Alias model IDs still share Cursor-only state, such as fast defaults, with their underlying Cursor base model. ## Thinking support @@ -214,7 +215,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. -- **Max Mode is not exposed for these local runs.** The extension only advertises exact context windows supported by the SDK path it uses. +- **Max Mode is not a manual pi variant.** Cursor's SDK may enable Max Mode automatically for models that require it. This extension only advertises exact context-window variants that the SDK catalog exposes and otherwise uses conservative SDK-derived default/non-Max context windows. - **Output token limits are conservative.** Cursor SDK model metadata does not currently expose output token limits directly. - **Token usage is approximate in pi.** Cursor SDK usage events include internal agent/tool/cache work, so the extension reports an approximate replayable pi prompt/output size for context display and compaction decisions. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 6382351..c01da04 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -20,7 +20,8 @@ Current implementation notes: - Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. When native replay splits one Cursor SDK run into multiple pi turns, prompt input is counted once for the run; later synthetic replay turns report `input: 0` and only their own output estimate. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. -- Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. +- Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.13 documentation says the SDK may enable Max Mode automatically when a selected model requires it, but the public local-agent `ModelSelection` path still does not expose a manual Max Mode selector. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. +- `@cursor/sdk` 1.0.13 adds latest-style `ModelListItem.aliases`. The extension registers those aliases as pi model IDs (with the same context suffixes when applicable) and sends the alias back in `ModelSelection.id`, while sharing Cursor-only state such as fast defaults with the underlying catalog `id`. ## Goal @@ -69,6 +70,7 @@ Users can persist the stored key through `/login` -> `Use an API key` -> `Cursor For each model, use: - `model.id` +- `model.aliases` - `model.displayName` - `model.parameters` - `model.variants` @@ -135,8 +137,8 @@ Register a `cursor` provider with `pi.registerProvider()`. Rules: -- Register one pi model for each Cursor base model when there is no Cursor `context` parameter. -- Register one pi model per Cursor `context` value when the model exposes a `context` parameter. +- Register one pi model for each Cursor base model and SDK alias when there is no Cursor `context` parameter. +- Register one pi model per Cursor `context` value for each Cursor base model and SDK alias when the model exposes a `context` parameter. - Do not encode `reasoning`, `effort`, `thinking`, or `fast` into pi model IDs. - Prefer stable, readable `@` suffixes that do not conflict with pi's final `:` suffix parser. - Sort Cursor models by base ID, then context value in Cursor SDK order before calling `pi.registerProvider()`. Registration order matters for `/model` display and model cycling; `--list-models` sorts output separately. @@ -177,7 +179,7 @@ Reason: Each registered model must set: -- `id`: context-qualified pi model ID when needed. +- `id`: context-qualified pi model ID when needed. For SDK aliases, this uses the alias as the pi-visible ID and the alias is sent back to Cursor as `ModelSelection.id`. - `name`: human-readable Cursor display name plus context when useful. - `reasoning`: `true` only if a Cursor `reasoning`, `effort`, or `thinking` parameter can map to pi thinking. This controls pi's thinking UI and `pi --list-models` `thinking` column; it must not be used to claim whether the Cursor model can think internally. Cursor SDK models are thinking-capable even when this is `false`. - `thinkingLevelMap`: model-specific pi-to-Cursor mapping for pi UI, clamping, persistence, and footer display. @@ -186,7 +188,7 @@ Each registered model must set: - `input`: supported input types. The installed Cursor SDK accepts `SDKUserMessage.images`, and Cursor models are expected to support image input, so advertise `["text", "image"]`. - `cost`: zeroed unless reliable Cursor costs are available. -The extension stores runtime metadata in an internal map keyed by registered pi model ID. That map records the Cursor base model ID, selected context param, default params, and discovered capabilities. `ProviderModelConfig` has no dedicated metadata field, so do not rely on hidden custom fields for this state. +The extension stores runtime metadata in an internal map keyed by registered pi model ID. That map records the Cursor base catalog model ID, the Cursor selection model ID (base ID or alias), selected context param, default params, and discovered capabilities. `ProviderModelConfig` has no dedicated metadata field, so do not rely on hidden custom fields for this state. ## Dynamic Capabilities diff --git a/package-lock.json b/package-lock.json index 7e6a831..9fc23e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,13 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@cursor/sdk": "^1.0.12" + "@cursor/sdk": "^1.0.13" }, "devDependencies": { "@earendil-works/pi-ai": "^0.74.0", "@earendil-works/pi-coding-agent": "^0.74.0", "typescript": "^6.0.3", - "vitest": "^4.1.5" + "vitest": "^4.1.6" }, "engines": { "node": ">=18" @@ -893,9 +893,9 @@ } }, "node_modules/@cursor/sdk": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk/-/sdk-1.0.12.tgz", - "integrity": "sha512-jGx0wFY1N9uIdIKr303CfM6m/dLXmRCUnU/0yNP/oiOpkBXqgqaThGbgYbcOeVrYonMZc/DZJ9EydXOEPJLcbg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk/-/sdk-1.0.13.tgz", + "integrity": "sha512-w6MWkgOTL6yb6GV/4Odx7QcamQgqhzX/CzcMBkqiiOPTPuEWItWrgA0qdivchm5YJXTt+LZkFSEQ/Ti44hVbfg==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@bufbuild/protobuf": "1.10.0", @@ -909,17 +909,17 @@ "node": ">=18" }, "optionalDependencies": { - "@cursor/sdk-darwin-arm64": "1.0.12", - "@cursor/sdk-darwin-x64": "1.0.12", - "@cursor/sdk-linux-arm64": "1.0.12", - "@cursor/sdk-linux-x64": "1.0.12", - "@cursor/sdk-win32-x64": "1.0.12" + "@cursor/sdk-darwin-arm64": "1.0.13", + "@cursor/sdk-darwin-x64": "1.0.13", + "@cursor/sdk-linux-arm64": "1.0.13", + "@cursor/sdk-linux-x64": "1.0.13", + "@cursor/sdk-win32-x64": "1.0.13" } }, "node_modules/@cursor/sdk-darwin-arm64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-arm64/-/sdk-darwin-arm64-1.0.12.tgz", - "integrity": "sha512-AOFx+aX+4SntAeC66YncHACXk5duxp+HzDrxxF4Tl93N6nLjHaHEKSAXbt87ivL34MCHop4v/3c70QzBhamB2g==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-arm64/-/sdk-darwin-arm64-1.0.13.tgz", + "integrity": "sha512-zHRTNtVRHw4KSAEFmtO0Av7jv9D60DrB+pygVNWGyKtRR44fcwtRHuLAJmO4HThxQw7MMvUJuAaNmCQxzHtPDQ==", "cpu": [ "arm64" ], @@ -930,9 +930,9 @@ ] }, "node_modules/@cursor/sdk-darwin-x64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-x64/-/sdk-darwin-x64-1.0.12.tgz", - "integrity": "sha512-/ZDAYFUrnPd8hAGRky9ZGcROqZSZ2b5W+aEjTdINzLhJ8x5ZNXtjaz0ZYSHabOn2BeErjXgTcq+4bX2/To4C1A==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-darwin-x64/-/sdk-darwin-x64-1.0.13.tgz", + "integrity": "sha512-7XsIkMKp6h/4W9zBx02Py1euJLAJVxlkwmm9GSoUjc+3hfFvHY/R/WTbX2TFgF4g1vOAq/HM7GmXBXq+e4M4+w==", "cpu": [ "x64" ], @@ -943,9 +943,9 @@ ] }, "node_modules/@cursor/sdk-linux-arm64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-arm64/-/sdk-linux-arm64-1.0.12.tgz", - "integrity": "sha512-kAxNqiB3dPtlW9fVjjIZEdbIGEGLA9moOM3zYwsXh8J1Qw942nJYMGDGR4o8x0zglwZ24a1JpovvZamrCaC3Yw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-arm64/-/sdk-linux-arm64-1.0.13.tgz", + "integrity": "sha512-bDgfPPgc84gUn3k+Iiq5OLZozzM0UYZdKbQ821pbZy1OPWTFaSkjXsoAB6xqf9wALWyW1eQxOC4RprPBLoy+yA==", "cpu": [ "arm64" ], @@ -956,9 +956,9 @@ ] }, "node_modules/@cursor/sdk-linux-x64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-x64/-/sdk-linux-x64-1.0.12.tgz", - "integrity": "sha512-RmBiBCPKMZC5McDerGk2Rk4P47xz2A+uzRoRgH6sMoOjklc33ry11iAZC0D5F5xH85chgY878086A/Q8+XrAuA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-linux-x64/-/sdk-linux-x64-1.0.13.tgz", + "integrity": "sha512-BTccnB5hVqK8Y0778oql6gbk7kIIlzQrBqt5QNLJpwBidjjde/mlvAajVB9hB3a29jelOwm0gJjMsLfqTkEPdw==", "cpu": [ "x64" ], @@ -969,9 +969,9 @@ ] }, "node_modules/@cursor/sdk-win32-x64": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cursor/sdk-win32-x64/-/sdk-win32-x64-1.0.12.tgz", - "integrity": "sha512-uH4shdHrKOdtNLapy1uuScJ9lL2Pc8zc9I9ZKC6b6bx+0UX6xLAqjPP7dqVPfO6D9u61yLq1Hs86XOLs5ZVkPA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cursor/sdk-win32-x64/-/sdk-win32-x64-1.0.13.tgz", + "integrity": "sha512-GxWlwj4G513EfGmvPVBa4y+vNn9B5Cj+npu8fVcJ0P+U9sruhgo4pvqGbWxkn5EIKbpGoraLq9QB4nFeoT1uRQ==", "cpu": [ "x64" ], @@ -1408,9 +1408,9 @@ "license": "MIT" }, "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "version": "0.129.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz", + "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==", "dev": true, "license": "MIT", "funding": { @@ -1492,9 +1492,9 @@ "license": "BSD-3-Clause" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz", + "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==", "cpu": [ "arm64" ], @@ -1509,9 +1509,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz", + "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==", "cpu": [ "arm64" ], @@ -1526,9 +1526,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz", + "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==", "cpu": [ "x64" ], @@ -1543,9 +1543,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz", + "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==", "cpu": [ "x64" ], @@ -1560,9 +1560,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz", + "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==", "cpu": [ "arm" ], @@ -1577,9 +1577,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz", + "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==", "cpu": [ "arm64" ], @@ -1597,9 +1597,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz", + "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==", "cpu": [ "arm64" ], @@ -1617,9 +1617,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz", + "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==", "cpu": [ "ppc64" ], @@ -1637,9 +1637,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz", + "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==", "cpu": [ "s390x" ], @@ -1657,9 +1657,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz", + "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==", "cpu": [ "x64" ], @@ -1677,9 +1677,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz", + "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==", "cpu": [ "x64" ], @@ -1697,9 +1697,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz", + "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==", "cpu": [ "arm64" ], @@ -1714,9 +1714,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz", + "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==", "cpu": [ "wasm32" ], @@ -1733,9 +1733,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz", + "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==", "cpu": [ "arm64" ], @@ -1750,9 +1750,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz", + "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==", "cpu": [ "x64" ], @@ -1767,9 +1767,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz", + "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==", "dev": true, "license": "MIT" }, @@ -2546,9 +2546,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -2588,16 +2588,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", + "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -2606,13 +2606,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", + "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.5", + "@vitest/spy": "4.1.6", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2633,9 +2633,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", + "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", "dev": true, "license": "MIT", "dependencies": { @@ -2646,13 +2646,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", + "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.6", "pathe": "^2.0.3" }, "funding": { @@ -2660,14 +2660,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", + "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/pretty-format": "4.1.6", + "@vitest/utils": "4.1.6", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2676,9 +2676,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", + "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", "dev": true, "license": "MIT", "funding": { @@ -2686,13 +2686,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", + "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", + "@vitest/pretty-format": "4.1.6", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -4774,14 +4774,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz", + "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" + "@oxc-project/types": "=0.129.0", + "@rolldown/pluginutils": "1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -4790,21 +4790,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + "@rolldown/binding-android-arm64": "1.0.0", + "@rolldown/binding-darwin-arm64": "1.0.0", + "@rolldown/binding-darwin-x64": "1.0.0", + "@rolldown/binding-freebsd-x64": "1.0.0", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", + "@rolldown/binding-linux-arm64-gnu": "1.0.0", + "@rolldown/binding-linux-arm64-musl": "1.0.0", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0", + "@rolldown/binding-linux-s390x-gnu": "1.0.0", + "@rolldown/binding-linux-x64-gnu": "1.0.0", + "@rolldown/binding-linux-x64-musl": "1.0.0", + "@rolldown/binding-openharmony-arm64": "1.0.0", + "@rolldown/binding-wasm32-wasi": "1.0.0", + "@rolldown/binding-win32-arm64-msvc": "1.0.0", + "@rolldown/binding-win32-x64-msvc": "1.0.0" } }, "node_modules/safe-buffer": { @@ -5342,16 +5342,16 @@ } }, "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz", + "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", + "postcss": "^8.5.14", + "rolldown": "1.0.0", "tinyglobby": "^0.2.16" }, "bin": { @@ -5368,7 +5368,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -5420,19 +5420,19 @@ } }, "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", + "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/expect": "4.1.6", + "@vitest/mocker": "4.1.6", + "@vitest/pretty-format": "4.1.6", + "@vitest/runner": "4.1.6", + "@vitest/snapshot": "4.1.6", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -5460,12 +5460,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", + "@vitest/browser-playwright": "4.1.6", + "@vitest/browser-preview": "4.1.6", + "@vitest/browser-webdriverio": "4.1.6", + "@vitest/coverage-istanbul": "4.1.6", + "@vitest/coverage-v8": "4.1.6", + "@vitest/ui": "4.1.6", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/package.json b/package.json index 4777507..633bac0 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "test:watch": "vitest" }, "dependencies": { - "@cursor/sdk": "^1.0.12" + "@cursor/sdk": "^1.0.13" }, "peerDependencies": { "@earendil-works/pi-ai": "*", @@ -48,7 +48,7 @@ "@earendil-works/pi-ai": "^0.74.0", "@earendil-works/pi-coding-agent": "^0.74.0", "typescript": "^6.0.3", - "vitest": "^4.1.5" + "vitest": "^4.1.6" }, "pi": { "extensions": [ diff --git a/src/context-window-cache.ts b/src/context-window-cache.ts index 1c3991d..3cc42fe 100644 --- a/src/context-window-cache.ts +++ b/src/context-window-cache.ts @@ -40,6 +40,10 @@ export function loadContextWindowCache(): Map { return cache; } +export function getCachedContextWindowExact(modelId: string): number | undefined { + return loadContextWindowCache().get(modelId); +} + export function getCachedContextWindow(modelId: string): number | undefined { const cache = loadContextWindowCache(); return cache.get(modelId) ?? cache.get("default"); diff --git a/src/model-discovery.ts b/src/model-discovery.ts index 874d04a..eb94975 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -7,7 +7,7 @@ import type { } from "@cursor/sdk"; import { AuthStorage, type ProviderModelConfig } from "@earendil-works/pi-coding-agent"; import type { ModelThinkingLevel, ThinkingLevelMap } from "@earendil-works/pi-ai"; -import { getCachedContextWindow } from "./context-window-cache.js"; +import { getCachedContextWindow, getCachedContextWindowExact } from "./context-window-cache.js"; const CURSOR_PROVIDER_ID = "cursor"; const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY"; @@ -217,6 +217,7 @@ async function getDiscoveryApiKey(): Promise { export interface CursorModelMetadata { piModelId: string; baseModelId: string; + selectionModelId: string; displayName: string; defaultParams: ModelParameterValue[]; context?: string; @@ -340,23 +341,25 @@ function getParamValue(params: ModelParameterValue[], id: string): string | unde return params.find((param) => param.id === id)?.value; } -function encodePiModelId(baseModelId: string, context?: string): string { - return context ? `${baseModelId}@${context}` : baseModelId; +function encodePiModelId(modelId: string, context?: string): string { + return context ? `${modelId}@${context}` : modelId; } -function getModelName(item: ModelListItem, context?: string): string { +function getModelName(item: ModelListItem, context?: string, alias?: string): string { const displayName = item.displayName || item.id; - return context ? `${displayName} @ ${context}` : displayName; + const baseName = alias ? `${displayName} (${alias})` : displayName; + return context ? `${baseName} @ ${context}` : baseName; } -function getContextWindow(piModelId: string, context?: string): number { +function getContextWindow(piModelId: string, context?: string, baseModelId?: string): number { if (context) return parseContextWindow(context) ?? FALLBACK_CONTEXT_WINDOW; - return getCachedContextWindow(piModelId) ?? FALLBACK_CONTEXT_WINDOW; + return getCachedContextWindowExact(piModelId) ?? (baseModelId ? getCachedContextWindow(baseModelId) : undefined) ?? FALLBACK_CONTEXT_WINDOW; } function toMetadata( item: ModelListItem, piModelId: string, + selectionModelId: string, defaultParams: ModelParameterValue[], context: string | undefined, ): CursorModelMetadata { @@ -365,10 +368,11 @@ function toMetadata( return { piModelId, baseModelId: item.id, + selectionModelId, displayName: item.displayName || item.id, defaultParams: cloneParams(defaultParams), ...(context ? { context } : {}), - contextWindow: getContextWindow(piModelId, context), + contextWindow: getContextWindow(piModelId, context, item.id), supportsFast: getParameter(item, "fast") !== undefined, defaultFast: fastValue === "true", supportsReasoning: thinkingLevelMap !== undefined, @@ -400,18 +404,40 @@ function getContextValues(item: ModelListItem): string[] { return getParameter(item, "context")?.values.map((value) => value.value) ?? []; } -function toModelConfigs(item: ModelListItem): ProviderModelConfig[] { +function getModelIds(item: ModelListItem, reservedBaseModelIds: Set): string[] { + const ids = [item.id]; + for (const rawAlias of item.aliases ?? []) { + const alias = rawAlias.trim(); + if (!alias || alias === item.id || ids.includes(alias) || reservedBaseModelIds.has(alias)) continue; + ids.push(alias); + } + return ids; +} + +function toModelConfigs( + item: ModelListItem, + usedPiModelIds: Set, + reservedBaseModelIds: Set, +): ProviderModelConfig[] { const defaultParams = getDefaultParams(item); const contextValues = getContextValues(item); const contexts = contextValues.length > 0 ? contextValues : [undefined]; + const configs: ProviderModelConfig[] = []; + + for (const selectionModelId of getModelIds(item, reservedBaseModelIds)) { + const alias = selectionModelId === item.id ? undefined : selectionModelId; + for (const context of contexts) { + const params = context ? replaceParam(defaultParams, "context", context) : defaultParams; + const piModelId = encodePiModelId(selectionModelId, context); + if (usedPiModelIds.has(piModelId)) continue; + usedPiModelIds.add(piModelId); + const metadata = toMetadata(item, piModelId, selectionModelId, params, context); + metadataByPiModelId.set(piModelId, metadata); + configs.push(toModelConfig(metadata, getModelName(item, context, alias))); + } + } - return contexts.map((context) => { - const params = context ? replaceParam(defaultParams, "context", context) : defaultParams; - const piModelId = encodePiModelId(item.id, context); - const metadata = toMetadata(item, piModelId, params, context); - metadataByPiModelId.set(piModelId, metadata); - return toModelConfig(metadata, getModelName(item, context)); - }); + return configs; } function sortModelsByBaseId(items: ModelListItem[]): ModelListItem[] { @@ -420,7 +446,9 @@ function sortModelsByBaseId(items: ModelListItem[]): ModelListItem[] { function registerModelItems(items: ModelListItem[]): ProviderModelConfig[] { metadataByPiModelId.clear(); - return sortModelsByBaseId(items).flatMap(toModelConfigs); + const usedPiModelIds = new Set(); + const reservedBaseModelIds = new Set(items.map((item) => item.id)); + return sortModelsByBaseId(items).flatMap((item) => toModelConfigs(item, usedPiModelIds, reservedBaseModelIds)); } export function getCursorModelMetadata(modelId: string): CursorModelMetadata | undefined { @@ -501,7 +529,7 @@ export function buildCursorModelSelection( setParam(params, "fast", fastEnabled ? "true" : "false"); } - return params.length > 0 ? { id: metadata.baseModelId, params } : { id: metadata.baseModelId }; + return params.length > 0 ? { id: metadata.selectionModelId, params } : { id: metadata.selectionModelId }; } function useFallbackModels(options: DiscoverModelsOptions, issue: CursorModelFallbackIssue): ProviderModelConfig[] { diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index bce91d8..60f0aed 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -1277,6 +1277,58 @@ describe("streamCursor", () => { ); }); + it("passes Cursor alias model selection back to the SDK", async () => { + modelDiscoveryTestUtils.registerModelItems([ + { + id: "gpt-5.5", + displayName: "GPT-5.5", + aliases: ["gpt-latest"], + parameters: [ + { id: "context", displayName: "Context", values: [{ value: "1m" }, { value: "272k" }] }, + { id: "reasoning", displayName: "Reasoning", values: [{ value: "none" }, { value: "medium" }] }, + ], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "reasoning", value: "medium" }, + ], + displayName: "GPT-5.5", + isDefault: true, + }, + ], + }, + ]); + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel("gpt-latest@272k"), makeContext(), { apiKey: "test-key", reasoning: "medium" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: { + id: "gpt-latest", + params: [ + { id: "context", value: "272k" }, + { id: "reasoning", value: "medium" }, + ], + }, + }), + ); + }); + it("passes Cursor model selection with context and pi thinking off to Agent.create", async () => { const modelWithParams = makeModel("gpt-5.5@1m"); const mockSend = vi.fn().mockResolvedValue({ diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 5bcc327..e5a098a 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -234,6 +234,89 @@ describe("discoverModels", () => { expect(getCursorModelMetadata("z-model@long")?.defaultParams).toEqual([{ id: "context", value: "long" }]); }); + it("registers Cursor model aliases with the same params and context variants", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "gpt-5.5", + displayName: "GPT-5.5", + aliases: ["gpt-latest", "gpt-latest", ""], + parameters: [ + { id: "context", displayName: "Context", values: [{ value: "1m" }, { value: "272k" }] }, + { id: "reasoning", displayName: "Reasoning", values: [{ value: "none" }, { value: "medium" }] }, + ], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "reasoning", value: "medium" }, + ], + displayName: "GPT-5.5", + isDefault: true, + }, + ], + }, + ]); + + const models = await discoverModels(); + + expect(models.map((model) => model.id)).toEqual(["gpt-5.5@1m", "gpt-5.5@272k", "gpt-latest@1m", "gpt-latest@272k"]); + expect(models[2].name).toBe("GPT-5.5 (gpt-latest) @ 1m"); + expect(getCursorModelMetadata("gpt-latest@272k")).toMatchObject({ + baseModelId: "gpt-5.5", + selectionModelId: "gpt-latest", + context: "272k", + }); + expect(buildCursorModelSelection("gpt-latest@272k", "medium")).toEqual({ + id: "gpt-latest", + params: [ + { id: "context", value: "272k" }, + { id: "reasoning", value: "medium" }, + ], + }); + }); + + it("skips aliases that collide with another Cursor base model id", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "model-a", + displayName: "Model A", + aliases: ["model-b", "model-a-latest"], + variants: [{ params: [], displayName: "Model A", isDefault: true }], + }, + { + id: "model-b", + displayName: "Model B", + variants: [{ params: [], displayName: "Model B", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(models.map((model) => model.id)).toEqual(["model-a", "model-a-latest", "model-b"]); + expect(getCursorModelMetadata("model-b")?.baseModelId).toBe("model-b"); + }); + + it("uses the aliased base model context-window cache for aliases without context params", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "gpt-5-mini", + displayName: "GPT-5 Mini", + aliases: ["gpt-mini-latest"], + variants: [{ params: [], displayName: "GPT-5 Mini", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(models.map((model) => [model.id, model.contextWindow])).toEqual([ + ["gpt-5-mini", 272000], + ["gpt-mini-latest", 272000], + ]); + }); + it("registers one pi model per Cursor context value", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ -- 2.51.2 From b3f8cff802e44cc5198c9f21cfd219d43003c247 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 14 May 2026 15:35:13 -0600 Subject: [PATCH 031/359] Release 0.1.8 --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72ca05f..be84c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.8 - 2026-05-14 ### Changed diff --git a/package-lock.json b/package-lock.json index 9fc23e1..ab94b2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.7", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.7", + "version": "0.1.8", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.13" diff --git a/package.json b/package.json index 633bac0..112a964 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.7", + "version": "0.1.8", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", -- 2.51.2 From b61aed1d60c17d6973ddb2c1d13e58a259617a74 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 14 May 2026 15:57:52 -0600 Subject: [PATCH 032/359] Fix cursor lifecycle and discovery edge cases --- README.md | 4 +- docs/cursor-model-ux-spec.md | 54 ++---- src/context-window-cache.ts | 6 + src/cursor-native-tool-display.ts | 10 +- src/cursor-provider.ts | 120 +++++++++++-- src/cursor-state.ts | 11 +- src/cursor-tool-transcript.ts | 15 +- src/model-discovery.ts | 71 ++++++-- test/cursor-provider.test.ts | 262 +++++++++++++++++++++++++++- test/cursor-state.test.ts | 18 ++ test/cursor-tool-transcript.test.ts | 24 +++ test/model-discovery.test.ts | 104 ++++++++++- 12 files changed, 620 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 2047268..00ec867 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ How to read model IDs: - `cursor/...` is the Cursor provider registered by this extension - `@1m`, `@272k`, and `@300k` are context-window variants - `:medium`, `:high`, and `:xhigh` are pi thinking-level suffixes for models where the Cursor SDK exposes a pi-controllable thinking parameter -- latest-style Cursor aliases returned by `Cursor.models.list()` are registered too, using the same context suffixes when the target model has context variants +- unambiguous latest-style Cursor aliases returned by `Cursor.models.list()` are registered too, using the same context suffixes when the target model has context variants; aliases shared by multiple base models or colliding with a base model ID are skipped because their SDK resolution and displayed metadata can diverge Examples with pi thinking controls: @@ -203,7 +203,7 @@ If no key is available from `/login`, `CURSOR_API_KEY`, or `--api-key`, model di - `composer-2` - `gpt-5.5@1m`, `gpt-5.5@272k` -- `claude-sonnet-4-6@1m`, `claude-sonnet-4-6@300k` +- `claude-sonnet-4-6@1m`, `claude-sonnet-4-6@200k` - `claude-opus-4-7@1m`, `claude-opus-4-7@300k` Fallback models are a conservative startup model list. Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-key`. If you add auth after startup, run `/reload` or restart pi to refresh the full live Cursor model catalog. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index c01da04..b5d3b0e 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -13,6 +13,7 @@ Current implementation notes: - Cursor `fast` is extension state, not model identity. - Cursor fast status uses `ctx.ui.setStatus()`; the default pi footer remains intact. - Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`. +- Product decision pending: image payload forwarding currently sends images only from the latest user message. If the latest user turn is plain text after an earlier image turn, the transcript keeps an `[image omitted from transcript]` placeholder but no image bytes are sent to Cursor. Changing this to carry images forward across turns requires a deliberate product decision about token cost, privacy, stale visual context, and expected multimodal follow-up behavior. - `@cursor/sdk` is a package dependency of this extension; users should not need a global SDK install. - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. @@ -21,7 +22,7 @@ Current implementation notes: - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. When native replay splits one Cursor SDK run into multiple pi turns, prompt input is counted once for the run; later synthetic replay turns report `input: 0` and only their own output estimate. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.13 documentation says the SDK may enable Max Mode automatically when a selected model requires it, but the public local-agent `ModelSelection` path still does not expose a manual Max Mode selector. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. -- `@cursor/sdk` 1.0.13 adds latest-style `ModelListItem.aliases`. The extension registers those aliases as pi model IDs (with the same context suffixes when applicable) and sends the alias back in `ModelSelection.id`, while sharing Cursor-only state such as fast defaults with the underlying catalog `id`. +- `@cursor/sdk` 1.0.13 adds latest-style `ModelListItem.aliases`. The extension registers only unambiguous aliases as pi model IDs (with the same context suffixes when applicable) and sends the alias back in `ModelSelection.id`, while sharing Cursor-only state such as fast defaults with the underlying catalog `id`. Aliases shared by multiple base models, such as generic family aliases, are skipped because the pi row metadata would otherwise imply one base model while Cursor may resolve the alias to another. ## Goal @@ -137,8 +138,9 @@ Register a `cursor` provider with `pi.registerProvider()`. Rules: -- Register one pi model for each Cursor base model and SDK alias when there is no Cursor `context` parameter. -- Register one pi model per Cursor `context` value for each Cursor base model and SDK alias when the model exposes a `context` parameter. +- Register one pi model for each Cursor base model and each unambiguous SDK alias when there is no Cursor `context` parameter. +- Register one pi model per Cursor `context` value for each Cursor base model and each unambiguous SDK alias when the model exposes a `context` parameter. +- Skip SDK aliases that collide with another base model ID or are shared by multiple base models; those aliases can resolve differently from the pi row metadata. - Do not encode `reasoning`, `effort`, `thinking`, or `fast` into pi model IDs. - Prefer stable, readable `@` suffixes that do not conflict with pi's final `:` suffix parser. - Sort Cursor models by base ID, then context value in Cursor SDK order before calling `pi.registerProvider()`. Registration order matters for `/model` display and model cycling; `--list-models` sorts output separately. @@ -486,42 +488,22 @@ Fast flag example: pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only" ``` -## Current Discovered Model Capability Examples +## Discovered Model Capability Examples -Current live Cursor data says: +These examples document the capability shapes the extension handles, not an exhaustive live catalog. The exact Cursor catalog changes over time; use `pi -e . --list-models cursor` or `Cursor.models.list()` for the current model surface. When the SDK reports aliases, only unambiguous aliases are registered; shared generic aliases are skipped. -| Model | Cursor controls | Pi representation | +| Example model shape | Cursor controls | Pi representation | |---|---|---| -| `default` | none | plain model | -| `composer-2` | fast | plain model + fast extension state | -| `composer-1.5` | none | plain model | -| `gpt-5.5` | context, reasoning, fast | context variants + native thinking + fast state | -| `gpt-5.4` | context, reasoning, fast | context variants + native thinking + fast state | -| `gpt-5.4-mini` | reasoning | plain model + native thinking | -| `gpt-5.4-nano` | reasoning | plain model + native thinking | -| `gpt-5.3-codex` | reasoning, fast | plain model + native thinking + fast state | -| `gpt-5.3-codex-spark` | reasoning | plain model + native thinking | -| `gpt-5.2` | reasoning, fast | plain model + native thinking + fast state | -| `gpt-5.2-codex` | reasoning, fast | plain model + native thinking + fast state | -| `gpt-5.1-codex-max` | reasoning, fast | plain model + native thinking + fast state | -| `gpt-5.1-codex-mini` | reasoning | plain model + native thinking | -| `gpt-5.1` | reasoning | plain model + native thinking | -| `claude-opus-4-7` | thinking, context, effort | context variants + native thinking | -| `claude-opus-4-6` | thinking, context, effort, fast | context variants + native thinking + fast state | -| `claude-opus-4-5` | thinking | plain model + native thinking | -| `claude-sonnet-4-6` | thinking, context, effort | context variants + native thinking | -| `claude-sonnet-4-5` | thinking, context | context-qualified model + native thinking | -| `claude-sonnet-4` | thinking, context | context-qualified model + native thinking | -| `claude-haiku-4-5` | thinking | plain model + native thinking | -| `grok-4.3` | context | context variants | -| `grok-4-20` | thinking | plain model + native thinking | -| `gemini-3.1-pro` | none | plain model | -| `gemini-3-flash` | none | plain model | -| `gemini-2.5-flash` | none | plain model | -| `gpt-5-mini` | none | plain model | -| `kimi-k2.5` | none | plain model | - -If Cursor later adds `fast`, `context`, `reasoning`, or `effort` to a model, the extension picks it up dynamically. +| plain model, such as `default` or models with no exposed controls | none | plain model | +| `composer-2`-style model | fast | plain model + fast extension state | +| GPT-style reasoning model with context variants | context, reasoning, fast when exposed | context variants + native thinking + optional fast state | +| Claude-style thinking model with context variants | thinking, context, effort when exposed | context variants + native thinking + optional fast state | +| Claude-style thinking model without context variants | thinking and/or effort | plain model + native thinking | +| context-only model | context | context variants | +| unique latest alias for any shape | aliases | same pi rows as the base model shape, using the alias as `ModelSelection.id` | +| shared generic alias across multiple base models | aliases | skipped to avoid misleading pi rows | + +If Cursor later adds `fast`, `context`, `reasoning`, `effort`, or aliases to a model, the extension picks up unambiguous capability changes dynamically. ## Detailed Examples diff --git a/src/context-window-cache.ts b/src/context-window-cache.ts index 3cc42fe..6c00788 100644 --- a/src/context-window-cache.ts +++ b/src/context-window-cache.ts @@ -4,6 +4,7 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent"; import { BUNDLED_CONTEXT_WINDOWS } from "./bundled-context-windows.js"; const CONTEXT_WINDOW_CACHE_FILE = "cursor-sdk-context-windows.json"; +let userContextWindowOverrideLoadCount = 0; interface ContextWindowCacheFile { contextWindows?: Record; @@ -18,6 +19,7 @@ function isPositiveInteger(value: unknown): value is number { } function loadUserContextWindowOverrides(): Map { + userContextWindowOverrideLoadCount += 1; const path = getCachePath(); const overrides = new Map(); if (!existsSync(path)) return overrides; @@ -80,4 +82,8 @@ export function saveCachedContextWindow(modelId: string, contextWindow: number): export const __testUtils = { getCachePath, + getUserContextWindowOverrideLoadCount: () => userContextWindowOverrideLoadCount, + resetUserContextWindowOverrideLoadCount: () => { + userContextWindowOverrideLoadCount = 0; + }, }; diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index d9a1df3..3300a8d 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -56,9 +56,14 @@ export function canRenderCursorToolNatively(toolName: string): boolean { return isNativeCursorToolName(toolName) && registeredNativeToolNames.has(toolName); } -export function recordCursorNativeToolDisplay(item: CursorNativeToolDisplayItem): void { - if (!canRenderCursorToolNatively(item.toolName)) return; +export function recordCursorNativeToolDisplay(item: CursorNativeToolDisplayItem): boolean { + if (!canRenderCursorToolNatively(item.toolName)) return false; nativeToolResults.set(item.id, item); + return true; +} + +export function deleteCursorNativeToolDisplay(id: string): void { + nativeToolResults.delete(id); } function consumeCursorNativeToolDisplay(id: string): CursorNativeToolDisplayItem | undefined { @@ -68,6 +73,7 @@ function consumeCursorNativeToolDisplay(id: string): CursorNativeToolDisplayItem } export const __testUtils = { + nativeToolResultCount: () => nativeToolResults.size, reset(): void { registeredNativeToolNames.clear(); nativeToolResults.clear(); diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index b9fb9c6..b1992c7 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -17,6 +17,7 @@ import { buildCursorPiToolDisplay, formatCursorToolTranscript, mergeCursorToolCa import { canRenderCursorToolNatively, isCursorNativeToolDisplayRuntimeEnabled, + deleteCursorNativeToolDisplay, recordCursorNativeToolDisplay, type CursorNativeToolDisplayItem, } from "./cursor-native-tool-display.js"; @@ -58,6 +59,7 @@ const AUTH_CURSOR_SDK_ERROR_MESSAGE = const APPROX_CHARS_PER_TOKEN = 4; const IMAGE_TOKEN_ESTIMATE = 1200; const CURSOR_ACTIVITY_TRACE_MAX_CHARS = 50000; +const DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS = 5 * 60 * 1000; const CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN = /^(cursor-replay-\d+-\d+)-tool-\d+$/; type CursorNativeQueuedEvent = @@ -73,10 +75,13 @@ interface CursorNativeLiveRun { promptInputTokensReported: boolean; pendingEvents: CursorNativeQueuedEvent[]; textDeltas: string[]; + recordedToolDisplayIds: string[]; finalText?: string; done: boolean; cancelled: boolean; + disposed: boolean; errorMessage?: string; + idleDisposeTimer?: ReturnType; waiters: Set<() => void>; } @@ -88,6 +93,7 @@ interface CursorNativeTurnState { } let cursorNativeReplayCounter = 0; +let cursorNativeReplayIdleDisposeMs = DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS; const pendingCursorNativeRuns = new Map(); function escapeRegExp(value: string): string { @@ -264,6 +270,21 @@ function queueCursorNativeEvent(run: CursorNativeLiveRun, event: CursorNativeQue notifyCursorNativeRun(run); } +function clearCursorNativeRunIdleDispose(run: CursorNativeLiveRun): void { + if (!run.idleDisposeTimer) return; + clearTimeout(run.idleDisposeTimer); + run.idleDisposeTimer = undefined; +} + +function scheduleCursorNativeRunIdleDispose(run: CursorNativeLiveRun): void { + if (run.disposed) return; + clearCursorNativeRunIdleDispose(run); + run.idleDisposeTimer = setTimeout(() => { + void disposeCursorNativeRun(run); + }, cursorNativeReplayIdleDisposeMs); + run.idleDisposeTimer.unref?.(); +} + function isCursorNativeRunReady(run: CursorNativeLiveRun): boolean { return run.pendingEvents.length > 0 || run.done || run.cancelled || run.errorMessage !== undefined; } @@ -310,12 +331,13 @@ function closeCursorNativeThinkingBlock(turn: CursorNativeTurnState): void { function closeCursorNativeTextBlock(turn: CursorNativeTurnState): string { if (turn.textContentIndex < 0) return ""; - const block = turn.partial.content[turn.textContentIndex]; + const contentIndex = turn.textContentIndex; + const block = turn.partial.content[contentIndex]; turn.textContentIndex = -1; if (block.type !== "text") return ""; turn.stream.push({ type: "text_end", - contentIndex: turn.partial.content.indexOf(block), + contentIndex, content: block.text, partial: turn.partial, }); @@ -402,15 +424,24 @@ function emitCursorNativeToolUseTurn( stream.push({ type: "toolcall_delta", contentIndex, delta: JSON.stringify(tool.args), partial }); const block = partial.content[contentIndex]; if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial }); - recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate }); + if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate })) { + run.recordedToolDisplayIds.push(tool.id); + } } setApproximateUsage(partial, takeCursorNativePromptInputTokens(run), outputText); partial.stopReason = "toolUse"; stream.push({ type: "done", reason: "toolUse", message: partial }); + scheduleCursorNativeRunIdleDispose(run); } async function disposeCursorNativeRun(run: CursorNativeLiveRun): Promise { + if (run.disposed) return; + run.disposed = true; pendingCursorNativeRuns.delete(run.id); + clearCursorNativeRunIdleDispose(run); + for (const toolDisplayId of run.recordedToolDisplayIds) deleteCursorNativeToolDisplay(toolDisplayId); + run.recordedToolDisplayIds = []; + run.waiters.clear(); try { await run.agent[Symbol.asyncDispose](); } catch { @@ -484,7 +515,13 @@ async function replayPendingCursorNativeRun( if (!replayId) return false; const run = pendingCursorNativeRuns.get(replayId); if (!run) return false; - await emitCursorNativeRunNextTurn(stream, partial, run, signal); + clearCursorNativeRunIdleDispose(run); + try { + await emitCursorNativeRunNextTurn(stream, partial, run, signal); + } catch (error) { + if (error instanceof CursorAbortError) await disposeCursorNativeRun(run); + throw error; + } return true; } @@ -498,6 +535,7 @@ export function streamCursor( (async () => { const partial = makeInitialMessage(model); let agent: SDKAgent | null = null; + let activeNativeRun: CursorNativeLiveRun | undefined; let resolvedApiKey: string | undefined; let abortSignal: AbortSignal | undefined; let abortListener: (() => void) | undefined; @@ -551,14 +589,21 @@ export function streamCursor( promptInputTokensReported: false, pendingEvents: [], textDeltas, + recordedToolDisplayIds: [], done: false, cancelled: false, + disposed: false, waiters: new Set(), } : undefined; - if (liveRun) pendingCursorNativeRuns.set(liveRun.id, liveRun); + if (liveRun) { + pendingCursorNativeRuns.set(liveRun.id, liveRun); + activeNativeRun = liveRun; + } const startedToolCalls = new Map(); - const completedToolFingerprints = new Set(); + const completedToolIdentities = new Set(); + const completedStartedToolFingerprints = new Set(); + const completedFallbackToolFingerprints = new Set(); const appendLiveTextDelta = (text: string): void => { if (textContentIndex < 0) { @@ -652,12 +697,25 @@ export function streamCursor( } }; - const handleCompletedToolCall = (toolCall: unknown): void => { + const handleCompletedToolCall = ( + toolCall: unknown, + options: { identity?: string; source?: "started" | "fallback" } = {}, + ): void => { const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd }), resolvedApiKey); const display = buildCursorPiToolDisplay(toolCall, { cwd }); const fingerprint = getToolFingerprint({ toolName: display.toolName, args: display.args, result: display.result }); - if (completedToolFingerprints.has(fingerprint)) return; - completedToolFingerprints.add(fingerprint); + if (options.identity && completedToolIdentities.has(options.identity)) return; + if (options.source === "started") { + if (completedFallbackToolFingerprints.has(fingerprint)) return; + } else if (completedStartedToolFingerprints.has(fingerprint) || completedFallbackToolFingerprints.has(fingerprint)) { + return; + } + if (options.identity) completedToolIdentities.add(options.identity); + if (options.source === "started") { + completedStartedToolFingerprints.add(fingerprint); + } else { + completedFallbackToolFingerprints.add(fingerprint); + } if (useNativeToolReplay && canRenderCursorToolNatively(display.toolName) && liveRun) { nativeToolReplayStarted = true; @@ -704,7 +762,11 @@ export function streamCursor( } else if (update.type === "tool-call-completed") { const mergedToolCall = mergeCursorToolCalls(startedToolCalls.get(update.callId), update.toolCall); startedToolCalls.delete(update.callId); - handleCompletedToolCall(mergedToolCall); + const identity = typeof update.callId === "string" ? `cursor-tool:${update.callId}` : undefined; + handleCompletedToolCall(mergedToolCall, { + identity, + source: identity ? "started" : "fallback", + }); } else if (update.type === "summary") { const summary = `Cursor summary: ${truncateSingleLine(update.summary)}\n`; if (liveRun && nativeToolReplayStarted) { @@ -723,7 +785,12 @@ export function streamCursor( const step = getObjectField(args.step, "message") ? args.step : undefined; if (getObjectField(args.step, "type") !== "toolCall") return; const toolCall = getObjectField(step, "message"); - if (toolCall) handleCompletedToolCall(toolCall); + const stepId = getObjectField(args.step, "id") ?? getObjectField(toolCall, "id") ?? getObjectField(toolCall, "callId"); + if (toolCall) { + handleCompletedToolCall(toolCall, { + identity: typeof stepId === "string" ? `cursor-tool:${stepId}` : undefined, + }); + } }; // Handle abort signal @@ -750,21 +817,31 @@ export function streamCursor( void run .wait() .then(async (result) => { + if (liveRun.disposed) return; await cacheSdkContextWindow(liveRun.agent.agentId, model.id); + if (liveRun.disposed) return; liveRun.cancelled = result.status === "cancelled"; liveRun.finalText = hasUsableText(result.result) ? result.result : liveRun.textDeltas.join(""); liveRun.done = true; notifyCursorNativeRun(liveRun); + scheduleCursorNativeRunIdleDispose(liveRun); }) .catch((error: unknown) => { + if (liveRun.disposed) return; liveRun.errorMessage = sanitizeError(error, resolvedApiKey ?? options?.apiKey); notifyCursorNativeRun(liveRun); + scheduleCursorNativeRunIdleDispose(liveRun); }); - await waitForCursorNativeRunProgress(liveRun, options?.signal); - await settleCursorNativeToolBatch(liveRun); - closeTraceBlock(); - await emitCursorNativeRunNextTurn(stream, partial, liveRun, options?.signal); + try { + await waitForCursorNativeRunProgress(liveRun, options?.signal); + await settleCursorNativeToolBatch(liveRun); + closeTraceBlock(); + await emitCursorNativeRunNextTurn(stream, partial, liveRun, options?.signal); + } catch (error) { + if (error instanceof CursorAbortError) await disposeCursorNativeRun(liveRun); + throw error; + } agent = null; return; } @@ -794,6 +871,8 @@ export function streamCursor( stream.push({ type: "error", reason: "error", error: partial }); } } finally { + if (activeNativeRun?.disposed) agent = null; + if (abortSignal && abortListener) { abortSignal.removeEventListener("abort", abortListener); } @@ -813,3 +892,14 @@ export function streamCursor( return stream; } + +export const __testUtils = { + DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS, + pendingCursorNativeRunCount: () => pendingCursorNativeRuns.size, + setCursorNativeReplayIdleDisposeMs: (value: number) => { + cursorNativeReplayIdleDisposeMs = value; + }, + resetCursorNativeReplayIdleDisposeMs: () => { + cursorNativeReplayIdleDisposeMs = DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS; + }, +}; diff --git a/src/cursor-state.ts b/src/cursor-state.ts index 30f5a25..9b2a606 100644 --- a/src/cursor-state.ts +++ b/src/cursor-state.ts @@ -105,16 +105,25 @@ function restoreMapValue(map: Map, key: string, previous: boole function persistFastPreference(pi: ExtensionAPI, baseModelId: string, fast: boolean): void { const previousSession = sessionFastPreferences.get(baseModelId); const previousGlobal = globalFastPreferences.get(baseModelId); + let savedGlobal = false; sessionFastPreferences.set(baseModelId, fast); globalFastPreferences.set(baseModelId, fast); try { saveGlobalFastPreferences(); + savedGlobal = true; + pi.appendEntry(FAST_ENTRY_TYPE, { baseModelId, fast }); } catch (error) { restoreMapValue(sessionFastPreferences, baseModelId, previousSession); restoreMapValue(globalFastPreferences, baseModelId, previousGlobal); + if (savedGlobal) { + try { + saveGlobalFastPreferences(); + } catch { + // Preserve the original append failure reported to the user. + } + } throw error; } - pi.appendEntry(FAST_ENTRY_TYPE, { baseModelId, fast }); } export function getEffectiveFastForModelId(modelId: string): boolean | undefined { diff --git a/src/cursor-tool-transcript.ts b/src/cursor-tool-transcript.ts index 827d68a..6d1cc49 100644 --- a/src/cursor-tool-transcript.ts +++ b/src/cursor-tool-transcript.ts @@ -281,15 +281,18 @@ function renderTreeNode(node: unknown, depth = 0, lines: string[] = []): string[ return lines; } -function formatLs(args: Record, result: NormalizedResult, options: TranscriptOptions): string { - const path = formatPathArg(args, options) ?? "."; - if (result.status === "error") return joinSections(`ls ${path}`, formatError(result.error)); - +function getLsBody(result: NormalizedResult, options: TranscriptOptions): string { const value = asRecord(result.value); const root = value?.directoryTreeRoot ?? result.value; const treeLines = renderTreeNode(root); const body = treeLines.length > 0 ? treeLines.join("\n") : stringifyUnknown(result.value); - return joinSections(`ls ${path}`, limitText(body, options)); + return limitText(body, options); +} + +function formatLs(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const path = formatPathArg(args, options) ?? "."; + if (result.status === "error") return joinSections(`ls ${path}`, formatError(result.error)); + return joinSections(`ls ${path}`, getLsBody(result, options)); } function formatGlob(args: Record, result: NormalizedResult, options: TranscriptOptions): string { @@ -528,7 +531,7 @@ export function buildCursorPiToolDisplay(toolCall: unknown, options: TranscriptO return { toolName: "ls", args, - result: textToolResult(result.status === "error" ? formatError(result.error) : formatLs(args, result, options).split("\n\n").slice(1).join("\n\n").trim()), + result: textToolResult(result.status === "error" ? formatError(result.error) : getLsBody(result, options).trim()), isError: result.status === "error", }; } diff --git a/src/model-discovery.ts b/src/model-discovery.ts index eb94975..43f2047 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -7,7 +7,7 @@ import type { } from "@cursor/sdk"; import { AuthStorage, type ProviderModelConfig } from "@earendil-works/pi-coding-agent"; import type { ModelThinkingLevel, ThinkingLevelMap } from "@earendil-works/pi-ai"; -import { getCachedContextWindow, getCachedContextWindowExact } from "./context-window-cache.js"; +import { loadContextWindowCache } from "./context-window-cache.js"; const CURSOR_PROVIDER_ID = "cursor"; const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY"; @@ -88,7 +88,7 @@ const FALLBACK_MODEL_ITEMS: ModelListItem[] = [ { id: "context", displayName: "Context", - values: [{ value: "1m" }, { value: "300k" }], + values: [{ value: "1m" }, { value: "200k" }], }, { id: "effort", @@ -165,6 +165,7 @@ export type CursorModelFallbackReason = "missing-api-key" | "discovery-failed" | export interface CursorModelFallbackIssue { reason: CursorModelFallbackReason; message: string; + errorMessage?: string; } export interface DiscoverModelsOptions { @@ -351,9 +352,14 @@ function getModelName(item: ModelListItem, context?: string, alias?: string): st return context ? `${baseName} @ ${context}` : baseName; } -function getContextWindow(piModelId: string, context?: string, baseModelId?: string): number { - if (context) return parseContextWindow(context) ?? FALLBACK_CONTEXT_WINDOW; - return getCachedContextWindowExact(piModelId) ?? (baseModelId ? getCachedContextWindow(baseModelId) : undefined) ?? FALLBACK_CONTEXT_WINDOW; +function getContextWindow(contextWindowCache: Map, piModelId: string, context?: string, baseModelId?: string): number { + return ( + contextWindowCache.get(piModelId) ?? + (context ? parseContextWindow(context) : undefined) ?? + (baseModelId ? contextWindowCache.get(baseModelId) : undefined) ?? + contextWindowCache.get("default") ?? + FALLBACK_CONTEXT_WINDOW + ); } function toMetadata( @@ -362,6 +368,7 @@ function toMetadata( selectionModelId: string, defaultParams: ModelParameterValue[], context: string | undefined, + contextWindowCache: Map, ): CursorModelMetadata { const thinkingLevelMap = getThinkingLevelMap(item); const fastValue = getParamValue(defaultParams, "fast")?.toLowerCase(); @@ -372,7 +379,7 @@ function toMetadata( displayName: item.displayName || item.id, defaultParams: cloneParams(defaultParams), ...(context ? { context } : {}), - contextWindow: getContextWindow(piModelId, context, item.id), + contextWindow: getContextWindow(contextWindowCache, piModelId, context, item.id), supportsFast: getParameter(item, "fast") !== undefined, defaultFast: fastValue === "true", supportsReasoning: thinkingLevelMap !== undefined, @@ -404,11 +411,25 @@ function getContextValues(item: ModelListItem): string[] { return getParameter(item, "context")?.values.map((value) => value.value) ?? []; } -function getModelIds(item: ModelListItem, reservedBaseModelIds: Set): string[] { +function getAmbiguousAliases(items: ModelListItem[]): Set { + const aliasOwners = new Map>(); + for (const item of items) { + for (const rawAlias of item.aliases ?? []) { + const alias = rawAlias.trim(); + if (!alias || alias === item.id) continue; + const owners = aliasOwners.get(alias) ?? new Set(); + owners.add(item.id); + aliasOwners.set(alias, owners); + } + } + return new Set([...aliasOwners.entries()].filter(([, owners]) => owners.size > 1).map(([alias]) => alias)); +} + +function getModelIds(item: ModelListItem, reservedBaseModelIds: Set, ambiguousAliases: Set): string[] { const ids = [item.id]; for (const rawAlias of item.aliases ?? []) { const alias = rawAlias.trim(); - if (!alias || alias === item.id || ids.includes(alias) || reservedBaseModelIds.has(alias)) continue; + if (!alias || alias === item.id || ids.includes(alias) || reservedBaseModelIds.has(alias) || ambiguousAliases.has(alias)) continue; ids.push(alias); } return ids; @@ -418,20 +439,22 @@ function toModelConfigs( item: ModelListItem, usedPiModelIds: Set, reservedBaseModelIds: Set, + ambiguousAliases: Set, + contextWindowCache: Map, ): ProviderModelConfig[] { const defaultParams = getDefaultParams(item); const contextValues = getContextValues(item); const contexts = contextValues.length > 0 ? contextValues : [undefined]; const configs: ProviderModelConfig[] = []; - for (const selectionModelId of getModelIds(item, reservedBaseModelIds)) { + for (const selectionModelId of getModelIds(item, reservedBaseModelIds, ambiguousAliases)) { const alias = selectionModelId === item.id ? undefined : selectionModelId; for (const context of contexts) { const params = context ? replaceParam(defaultParams, "context", context) : defaultParams; const piModelId = encodePiModelId(selectionModelId, context); if (usedPiModelIds.has(piModelId)) continue; usedPiModelIds.add(piModelId); - const metadata = toMetadata(item, piModelId, selectionModelId, params, context); + const metadata = toMetadata(item, piModelId, selectionModelId, params, context, contextWindowCache); metadataByPiModelId.set(piModelId, metadata); configs.push(toModelConfig(metadata, getModelName(item, context, alias))); } @@ -448,7 +471,9 @@ function registerModelItems(items: ModelListItem[]): ProviderModelConfig[] { metadataByPiModelId.clear(); const usedPiModelIds = new Set(); const reservedBaseModelIds = new Set(items.map((item) => item.id)); - return sortModelsByBaseId(items).flatMap((item) => toModelConfigs(item, usedPiModelIds, reservedBaseModelIds)); + const ambiguousAliases = getAmbiguousAliases(items); + const contextWindowCache = loadContextWindowCache(); + return sortModelsByBaseId(items).flatMap((item) => toModelConfigs(item, usedPiModelIds, reservedBaseModelIds, ambiguousAliases, contextWindowCache)); } export function getCursorModelMetadata(modelId: string): CursorModelMetadata | undefined { @@ -532,6 +557,24 @@ export function buildCursorModelSelection( return params.length > 0 ? { id: metadata.selectionModelId, params } : { id: metadata.selectionModelId }; } +function scrubDiscoveryErrorText(text: string, apiKey: string): string { + let scrubbed = text.replace(new RegExp(apiKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "[redacted]"); + return scrubbed + .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]") + .replace(/((?:^|[\s,{])cookie["']?\s*[:=]\s*["']?)[^\n]+/gi, "$1[redacted]") + .replace( + /((?:authorization|api[_-]?key|apiKey|token|session(?:[_-]?id)?)["']?\s*[:=]\s*["']?)[^"'\s,;}]+/gi, + "$1[redacted]", + ) + .trim(); +} + +function sanitizeDiscoveryError(error: unknown, apiKey: string): string | undefined { + const message = error instanceof Error ? error.message : typeof error === "string" ? error : ""; + const scrubbed = scrubDiscoveryErrorText(message, apiKey); + return scrubbed || undefined; +} + function useFallbackModels(options: DiscoverModelsOptions, issue: CursorModelFallbackIssue): ProviderModelConfig[] { options.onFallback?.(issue); return registerModelItems(FALLBACK_MODEL_ITEMS); @@ -555,10 +598,12 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi reason: "empty-model-list", message: `Cursor model discovery returned no models. Using fallback Cursor models; verify ${AUTH_SETUP_HINT}. ${CATALOG_REFRESH_HINT}`, }); - } catch { + } catch (error) { + const errorMessage = sanitizeDiscoveryError(error, apiKey); return useFallbackModels(options, { reason: "discovery-failed", - message: `Cursor model discovery failed. Using fallback Cursor models; verify ${AUTH_SETUP_HINT}. ${CATALOG_REFRESH_HINT}`, + message: `Cursor model discovery failed${errorMessage ? `: ${errorMessage}` : ""}. Using fallback Cursor models; verify ${AUTH_SETUP_HINT}. ${CATALOG_REFRESH_HINT}`, + ...(errorMessage ? { errorMessage } : {}), }); } } diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 60f0aed..b9bb8d5 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -32,7 +32,7 @@ vi.mock("@cursor/sdk", () => { }); import { Agent, createAgentPlatform } from "@cursor/sdk"; -import { streamCursor } from "../src/cursor-provider.js"; +import { streamCursor, __testUtils as cursorProviderTestUtils } from "../src/cursor-provider.js"; import { __testUtils as modelDiscoveryTestUtils } from "../src/model-discovery.js"; import { __testUtils as contextWindowCacheTestUtils } from "../src/context-window-cache.js"; import { __testUtils as nativeToolDisplayTestUtils, registerCursorNativeToolDisplay } from "../src/cursor-native-tool-display.js"; @@ -197,6 +197,8 @@ describe("streamCursor", () => { vi.clearAllMocks(); delete process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY; delete process.env.PI_CURSOR_REGISTER_NATIVE_TOOLS; + expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(0); + cursorProviderTestUtils.resetCursorNativeReplayIdleDisposeMs(); nativeToolDisplayTestUtils.reset(); modelDiscoveryTestUtils.registerModelItems(cursorModelItems); // Re-setup default mock return after clearing @@ -383,6 +385,52 @@ describe("streamCursor", () => { expect(trace).toContain("# pi-cursor-sdk"); }); + it("dedupes a completed tool call reported through both delta and step callbacks", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void; onStep: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "README.md" } }, callId: "c1" } }); + opts.onStep({ + step: { + type: "toolCall", + message: { + type: "read", + args: { path: "README.md" }, + result: { status: "success", value: { content: "# pi-cursor-sdk" } }, + }, + }, + }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { status: "success", value: { content: "# pi-cursor-sdk" } }, + }, + callId: "c1", + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "done" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + const trace = events.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + + expect(trace.match(/read README\.md/g)).toHaveLength(1); + expect(trace.match(/# pi-cursor-sdk/g)).toHaveLength(1); + }); + it("replays native Cursor tools as a toolUse turn before final text", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; const registeredTools: RegisteredTool[] = []; @@ -472,6 +520,140 @@ describe("streamCursor", () => { expect(replayDone.message.content).toEqual([{ type: "text", text: "Final answer only." }]); }); + it("disposes abandoned native replay runs after the idle timeout", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + cursorProviderTestUtils.setCursorNativeReplayIdleDisposeMs(1); + const registeredTools: RegisteredTool[] = []; + await registerNativeToolDisplayForTest(registeredTools); + + const mockDispose = vi.fn().mockResolvedValue(undefined); + const runWait = vi.fn(() => new Promise<{ id: string; status: "finished"; result: string }>(() => {})); + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "README.md" } }, callId: "c1" } }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { status: "success", value: { content: "# pi-cursor-sdk" } }, + }, + callId: "c1", + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "running", + wait: runWait, + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + agentId: "agent-1", + send: mockSend, + [Symbol.asyncDispose]: mockDispose, + }); + + const events = await collectEvents(streamCursor(makeModel(), makeContext(), { apiKey: "test-key" })); + const done = events.find((e: any) => e.type === "done") as any; + + expect(done.reason).toBe("toolUse"); + expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(1); + expect(nativeToolDisplayTestUtils.nativeToolResultCount()).toBe(1); + expect(mockDispose).not.toHaveBeenCalled(); + + await vi.waitFor(() => expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(0)); + expect(nativeToolDisplayTestUtils.nativeToolResultCount()).toBe(0); + expect(mockDispose).toHaveBeenCalledTimes(1); + }); + + it("cleans up pending native replay runs when replay aborts mid-flight", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + const registeredTools: RegisteredTool[] = []; + await registerNativeToolDisplayForTest(registeredTools); + + const controller = new AbortController(); + const mockDispose = vi.fn().mockResolvedValue(undefined); + let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; + const runWait = vi.fn( + () => + new Promise<{ id: string; status: "finished"; result: string }>((resolve) => { + resolveRun = resolve; + }), + ); + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "README.md" } }, callId: "c1" } }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "read", + result: { status: "success", value: { content: "# pi-cursor-sdk" } }, + }, + callId: "c1", + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "running", + wait: runWait, + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + agentId: "agent-1", + send: mockSend, + [Symbol.asyncDispose]: mockDispose, + }); + + const firstEvents = await collectEvents(streamCursor(makeModel(), makeContext(), { apiKey: "test-key" })); + const firstDone = firstEvents.find((e: any) => e.type === "done") as any; + const toolCall = firstDone.message.content.find((block: any) => block.type === "toolCall"); + const readTool = registeredTools.find((tool) => tool.name === "read"); + const toolResult = await readTool.execute(toolCall.id, toolCall.arguments, undefined, undefined, {}); + + expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(1); + expect(mockDispose).not.toHaveBeenCalled(); + + const replayContext = makeContext(); + replayContext.messages = [ + ...replayContext.messages, + firstDone.message, + { + role: "toolResult", + toolCallId: toolCall.id, + toolName: "read", + content: toolResult.content, + details: toolResult.details, + isError: false, + timestamp: 2, + }, + ]; + + const replayEventsPromise = collectEvents(streamCursor(makeModel(), replayContext, { apiKey: "test-key", signal: controller.signal })); + await Promise.resolve(); + controller.abort(); + const replayEvents = await replayEventsPromise; + const error = replayEvents.find((e: any) => e.type === "error") as any; + + expect(error.reason).toBe("aborted"); + expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(0); + expect(nativeToolDisplayTestUtils.nativeToolResultCount()).toBe(0); + expect(mockDispose).toHaveBeenCalledTimes(1); + + resolveRun({ id: "run-1", status: "finished", result: "late result" }); + await Promise.resolve(); + await Promise.resolve(); + + expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(0); + expect(mockDispose).toHaveBeenCalledTimes(1); + }); + it("streams post-tool Cursor thinking and text while a native replay run is still active", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; const registeredTools: RegisteredTool[] = []; @@ -569,6 +751,7 @@ describe("streamCursor", () => { expect(replayText).toBe("Final answer."); expect(finalDone.reason).toBe("stop"); expect(finalDone.message.content.map((block: any) => block.type)).toEqual(["thinking", "text"]); + expect(replayEvents.find((event: any) => event.type === "text_end")?.contentIndex).toBe(1); }); it("queues post-tool thinking and text that arrive before the native tool-use turn closes", async () => { @@ -665,6 +848,7 @@ describe("streamCursor", () => { expect(replayThinking).toBe("Post-tool thought."); expect(replayText).toBe("Final answer."); expect(finalDone.message.content.map((block: any) => block.type)).toEqual(["thinking", "text"]); + expect(replayEvents.find((event: any) => event.type === "text_end")?.contentIndex).toBe(1); }); it("does not duplicate final result after an earlier post-tool text turn", async () => { @@ -844,6 +1028,82 @@ describe("streamCursor", () => { expect(trace).not.toContain("fc_secret"); }); + it("keeps distinct completed tool calls with identical display payloads", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + for (const callId of ["c1", "c2"]) { + opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "shell", args: { command: "date" } }, callId } }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "shell", + result: { status: "success", value: { stdout: "Thu May 14\n", stderr: "", exitCode: 0 } }, + }, + callId, + }, + }); + } + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "done" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + const trace = events.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + + expect(trace.match(/\$ date/g)).toHaveLength(2); + expect(trace.match(/Thu May 14/g)).toHaveLength(2); + }); + + it("keeps distinct completed tool calls with identical payloads even without started events", async () => { + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + for (const callId of ["c1", "c2"]) { + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + name: "shell", + args: { command: "date" }, + result: { status: "success", value: { stdout: "Thu May 14\n", stderr: "", exitCode: 0 } }, + }, + callId, + }, + }); + } + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished", result: "done" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel(), makeContext(), { apiKey: "test-key" }); + const events = await collectEvents(stream); + const trace = events.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + + expect(trace.match(/\$ date/g)).toHaveLength(2); + expect(trace.match(/Thu May 14/g)).toHaveLength(2); + }); + it("scrubs secrets from cursor tool transcript output", async () => { const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { opts.onDelta({ update: { type: "tool-call-started", toolCall: { name: "read", args: { path: "secrets.txt" } }, callId: "c1" } }); diff --git a/test/cursor-state.test.ts b/test/cursor-state.test.ts index c60da7b..95930bd 100644 --- a/test/cursor-state.test.ts +++ b/test/cursor-state.test.ts @@ -134,6 +134,24 @@ describe("Cursor fast state", () => { expect(pi.appendEntry).not.toHaveBeenCalled(); }); + it("rolls fast state back when the session journal append fails", async () => { + writeFileSync(__testUtils.getConfigPath(), JSON.stringify({ fastDefaults: { "composer-2": true } })); + const { pi, ctx, commands, handlers } = createHarness({ modelId: "composer-2" }); + pi.appendEntry.mockImplementationOnce(() => { + throw new Error("journal unavailable"); + }); + await handlers.get("session_start")({}, ctx); + + await commands.get("cursor-fast").handler("", ctx); + + expect(ctx.ui.notify).toHaveBeenCalledWith(expect.stringContaining("Failed to save Cursor fast preference"), "error"); + expect(ctx.ui.setStatus).toHaveBeenLastCalledWith("cursor", "cursor fast"); + expect(getEffectiveFastForModelId("composer-2")).toBe(true); + expect(JSON.parse(readFileSync(__testUtils.getConfigPath(), "utf-8"))).toEqual({ + fastDefaults: { "composer-2": true }, + }); + }); + it("restores fast state from the active session branch", async () => { const { ctx, handlers } = createHarness({ modelId: "composer-2", diff --git a/test/cursor-tool-transcript.test.ts b/test/cursor-tool-transcript.test.ts index 547c131..4d78e9b 100644 --- a/test/cursor-tool-transcript.test.ts +++ b/test/cursor-tool-transcript.test.ts @@ -100,6 +100,30 @@ describe("formatCursorToolTranscript", () => { expect(transcript).toContain("Took 0.0s"); }); + it("builds native pi display data for Cursor ls calls without parsing formatted transcript headers", () => { + const display = buildCursorPiToolDisplay({ + name: "ls", + args: { path: "." }, + result: { + status: "success", + value: { + directoryTreeRoot: { + name: "root", + children: [{ name: "src" }, { name: "test" }], + }, + }, + }, + }); + + expect(display).toMatchObject({ + toolName: "ls", + args: { path: "." }, + result: { content: [{ type: "text", text: "root\n src\n test" }] }, + isError: false, + }); + expect(display.result.content[0].text).not.toContain("ls ."); + }); + it("builds native pi display data for Cursor read and shell calls", () => { const readDisplay = buildCursorPiToolDisplay({ name: "read", diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index e5a098a..5001069 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -10,7 +10,7 @@ import { __testUtils, type CursorModelFallbackIssue, } from "../src/model-discovery.js"; -import { saveCachedContextWindow } from "../src/context-window-cache.js"; +import { saveCachedContextWindow, __testUtils as contextWindowCacheTestUtils } from "../src/context-window-cache.js"; vi.mock("@cursor/sdk", () => ({ Cursor: { @@ -64,7 +64,7 @@ describe("discoverModels", () => { "claude-opus-4-7@1m", "claude-opus-4-7@300k", "claude-sonnet-4-6@1m", - "claude-sonnet-4-6@300k", + "claude-sonnet-4-6@200k", "composer-2", "gpt-5.5@1m", "gpt-5.5@272k", @@ -276,6 +276,29 @@ describe("discoverModels", () => { }); }); + it("skips aliases that multiple Cursor base models share", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "model-a", + displayName: "Model A", + aliases: ["model-latest", "model-shared"], + variants: [{ params: [], displayName: "Model A", isDefault: true }], + }, + { + id: "model-b", + displayName: "Model B", + aliases: ["model-stable", "model-shared"], + variants: [{ params: [], displayName: "Model B", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(models.map((model) => model.id)).toEqual(["model-a", "model-latest", "model-b", "model-stable"]); + expect(getCursorModelMetadata("model-shared")).toBeUndefined(); + }); + it("skips aliases that collide with another Cursor base model id", async () => { process.env.CURSOR_API_KEY = "test-key-123"; mockedList.mockResolvedValueOnce([ @@ -425,6 +448,54 @@ describe("discoverModels", () => { } }); + it("loads the context-window cache once while registering a model catalog", async () => { + const tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-cursor-context-window-count-")); + process.env.PI_CODING_AGENT_DIR = tmpAgentDir; + try { + contextWindowCacheTestUtils.resetUserContextWindowOverrideLoadCount(); + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce( + Array.from({ length: 25 }, (_, index) => ({ + id: `synthetic-model-${index}`, + displayName: `Synthetic Model ${index}`, + variants: [{ params: [], displayName: `Synthetic Model ${index}`, isDefault: true }], + })), + ); + + await discoverModels(); + + expect(contextWindowCacheTestUtils.getUserContextWindowOverrideLoadCount()).toBe(1); + } finally { + rmSync(tmpAgentDir, { recursive: true, force: true }); + } + }); + + it("lets user cache override context-qualified model IDs", async () => { + const tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-cursor-context-window-qualified-")); + process.env.PI_CODING_AGENT_DIR = tmpAgentDir; + try { + saveCachedContextWindow("gpt-5.5@1m", 950000); + process.env.CURSOR_API_KEY = "test-key-123"; + mockedList.mockResolvedValueOnce([ + { + id: "gpt-5.5", + displayName: "GPT-5.5", + parameters: [{ id: "context", displayName: "Context", values: [{ value: "1m" }, { value: "272k" }] }], + variants: [{ params: [{ id: "context", value: "1m" }], displayName: "GPT-5.5", isDefault: true }], + }, + ]); + + const models = await discoverModels(); + + expect(models.map((model) => [model.id, model.contextWindow])).toEqual([ + ["gpt-5.5@1m", 950000], + ["gpt-5.5@272k", 272000], + ]); + } finally { + rmSync(tmpAgentDir, { recursive: true, force: true }); + } + }); + it("lets user cache override bundled context windows", async () => { const tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-cursor-context-window-")); process.env.PI_CODING_AGENT_DIR = tmpAgentDir; @@ -688,7 +759,7 @@ describe("discoverModels", () => { "claude-opus-4-7@1m", "claude-opus-4-7@300k", "claude-sonnet-4-6@1m", - "claude-sonnet-4-6@300k", + "claude-sonnet-4-6@200k", "composer-2", "gpt-5.5@1m", "gpt-5.5@272k", @@ -707,10 +778,37 @@ describe("discoverModels", () => { message: expect.stringContaining("Cursor model discovery failed"), }), ]); + expect(issues[0].message).toContain("network error"); + expect(issues[0].errorMessage).toBe("network error"); expect(issues[0].message).toContain("/login"); expect(issues[0].message).not.toContain("test-key-123"); }); + it("redacts sensitive values from fallback failure details", async () => { + process.env.CURSOR_API_KEY = "test-key-123"; + const issues: CursorModelFallbackIssue[] = []; + mockedList.mockRejectedValueOnce( + new Error( + 'Unauthorized Bearer test-key-123 {"apiKey":"test-key-123","token":"token-value","session_id":"session-value"} cookie: foo=bar; baz=qux', + ), + ); + + await discoverModels({ onFallback: (issue) => issues.push(issue) }); + + expect(issues[0].reason).toBe("discovery-failed"); + expect(issues[0].message).toContain("Bearer [redacted]"); + expect(issues[0].message).toContain('"apiKey":"[redacted]"'); + expect(issues[0].message).toContain('"token":"[redacted]"'); + expect(issues[0].message).toContain('"session_id":"[redacted]"'); + expect(issues[0].message).toContain("cookie: [redacted]"); + expect(issues[0].errorMessage).toContain("Bearer [redacted]"); + expect(issues[0].message).not.toContain("test-key-123"); + expect(issues[0].message).not.toContain("token-value"); + expect(issues[0].message).not.toContain("session-value"); + expect(issues[0].message).not.toContain("foo=bar"); + expect(issues[0].message).not.toContain("baz=qux"); + }); + it("falls back and reports empty model list when Cursor.models.list returns empty", async () => { process.env.CURSOR_API_KEY = "test-key-123"; const issues: CursorModelFallbackIssue[] = []; -- 2.51.2 From 181103926abf80ec2cb1e837b01e4b5fb33e49cc Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 14 May 2026 17:53:07 -0600 Subject: [PATCH 033/359] Release 0.1.9 --- CHANGELOG.md | 18 ++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be84c9a..2c7ebd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 0.1.9 - 2026-05-14 + +### Fixed + +- Clean up recorded native Cursor tool replay outputs when abandoned replay runs are disposed, avoiding retained file or command output in process memory. +- Restore `/cursor-fast` state when session persistence fails during command handling. +- Preserve distinct same-payload Cursor tool completions while deduplicating duplicate SDK completion surfaces. +- Respect exact `model@context` context-window cache overrides before falling back to parsed base-model context values. +- Emit native replay text block endings with saved content indexes instead of searching by object identity. +- Redact discovery failure details with the same secret patterns used for stream errors. + +### Changed + +- Update fallback Sonnet 4.6 context variants from `300k` to the current `200k` catalog variant. +- Skip ambiguous Cursor SDK aliases shared by multiple base models or colliding with base model IDs, preventing misleading pi model rows. +- Reduce context-window cache reloads during model catalog registration. +- Document image carry-forward as a product decision rather than silently changing current latest-user-message image forwarding behavior. + ## 0.1.8 - 2026-05-14 ### Changed diff --git a/package-lock.json b/package-lock.json index ab94b2d..12df9fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.8", + "version": "0.1.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.8", + "version": "0.1.9", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.13" diff --git a/package.json b/package.json index 112a964..355095f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.8", + "version": "0.1.9", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", -- 2.51.2 From f3d04f232d476ca76cbcfaafcac74eba6d731360 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 14 May 2026 21:06:23 -0600 Subject: [PATCH 034/359] Enhance Cursor SDK integration by making setting sources opt-in to prevent terminal log noise during startup. Update documentation to clarify usage of Cursor tools and ensure proper handling of web search claims. Add tests for setting source functionality and improve context prompt instructions regarding tool access. --- README.md | 8 ++++-- docs/cursor-model-ux-spec.md | 2 +- src/context.ts | 16 +++++++++-- src/cursor-provider.ts | 20 ++++++++++--- test/context.test.ts | 12 ++++++++ test/cursor-provider.test.ts | 55 +++++++++++++++++++++++++++++++++++- 6 files changed, 103 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 00ec867..ae5242f 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still - **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. -- **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out. +- **Cursor setting sources are opt-in.** The extension does not pass `local.settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. To load configured Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities, start pi with `PI_CURSOR_SETTING_SOURCES=all`. To narrow loading, set a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. - **Max Mode is not a manual pi variant.** Cursor's SDK may enable Max Mode automatically for models that require it. This extension only advertises exact context-window variants that the SDK catalog exposes and otherwise uses conservative SDK-derived default/non-Max context windows. - **Output token limits are conservative.** Cursor SDK model metadata does not currently expose output token limits directly. - **Token usage is approximate in pi.** Cursor SDK usage events include internal agent/tool/cache work, so the extension reports an approximate replayable pi prompt/output size for context display and compaction decisions. @@ -262,7 +262,11 @@ Fast mode is currently off. The footer only shows `cursor fast` when fast mode i ### My Cursor app settings or rules do not seem to apply -They are not loaded by default in this extension. See [Limits](#limits). +Cursor setting sources are not loaded by default because the Cursor SDK can print settings/skills loading output directly to the terminal. Start pi with `PI_CURSOR_SETTING_SOURCES=all`, or choose a narrower list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. + +### Cursor does not call my web search MCP/tool + +Cursor SDK local agents load MCP servers from Cursor setting sources and inline SDK config. This extension leaves Cursor setting sources off by default to avoid startup log noise, so a web search tool needs to be configured in Cursor and settings sources need to be enabled with `PI_CURSOR_SETTING_SOURCES=all` or a narrower list. ### Cursor native tool cards conflict with another extension diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index b5d3b0e..48edc80 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -16,7 +16,7 @@ Current implementation notes: - Product decision pending: image payload forwarding currently sends images only from the latest user message. If the latest user turn is plain text after an earlier image turn, the transcript keeps an `[image omitted from transcript]` placeholder but no image bytes are sent to Cursor. Changing this to carry images forward across turns requires a deliberate product decision about token cost, privacy, stale visual context, and expected multimodal follow-up behavior. - `@cursor/sdk` is a package dependency of this extension; users should not need a global SDK install. - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. -- Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI. +- Local agents do not pass `settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. Users can opt in with `PI_CURSOR_SETTING_SOURCES=all` or narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. - Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. When native replay splits one Cursor SDK run into multiple pi turns, prompt input is counted once for the run; later synthetic replay turns report `input: 0` and only their own output estimate. diff --git a/src/context.ts b/src/context.ts index 11fe0dd..86834ad 100644 --- a/src/context.ts +++ b/src/context.ts @@ -54,7 +54,14 @@ function formatToolCall(toolCall: ToolCall): string { } export function buildCursorPrompt(context: Context): CursorPrompt { - const parts: string[] = []; + const parts: string[] = [ + [ + "Cursor SDK tool boundary:", + "Only tools exposed by the Cursor SDK in this run are callable. The pi system prompt and transcript are context only; they do not grant access to pi tools or tool names mentioned there.", + "If the user asks you to search, fetch, browse, or research the web, use an actual Cursor SDK web/search/browser/MCP tool call. If no such Cursor SDK tool is available, say that web search is not configured for this Cursor SDK run.", + "Do not plan to use or claim to have used pi-only tools such as WebSearch or WebFetch unless the Cursor SDK actually exposes and executes that tool in this run.", + ].join("\n"), + ]; if (context.systemPrompt) { parts.push(`System instructions from pi:\n${context.systemPrompt}`); @@ -92,7 +99,12 @@ export function buildCursorPrompt(context: Context): CursorPrompt { } } - parts.push("Answer the latest user request above using your capabilities. Do not assume access to pi tools."); + parts.push( + [ + "Answer the latest user request above using your capabilities. Do not assume access to pi tools.", + "If the user asks for web research, do not claim to have searched the web unless a Cursor SDK web/search/browser/MCP tool was actually used.", + ].join("\n"), + ); const images = extractLatestImages(context.messages); diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index b1992c7..9c21fa7 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -8,7 +8,7 @@ import { type AssistantMessage, } from "@earendil-works/pi-ai"; import { Agent, createAgentPlatform } from "@cursor/sdk"; -import type { InteractionUpdate, SDKAgent } from "@cursor/sdk"; +import type { InteractionUpdate, SDKAgent, SettingSource } from "@cursor/sdk"; import { buildCursorPrompt, type CursorPrompt } from "./context.js"; import { getEffectiveFastForModelId } from "./cursor-state.js"; import { buildCursorModelSelection } from "./model-discovery.js"; @@ -61,6 +61,7 @@ const IMAGE_TOKEN_ESTIMATE = 1200; const CURSOR_ACTIVITY_TRACE_MAX_CHARS = 50000; const DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS = 5 * 60 * 1000; const CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN = /^(cursor-replay-\d+-\d+)-tool-\d+$/; +const CURSOR_SETTING_SOURCES_ENV = "PI_CURSOR_SETTING_SOURCES"; type CursorNativeQueuedEvent = | { type: "thinking-delta"; text: string } @@ -131,6 +132,18 @@ function resolveCursorApiKey(apiKey?: string): string | undefined { return trimmed; } +function resolveCursorSettingSources(): SettingSource[] | undefined { + const raw = process.env[CURSOR_SETTING_SOURCES_ENV]?.trim(); + if (!raw) return undefined; + const normalized = raw.toLowerCase(); + if (["0", "false", "off", "none", "omit", "disabled"].includes(normalized)) return undefined; + if (["1", "true", "on", "all"].includes(normalized)) return ["all"]; + return raw + .split(",") + .map((entry) => entry.trim()) + .filter((entry): entry is SettingSource => Boolean(entry)); +} + function sanitizeError(error: unknown, apiKey?: string): string { const message = error instanceof Error ? error.message : typeof error === "string" ? error : ""; if (message === MISSING_API_KEY_MESSAGE) return MISSING_API_KEY_MESSAGE; @@ -560,13 +573,12 @@ export function streamCursor( const cwd = process.cwd(); const fastEnabled = getEffectiveFastForModelId(model.id); const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled); + const settingSources = resolveCursorSettingSources(); agent = await Agent.create({ apiKey, model: selection, - // Do not pass settingSources here. The Cursor SDK currently writes - // setting/rule loading INFO logs directly to process output, which corrupts pi's TUI. - local: { cwd }, + local: settingSources ? { cwd, settingSources } : { cwd }, }); throwIfAborted(); diff --git a/test/context.test.ts b/test/context.test.ts index ab36517..25a0a37 100644 --- a/test/context.test.ts +++ b/test/context.test.ts @@ -189,4 +189,16 @@ describe("buildCursorPrompt", () => { const result = buildCursorPrompt(ctx); expect(result.text).toContain("Answer the latest user request"); }); + + it("instructs Cursor not to claim web search without an actual Cursor web tool", () => { + const ctx: Context = { + systemPrompt: "You can use WebSearch and WebFetch.", + messages: [{ role: "user", content: "search the web for Cursor SDK best practices", timestamp: 1 }], + }; + const result = buildCursorPrompt(ctx); + expect(result.text.indexOf("Cursor SDK tool boundary:")).toBeLessThan(result.text.indexOf("System instructions from pi:")); + expect(result.text).toContain("they do not grant access to pi tools or tool names mentioned there"); + expect(result.text).toContain("Do not plan to use or claim to have used pi-only tools such as WebSearch or WebFetch"); + expect(result.text).toContain("do not claim to have searched the web unless a Cursor SDK web/search/browser/MCP tool was actually used"); + }); }); diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index b9bb8d5..19a6612 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -197,6 +197,7 @@ describe("streamCursor", () => { vi.clearAllMocks(); delete process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY; delete process.env.PI_CURSOR_REGISTER_NATIVE_TOOLS; + delete process.env.PI_CURSOR_SETTING_SOURCES; expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(0); cursorProviderTestUtils.resetCursorNativeReplayIdleDisposeMs(); nativeToolDisplayTestUtils.reset(); @@ -1512,7 +1513,7 @@ describe("streamCursor", () => { } }); - it("creates local Cursor agents without ambient setting sources that write SDK logs to the terminal", async () => { + it("does not load Cursor setting sources by default to avoid SDK startup logs in pi", async () => { const mockSend = vi.fn().mockResolvedValue({ id: "run-1", agentId: "agent-1", @@ -1537,6 +1538,58 @@ describe("streamCursor", () => { ); }); + it("allows Cursor setting sources to be enabled for users who want ambient MCP/tools", async () => { + process.env.PI_CURSOR_SETTING_SOURCES = "all"; + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel("composer-2"), makeContext(), { apiKey: "test-key" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + local: { cwd: process.cwd(), settingSources: ["all"] }, + }), + ); + }); + + it("allows Cursor setting sources to be narrowed", async () => { + process.env.PI_CURSOR_SETTING_SOURCES = "project,user"; + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const stream = streamCursor(makeModel("composer-2"), makeContext(), { apiKey: "test-key" }); + await collectEvents(stream); + + expect(mockedCreate).toHaveBeenCalledWith( + expect.objectContaining({ + local: { cwd: process.cwd(), settingSources: ["project", "user"] }, + }), + ); + }); + it("passes Cursor alias model selection back to the SDK", async () => { modelDiscoveryTestUtils.registerModelItems([ { -- 2.51.2 From fd0e513b6ed08878678f8933b6ea3979b9085982 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 09:56:40 -0600 Subject: [PATCH 035/359] Fix Cursor read preview symlink escape --- .cueloop/README.md | 233 +++++++++++++++++++++++++ .cueloop/config.jsonc | 3 + .cueloop/done.jsonc | 43 +++++ .cueloop/queue.jsonc | 255 ++++++++++++++++++++++++++++ .gitignore | 10 ++ src/cursor-tool-transcript.ts | 11 +- test/cursor-tool-transcript.test.ts | 26 ++- 7 files changed, 576 insertions(+), 5 deletions(-) create mode 100644 .cueloop/README.md create mode 100644 .cueloop/config.jsonc create mode 100644 .cueloop/done.jsonc create mode 100644 .cueloop/queue.jsonc diff --git a/.cueloop/README.md b/.cueloop/README.md new file mode 100644 index 0000000..0e8530b --- /dev/null +++ b/.cueloop/README.md @@ -0,0 +1,233 @@ + +# CueLoop runtime files + +This repo is using CueLoop. The `cueloop` executable is the primary command name. This project stores runtime state in `.cueloop/`. New repos default to `.cueloop/`. + +> This file is generated and owned by CueLoop. `cueloop init` and agent-facing write-enabled commands may refresh it when CueLoop ships a newer template. Avoid hand-editing it unless you intentionally accept that local drift may be replaced. + +## Files + +- `.cueloop/config.jsonc` — repo-local configuration. +- `.cueloop/queue.jsonc` — JSONC task queue; source of truth for active work. +- `.cueloop/done.jsonc` — JSONC archive for completed tasks; only `done`/`rejected` statuses are valid. +- `.cueloop/cache/` — runtime cache for plans, completions, sessions, and temporary state. +- `.cueloop/logs/` — debug logs; should stay gitignored. +- `.cueloop/trust.jsonc` — machine-local trust decision; should stay gitignored. + +Do not rename runtime directories manually. Use `cueloop migrate runtime-dir --check` to preview runtime migration status and `cueloop migrate runtime-dir --apply` to move supported old project state when safe. + +## Core commands + +### Bootstrap and health + +- Bootstrap repo files: + - `cueloop init` +- Check this generated README: + - `cueloop init --check` +- Verify environment readiness: + - `cueloop doctor` +- Validate queue state: + - `cueloop queue validate` + +### Queue management + +- Inspect queue: + - `cueloop queue list` + - `cueloop queue next --with-title` +- Get next task ID: + - `cueloop queue next-id` + - `cueloop queue next-id --count 7` +- Show task details: + - `cueloop queue show RQ-0001` +- Archive completed tasks: + - `cueloop queue archive` +- Repair queue issues: + - `cueloop queue repair` +- Remove queue lock: + - `cueloop queue unlock` +- Sort and search tasks: + - `cueloop queue sort` + - `cueloop queue search "authentication"` + - `cueloop queue search "TODO" --status todo` +- Queue reports: + - `cueloop queue stats` + - `cueloop queue history --days 14` + - `cueloop queue burndown --days 30` + - `cueloop queue prune --age 90 --keep-last 100` + +### Task creation and updates + +- Build a task from a request: + - `cueloop task "Add tests for X"` +- Insert fully-shaped tasks atomically: + - `cueloop task insert --input /tmp/task-insert.json` + - `cueloop task insert --dry-run --format json --input /tmp/task-insert.json` +- Update task fields from repo state: + - `cueloop task update RQ-0001` + - `cueloop task update` +- Edit task fields: + - `cueloop task edit title "New title" RQ-0001` + - `cueloop task edit tags "rust, cli" RQ-0001` +- Change task status: + - `cueloop task status doing RQ-0001` +- Show task details: + - `cueloop task show RQ-0001` + +### Execution + +- Open the macOS app (macOS-only): + - `cueloop app open` +- Run one task: + - `cueloop run one` + - `cueloop run one --phases 3` + - `cueloop run one --quick` + - `cueloop run one --include-draft` +- Run a capped loop: + - `cueloop run loop --max-tasks 1` + - `cueloop run loop --phases 2 --max-tasks 1` +- Advanced unlimited loop mode (intentional only): + - `cueloop run loop --max-tasks 0` + +### PRD, context, and scans + +- Convert PRD markdown to tasks: + - `cueloop prd create docs/prd/feature.md` + - `cueloop prd create docs/prd/feature.md --multi` + - `cueloop prd create docs/prd/feature.md --dry-run` +- Generate or update AGENTS.md: + - `cueloop context init` + - `cueloop context update --section troubleshooting` + - `cueloop context validate` +- Seed tasks from a scan: + - `cueloop scan --focus "CI gaps"` + - `cueloop scan --focus "risk audit" --runner claude --model sonnet` + +## Troubleshooting + +### Duplicate task ID error + +If `cueloop queue validate` reports a duplicate task ID, this usually means a new task was added without incrementing the ID. Do not delete tasks. + +1. Run `cueloop queue next-id` to preview the next available ID. +2. Edit `.cueloop/queue.jsonc` and change the colliding task ID. +3. Re-run `cueloop queue validate`. + +Task IDs must be unique across both `queue.jsonc` and `done.jsonc`. + +### Generating multiple task IDs + +Use `--count` to generate IDs in one call: + +```bash +cueloop queue next-id --count 7 +``` + +`next-id` does not reserve IDs. For agent or script queue growth, prefer `cueloop task insert` so CueLoop assigns IDs under the queue lock. Keep `next-id` for manual recovery or one-off JSON surgery. + +## Template variables + +Prompt templates support variable interpolation for environment variables and config values: + +- `${VAR}` — expand environment variable, leaving the literal when unset. +- `${VAR:-default}` — expand with a default value when unset. +- `{{config.agent.runner}}` — current runner. +- `{{config.agent.model}}` — current model. +- `{{config.queue.file}}` — queue file path, for example `.cueloop/queue.jsonc`. +- `{{config.queue.done_file}}` — done archive path, for example `.cueloop/done.jsonc`. +- `{{config.queue.id_prefix}}` — task ID prefix, for example `RQ`. +- `{{config.queue.id_width}}` — task ID width, for example `4`. +- `{{config.project_type}}` — project type. + +Escaping: + +- `$${VAR}` — outputs literal `${VAR}`. +- `\${VAR}` — outputs literal `${VAR}`. + +Standard placeholders like `{{USER_REQUEST}}` are still processed after variable expansion. + +## Prompt overrides + +Default prompts are embedded in the `cueloop` binary. Custom prompt files should live in `.cueloop/prompts/`. + +Useful commands: + +- `cueloop prompt worker --phase 1` +- `cueloop prompt worker --phase 2` +- `cueloop prompt worker --phase 3` +- `cueloop prompt list` +- `cueloop prompt show worker --raw` +- `cueloop prompt diff worker` +- `cueloop prompt export --all` +- `cueloop prompt sync --dry-run` +- `cueloop prompt sync` + +## Runner configuration + +CueLoop can use built-in runner IDs (`codex`, `opencode`, `gemini`, `claude`, `cursor`, `kimi`, `pi`) or plugin runner IDs. + +One-off usage: + +- `cueloop task --runner opencode --model gpt-5.2 "Add tests for X"` +- `cueloop scan --runner gemini --model gemini-3-flash-preview --focus "risk audit"` +- `cueloop task --runner claude --model opus --repo-prompt plan "Add tests for X"` +- `cueloop run one --phases 3` +- `cueloop run one --phases 2` +- `cueloop run one --quick` + +Defaults via config: + +```json +{ + "version": 2, + "agent": { + "runner": "claude", + "model": "sonnet", + "phases": 3, + "iterations": 1, + "ci_gate": { + "enabled": true, + "argv": ["make", "ci"] + } + } +} +``` + +## Three-phase workflow + +CueLoop supports a 3-phase workflow by default: + +1. **Phase 1 (Planning):** generate a detailed plan and cache it in `.cueloop/cache/plans/.md`. +2. **Phase 2 (Implementation + CI):** implement the plan and pass the configured CI gate. +3. **Phase 3 (Code Review + Completion):** review the diff, refine if needed, rerun the CI gate, and complete the task. + +Use `cueloop run one --phases 3` for the full workflow. Use `--quick` as shorthand for `--phases 1`. + +## Security: safeguard dumps and redaction + +When runner operations fail, CueLoop writes safeguard dumps to temp directories for troubleshooting. By default, dumps are redacted before writing. + +Raw, non-redacted dumps require explicit opt-in: + +```bash +CUELOOP_RAW_DUMP=1 cueloop run one +cueloop run one --debug +``` + +Security notes: + +- Never commit safeguard dumps. +- Debug mode writes raw runner output to `.cueloop/logs/debug.log`. +- Temporary safeguard dumps use CueLoop-owned temp paths; inspect the reported path when troubleshooting. + +## Common flags + +- `--quick` — shorthand for `--phases 1`. +- `--include-draft` — include draft tasks when selecting work. +- `--runner ` — override runner. +- `--model ` — override model. +- `--repo-prompt ` / `-rp` — RepoPrompt mode. +- `--git-revert-mode ` — control revert behavior on errors. +- `--git-commit-push-on` / `--git-commit-push-off` — toggle auto commit/push. +- `--debug` — capture raw output and imply raw dumps. +- `--force` — bypass locks or overwrite files where supported. +- `-v`, `--verbose` — increase output verbosity. diff --git a/.cueloop/config.jsonc b/.cueloop/config.jsonc new file mode 100644 index 0000000..12dc0a6 --- /dev/null +++ b/.cueloop/config.jsonc @@ -0,0 +1,3 @@ +{ + "version": 2 +} \ No newline at end of file diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc new file mode 100644 index 0000000..933023f --- /dev/null +++ b/.cueloop/done.jsonc @@ -0,0 +1,43 @@ +{ + "version": 1, + "tasks": [ + { + "id": "RQ-0001", + "status": "done", + "title": "Fix symlink escape in Cursor read transcript fallback", + "priority": "high", + "tags": [ + "security", + "cursor-sdk", + "transcript" + ], + "scope": [ + "src/cursor-tool-transcript.ts", + "test/cursor-tool-transcript.test.ts" + ], + "evidence": [ + "src/cursor-tool-transcript.ts:194-205 re-reads empty Cursor read results after only a textual inside-cwd check.", + "src/cursor-tool-transcript.ts:172-174 checks relative path text, while statSync/openSync follow symlinks.", + "test/cursor-tool-transcript.test.ts:43-68 covers sensitive/direct outside files but not symlink-to-outside paths." + ], + "plan": [ + "Add a regression test for an in-workspace symlink pointing to an outside file.", + "Resolve real paths for cwd and target before inside-cwd and sensitivity checks, or reject symlinks with lstatSync.", + "Run npm test and npm run typecheck." + ], + "notes": [ + "Started by agent to fix symlink escape in Cursor read transcript fallback.", + "Implemented realpath-based preview validation for cwd and target before opening fallback read preview. Added symlink-to-outside regression test. Validated with npm test -- test/cursor-tool-transcript.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." + ], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T15:56:28.437515000Z", + "completed_at": "2026-05-15T15:56:28.437515000Z", + "started_at": "2026-05-15T15:54:25.593372000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} + } + ] +} \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc new file mode 100644 index 0000000..ec46b9e --- /dev/null +++ b/.cueloop/queue.jsonc @@ -0,0 +1,255 @@ +{ + "version": 1, + "tasks": [ + { + "id": "RQ-0002", + "status": "todo", + "title": "Add prompt/context budgeting before Cursor Agent.send", + "priority": "high", + "tags": [ + "context", + "cursor-sdk", + "performance" + ], + "scope": [ + "src/context.ts", + "src/cursor-provider.ts", + "test/context.test.ts", + "test/cursor-provider.test.ts" + ], + "evidence": [ + "src/context.ts:70-100 serializes every pi message/tool result into one Cursor prompt.", + "src/cursor-provider.ts:585-586 estimates prompt tokens after building the full prompt but does not trim against model.contextWindow.", + "Long sessions or large tool outputs can exceed Cursor context despite accurate model metadata." + ], + "plan": [ + "Design a budgeted transcript builder that preserves system instructions and the latest user request.", + "Trim or summarize oldest turns and large tool outputs with an output-token reserve.", + "Add tests for oversized histories and oversized tool results.", + "Run npm test and npm run typecheck." + ], + "notes": [], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T15:45:25.103158000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} + }, + { + "id": "RQ-0003", + "status": "todo", + "title": "Preserve assistant text emitted before native Cursor tool replay", + "priority": "medium", + "tags": [ + "cursor-sdk", + "native-tool-replay", + "ux" + ], + "scope": [ + "src/cursor-provider.ts", + "test/cursor-provider.test.ts" + ], + "evidence": [ + "src/cursor-provider.ts:753-759 stores pre-tool textDeltas but does not stream them when native replay is enabled before the first tool.", + "test/cursor-provider.test.ts:476-522 currently expects firstText to be empty after Cursor emits pre-tool text." + ], + "plan": [ + "Queue or emit pre-tool text into the first replay turn before the native tool call.", + "Update tests to assert the pre-tool assistant text is preserved.", + "Verify native replay still mirrors pi toolUse/toolResult turn shape.", + "Run npm test and npm run typecheck." + ], + "notes": [], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T15:45:25.103158000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} + }, + { + "id": "RQ-0004", + "status": "todo", + "title": "Add live Cursor model catalog refresh after login/auth changes", + "priority": "medium", + "tags": [ + "models", + "auth", + "pi-extension" + ], + "scope": [ + "src/index.ts", + "src/model-discovery.ts", + "README.md", + "docs/cursor-model-ux-spec.md", + "test/index.test.ts", + "test/model-discovery.test.ts" + ], + "evidence": [ + "src/index.ts:10-31 discovers models only during async extension startup.", + "src/model-discovery.ts:583-608 falls back and tells users to /reload or restart after auth is added.", + "Installed pi registerProvider docs state post-load provider registrations take effect immediately without /reload." + ], + "plan": [ + "Add a /cursor-refresh-models command that re-runs discoverModels().", + "Call pi.registerProvider('cursor', ...) with the refreshed model list and existing streamSimple config.", + "Notify users whether a live catalog replaced fallback models or fallback remains active.", + "Update README/spec and tests.", + "Run npm test and npm run typecheck." + ], + "notes": [], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T15:45:25.103158000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} + }, + { + "id": "RQ-0005", + "status": "todo", + "title": "Improve or explicitly constrain image follow-up behavior", + "priority": "medium", + "tags": [ + "images", + "multimodal", + "ux" + ], + "scope": [ + "src/context.ts", + "test/context.test.ts", + "README.md", + "docs/cursor-model-ux-spec.md" + ], + "evidence": [ + "src/context.ts:21-37 forwards images only from the latest user message.", + "src/context.ts:39-48 replaces historical images with [image omitted from transcript].", + "docs/cursor-model-ux-spec.md:15-16 records this as a pending product decision." + ], + "plan": [ + "Choose a policy: keep current behavior with explicit prompt limitation, or add opt-in recent-image carry-forward with token/privacy budget.", + "Implement the chosen behavior and update tests.", + "Update README/spec so users understand multimodal follow-up semantics.", + "Run npm test and npm run typecheck." + ], + "notes": [], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T15:45:25.103158000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} + }, + { + "id": "RQ-0006", + "status": "todo", + "title": "Use pi session cwd instead of process.cwd where available", + "priority": "low", + "tags": [ + "pi-extension", + "cwd", + "lifecycle" + ], + "scope": [ + "src/cursor-native-tool-display.ts", + "src/cursor-provider.ts", + "test/cursor-provider.test.ts" + ], + "evidence": [ + "src/cursor-native-tool-display.ts:119-133 registers native replay tools using process.cwd() despite receiving ExtensionContext.", + "src/cursor-provider.ts:573-582 passes process.cwd() to Cursor Agent.create local cwd.", + "Installed pi extension docs expose ctx.cwd as the session working directory." + ], + "plan": [ + "Replace process.cwd() with ctx.cwd for native tool replay registration.", + "Investigate whether provider stream context/options expose session cwd; if not, document limitation or upstream a pi provider option.", + "Add/update tests for non-default cwd where feasible.", + "Run npm test and npm run typecheck." + ], + "notes": [], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T15:45:25.103158000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} + }, + { + "id": "RQ-0007", + "status": "todo", + "title": "Reduce local re-read fallback risk in Cursor tool transcripts", + "priority": "low", + "tags": [ + "transcript", + "correctness", + "cursor-sdk" + ], + "scope": [ + "src/cursor-tool-transcript.ts", + "test/cursor-tool-transcript.test.ts", + "README.md" + ], + "evidence": [ + "src/cursor-tool-transcript.ts:218-253 fills empty Cursor read content by reading the current local file.", + "That preview may show content Cursor did not actually see if the file changed after Cursor's read." + ], + "plan": [ + "Decide whether to remove local read fallback or label it explicitly as a local preview at replay time.", + "Adjust transcript and native display output accordingly.", + "Update tests and docs for empty Cursor read-result behavior.", + "Run npm test and npm run typecheck." + ], + "notes": [], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T15:45:25.103158000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} + }, + { + "id": "RQ-0008", + "status": "todo", + "title": "Add maintenance path for Cursor fallback model/context snapshot drift", + "priority": "low", + "tags": [ + "models", + "maintenance", + "release" + ], + "scope": [ + "src/model-discovery.ts", + "src/bundled-context-windows.ts", + "docs/cursor-model-ux-spec.md", + "README.md", + "package.json" + ], + "evidence": [ + "src/model-discovery.ts:22-160 hardcodes fallback Cursor catalog entries.", + "src/bundled-context-windows.ts:1-24 contains a generated context-window snapshot from 2026-05-04.", + "Cursor model IDs, aliases, defaults, and context windows can drift as Cursor changes its catalog." + ], + "plan": [ + "Add a maintainer script to refresh fallback/catalog snapshots from Cursor.models.list() and checkpoint context metadata.", + "Make generated output reviewable and documented; do not store API keys or secrets.", + "Update docs with when to run the script before releases.", + "Run npm test and npm run typecheck." + ], + "notes": [], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T15:45:25.103158000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} + } + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0b2dbd0..a7d231b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,13 @@ coverage/ recording/ demo-script.md docs/public-demo-script.md + +# CueLoop local/runtime artifacts (do not commit) +.cueloop/cache/ +.cueloop/logs/ +.cueloop/workspaces/ +.cueloop/lock/ +.cueloop/undo/ +.cueloop/webhooks/ +.cueloop/trust.json +.cueloop/trust.jsonc diff --git a/src/cursor-tool-transcript.ts b/src/cursor-tool-transcript.ts index 6d1cc49..e0e0cf8 100644 --- a/src/cursor-tool-transcript.ts +++ b/src/cursor-tool-transcript.ts @@ -1,4 +1,4 @@ -import { closeSync, openSync, readSync, statSync } from "node:fs"; +import { closeSync, openSync, readSync, realpathSync, statSync } from "node:fs"; import { isAbsolute, relative, resolve } from "node:path"; const DEFAULT_MAX_TRANSCRIPT_CHARS = 24000; @@ -194,15 +194,18 @@ function isSensitivePreviewPath(filePath: string): boolean { function readFilePreview(path: string, options: TranscriptOptions): string | undefined { const cwd = options.cwd ?? process.cwd(); const filePath = resolveFilePath(path, cwd); - if (!isPathWithinCwd(filePath, cwd) || isSensitivePreviewPath(filePath)) return undefined; const maxChars = options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS; const maxBytes = Math.max(8192, maxChars * 4); let fd: number | undefined; try { - const stat = statSync(filePath); + const realCwd = realpathSync(cwd); + const realFilePath = realpathSync(filePath); + if (!isPathWithinCwd(realFilePath, realCwd) || isSensitivePreviewPath(realFilePath)) return undefined; + + const stat = statSync(realFilePath); if (!stat.isFile()) return undefined; - fd = openSync(filePath, "r"); + fd = openSync(realFilePath, "r"); const buffer = Buffer.alloc(Math.min(stat.size, maxBytes)); const bytesRead = readSync(fd, buffer, 0, buffer.length, 0); const text = buffer.toString("utf8", 0, bytesRead); diff --git a/test/cursor-tool-transcript.test.ts b/test/cursor-tool-transcript.test.ts index 4d78e9b..8b7a670 100644 --- a/test/cursor-tool-transcript.test.ts +++ b/test/cursor-tool-transcript.test.ts @@ -1,4 +1,4 @@ -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; @@ -72,6 +72,30 @@ describe("formatCursorToolTranscript", () => { } }); + it("does not fill empty Cursor read results through workspace symlinks to outside files", () => { + const dir = mkdtempSync(join(tmpdir(), "cursor-tool-transcript-")); + const outsideDir = mkdtempSync(join(tmpdir(), "cursor-tool-transcript-outside-")); + try { + writeFileSync(join(outsideDir, "secret.txt"), "outside secret content\n"); + symlinkSync(join(outsideDir, "secret.txt"), join(dir, "linked-secret.txt")); + + const transcript = formatCursorToolTranscript( + { + name: "read", + args: { path: join(dir, "linked-secret.txt") }, + result: { status: "success", value: { content: "", totalLines: 1, fileSize: 23 } }, + }, + { cwd: dir }, + ); + + expect(transcript).toContain("read linked-secret.txt"); + expect(transcript).not.toContain("outside secret content"); + } finally { + rmSync(dir, { recursive: true, force: true }); + rmSync(outsideDir, { recursive: true, force: true }); + } + }); + it("shortens absolute workspace paths to relative paths", () => { const transcript = formatCursorToolTranscript( { -- 2.51.2 From 7c8f44b5f7ea359463ae8a76fc0686abb22011cd Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 10:04:05 -0600 Subject: [PATCH 036/359] Budget Cursor prompts before send --- .cueloop/done.jsonc | 41 +++++++++ .cueloop/queue.jsonc | 36 -------- src/context.ts | 158 ++++++++++++++++++++++++++--------- src/cursor-provider.ts | 11 ++- test/context.test.ts | 78 +++++++++++++++++ test/cursor-provider.test.ts | 73 ++++++++++++++++ 6 files changed, 321 insertions(+), 76 deletions(-) diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc index 933023f..70431dd 100644 --- a/.cueloop/done.jsonc +++ b/.cueloop/done.jsonc @@ -38,6 +38,47 @@ "blocks": [], "relates_to": [], "custom_fields": {} + }, + { + "id": "RQ-0002", + "status": "done", + "title": "Add prompt/context budgeting before Cursor Agent.send", + "priority": "high", + "tags": [ + "context", + "cursor-sdk", + "performance" + ], + "scope": [ + "src/context.ts", + "src/cursor-provider.ts", + "test/context.test.ts", + "test/cursor-provider.test.ts" + ], + "evidence": [ + "src/context.ts:70-100 serializes every pi message/tool result into one Cursor prompt.", + "src/cursor-provider.ts:585-586 estimates prompt tokens after building the full prompt but does not trim against model.contextWindow.", + "Long sessions or large tool outputs can exceed Cursor context despite accurate model metadata." + ], + "plan": [ + "Design a budgeted transcript builder that preserves system instructions and the latest user request.", + "Trim or summarize oldest turns and large tool outputs with an output-token reserve.", + "Add tests for oversized histories and oversized tool results.", + "Run npm test and npm run typecheck." + ], + "notes": [ + "Started by agent to add prompt/context budgeting before Cursor Agent.send.", + "Added model-derived prompt input budgeting before Cursor Agent.send. Preserves system/tool-boundary instructions and latest user request, keeps recent history that fits, omits older oversized history/tool output with an omission notice, and reserves estimated image tokens. Validated with npm test -- test/context.test.ts, npm test -- test/cursor-provider.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings after image-budget fix." + ], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T16:03:54.301207000Z", + "completed_at": "2026-05-15T16:03:54.301207000Z", + "started_at": "2026-05-15T15:56:50.094857000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} } ] } \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc index ec46b9e..e9f55e1 100644 --- a/.cueloop/queue.jsonc +++ b/.cueloop/queue.jsonc @@ -1,42 +1,6 @@ { "version": 1, "tasks": [ - { - "id": "RQ-0002", - "status": "todo", - "title": "Add prompt/context budgeting before Cursor Agent.send", - "priority": "high", - "tags": [ - "context", - "cursor-sdk", - "performance" - ], - "scope": [ - "src/context.ts", - "src/cursor-provider.ts", - "test/context.test.ts", - "test/cursor-provider.test.ts" - ], - "evidence": [ - "src/context.ts:70-100 serializes every pi message/tool result into one Cursor prompt.", - "src/cursor-provider.ts:585-586 estimates prompt tokens after building the full prompt but does not trim against model.contextWindow.", - "Long sessions or large tool outputs can exceed Cursor context despite accurate model metadata." - ], - "plan": [ - "Design a budgeted transcript builder that preserves system instructions and the latest user request.", - "Trim or summarize oldest turns and large tool outputs with an output-token reserve.", - "Add tests for oversized histories and oversized tool results.", - "Run npm test and npm run typecheck." - ], - "notes": [], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T15:45:25.103158000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, { "id": "RQ-0003", "status": "todo", diff --git a/src/context.ts b/src/context.ts index 86834ad..751533a 100644 --- a/src/context.ts +++ b/src/context.ts @@ -6,6 +6,15 @@ export interface CursorPrompt { images: SDKImage[]; } +export interface CursorPromptOptions { + maxInputTokens?: number; + charsPerToken?: number; + imageTokenEstimate?: number; +} + +const DEFAULT_CHARS_PER_TOKEN = 4; +const SECTION_SEPARATOR = "\n\n"; + function isTextBlock(block: { type: string }): block is { type: "text"; text: string } { return block.type === "text"; } @@ -53,8 +62,94 @@ function formatToolCall(toolCall: ToolCall): string { return `Tool call (${toolCall.name}, call ${toolCall.id}): ${args}`; } -export function buildCursorPrompt(context: Context): CursorPrompt { - const parts: string[] = [ +function formatMessage(msg: Message): string | undefined { + switch (msg.role) { + case "user": { + const text = formatContentBlocks(msg.content); + return text ? `User: ${text}` : undefined; + } + case "assistant": { + const blocks = Array.isArray(msg.content) ? msg.content : [{ type: "text" as const, text: String(msg.content) }]; + const textParts: string[] = []; + for (const block of blocks) { + if (isTextBlock(block)) { + textParts.push(block.text); + } else if (isToolCallBlock(block)) { + textParts.push(formatToolCall(block)); + } + // Omit thinking content from transcript + } + return textParts.length > 0 ? `Assistant: ${textParts.join("\n")}` : undefined; + } + case "toolResult": { + const text = formatContentBlocks(msg.content); + const label = msg.isError ? "Tool error" : "Tool result"; + return `${label} (${msg.toolName}, call ${msg.toolCallId}): ${text}`; + } + } +} + +function getLatestUserMessageIndex(messages: Message[]): number { + for (let index = messages.length - 1; index >= 0; index -= 1) { + if (messages[index].role === "user") return index; + } + return -1; +} + +function getSectionCost(section: string): number { + return section.length + SECTION_SEPARATOR.length; +} + +function applyPromptBudget( + sectionsBeforeMessages: string[], + messageSections: Array<{ index: number; text: string }>, + sectionsAfterMessages: string[], + latestUserMessageIndex: number, + options: CursorPromptOptions, +): string[] { + const maxInputTokens = options.maxInputTokens; + if (maxInputTokens === undefined || !Number.isFinite(maxInputTokens) || maxInputTokens <= 0) { + return [...sectionsBeforeMessages, ...messageSections.map((section) => section.text), ...sectionsAfterMessages]; + } + + const charsPerToken = options.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN; + const maxChars = Math.max(1, Math.floor(maxInputTokens * charsPerToken)); + const requiredMessageSections = messageSections.filter((section) => section.index === latestUserMessageIndex); + const requiredCost = [...sectionsBeforeMessages, ...requiredMessageSections.map((section) => section.text), ...sectionsAfterMessages].reduce( + (total, section) => total + getSectionCost(section), + 0, + ); + let remainingChars = maxChars - requiredCost; + const includedMessageIndexes = new Set(requiredMessageSections.map((section) => section.index)); + let omittedMessageCount = 0; + + for (let index = messageSections.length - 1; index >= 0; index -= 1) { + const section = messageSections[index]; + if (includedMessageIndexes.has(section.index)) continue; + const cost = getSectionCost(section.text); + if (cost <= remainingChars) { + includedMessageIndexes.add(section.index); + remainingChars -= cost; + continue; + } + omittedMessageCount += messageSections + .slice(0, index + 1) + .filter((candidate) => !includedMessageIndexes.has(candidate.index)).length; + break; + } + + const budgetNotice = + omittedMessageCount > 0 + ? [`[Earlier transcript omitted: ${omittedMessageCount} message${omittedMessageCount === 1 ? "" : "s"} to fit Cursor context budget]`] + : []; + const includedMessages = messageSections + .filter((section) => includedMessageIndexes.has(section.index)) + .map((section) => section.text); + return [...sectionsBeforeMessages, ...budgetNotice, ...includedMessages, ...sectionsAfterMessages]; +} + +export function buildCursorPrompt(context: Context, options: CursorPromptOptions = {}): CursorPrompt { + const sectionsBeforeMessages: string[] = [ [ "Cursor SDK tool boundary:", "Only tools exposed by the Cursor SDK in this run are callable. The pi system prompt and transcript are context only; they do not grant access to pi tools or tool names mentioned there.", @@ -64,49 +159,34 @@ export function buildCursorPrompt(context: Context): CursorPrompt { ]; if (context.systemPrompt) { - parts.push(`System instructions from pi:\n${context.systemPrompt}`); + sectionsBeforeMessages.push(`System instructions from pi:\n${context.systemPrompt}`); } - for (const msg of context.messages) { - switch (msg.role) { - case "user": { - const text = formatContentBlocks(msg.content); - if (text) parts.push(`User: ${text}`); - break; - } - case "assistant": { - const blocks = Array.isArray(msg.content) ? msg.content : [{ type: "text" as const, text: String(msg.content) }]; - const textParts: string[] = []; - for (const block of blocks) { - if (isTextBlock(block)) { - textParts.push(block.text); - } else if (isToolCallBlock(block)) { - textParts.push(formatToolCall(block)); - } - // Omit thinking content from transcript - } - if (textParts.length > 0) { - parts.push(`Assistant: ${textParts.join("\n")}`); - } - break; - } - case "toolResult": { - const text = formatContentBlocks(msg.content); - const label = msg.isError ? "Tool error" : "Tool result"; - parts.push(`${label} (${msg.toolName}, call ${msg.toolCallId}): ${text}`); - break; - } - } - } - - parts.push( + const messageSections = context.messages + .map((msg, index) => { + const text = formatMessage(msg); + return text ? { index, text } : undefined; + }) + .filter((section): section is { index: number; text: string } => section !== undefined); + const sectionsAfterMessages = [ [ "Answer the latest user request above using your capabilities. Do not assume access to pi tools.", "If the user asks for web research, do not claim to have searched the web unless a Cursor SDK web/search/browser/MCP tool was actually used.", ].join("\n"), - ); - + ]; const images = extractLatestImages(context.messages); + const imageTokenReserve = images.length * (options.imageTokenEstimate ?? 0); + const budgetOptions = + options.maxInputTokens === undefined + ? options + : { ...options, maxInputTokens: Math.max(1, options.maxInputTokens - imageTokenReserve) }; + const parts = applyPromptBudget( + sectionsBeforeMessages, + messageSections, + sectionsAfterMessages, + getLatestUserMessageIndex(context.messages), + budgetOptions, + ); - return { text: parts.join("\n\n"), images }; + return { text: parts.join(SECTION_SEPARATOR), images }; } diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 9c21fa7..2b8cb3d 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -185,6 +185,11 @@ function estimatePromptInputTokens(prompt: CursorPrompt): number { return estimateTextTokens(prompt.text) + prompt.images.length * IMAGE_TOKEN_ESTIMATE; } +function getPromptInputTokenBudget(model: Model): number { + const outputReserveTokens = Math.min(model.maxTokens, Math.max(1, Math.floor(model.contextWindow * 0.2))); + return Math.max(1, model.contextWindow - outputReserveTokens); +} + function setApproximateUsage(partial: AssistantMessage, promptInputTokens: number, outputText: string): void { partial.usage.input = promptInputTokens; partial.usage.output = estimateTextTokens(outputText); @@ -582,7 +587,11 @@ export function streamCursor( }); throwIfAborted(); - const prompt = buildCursorPrompt(context); + const prompt = buildCursorPrompt(context, { + maxInputTokens: getPromptInputTokenBudget(model), + charsPerToken: APPROX_CHARS_PER_TOKEN, + imageTokenEstimate: IMAGE_TOKEN_ESTIMATE, + }); const promptInputTokens = estimatePromptInputTokens(prompt); let thinkingContentIndex = -1; let activityTraceChars = 0; diff --git a/test/context.test.ts b/test/context.test.ts index 25a0a37..d7158c1 100644 --- a/test/context.test.ts +++ b/test/context.test.ts @@ -182,6 +182,84 @@ describe("buildCursorPrompt", () => { expect(result.images).toHaveLength(0); }); + it("budgets transcript history while preserving system prompt and latest user request", () => { + const ctx: Context = { + systemPrompt: "Always preserve this system instruction.", + messages: [ + { role: "user", content: `old request ${"x".repeat(200)}`, timestamp: 1 } satisfies UserMessage, + { + role: "assistant", + content: [{ type: "text", text: `old answer ${"y".repeat(200)}` }], + api: "cursor-sdk", + provider: "cursor", + model: "test", + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, + stopReason: "stop", + timestamp: 2, + } satisfies AssistantMessage, + { role: "user", content: "latest request must stay", timestamp: 3 } satisfies UserMessage, + ], + }; + + const result = buildCursorPrompt(ctx, { maxInputTokens: 120, charsPerToken: 1 }); + + expect(result.text).toContain("Always preserve this system instruction."); + expect(result.text).toContain("User: latest request must stay"); + expect(result.text).toContain("Answer the latest user request"); + expect(result.text).toContain("[Earlier transcript omitted: 2 messages to fit Cursor context budget]"); + expect(result.text).not.toContain("old request"); + expect(result.text).not.toContain("old answer"); + }); + + it("keeps recent transcript messages that fit the budget", () => { + const ctx: Context = { + messages: [ + { role: "user", content: `old request ${"x".repeat(1200)}`, timestamp: 1 } satisfies UserMessage, + { role: "user", content: "recent request", timestamp: 2 } satisfies UserMessage, + { + role: "toolResult", + toolCallId: "tc1", + toolName: "bash", + content: [{ type: "text", text: "recent tool output" }], + isError: false, + timestamp: 3, + } satisfies ToolResultMessage, + { role: "user", content: "latest request", timestamp: 4 } satisfies UserMessage, + ], + }; + + const result = buildCursorPrompt(ctx, { maxInputTokens: 1000, charsPerToken: 1 }); + + expect(result.text).toContain("User: latest request"); + expect(result.text).toContain("User: recent request"); + expect(result.text).toContain("Tool result (bash, call tc1): recent tool output"); + expect(result.text).not.toContain("old request"); + }); + + it("omits oversized old tool results before older text that still fits", () => { + const ctx: Context = { + messages: [ + { + role: "toolResult", + toolCallId: "tc1", + toolName: "bash", + content: [{ type: "text", text: `large output ${"z".repeat(1200)}` }], + isError: false, + timestamp: 1, + } satisfies ToolResultMessage, + { role: "user", content: "recent request", timestamp: 2 } satisfies UserMessage, + { role: "user", content: "latest request", timestamp: 3 } satisfies UserMessage, + ], + }; + + const result = buildCursorPrompt(ctx, { maxInputTokens: 1000, charsPerToken: 1 }); + + expect(result.text).toContain("User: latest request"); + expect(result.text).toContain("User: recent request"); + expect(result.text).toContain("[Earlier transcript omitted: 1 message to fit Cursor context budget]"); + expect(result.text).not.toContain("large output"); + }); + it("appends answer instruction", () => { const ctx: Context = { messages: [{ role: "user", content: "test", timestamp: 1 }], diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 19a6612..cef2034 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -1436,6 +1436,79 @@ describe("streamCursor", () => { expect(mockCancel).toHaveBeenCalled(); }); + it("budgets oversized prompt history before Cursor Agent.send", async () => { + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + const context: Context = { + systemPrompt: "Keep this system prompt.", + messages: [ + { role: "user", content: `old request ${"x".repeat(1200)}`, timestamp: 1 }, + { role: "user", content: "latest request must remain", timestamp: 2 }, + ], + }; + const smallModel = { ...makeModel("gpt-5.5@1m"), contextWindow: 250, maxTokens: 50 }; + + const stream = streamCursor(smallModel, context, { apiKey: "test-key" }); + await collectEvents(stream); + + const sentMessage = mockSend.mock.calls[0]?.[0] as { text: string }; + expect(sentMessage.text).toContain("Keep this system prompt."); + expect(sentMessage.text).toContain("latest request must remain"); + expect(sentMessage.text).toContain("Earlier transcript omitted"); + expect(sentMessage.text).not.toContain("old request"); + }); + + it("reserves image tokens when budgeting oversized prompt history", async () => { + const mockSend = vi.fn().mockResolvedValue({ + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }); + mockedCreate.mockResolvedValue({ + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + const context: Context = { + systemPrompt: "Keep image prompt compact.", + messages: [ + { role: "user", content: `old request ${"x".repeat(1200)}`, timestamp: 1 }, + { + role: "user", + content: [ + { type: "text", text: "latest image request" }, + { type: "image", data: "base64-image", mimeType: "image/png" }, + ], + timestamp: 2, + }, + ], + }; + const smallModel = { ...makeModel("gpt-5.5@1m"), contextWindow: 250, maxTokens: 50 }; + + const stream = streamCursor(smallModel, context, { apiKey: "test-key" }); + await collectEvents(stream); + + const sentMessage = mockSend.mock.calls[0]?.[0] as { text: string; images?: unknown[] }; + expect(sentMessage.text).toContain("latest image request"); + expect(sentMessage.text).toContain("Earlier transcript omitted"); + expect(sentMessage.text).not.toContain("old request"); + expect(sentMessage.images).toEqual([{ data: "base64-image", mimeType: "image/png" }]); + }); + it("forwards latest user images to Cursor Agent.send", async () => { const mockSend = vi.fn().mockResolvedValue({ id: "run-1", -- 2.51.2 From 5be7a01b5b363d173611293514ba049f7de9d5d5 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 10:06:23 -0600 Subject: [PATCH 037/359] Preserve pre-tool text in Cursor native replay --- .cueloop/done.jsonc | 38 ++++++++++++++++++++++++++++++++++++ .cueloop/queue.jsonc | 33 ------------------------------- src/cursor-provider.ts | 4 ++++ test/cursor-provider.test.ts | 5 +++-- 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc index 70431dd..18335b6 100644 --- a/.cueloop/done.jsonc +++ b/.cueloop/done.jsonc @@ -79,6 +79,44 @@ "blocks": [], "relates_to": [], "custom_fields": {} + }, + { + "id": "RQ-0003", + "status": "done", + "title": "Preserve assistant text emitted before native Cursor tool replay", + "priority": "medium", + "tags": [ + "cursor-sdk", + "native-tool-replay", + "ux" + ], + "scope": [ + "src/cursor-provider.ts", + "test/cursor-provider.test.ts" + ], + "evidence": [ + "src/cursor-provider.ts:753-759 stores pre-tool textDeltas but does not stream them when native replay is enabled before the first tool.", + "test/cursor-provider.test.ts:476-522 currently expects firstText to be empty after Cursor emits pre-tool text." + ], + "plan": [ + "Queue or emit pre-tool text into the first replay turn before the native tool call.", + "Update tests to assert the pre-tool assistant text is preserved.", + "Verify native replay still mirrors pi toolUse/toolResult turn shape.", + "Run npm test and npm run typecheck." + ], + "notes": [ + "Started by agent to preserve pre-tool assistant text during native Cursor tool replay.", + "Queued buffered pre-tool text deltas into native replay before the first native tool event and cleared the buffer to avoid duplicate fallback replay. Updated native replay test to assert text plus toolCall in the first toolUse turn. Validated with npm test -- test/cursor-provider.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." + ], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T16:06:18.803764000Z", + "completed_at": "2026-05-15T16:06:18.803764000Z", + "started_at": "2026-05-15T16:04:13.282764000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} } ] } \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc index e9f55e1..d6e4a09 100644 --- a/.cueloop/queue.jsonc +++ b/.cueloop/queue.jsonc @@ -1,39 +1,6 @@ { "version": 1, "tasks": [ - { - "id": "RQ-0003", - "status": "todo", - "title": "Preserve assistant text emitted before native Cursor tool replay", - "priority": "medium", - "tags": [ - "cursor-sdk", - "native-tool-replay", - "ux" - ], - "scope": [ - "src/cursor-provider.ts", - "test/cursor-provider.test.ts" - ], - "evidence": [ - "src/cursor-provider.ts:753-759 stores pre-tool textDeltas but does not stream them when native replay is enabled before the first tool.", - "test/cursor-provider.test.ts:476-522 currently expects firstText to be empty after Cursor emits pre-tool text." - ], - "plan": [ - "Queue or emit pre-tool text into the first replay turn before the native tool call.", - "Update tests to assert the pre-tool assistant text is preserved.", - "Verify native replay still mirrors pi toolUse/toolResult turn shape.", - "Run npm test and npm run typecheck." - ], - "notes": [], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T15:45:25.103158000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, { "id": "RQ-0004", "status": "todo", diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index 2b8cb3d..ff56e06 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -739,6 +739,10 @@ export function streamCursor( } if (useNativeToolReplay && canRenderCursorToolNatively(display.toolName) && liveRun) { + if (!nativeToolReplayStarted && textDeltas.length > 0) { + for (const text of textDeltas) queueCursorNativeEvent(liveRun, { type: "text-delta", text }); + textDeltas.length = 0; + } nativeToolReplayStarted = true; const id = `${nativeReplayId}-tool-${++nativeToolDisplayCounter}`; queueCursorNativeEvent(liveRun, { diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index cef2034..a56c70e 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -479,10 +479,11 @@ describe("streamCursor", () => { const firstText = firstEvents.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); const toolCall = firstDone.message.content.find((block: any) => block.type === "toolCall"); - expect(firstText).toBe(""); + expect(firstText).toBe("I am checking files."); expect(firstDone.reason).toBe("toolUse"); expect(firstDone.message.stopReason).toBe("toolUse"); - expect(firstDone.message.content.map((block: any) => block.type)).toEqual(["toolCall"]); + expect(firstDone.message.content.map((block: any) => block.type)).toEqual(["text", "toolCall"]); + expect(firstDone.message.content[0]).toEqual({ type: "text", text: "I am checking files." }); expect(toolCall.name).toBe("read"); expect(firstEvents.some((event: any) => event.type === "toolcall_delta")).toBe(true); -- 2.51.2 From 8d9232de05e7dee43ef40890bb87901f7eb8eab2 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 10:11:15 -0600 Subject: [PATCH 038/359] Refresh Cursor model catalog without reload --- .cueloop/done.jsonc | 44 +++++++++++++++++++ .cueloop/queue.jsonc | 39 ----------------- README.md | 10 ++--- docs/cursor-model-ux-spec.md | 4 +- src/index.ts | 43 +++++++++++++++---- src/model-discovery.ts | 2 +- test/index.test.ts | 82 ++++++++++++++++++++++++++++++++++-- test/model-discovery.test.ts | 4 +- 8 files changed, 167 insertions(+), 61 deletions(-) diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc index 18335b6..789c833 100644 --- a/.cueloop/done.jsonc +++ b/.cueloop/done.jsonc @@ -117,6 +117,50 @@ "blocks": [], "relates_to": [], "custom_fields": {} + }, + { + "id": "RQ-0004", + "status": "done", + "title": "Add live Cursor model catalog refresh after login/auth changes", + "priority": "medium", + "tags": [ + "models", + "auth", + "pi-extension" + ], + "scope": [ + "src/index.ts", + "src/model-discovery.ts", + "README.md", + "docs/cursor-model-ux-spec.md", + "test/index.test.ts", + "test/model-discovery.test.ts" + ], + "evidence": [ + "src/index.ts:10-31 discovers models only during async extension startup.", + "src/model-discovery.ts:583-608 falls back and tells users to /reload or restart after auth is added.", + "Installed pi registerProvider docs state post-load provider registrations take effect immediately without /reload." + ], + "plan": [ + "Add a /cursor-refresh-models command that re-runs discoverModels().", + "Call pi.registerProvider('cursor', ...) with the refreshed model list and existing streamSimple config.", + "Notify users whether a live catalog replaced fallback models or fallback remains active.", + "Update README/spec and tests.", + "Run npm test and npm run typecheck." + ], + "notes": [ + "Started by agent to add live Cursor model catalog refresh.", + "Added /cursor-refresh-models command that re-runs discoverModels and re-registers the cursor provider with refreshed models and streamCursor without /reload. Updated fallback guidance in source, README, UX spec, and tests. Validated with npm test -- test/index.test.ts test/model-discovery.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." + ], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T16:11:11.758300000Z", + "completed_at": "2026-05-15T16:11:11.758300000Z", + "started_at": "2026-05-15T16:06:36.556384000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} } ] } \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc index d6e4a09..da687d5 100644 --- a/.cueloop/queue.jsonc +++ b/.cueloop/queue.jsonc @@ -1,45 +1,6 @@ { "version": 1, "tasks": [ - { - "id": "RQ-0004", - "status": "todo", - "title": "Add live Cursor model catalog refresh after login/auth changes", - "priority": "medium", - "tags": [ - "models", - "auth", - "pi-extension" - ], - "scope": [ - "src/index.ts", - "src/model-discovery.ts", - "README.md", - "docs/cursor-model-ux-spec.md", - "test/index.test.ts", - "test/model-discovery.test.ts" - ], - "evidence": [ - "src/index.ts:10-31 discovers models only during async extension startup.", - "src/model-discovery.ts:583-608 falls back and tells users to /reload or restart after auth is added.", - "Installed pi registerProvider docs state post-load provider registrations take effect immediately without /reload." - ], - "plan": [ - "Add a /cursor-refresh-models command that re-runs discoverModels().", - "Call pi.registerProvider('cursor', ...) with the refreshed model list and existing streamSimple config.", - "Notify users whether a live catalog replaced fallback models or fallback remains active.", - "Update README/spec and tests.", - "Run npm test and npm run typecheck." - ], - "notes": [], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T15:45:25.103158000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, { "id": "RQ-0005", "status": "todo", diff --git a/README.md b/README.md index ae5242f..13e8843 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ pi --model cursor/composer-2 3. In pi, run `/login`, choose `Use an API key`, choose `Cursor`, and paste your Cursor API key. -If pi started without a key, run `/reload` or restart pi after `/login` to refresh the full live Cursor model catalog. Inside pi, use `/model` to choose another Cursor model. +If pi started without a key, run `/cursor-refresh-models` after `/login` to refresh the full live Cursor model catalog without restarting pi. Inside pi, use `/model` to choose another Cursor model. ## Requirements @@ -83,7 +83,7 @@ Then, inside pi: 4. Paste your Cursor API key. 5. The key is saved in pi's native `~/.pi/agent/auth.json`. -If pi started without a key, fallback Cursor models still register so `/login` is reachable. After `/login`, fallback model runs can use the stored key, but `/reload` or a restart is needed to refresh the full live Cursor model catalog discovered from the Cursor SDK. +If pi started without a key, fallback Cursor models still register so `/login` is reachable. After `/login`, fallback model runs can use the stored key, and `/cursor-refresh-models` refreshes the full live Cursor model catalog discovered from the Cursor SDK without restarting pi. Environment setup: @@ -113,7 +113,7 @@ pi --list-models cursor Expected behavior: - with a valid key, Cursor models appear under the `cursor` provider -- if discovery cannot authenticate or reach Cursor, pi may still show fallback Cursor models; after adding auth with `/login`, fallback model runs can use the saved key, and `/reload` or restart refreshes the live catalog +- if discovery cannot authenticate or reach Cursor, pi may still show fallback Cursor models; after adding auth with `/login`, fallback model runs can use the saved key, and `/cursor-refresh-models` refreshes the live catalog Smoke test: @@ -206,7 +206,7 @@ If no key is available from `/login`, `CURSOR_API_KEY`, or `--api-key`, model di - `claude-sonnet-4-6@1m`, `claude-sonnet-4-6@200k` - `claude-opus-4-7@1m`, `claude-opus-4-7@300k` -Fallback models are a conservative startup model list. Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-key`. If you add auth after startup, run `/reload` or restart pi to refresh the full live Cursor model catalog. +Fallback models are a conservative startup model list. Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-key`. If you add auth after startup, run `/cursor-refresh-models` to refresh the full live Cursor model catalog without restarting pi. ## Limits @@ -223,7 +223,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ### I can see Cursor models, but runs fail -You may be seeing fallback startup models or a missing/invalid key. In interactive pi, run `/login`, choose `Use an API key`, choose `Cursor`, paste the key, then run `/reload` or restart pi. +You may be seeing fallback startup models or a missing/invalid key. In interactive pi, run `/login`, choose `Use an API key`, choose `Cursor`, paste the key, then run `/cursor-refresh-models`. You can also restart pi with a key in the same shell or launcher that starts pi: diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 48edc80..ecc0dca 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -66,7 +66,7 @@ Discovery resolves `apiKey` in this order: 2. Stored pi auth for provider `cursor` from `AuthStorage.create().getApiKey("cursor", { includeFallback: false })`. 3. `CURSOR_API_KEY`. -Users can persist the stored key through `/login` -> `Use an API key` -> `Cursor`. If auth is added after startup, fallback models can run once pi resolves the saved key for provider requests, but `/reload` or restart is required to refresh the full live Cursor model catalog. +Users can persist the stored key through `/login` -> `Use an API key` -> `Cursor`. If auth is added after startup, fallback models can run once pi resolves the saved key for provider requests, and `/cursor-refresh-models` refreshes the full live Cursor model catalog without restarting pi. For each model, use: @@ -77,7 +77,7 @@ For each model, use: - `model.variants` - default variant: `variant.isDefault === true`, else first variant -This means new Cursor models and changed Cursor parameters are picked up after reload/restart. +This means new Cursor models and changed Cursor parameters are picked up after `/cursor-refresh-models`, reload, or restart. Pi model metadata is also a source of truth for pi-native behavior: diff --git a/src/index.ts b/src/index.ts index a6798f6..ea3e3fd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,24 @@ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import type { ExtensionAPI, ProviderConfig, ProviderModelConfig } from "@earendil-works/pi-coding-agent"; import { discoverModels, type CursorModelFallbackIssue } from "./model-discovery.js"; import { registerCursorFastControls } from "./cursor-state.js"; import { registerCursorNativeToolDisplay } from "./cursor-native-tool-display.js"; import { streamCursor } from "./cursor-provider.js"; +function createCursorProviderConfig(models: ProviderModelConfig[]): ProviderConfig { + return { + name: "Cursor", + baseUrl: "https://cursor.com", + apiKey: "CURSOR_API_KEY", + api: "cursor-sdk", + models, + streamSimple: streamCursor, + }; +} + +function registerCursorProvider(pi: ExtensionAPI, models: ProviderModelConfig[]): void { + pi.registerProvider("cursor", createCursorProviderConfig(models)); +} + export default async function (pi: ExtensionAPI) { registerCursorFastControls(pi); registerCursorNativeToolDisplay(pi); @@ -21,12 +36,24 @@ export default async function (pi: ExtensionAPI) { }); } - pi.registerProvider("cursor", { - name: "Cursor", - baseUrl: "https://cursor.com", - apiKey: "CURSOR_API_KEY", - api: "cursor-sdk", - models, - streamSimple: streamCursor, + pi.registerCommand("cursor-refresh-models", { + description: "Refresh the live Cursor model catalog without restarting pi", + handler: async (_args, ctx) => { + let refreshFallbackIssue: CursorModelFallbackIssue | undefined; + const refreshedModels = await discoverModels({ + onFallback: (issue) => { + refreshFallbackIssue = issue; + }, + }); + registerCursorProvider(pi, refreshedModels); + if (!ctx.hasUI) return; + if (refreshFallbackIssue) { + ctx.ui.notify(`Cursor model catalog refresh still using fallback models: ${refreshFallbackIssue.message}`, "warning"); + } else { + ctx.ui.notify(`Cursor model catalog refreshed with ${refreshedModels.length} model${refreshedModels.length === 1 ? "" : "s"}.`, "info"); + } + }, }); + + registerCursorProvider(pi, models); } diff --git a/src/model-discovery.ts b/src/model-discovery.ts index 43f2047..cfa4d44 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -17,7 +17,7 @@ const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const TEXT_AND_IMAGE_INPUT: ProviderModelConfig["input"] = ["text", "image"]; const AUTH_SETUP_HINT = "/login (Use an API key -> Cursor), CURSOR_API_KEY, or --api-key"; const CATALOG_REFRESH_HINT = - "After adding auth to an already-started pi session, run /reload or restart pi to refresh the full live Cursor model catalog."; + "After adding auth to an already-started pi session, run /cursor-refresh-models to refresh the full live Cursor model catalog without restarting pi."; const FALLBACK_MODEL_ITEMS: ModelListItem[] = [ { diff --git a/test/index.test.ts b/test/index.test.ts index 29a23b1..a8f4d8d 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import type { ExtensionAPI, ExtensionContext, ProviderConfig, ToolDefinition, ToolInfo } from "@earendil-works/pi-coding-agent"; +import type { ExtensionAPI, ExtensionContext, ProviderConfig, RegisteredCommand, ToolDefinition, ToolInfo } from "@earendil-works/pi-coding-agent"; import { Type, type TSchema } from "typebox"; vi.mock("../src/model-discovery.js", () => ({ @@ -54,6 +54,7 @@ async function runSessionStartHandlers(pi: ReturnType, ctxO function createMockPi(existingTools?: ToolInfo[]) { const registered: Array<{ name: string; config: ProviderConfig }> = []; + const commands = new Map(); const tools: RegisteredTool[] = []; const handlers = new Map(); const initialTools = existingTools ?? ["read", "bash", "ls"].map(createBuiltinToolInfo); @@ -62,7 +63,9 @@ function createMockPi(existingTools?: ToolInfo[]) { registered.push({ name, config }); }), registerFlag: vi.fn(), - registerCommand: vi.fn(), + registerCommand: vi.fn((name: string, command: RegisteredCommand) => { + commands.set(name, command); + }), registerTool: vi.fn((tool: RegisteredTool) => { tools.push(tool); }), @@ -86,6 +89,7 @@ function createMockPi(existingTools?: ToolInfo[]) { getFlag: vi.fn().mockReturnValue(false), appendEntry: vi.fn(), _registered: registered, + _commands: commands, _tools: tools, _handlers: handlers, }; @@ -130,6 +134,10 @@ describe("extension factory", () => { "cursor-fast", expect.objectContaining({ description: expect.stringContaining("Toggle Cursor fast") }), ); + expect(pi.registerCommand).toHaveBeenCalledWith( + "cursor-refresh-models", + expect.objectContaining({ description: expect.stringContaining("Refresh the live Cursor model catalog") }), + ); expect(pi.registerTool).toHaveBeenCalledTimes(3); expect(pi._tools.map((tool) => tool.name)).toEqual(["read", "bash", "ls"]); expect(pi.on).toHaveBeenCalledWith("session_start", expect.any(Function)); @@ -176,12 +184,78 @@ describe("extension factory", () => { expect(call.config.models).toHaveLength(2); }); + it("refreshes Cursor models through a live command without reload", async () => { + const startupModels = [ + { + id: "composer-2", + name: "Cursor Composer 2", + reasoning: false, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + ]; + const refreshedModels = [ + { + id: "gpt-5.5@1m", + name: "GPT-5.5 @ 1m", + reasoning: true, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1000000, + maxTokens: 16384, + }, + ]; + mockedDiscover.mockResolvedValueOnce(startupModels).mockResolvedValueOnce(refreshedModels); + const pi = createMockPi(); + await extensionFactory(pi as unknown as ExtensionAPI); + const notify = vi.fn(); + + await pi._commands.get("cursor-refresh-models")!.handler("", { + hasUI: true, + ui: { notify, setStatus: vi.fn() }, + sessionManager: { getBranch: vi.fn(() => []) }, + } as unknown as ExtensionContext); + + expect(mockedDiscover).toHaveBeenCalledTimes(2); + expect(pi.registerProvider).toHaveBeenCalledTimes(2); + expect(pi._registered[0].config.models).toBe(startupModels); + expect(pi._registered[1].config.models).toBe(refreshedModels); + expect(pi._registered[1].config.streamSimple).toBe(mockedStreamCursor); + expect(notify).toHaveBeenCalledWith("Cursor model catalog refreshed with 1 model.", "info"); + }); + + it("warns when live Cursor model refresh still uses fallback models", async () => { + mockedDiscover + .mockResolvedValueOnce([]) + .mockImplementationOnce(async (options: DiscoverOptions) => { + options.onFallback({ reason: "missing-api-key", message: "missing key; using fallback models" }); + return []; + }); + const pi = createMockPi(); + await extensionFactory(pi as unknown as ExtensionAPI); + const notify = vi.fn(); + + await pi._commands.get("cursor-refresh-models")!.handler("", { + hasUI: true, + ui: { notify, setStatus: vi.fn() }, + sessionManager: { getBranch: vi.fn(() => []) }, + } as unknown as ExtensionContext); + + expect(pi.registerProvider).toHaveBeenCalledTimes(2); + expect(notify).toHaveBeenCalledWith( + "Cursor model catalog refresh still using fallback models: missing key; using fallback models", + "warning", + ); + }); + it("notifies interactive users when fallback models are registered", async () => { mockedDiscover.mockImplementationOnce(async (options: DiscoverOptions) => { options.onFallback({ reason: "missing-api-key", message: - "Cursor model discovery needs an API key from /login (Use an API key -> Cursor), CURSOR_API_KEY, or --api-key. Using fallback Cursor models so /login and model selection still work; fallback models can run once auth exists. After adding auth to an already-started pi session, run /reload or restart pi to refresh the full live Cursor model catalog.", + "Cursor model discovery needs an API key from /login (Use an API key -> Cursor), CURSOR_API_KEY, or --api-key. Using fallback Cursor models so /login and model selection still work; fallback models can run once auth exists. After adding auth to an already-started pi session, run /cursor-refresh-models to refresh the full live Cursor model catalog without restarting pi.", }); return [ { @@ -209,7 +283,7 @@ describe("extension factory", () => { await sessionHandlers.at(-1)!({}, ctx); expect(notify).toHaveBeenCalledWith( - "Cursor model discovery needs an API key from /login (Use an API key -> Cursor), CURSOR_API_KEY, or --api-key. Using fallback Cursor models so /login and model selection still work; fallback models can run once auth exists. After adding auth to an already-started pi session, run /reload or restart pi to refresh the full live Cursor model catalog.", + "Cursor model discovery needs an API key from /login (Use an API key -> Cursor), CURSOR_API_KEY, or --api-key. Using fallback Cursor models so /login and model selection still work; fallback models can run once auth exists. After adding auth to an already-started pi session, run /cursor-refresh-models to refresh the full live Cursor model catalog without restarting pi.", "warning", ); }); diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index 5001069..c3b4498 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -78,7 +78,7 @@ describe("discoverModels", () => { expect(issues[0].message).toContain("/login"); expect(issues[0].message).toContain("--api-key"); expect(issues[0].message).toContain("fallback models can run once auth exists"); - expect(issues[0].message).toContain("/reload"); + expect(issues[0].message).toContain("/cursor-refresh-models"); expect(issues[0].message).not.toContain("will fail until pi is restarted"); expect(mockedList).not.toHaveBeenCalled(); }); @@ -822,7 +822,7 @@ describe("discoverModels", () => { }), ]); expect(issues[0].message).toContain("/login"); - expect(issues[0].message).toContain("/reload"); + expect(issues[0].message).toContain("/cursor-refresh-models"); }); it("uses id as name when displayName is missing", async () => { -- 2.51.2 From 6bf5b3a28d46a115387827d1c4db5df4b2f49e89 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 10:13:32 -0600 Subject: [PATCH 039/359] Clarify Cursor image follow-up limits --- .cueloop/done.jsonc | 41 ++++++++++++++++++++++++++++++++++++ .cueloop/queue.jsonc | 36 ------------------------------- README.md | 2 +- docs/cursor-model-ux-spec.md | 2 +- src/context.ts | 1 + test/context.test.ts | 12 ++++++++--- 6 files changed, 53 insertions(+), 41 deletions(-) diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc index 789c833..d49f2b1 100644 --- a/.cueloop/done.jsonc +++ b/.cueloop/done.jsonc @@ -161,6 +161,47 @@ "blocks": [], "relates_to": [], "custom_fields": {} + }, + { + "id": "RQ-0005", + "status": "done", + "title": "Improve or explicitly constrain image follow-up behavior", + "priority": "medium", + "tags": [ + "images", + "multimodal", + "ux" + ], + "scope": [ + "src/context.ts", + "test/context.test.ts", + "README.md", + "docs/cursor-model-ux-spec.md" + ], + "evidence": [ + "src/context.ts:21-37 forwards images only from the latest user message.", + "src/context.ts:39-48 replaces historical images with [image omitted from transcript].", + "docs/cursor-model-ux-spec.md:15-16 records this as a pending product decision." + ], + "plan": [ + "Choose a policy: keep current behavior with explicit prompt limitation, or add opt-in recent-image carry-forward with token/privacy budget.", + "Implement the chosen behavior and update tests.", + "Update README/spec so users understand multimodal follow-up semantics.", + "Run npm test and npm run typecheck." + ], + "notes": [ + "Started by agent to clarify image follow-up behavior.", + "Chose explicit-constraining policy for multimodal follow-ups. Added prompt boundary that only latest-user images are available as bytes and Cursor should ask for reattachment/description for prior images. Updated context tests, README, and UX spec. Validated with npm test -- test/context.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." + ], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T16:13:29.234095000Z", + "completed_at": "2026-05-15T16:13:29.234095000Z", + "started_at": "2026-05-15T16:11:20.530130000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} } ] } \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc index da687d5..a35c3f6 100644 --- a/.cueloop/queue.jsonc +++ b/.cueloop/queue.jsonc @@ -1,42 +1,6 @@ { "version": 1, "tasks": [ - { - "id": "RQ-0005", - "status": "todo", - "title": "Improve or explicitly constrain image follow-up behavior", - "priority": "medium", - "tags": [ - "images", - "multimodal", - "ux" - ], - "scope": [ - "src/context.ts", - "test/context.test.ts", - "README.md", - "docs/cursor-model-ux-spec.md" - ], - "evidence": [ - "src/context.ts:21-37 forwards images only from the latest user message.", - "src/context.ts:39-48 replaces historical images with [image omitted from transcript].", - "docs/cursor-model-ux-spec.md:15-16 records this as a pending product decision." - ], - "plan": [ - "Choose a policy: keep current behavior with explicit prompt limitation, or add opt-in recent-image carry-forward with token/privacy budget.", - "Implement the chosen behavior and update tests.", - "Update README/spec so users understand multimodal follow-up semantics.", - "Run npm test and npm run typecheck." - ], - "notes": [], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T15:45:25.103158000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, { "id": "RQ-0006", "status": "todo", diff --git a/README.md b/README.md index 13e8843..3cc74d7 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ If you do not see `cursor fast`, fast mode is off. ## Images -Images from the latest user message are forwarded to Cursor. Historical images are kept out of the transcript. The extension advertises `text` and `image` input for Cursor models because Cursor's SDK accepts image messages and Cursor models are expected to support them. +Images from the latest user message are forwarded to Cursor. Historical images are kept out of the transcript and appear only as `[image omitted from transcript]` placeholders, so follow-up questions about an earlier image should reattach the image or include a textual description. The extension advertises `text` and `image` input for Cursor models because Cursor's SDK accepts image messages and Cursor models are expected to support them. ## Fallback models diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index ecc0dca..a77fa2c 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -13,7 +13,7 @@ Current implementation notes: - Cursor `fast` is extension state, not model identity. - Cursor fast status uses `ctx.ui.setStatus()`; the default pi footer remains intact. - Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`. -- Product decision pending: image payload forwarding currently sends images only from the latest user message. If the latest user turn is plain text after an earlier image turn, the transcript keeps an `[image omitted from transcript]` placeholder but no image bytes are sent to Cursor. Changing this to carry images forward across turns requires a deliberate product decision about token cost, privacy, stale visual context, and expected multimodal follow-up behavior. +- Image payload forwarding sends images only from the latest user message. If the latest user turn is plain text after an earlier image turn, the transcript keeps an `[image omitted from transcript]` placeholder but no image bytes are sent to Cursor. The prompt explicitly tells Cursor that prior image bytes are unavailable and to ask the user to reattach or describe a prior image when needed. Carrying images forward across turns remains a future product decision because it affects token cost, privacy, stale visual context, and expected multimodal follow-up behavior. - `@cursor/sdk` is a package dependency of this extension; users should not need a global SDK install. - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. Users can opt in with `PI_CURSOR_SETTING_SOURCES=all` or narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. diff --git a/src/context.ts b/src/context.ts index 751533a..56c47a7 100644 --- a/src/context.ts +++ b/src/context.ts @@ -155,6 +155,7 @@ export function buildCursorPrompt(context: Context, options: CursorPromptOptions "Only tools exposed by the Cursor SDK in this run are callable. The pi system prompt and transcript are context only; they do not grant access to pi tools or tool names mentioned there.", "If the user asks you to search, fetch, browse, or research the web, use an actual Cursor SDK web/search/browser/MCP tool call. If no such Cursor SDK tool is available, say that web search is not configured for this Cursor SDK run.", "Do not plan to use or claim to have used pi-only tools such as WebSearch or WebFetch unless the Cursor SDK actually exposes and executes that tool in this run.", + "Image payload boundary: only images attached to the latest user message are available as image bytes. Earlier images appear only as [image omitted from transcript] placeholders; ask the user to reattach or describe a prior image if the latest request depends on it.", ].join("\n"), ]; diff --git a/test/context.test.ts b/test/context.test.ts index d7158c1..48ebabb 100644 --- a/test/context.test.ts +++ b/test/context.test.ts @@ -159,6 +159,12 @@ describe("buildCursorPrompt", () => { expect(result.images[0]).toEqual({ data: "newbase64", mimeType: "image/jpeg" }); }); + it("explains that only latest user images are available as image bytes", () => { + const result = buildCursorPrompt({ messages: [{ role: "user", content: "test", timestamp: 1 }] }); + expect(result.text).toContain("only images attached to the latest user message are available as image bytes"); + expect(result.text).toContain("ask the user to reattach or describe a prior image"); + }); + it("replaces historical images with placeholder text", () => { const ctx: Context = { messages: [ @@ -214,7 +220,7 @@ describe("buildCursorPrompt", () => { it("keeps recent transcript messages that fit the budget", () => { const ctx: Context = { messages: [ - { role: "user", content: `old request ${"x".repeat(1200)}`, timestamp: 1 } satisfies UserMessage, + { role: "user", content: `old request ${"x".repeat(3000)}`, timestamp: 1 } satisfies UserMessage, { role: "user", content: "recent request", timestamp: 2 } satisfies UserMessage, { role: "toolResult", @@ -228,7 +234,7 @@ describe("buildCursorPrompt", () => { ], }; - const result = buildCursorPrompt(ctx, { maxInputTokens: 1000, charsPerToken: 1 }); + const result = buildCursorPrompt(ctx, { maxInputTokens: 1500, charsPerToken: 1 }); expect(result.text).toContain("User: latest request"); expect(result.text).toContain("User: recent request"); @@ -252,7 +258,7 @@ describe("buildCursorPrompt", () => { ], }; - const result = buildCursorPrompt(ctx, { maxInputTokens: 1000, charsPerToken: 1 }); + const result = buildCursorPrompt(ctx, { maxInputTokens: 1150, charsPerToken: 1 }); expect(result.text).toContain("User: latest request"); expect(result.text).toContain("User: recent request"); -- 2.51.2 From 19b61b9039166e569935a9f8ac703538897b0df1 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 10:20:10 -0600 Subject: [PATCH 040/359] Use session cwd for native Cursor replay --- .cueloop/done.jsonc | 40 +++++++++++++++++++++++++++++++ .cueloop/queue.jsonc | 28 ++++++++++------------ src/cursor-native-tool-display.ts | 2 +- src/cursor-provider.ts | 2 ++ test/index.test.ts | 30 +++++++++++++++++++++-- 5 files changed, 84 insertions(+), 18 deletions(-) diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc index d49f2b1..70276ae 100644 --- a/.cueloop/done.jsonc +++ b/.cueloop/done.jsonc @@ -202,6 +202,46 @@ "blocks": [], "relates_to": [], "custom_fields": {} + }, + { + "id": "RQ-0006", + "status": "done", + "title": "Use pi session cwd instead of process.cwd where available", + "priority": "low", + "tags": [ + "pi-extension", + "cwd", + "lifecycle" + ], + "scope": [ + "src/cursor-native-tool-display.ts", + "src/cursor-provider.ts", + "test/cursor-provider.test.ts" + ], + "evidence": [ + "src/cursor-native-tool-display.ts:119-133 registers native replay tools using process.cwd() despite receiving ExtensionContext.", + "src/cursor-provider.ts:573-582 passes process.cwd() to Cursor Agent.create local cwd.", + "Installed pi extension docs expose ctx.cwd as the session working directory." + ], + "plan": [ + "Replace process.cwd() with ctx.cwd for native tool replay registration.", + "Investigate whether provider stream context/options expose session cwd; if not, document limitation or upstream a pi provider option.", + "Add/update tests for non-default cwd where feasible.", + "Run npm test and npm run typecheck." + ], + "notes": [ + "Started by agent to use pi session cwd where available.", + "Native Cursor tool replay now registers wrapped read/bash/ls tools with ExtensionContext.cwd instead of process.cwd. Provider streamCursor remains on process.cwd because pi-ai Context/SimpleStreamOptions do not expose session cwd; documented in code and added follow-up RQ-0009. Added test proving native read resolves relative paths against session cwd. Validated with npm test -- test/index.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings after RQ-0009 follow-up." + ], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T16:20:04.637091000Z", + "completed_at": "2026-05-15T16:20:04.637091000Z", + "started_at": "2026-05-15T16:13:36.989387000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} } ] } \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc index a35c3f6..962e8b8 100644 --- a/.cueloop/queue.jsonc +++ b/.cueloop/queue.jsonc @@ -2,35 +2,33 @@ "version": 1, "tasks": [ { - "id": "RQ-0006", + "id": "RQ-0009", "status": "todo", - "title": "Use pi session cwd instead of process.cwd where available", + "title": "Use pi session cwd in Cursor provider stream when pi exposes it", "priority": "low", "tags": [ "pi-extension", "cwd", - "lifecycle" + "provider" ], "scope": [ - "src/cursor-native-tool-display.ts", - "src/cursor-provider.ts", - "test/cursor-provider.test.ts" + "src/cursor-provider.ts" ], "evidence": [ - "src/cursor-native-tool-display.ts:119-133 registers native replay tools using process.cwd() despite receiving ExtensionContext.", - "src/cursor-provider.ts:573-582 passes process.cwd() to Cursor Agent.create local cwd.", - "Installed pi extension docs expose ctx.cwd as the session working directory." + "RQ-0006 replaced process.cwd() with ctx.cwd for native tool replay where ExtensionContext is available.", + "src/cursor-provider.ts still uses process.cwd() because pi-ai Context/SimpleStreamOptions do not currently expose session cwd to streamSimple.", + "Reviewer noted this provider cwd limitation as optional future debt." ], "plan": [ - "Replace process.cwd() with ctx.cwd for native tool replay registration.", - "Investigate whether provider stream context/options expose session cwd; if not, document limitation or upstream a pi provider option.", - "Add/update tests for non-default cwd where feasible.", + "Monitor or update against pi provider API if Context/SimpleStreamOptions gains session cwd.", + "Replace process.cwd() in streamCursor Agent.create local cwd with the provider/session cwd when available.", + "Add a provider-level test proving non-process cwd is passed to Cursor Agent.create.", "Run npm test and npm run typecheck." ], "notes": [], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T15:45:25.103158000Z", + "request": "Follow-up from RQ-0006 reviewer finding.", + "created_at": "2026-05-15T16:19:57.688396000Z", + "updated_at": "2026-05-15T16:19:57.688396000Z", "depends_on": [], "blocks": [], "relates_to": [], diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index 3300a8d..ad51f9c 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -122,7 +122,7 @@ function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionCont return; } - const cwd = process.cwd(); + const cwd = ctx.cwd; const skippedToolNames: string[] = []; for (const toolName of NATIVE_CURSOR_TOOL_NAMES) { if (registeredNativeToolNames.has(toolName)) continue; diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index ff56e06..c25aa60 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -575,6 +575,8 @@ export function streamCursor( if (!apiKey) throw new Error(MISSING_API_KEY_MESSAGE); resolvedApiKey = apiKey; + // pi-ai Context/SimpleStreamOptions do not currently expose ExtensionContext.cwd; + // provider calls use the process cwd until pi exposes a session cwd to streamSimple. const cwd = process.cwd(); const fastEnabled = getEffectiveFastForModelId(model.id); const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled); diff --git a/test/index.test.ts b/test/index.test.ts index a8f4d8d..5342f57 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,3 +1,6 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { describe, it, expect, vi, beforeEach } from "vitest"; import type { ExtensionAPI, ExtensionContext, ProviderConfig, RegisteredCommand, ToolDefinition, ToolInfo } from "@earendil-works/pi-coding-agent"; import { Type, type TSchema } from "typebox"; @@ -24,7 +27,7 @@ const mockedStreamCursor = vi.mocked(streamCursor); type DiscoverOptions = Parameters[0]; type RegisteredTool = ToolDefinition; -type TestExtensionContext = Pick & { +type TestExtensionContext = Pick & { ui: Pick; sessionManager: Pick; }; @@ -42,6 +45,7 @@ function createBuiltinToolInfo(name: string): ToolInfo { async function runSessionStartHandlers(pi: ReturnType, ctxOverrides: Partial = {}): Promise { const notify = vi.fn(); const ctx: TestExtensionContext = { + cwd: process.cwd(), hasUI: true, ui: { notify, setStatus: vi.fn() }, sessionManager: { getBranch: vi.fn(() => []) }, @@ -213,6 +217,7 @@ describe("extension factory", () => { const notify = vi.fn(); await pi._commands.get("cursor-refresh-models")!.handler("", { + cwd: process.cwd(), hasUI: true, ui: { notify, setStatus: vi.fn() }, sessionManager: { getBranch: vi.fn(() => []) }, @@ -238,6 +243,7 @@ describe("extension factory", () => { const notify = vi.fn(); await pi._commands.get("cursor-refresh-models")!.handler("", { + cwd: process.cwd(), hasUI: true, ui: { notify, setStatus: vi.fn() }, sessionManager: { getBranch: vi.fn(() => []) }, @@ -275,6 +281,7 @@ describe("extension factory", () => { const notify = vi.fn(); const ctx = { + cwd: process.cwd(), hasUI: true, ui: { notify, setStatus: vi.fn() }, sessionManager: { getBranch: vi.fn(() => []) }, @@ -298,13 +305,32 @@ describe("extension factory", () => { await extensionFactory(pi as unknown as ExtensionAPI); const notify = vi.fn(); - const ctx = { hasUI: false, ui: { notify, setStatus: vi.fn() }, sessionManager: { getBranch: vi.fn(() => []) } }; + const ctx = { cwd: process.cwd(), hasUI: false, ui: { notify, setStatus: vi.fn() }, sessionManager: { getBranch: vi.fn(() => []) } }; const sessionHandlers = pi._handlers.get("session_start") ?? []; await sessionHandlers.at(-1)!({}, ctx); expect(notify).not.toHaveBeenCalled(); }); + it("registers native Cursor tool wrappers with the pi session cwd", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + mockedDiscover.mockResolvedValueOnce([]); + const dir = mkdtempSync(join(tmpdir(), "pi-cursor-native-cwd-")); + try { + writeFileSync(join(dir, "session-file.txt"), "from session cwd\n"); + const pi = createMockPi(); + await extensionFactory(pi as unknown as ExtensionAPI); + await runSessionStartHandlers(pi, { cwd: dir }); + + const readTool = pi._tools.find((tool) => tool.name === "read"); + const result = await readTool.execute("ordinary-read", { path: "session-file.txt" }, undefined, undefined, {}); + + expect(result.content).toEqual([{ type: "text", text: "from session cwd\n" }]); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + it("registered native Cursor tool wrappers return recorded Cursor results without executing built-ins", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; mockedDiscover.mockResolvedValueOnce([]); -- 2.51.2 From d8d37f7202483d1040f465c0022154c8efee0e46 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 10:57:20 -0600 Subject: [PATCH 041/359] Reject unavailable provider cwd follow-up --- .cueloop/done.jsonc | 38 ++++++++++++++++++++++++++++++++++++++ .cueloop/queue.jsonc | 33 --------------------------------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc index 70276ae..3285c8e 100644 --- a/.cueloop/done.jsonc +++ b/.cueloop/done.jsonc @@ -242,6 +242,44 @@ "blocks": [], "relates_to": [], "custom_fields": {} + }, + { + "id": "RQ-0009", + "status": "rejected", + "title": "Use pi session cwd in Cursor provider stream when pi exposes it", + "priority": "low", + "tags": [ + "pi-extension", + "cwd", + "provider" + ], + "scope": [ + "src/cursor-provider.ts" + ], + "evidence": [ + "RQ-0006 replaced process.cwd() with ctx.cwd for native tool replay where ExtensionContext is available.", + "src/cursor-provider.ts still uses process.cwd() because pi-ai Context/SimpleStreamOptions do not currently expose session cwd to streamSimple.", + "Reviewer noted this provider cwd limitation as optional future debt." + ], + "plan": [ + "Monitor or update against pi provider API if Context/SimpleStreamOptions gains session cwd.", + "Replace process.cwd() in streamCursor Agent.create local cwd with the provider/session cwd when available.", + "Add a provider-level test proving non-process cwd is passed to Cursor Agent.create.", + "Run npm test and npm run typecheck." + ], + "notes": [ + "Started by agent to verify whether provider stream can use pi session cwd.", + "Rejected as not currently actionable: installed pi-ai Context and SimpleStreamOptions expose no session cwd to streamSimple. RQ-0006 already applied ctx.cwd where available for native replay and documented provider-stream limitation in src/cursor-provider.ts. Recreate this task if pi provider APIs later expose session cwd." + ], + "request": "Follow-up from RQ-0006 reviewer finding.", + "created_at": "2026-05-15T16:19:57.688396000Z", + "updated_at": "2026-05-15T16:57:15.431670000Z", + "completed_at": "2026-05-15T16:57:15.431670000Z", + "started_at": "2026-05-15T16:55:33.003869000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} } ] } \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc index 962e8b8..a30ec30 100644 --- a/.cueloop/queue.jsonc +++ b/.cueloop/queue.jsonc @@ -1,39 +1,6 @@ { "version": 1, "tasks": [ - { - "id": "RQ-0009", - "status": "todo", - "title": "Use pi session cwd in Cursor provider stream when pi exposes it", - "priority": "low", - "tags": [ - "pi-extension", - "cwd", - "provider" - ], - "scope": [ - "src/cursor-provider.ts" - ], - "evidence": [ - "RQ-0006 replaced process.cwd() with ctx.cwd for native tool replay where ExtensionContext is available.", - "src/cursor-provider.ts still uses process.cwd() because pi-ai Context/SimpleStreamOptions do not currently expose session cwd to streamSimple.", - "Reviewer noted this provider cwd limitation as optional future debt." - ], - "plan": [ - "Monitor or update against pi provider API if Context/SimpleStreamOptions gains session cwd.", - "Replace process.cwd() in streamCursor Agent.create local cwd with the provider/session cwd when available.", - "Add a provider-level test proving non-process cwd is passed to Cursor Agent.create.", - "Run npm test and npm run typecheck." - ], - "notes": [], - "request": "Follow-up from RQ-0006 reviewer finding.", - "created_at": "2026-05-15T16:19:57.688396000Z", - "updated_at": "2026-05-15T16:19:57.688396000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, { "id": "RQ-0007", "status": "todo", -- 2.51.2 From 6240d01bd06dcc31112837bc700e843d4c3d9d57 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 10:59:09 -0600 Subject: [PATCH 042/359] Label Cursor read fallback previews --- .cueloop/done.jsonc | 39 +++++++++++++++++++++++++++++ .cueloop/queue.jsonc | 34 ------------------------- README.md | 2 +- src/cursor-tool-transcript.ts | 7 +++++- test/cursor-tool-transcript.test.ts | 26 ++++++++++++++++++- 5 files changed, 71 insertions(+), 37 deletions(-) diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc index 3285c8e..f47f3e9 100644 --- a/.cueloop/done.jsonc +++ b/.cueloop/done.jsonc @@ -280,6 +280,45 @@ "blocks": [], "relates_to": [], "custom_fields": {} + }, + { + "id": "RQ-0007", + "status": "done", + "title": "Reduce local re-read fallback risk in Cursor tool transcripts", + "priority": "low", + "tags": [ + "transcript", + "correctness", + "cursor-sdk" + ], + "scope": [ + "src/cursor-tool-transcript.ts", + "test/cursor-tool-transcript.test.ts", + "README.md" + ], + "evidence": [ + "src/cursor-tool-transcript.ts:218-253 fills empty Cursor read content by reading the current local file.", + "That preview may show content Cursor did not actually see if the file changed after Cursor's read." + ], + "plan": [ + "Decide whether to remove local read fallback or label it explicitly as a local preview at replay time.", + "Adjust transcript and native display output accordingly.", + "Update tests and docs for empty Cursor read-result behavior.", + "Run npm test and npm run typecheck." + ], + "notes": [ + "Started by agent to reduce local re-read fallback risk in Cursor tool transcripts.", + "Kept safe local fallback read previews but labeled them as transcript-time local previews when Cursor read result content is unavailable. Native read display uses same label. README documents the semantics. Validated with npm test -- test/cursor-tool-transcript.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." + ], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T16:59:05.869384000Z", + "completed_at": "2026-05-15T16:59:05.869384000Z", + "started_at": "2026-05-15T16:57:25.139310000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} } ] } \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc index a30ec30..ae7d7c7 100644 --- a/.cueloop/queue.jsonc +++ b/.cueloop/queue.jsonc @@ -1,40 +1,6 @@ { "version": 1, "tasks": [ - { - "id": "RQ-0007", - "status": "todo", - "title": "Reduce local re-read fallback risk in Cursor tool transcripts", - "priority": "low", - "tags": [ - "transcript", - "correctness", - "cursor-sdk" - ], - "scope": [ - "src/cursor-tool-transcript.ts", - "test/cursor-tool-transcript.test.ts", - "README.md" - ], - "evidence": [ - "src/cursor-tool-transcript.ts:218-253 fills empty Cursor read content by reading the current local file.", - "That preview may show content Cursor did not actually see if the file changed after Cursor's read." - ], - "plan": [ - "Decide whether to remove local read fallback or label it explicitly as a local preview at replay time.", - "Adjust transcript and native display output accordingly.", - "Update tests and docs for empty Cursor read-result behavior.", - "Run npm test and npm run typecheck." - ], - "notes": [], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T15:45:25.103158000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, { "id": "RQ-0008", "status": "todo", diff --git a/README.md b/README.md index 3cc74d7..6255b23 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths; that preview is explicitly labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Cursor setting sources are opt-in.** The extension does not pass `local.settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. To load configured Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities, start pi with `PI_CURSOR_SETTING_SOURCES=all`. To narrow loading, set a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. diff --git a/src/cursor-tool-transcript.ts b/src/cursor-tool-transcript.ts index e0e0cf8..9d6fabc 100644 --- a/src/cursor-tool-transcript.ts +++ b/src/cursor-tool-transcript.ts @@ -6,6 +6,8 @@ const DEFAULT_MAX_TRANSCRIPT_LINES = 800; const DEFAULT_MAX_LIST_ITEMS = 200; const DEFAULT_READ_TRANSCRIPT_CHARS = 4000; const DEFAULT_READ_TRANSCRIPT_LINES = 12; +const LOCAL_READ_PREVIEW_NOTICE = + "[local file preview at transcript time; Cursor read result content was unavailable]"; interface TranscriptOptions { maxChars?: number; @@ -232,7 +234,10 @@ function getReadContent(args: Record, result: NormalizedResult, }; const value = asRecord(result.value); const resultContent = getString(value, "content"); - return resultContent && resultContent.length > 0 ? resultContent : rawPath ? (readFilePreview(rawPath, readOptions) ?? stringifyUnknown(result.value)) : stringifyUnknown(result.value); + if (resultContent && resultContent.length > 0) return resultContent; + if (!rawPath) return stringifyUnknown(result.value); + const localPreview = readFilePreview(rawPath, readOptions); + return localPreview ? `${LOCAL_READ_PREVIEW_NOTICE}\n${localPreview}` : stringifyUnknown(result.value); } function formatRead(args: Record, result: NormalizedResult, options: TranscriptOptions): string { diff --git a/test/cursor-tool-transcript.test.ts b/test/cursor-tool-transcript.test.ts index 8b7a670..555383b 100644 --- a/test/cursor-tool-transcript.test.ts +++ b/test/cursor-tool-transcript.test.ts @@ -18,7 +18,7 @@ describe("formatCursorToolTranscript", () => { expect(transcript).toBe("read README.md\n\n# pi-cursor-sdk\n\nA pi provider extension\n"); }); - it("fills empty Cursor read results from a safe local file preview", () => { + it("labels empty Cursor read result local file previews", () => { const dir = mkdtempSync(join(tmpdir(), "cursor-tool-transcript-")); try { writeFileSync(join(dir, "README.md"), "# Local title\n\nLocal body\n"); @@ -33,6 +33,7 @@ describe("formatCursorToolTranscript", () => { ); expect(transcript).toContain("read README.md"); + expect(transcript).toContain("[local file preview at transcript time; Cursor read result content was unavailable]"); expect(transcript).toContain("# Local title"); expect(transcript).toContain("Local body"); } finally { @@ -174,6 +175,29 @@ describe("formatCursorToolTranscript", () => { }); }); + it("labels native read display local previews when Cursor read content is unavailable", () => { + const dir = mkdtempSync(join(tmpdir(), "cursor-tool-display-")); + try { + writeFileSync(join(dir, "README.md"), "# Local display preview\n"); + + const display = buildCursorPiToolDisplay( + { + name: "read", + args: { path: join(dir, "README.md") }, + result: { status: "success", value: { content: "", totalLines: 1, fileSize: 24 } }, + }, + { cwd: dir }, + ); + + expect(display.result.content[0].text).toContain( + "[local file preview at transcript time; Cursor read result content was unavailable]", + ); + expect(display.result.content[0].text).toContain("# Local display preview"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + it("keeps started tool args when the completed Cursor update only contains a result", () => { const merged = mergeCursorToolCalls( { name: "read", args: { path: "src/index.ts" } }, -- 2.51.2 From 391734ae6978c87e0c9adf41538764ec6414415b Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 11:06:17 -0600 Subject: [PATCH 043/359] Add Cursor snapshot refresh maintenance --- .cueloop/done.jsonc | 42 ++++ .cueloop/queue.jsonc | 40 +--- README.md | 15 ++ docs/cursor-model-ux-spec.md | 25 +-- package.json | 4 +- scripts/refresh-cursor-model-snapshots.mjs | 234 +++++++++++++++++++++ src/cursor-fallback-models.generated.ts | 145 +++++++++++++ src/model-discovery.ts | 142 +------------ 8 files changed, 452 insertions(+), 195 deletions(-) create mode 100644 scripts/refresh-cursor-model-snapshots.mjs create mode 100644 src/cursor-fallback-models.generated.ts diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc index f47f3e9..6fa1b00 100644 --- a/.cueloop/done.jsonc +++ b/.cueloop/done.jsonc @@ -319,6 +319,48 @@ "blocks": [], "relates_to": [], "custom_fields": {} + }, + { + "id": "RQ-0008", + "status": "done", + "title": "Add maintenance path for Cursor fallback model/context snapshot drift", + "priority": "low", + "tags": [ + "models", + "maintenance", + "release" + ], + "scope": [ + "src/model-discovery.ts", + "src/bundled-context-windows.ts", + "docs/cursor-model-ux-spec.md", + "README.md", + "package.json" + ], + "evidence": [ + "src/model-discovery.ts:22-160 hardcodes fallback Cursor catalog entries.", + "src/bundled-context-windows.ts:1-24 contains a generated context-window snapshot from 2026-05-04.", + "Cursor model IDs, aliases, defaults, and context windows can drift as Cursor changes its catalog." + ], + "plan": [ + "Add a maintainer script to refresh fallback/catalog snapshots from Cursor.models.list() and checkpoint context metadata.", + "Make generated output reviewable and documented; do not store API keys or secrets.", + "Update docs with when to run the script before releases.", + "Run npm test and npm run typecheck." + ], + "notes": [ + "Started by agent to add maintenance path for Cursor fallback model/context snapshot drift.", + "Added npm run refresh:cursor-snapshots maintainer script. Fallback Cursor catalog now lives in reviewable generated source src/cursor-fallback-models.generated.ts imported by model discovery. Script refreshes fallback metadata from Cursor.models.list, scrubs known auth material from SDK errors, and optionally merges checkpoint-derived context windows only when --context-windows is provided. README and UX spec document release maintenance path. Validated with node --check scripts/refresh-cursor-model-snapshots.mjs, npm run refresh:cursor-snapshots -- --help, npm test, npm run typecheck, git diff --check. Reviewer subagent reported no material findings after fixes." + ], + "request": "From 2026-05-15 extension improvement audit requested by user.", + "created_at": "2026-05-15T15:45:25.103158000Z", + "updated_at": "2026-05-15T17:06:14.707937000Z", + "completed_at": "2026-05-15T17:06:14.707937000Z", + "started_at": "2026-05-15T16:59:29.255767000Z", + "depends_on": [], + "blocks": [], + "relates_to": [], + "custom_fields": {} } ] } \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc index ae7d7c7..1e42945 100644 --- a/.cueloop/queue.jsonc +++ b/.cueloop/queue.jsonc @@ -1,42 +1,4 @@ { "version": 1, - "tasks": [ - { - "id": "RQ-0008", - "status": "todo", - "title": "Add maintenance path for Cursor fallback model/context snapshot drift", - "priority": "low", - "tags": [ - "models", - "maintenance", - "release" - ], - "scope": [ - "src/model-discovery.ts", - "src/bundled-context-windows.ts", - "docs/cursor-model-ux-spec.md", - "README.md", - "package.json" - ], - "evidence": [ - "src/model-discovery.ts:22-160 hardcodes fallback Cursor catalog entries.", - "src/bundled-context-windows.ts:1-24 contains a generated context-window snapshot from 2026-05-04.", - "Cursor model IDs, aliases, defaults, and context windows can drift as Cursor changes its catalog." - ], - "plan": [ - "Add a maintainer script to refresh fallback/catalog snapshots from Cursor.models.list() and checkpoint context metadata.", - "Make generated output reviewable and documented; do not store API keys or secrets.", - "Update docs with when to run the script before releases.", - "Run npm test and npm run typecheck." - ], - "notes": [], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T15:45:25.103158000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - } - ] + "tasks": [] } \ No newline at end of file diff --git a/README.md b/README.md index 6255b23..d57c59f 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,21 @@ npm test npm run typecheck ``` +Refresh the reviewable Cursor fallback catalog before releases or after Cursor model changes: + +```bash +CURSOR_API_KEY="your-key" npm run refresh:cursor-snapshots -- --write +``` + +Refresh the bundled default/non-Max context-window snapshot only when checkpoint-derived context windows have been collected from live local runs: + +```bash +CURSOR_API_KEY="your-key" npm run refresh:cursor-snapshots -- --write \ + --context-windows ~/.pi/agent/cursor-sdk-context-windows.json +``` + +The refresh script writes public model metadata only and scrubs known auth material from SDK errors. It must not be run with shell tracing that would echo API keys. + Local development run: ```bash diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index a77fa2c..71f42fb 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -93,24 +93,21 @@ If a Cursor parameter changes any of those pi-native fields, model registration ### Refresh Current Cursor Matrix -Run this whenever Cursor releases or changes models: +Run this whenever Cursor releases or changes models, and before releases that may ship stale fallback metadata: ```bash -node --input-type=module <<'EOF' -import { Cursor } from '@cursor/sdk'; - -const models = await Cursor.models.list({ apiKey: process.env.CURSOR_API_KEY }); -for (const model of models) { - const options = (model.parameters ?? []) - .map((param) => `${param.id}: ${param.values.map((value) => value.value).join(', ')}`) - .join(' | ') || 'none'; - const defaultVariant = model.variants?.find((variant) => variant.isDefault) ?? model.variants?.[0]; - const defaults = defaultVariant?.params?.map((param) => `${param.id}=${param.value}`).join('; ') || 'none'; - console.log(`${model.id}\t${model.displayName}\t${options}\t${defaults}`); -} -EOF +CURSOR_API_KEY="your-key" npm run refresh:cursor-snapshots -- --write +``` + +That command refreshes `src/cursor-fallback-models.generated.ts` only. If live local Cursor runs have collected checkpoint-derived context windows, merge them into the bundled default/non-Max snapshot too: + +```bash +CURSOR_API_KEY="your-key" npm run refresh:cursor-snapshots -- --write \ + --context-windows ~/.pi/agent/cursor-sdk-context-windows.json ``` +The script calls `Cursor.models.list({ apiKey })`, writes `src/cursor-fallback-models.generated.ts`, and updates `src/bundled-context-windows.ts` only when `--context-windows` is provided. It prints model IDs/counts only and scrubs known auth material from SDK errors; it must not print or store API keys. Review the generated diff before committing because Cursor can change aliases, defaults, and parameter meanings. + ## Design Direction Use native pi abstractions wherever possible: diff --git a/package.json b/package.json index 355095f..d825845 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "homepage": "https://github.com/fitchmultz/pi-cursor-sdk#readme", "files": [ "src", + "scripts/refresh-cursor-model-snapshots.mjs", "README.md", "docs/cursor-model-ux-spec.md", "LICENSE", @@ -35,7 +36,8 @@ "scripts": { "typecheck": "tsc --noEmit", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "refresh:cursor-snapshots": "node scripts/refresh-cursor-model-snapshots.mjs" }, "dependencies": { "@cursor/sdk": "^1.0.13" diff --git a/scripts/refresh-cursor-model-snapshots.mjs b/scripts/refresh-cursor-model-snapshots.mjs new file mode 100644 index 0000000..91a99e8 --- /dev/null +++ b/scripts/refresh-cursor-model-snapshots.mjs @@ -0,0 +1,234 @@ +#!/usr/bin/env node +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { basename, resolve } from "node:path"; +import { Cursor } from "@cursor/sdk"; + +const FALLBACK_MODELS_PATH = "src/cursor-fallback-models.generated.ts"; +const CONTEXT_WINDOWS_PATH = "src/bundled-context-windows.ts"; +const DEFAULT_CONTEXT_WINDOW = 200000; + +function printHelp() { + console.log(`Refresh reviewable Cursor model fallback snapshots. + +Usage: + npm run refresh:cursor-snapshots -- --write [options] + node scripts/refresh-cursor-model-snapshots.mjs [options] + +Options: + --write Write ${FALLBACK_MODELS_PATH}. Also write + ${CONTEXT_WINDOWS_PATH} when --context-windows is supplied. + Without --write, print a summary only. + --api-key Cursor API key. Prefer CURSOR_API_KEY to avoid shell history. + --context-windows Optional JSON file with {"contextWindows": {"model": 123}} + or a plain {"model": 123} map, usually copied from + ~/.pi/agent/cursor-sdk-context-windows.json after live runs. + --fallback-context-window Context window to use for newly seen models that lack + a catalog context parameter and no checkpoint override. + Default: ${DEFAULT_CONTEXT_WINDOW}. + -h, --help Show this help. + +Exit codes: + 0 success + 1 invalid arguments, missing auth, or Cursor SDK failure + +Notes: + - This script prints model IDs/counts only; it never prints API keys. + - Cursor.models.list() is the source of truth for fallback catalog metadata. + - Checkpoint-derived context windows are optional input because collecting them + requires successful local SDK runs; this script does not start agents.`); +} + +function fail(message) { + console.error(`refresh-cursor-snapshots: ${message}`); + process.exit(1); +} + +function scrubSensitiveText(text, secrets = []) { + let scrubbed = text; + for (const secret of secrets) { + if (secret) scrubbed = scrubbed.split(secret).join("[REDACTED]"); + } + return scrubbed + .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]") + .replace(/(api[_-]?key|authorization|auth[_-]?token)([\"'\s:=]+)[^\"'\s,}]+/gi, "$1$2[REDACTED]"); +} + +function parseArgs(argv) { + const args = { + write: false, + apiKey: process.env.CURSOR_API_KEY?.trim() || undefined, + contextWindowsPath: undefined, + fallbackContextWindow: DEFAULT_CONTEXT_WINDOW, + }; + for (let index = 0; index < argv.length; index++) { + const arg = argv[index]; + if (arg === "-h" || arg === "--help") { + printHelp(); + process.exit(0); + } + if (arg === "--write") { + args.write = true; + continue; + } + if (arg === "--api-key") { + const value = argv[++index]; + if (!value || value.startsWith("--")) fail("--api-key requires a value"); + args.apiKey = value.trim(); + continue; + } + if (arg.startsWith("--api-key=")) { + args.apiKey = arg.slice("--api-key=".length).trim(); + continue; + } + if (arg === "--context-windows") { + const value = argv[++index]; + if (!value || value.startsWith("--")) fail("--context-windows requires a file path"); + args.contextWindowsPath = value; + continue; + } + if (arg.startsWith("--context-windows=")) { + args.contextWindowsPath = arg.slice("--context-windows=".length); + continue; + } + if (arg === "--fallback-context-window") { + const value = argv[++index]; + if (!value || value.startsWith("--")) fail("--fallback-context-window requires a positive integer"); + args.fallbackContextWindow = parsePositiveInteger(value, "--fallback-context-window"); + continue; + } + if (arg.startsWith("--fallback-context-window=")) { + args.fallbackContextWindow = parsePositiveInteger(arg.slice("--fallback-context-window=".length), "--fallback-context-window"); + continue; + } + fail(`unknown argument ${arg}`); + } + if (!args.apiKey) fail("missing Cursor API key; set CURSOR_API_KEY or pass --api-key"); + return args; +} + +function parsePositiveInteger(value, label) { + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed <= 0) fail(`${label} must be a positive integer`); + return parsed; +} + +function sanitizeModelItem(item) { + return stripUndefined({ + id: item.id, + displayName: item.displayName, + description: item.description, + aliases: Array.isArray(item.aliases) ? [...item.aliases] : undefined, + parameters: Array.isArray(item.parameters) + ? item.parameters.map((parameter) => + stripUndefined({ + id: parameter.id, + displayName: parameter.displayName, + values: Array.isArray(parameter.values) + ? parameter.values.map((value) => stripUndefined({ value: value.value, displayName: value.displayName })) + : [], + }), + ) + : undefined, + variants: Array.isArray(item.variants) + ? item.variants.map((variant) => + stripUndefined({ + params: Array.isArray(variant.params) ? variant.params.map((param) => ({ id: param.id, value: param.value })) : [], + displayName: variant.displayName, + description: variant.description, + isDefault: variant.isDefault, + }), + ) + : undefined, + }); +} + +function stripUndefined(value) { + return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)); +} + +function stableStringify(value) { + return JSON.stringify(value, null, "\t").replace(/"([^"\\]+)":/g, "$1:"); +} + +function formatFallbackModels(models) { + return `import type { ModelListItem } from "@cursor/sdk";\n\n// Generated/maintained fallback Cursor catalog snapshot.\n// Refresh with: npm run refresh:cursor-snapshots -- --write\n// Do not add secrets; this file stores public model metadata only.\nexport const FALLBACK_MODEL_ITEMS = ${stableStringify(models)} satisfies ModelListItem[];\n`; +} + +function parseExistingContextWindows() { + if (!existsSync(CONTEXT_WINDOWS_PATH)) return new Map(); + const source = readFileSync(CONTEXT_WINDOWS_PATH, "utf8"); + const entries = new Map(); + const entryPattern = /(?:"([^"]+)"|([A-Za-z_$][\w$]*)):\s*(\d+)/g; + for (const match of source.matchAll(entryPattern)) { + entries.set(match[1] ?? match[2], Number(match[3])); + } + return entries; +} + +function parseContextWindowsFile(path) { + if (!path) return new Map(); + let parsed; + try { + parsed = JSON.parse(readFileSync(path, "utf8")); + } catch (error) { + fail(`could not read ${path}: ${error instanceof Error ? error.message : String(error)}`); + } + const source = parsed.contextWindows && typeof parsed.contextWindows === "object" ? parsed.contextWindows : parsed; + const windows = new Map(); + for (const [modelId, contextWindow] of Object.entries(source)) { + if (Number.isInteger(contextWindow) && contextWindow > 0) windows.set(modelId, contextWindow); + } + return windows; +} + +function hasContextParameter(model) { + return (model.parameters ?? []).some((parameter) => parameter.id === "context"); +} + +function formatContextWindows(models, checkpointWindows, fallbackContextWindow) { + const existing = parseExistingContextWindows(); + const merged = new Map(existing); + for (const [modelId, contextWindow] of checkpointWindows) merged.set(modelId, contextWindow); + for (const model of models) { + if (!hasContextParameter(model) && !merged.has(model.id)) merged.set(model.id, fallbackContextWindow); + } + if (!merged.has("default")) merged.set("default", fallbackContextWindow); + + const date = new Date().toISOString().slice(0, 10); + const sorted = [...merged.entries()].sort(([a], [b]) => (a === "default" ? -1 : b === "default" ? 1 : a.localeCompare(b))); + const lines = sorted.map(([modelId, contextWindow]) => `\t${JSON.stringify(modelId)}: ${contextWindow},`); + return `// Generated from Cursor SDK checkpoint tokenDetails.maxTokens on ${date}.\n// Refresh with: npm run refresh:cursor-snapshots -- --write --context-windows ~/.pi/agent/cursor-sdk-context-windows.json\n// These are default/non-Max-mode SDK context windows for Cursor models that do not\n// expose a catalog \`context\` parameter. Do not replace them with Max Mode values\n// unless the Cursor SDK exposes an exact Max Mode model selection and the extension\n// uses that selection for matching pi model IDs.\nexport const BUNDLED_CONTEXT_WINDOWS = {\n${lines.join("\n")}\n} as const satisfies Record;\n`; +} + +const args = parseArgs(process.argv.slice(2)); +let rawModels; +try { + rawModels = await Cursor.models.list({ apiKey: args.apiKey }); +} catch (error) { + const rawMessage = error instanceof Error ? error.message : String(error); + fail(`Cursor.models.list() failed: ${scrubSensitiveText(rawMessage, [args.apiKey])}`); +} +if (!Array.isArray(rawModels) || rawModels.length === 0) fail("Cursor.models.list() returned no models"); + +const models = rawModels.map(sanitizeModelItem).sort((a, b) => a.id.localeCompare(b.id)); +const checkpointWindows = parseContextWindowsFile(args.contextWindowsPath); +const fallbackSource = formatFallbackModels(models); +const contextWindowSource = args.contextWindowsPath ? formatContextWindows(models, checkpointWindows, args.fallbackContextWindow) : undefined; +const existingContextWindowCount = parseExistingContextWindows().size; + +console.log(`Fetched ${models.length} Cursor models.`); +console.log(`Context windows: ${checkpointWindows.size} checkpoint override(s), ${existingContextWindowCount} existing bundled entr${existingContextWindowCount === 1 ? "y" : "ies"}.`); +console.log(`First models: ${models.slice(0, 8).map((model) => model.id).join(", ")}${models.length > 8 ? ", ..." : ""}`); + +if (args.write) { + writeFileSync(FALLBACK_MODELS_PATH, fallbackSource); + console.log(`Wrote ${FALLBACK_MODELS_PATH}`); + if (contextWindowSource) { + writeFileSync(CONTEXT_WINDOWS_PATH, contextWindowSource); + console.log(`Wrote ${CONTEXT_WINDOWS_PATH}`); + } else { + console.log(`Skipped ${CONTEXT_WINDOWS_PATH}; pass --context-windows to refresh checkpoint-derived context windows.`); + } +} else { + console.log("Dry run only. Re-run with --write to update snapshots."); +} diff --git a/src/cursor-fallback-models.generated.ts b/src/cursor-fallback-models.generated.ts new file mode 100644 index 0000000..1327cf9 --- /dev/null +++ b/src/cursor-fallback-models.generated.ts @@ -0,0 +1,145 @@ +import type { ModelListItem } from "@cursor/sdk"; + +// Generated/maintained fallback Cursor catalog snapshot. +// Refresh with: npm run refresh:cursor-snapshots -- --write +// Do not add secrets; this file stores public model metadata only. +export const FALLBACK_MODEL_ITEMS = [ + { + id: "composer-2", + displayName: "Cursor Composer 2", + parameters: [ + { + id: "fast", + displayName: "Fast", + values: [{ value: "false" }, { value: "true" }], + }, + ], + variants: [ + { + params: [{ id: "fast", value: "true" }], + displayName: "Cursor Composer 2", + isDefault: true, + }, + ], + }, + { + id: "gpt-5.5", + displayName: "GPT-5.5", + parameters: [ + { + id: "context", + displayName: "Context", + values: [{ value: "1m" }, { value: "272k" }], + }, + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { value: "none" }, + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "extra-high" }, + ], + }, + { + id: "fast", + displayName: "Fast", + values: [{ value: "false" }, { value: "true" }], + }, + ], + variants: [ + { + params: [ + { id: "context", value: "1m" }, + { id: "reasoning", value: "medium" }, + { id: "fast", value: "false" }, + ], + displayName: "GPT-5.5", + isDefault: true, + }, + ], + }, + { + id: "claude-sonnet-4-6", + displayName: "Sonnet 4.6", + parameters: [ + { + id: "thinking", + displayName: "Thinking", + values: [{ value: "false" }, { value: "true" }], + }, + { + id: "context", + displayName: "Context", + values: [{ value: "1m" }, { value: "200k" }], + }, + { + id: "effort", + displayName: "Effort", + values: [ + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "xhigh" }, + { value: "max" }, + ], + }, + { + id: "fast", + displayName: "Fast", + values: [{ value: "false" }, { value: "true" }], + }, + ], + variants: [ + { + params: [ + { id: "thinking", value: "true" }, + { id: "context", value: "1m" }, + { id: "effort", value: "medium" }, + { id: "fast", value: "false" }, + ], + displayName: "Sonnet 4.6", + isDefault: true, + }, + ], + }, + { + id: "claude-opus-4-7", + displayName: "Opus 4.7", + parameters: [ + { + id: "thinking", + displayName: "Thinking", + values: [{ value: "false" }, { value: "true" }], + }, + { + id: "context", + displayName: "Context", + values: [{ value: "1m" }, { value: "300k" }], + }, + { + id: "effort", + displayName: "Effort", + values: [ + { value: "low" }, + { value: "medium" }, + { value: "high" }, + { value: "xhigh" }, + { value: "max" }, + ], + }, + ], + variants: [ + { + params: [ + { id: "thinking", value: "true" }, + { id: "context", value: "1m" }, + { id: "effort", value: "xhigh" }, + ], + displayName: "Opus 4.7", + isDefault: true, + }, + ], + }, +] satisfies ModelListItem[]; diff --git a/src/model-discovery.ts b/src/model-discovery.ts index cfa4d44..376ebe3 100644 --- a/src/model-discovery.ts +++ b/src/model-discovery.ts @@ -8,6 +8,7 @@ import type { import { AuthStorage, type ProviderModelConfig } from "@earendil-works/pi-coding-agent"; import type { ModelThinkingLevel, ThinkingLevelMap } from "@earendil-works/pi-ai"; import { loadContextWindowCache } from "./context-window-cache.js"; +import { FALLBACK_MODEL_ITEMS } from "./cursor-fallback-models.generated.js"; const CURSOR_PROVIDER_ID = "cursor"; const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY"; @@ -19,147 +20,6 @@ const AUTH_SETUP_HINT = "/login (Use an API key -> Cursor), CURSOR_API_KEY, or - const CATALOG_REFRESH_HINT = "After adding auth to an already-started pi session, run /cursor-refresh-models to refresh the full live Cursor model catalog without restarting pi."; -const FALLBACK_MODEL_ITEMS: ModelListItem[] = [ - { - id: "composer-2", - displayName: "Cursor Composer 2", - parameters: [ - { - id: "fast", - displayName: "Fast", - values: [{ value: "false" }, { value: "true" }], - }, - ], - variants: [ - { - params: [{ id: "fast", value: "true" }], - displayName: "Cursor Composer 2", - isDefault: true, - }, - ], - }, - { - id: "gpt-5.5", - displayName: "GPT-5.5", - parameters: [ - { - id: "context", - displayName: "Context", - values: [{ value: "1m" }, { value: "272k" }], - }, - { - id: "reasoning", - displayName: "Reasoning", - values: [ - { value: "none" }, - { value: "low" }, - { value: "medium" }, - { value: "high" }, - { value: "extra-high" }, - ], - }, - { - id: "fast", - displayName: "Fast", - values: [{ value: "false" }, { value: "true" }], - }, - ], - variants: [ - { - params: [ - { id: "context", value: "1m" }, - { id: "reasoning", value: "medium" }, - { id: "fast", value: "false" }, - ], - displayName: "GPT-5.5", - isDefault: true, - }, - ], - }, - { - id: "claude-sonnet-4-6", - displayName: "Sonnet 4.6", - parameters: [ - { - id: "thinking", - displayName: "Thinking", - values: [{ value: "false" }, { value: "true" }], - }, - { - id: "context", - displayName: "Context", - values: [{ value: "1m" }, { value: "200k" }], - }, - { - id: "effort", - displayName: "Effort", - values: [ - { value: "low" }, - { value: "medium" }, - { value: "high" }, - { value: "xhigh" }, - { value: "max" }, - ], - }, - { - id: "fast", - displayName: "Fast", - values: [{ value: "false" }, { value: "true" }], - }, - ], - variants: [ - { - params: [ - { id: "thinking", value: "true" }, - { id: "context", value: "1m" }, - { id: "effort", value: "medium" }, - { id: "fast", value: "false" }, - ], - displayName: "Sonnet 4.6", - isDefault: true, - }, - ], - }, - { - id: "claude-opus-4-7", - displayName: "Opus 4.7", - parameters: [ - { - id: "thinking", - displayName: "Thinking", - values: [{ value: "false" }, { value: "true" }], - }, - { - id: "context", - displayName: "Context", - values: [{ value: "1m" }, { value: "300k" }], - }, - { - id: "effort", - displayName: "Effort", - values: [ - { value: "low" }, - { value: "medium" }, - { value: "high" }, - { value: "xhigh" }, - { value: "max" }, - ], - }, - ], - variants: [ - { - params: [ - { id: "thinking", value: "true" }, - { id: "context", value: "1m" }, - { id: "effort", value: "xhigh" }, - ], - displayName: "Opus 4.7", - isDefault: true, - }, - ], - }, -]; - export type CursorModelFallbackReason = "missing-api-key" | "discovery-failed" | "empty-model-list"; export interface CursorModelFallbackIssue { -- 2.51.2 From d71bacd646192c2e03ff6139e32b99262d1079e8 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 12:21:32 -0600 Subject: [PATCH 044/359] Replay Cursor edit and write tool activity --- README.md | 4 +- docs/cursor-model-ux-spec.md | 2 +- src/cursor-native-tool-display.ts | 161 +++++++++++++++++++++++++--- src/cursor-tool-transcript.ts | 33 +++++- test/cursor-provider.test.ts | 90 +++++++++++++++- test/cursor-tool-transcript.test.ts | 52 +++++++++ test/index.test.ts | 31 +++++- 7 files changed, 351 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index d57c59f..704853f 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths; that preview is explicitly labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, `ls`, `edit`, and `write` activity through pi's native tool-call path with recorded results (for example green `read`, `$ ...`, and Cursor edit/write cards) without forcing Cursor to call pi tools or rerun commands. Cursor edit/write activity uses replay-only `cursor_edit` and `cursor_write` tool cards because Cursor's file-editing schema is not the same as pi's built-in `edit`/`write` schemas; those replay tools only display recorded Cursor results and never mutate files directly. If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths; that preview is explicitly labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Cursor setting sources are opt-in.** The extension does not pass `local.settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. To load configured Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities, start pi with `PI_CURSOR_SETTING_SOURCES=all`. To narrow loading, set a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. @@ -270,7 +270,7 @@ Cursor SDK local agents load MCP servers from Cursor setting sources and inline ### Cursor native tool cards conflict with another extension -Cursor native replay is a UI enhancement for interactive TTY sessions. If another extension already owns `read`, `bash`, or `ls`, this extension skips only the conflicting native replay wrapper and uses the scrubbed Cursor activity transcript for that tool instead. To disable Cursor native replay registration entirely, start pi with: +Cursor native replay is a UI enhancement for interactive TTY sessions. If another extension already owns `read`, `bash`, `ls`, `cursor_edit`, or `cursor_write`, this extension skips only the conflicting native replay wrapper and uses the scrubbed Cursor activity transcript for that tool instead. To disable Cursor native replay registration entirely, start pi with: ```bash PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2 diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 71f42fb..6eb1793 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -18,7 +18,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. Users can opt in with `PI_CURSOR_SETTING_SOURCES=all` or narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, `ls`, `edit`, and `write` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands/file edits. Cursor edit/write activity is replayed through `cursor_edit` and `cursor_write` cards rather than pi's built-in `edit`/`write` names because Cursor's edit/write schemas differ from pi's schemas; these replay-only tools display recorded Cursor results and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. When native replay splits one Cursor SDK run into multiple pi turns, prompt input is counted once for the run; later synthetic replay turns report `input: 0` and only their own output estimate. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.13 documentation says the SDK may enable Max Mode automatically when a selected model requires it, but the public local-agent `ModelSelection` path still does not expose a manual Max Mode selector. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index ad51f9c..b311b0e 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -6,14 +6,16 @@ import { type ExtensionContext, type ToolDefinition, } from "@earendil-works/pi-coding-agent"; -import type { TSchema } from "typebox"; +import { Text } from "@earendil-works/pi-tui"; +import { Type, type TSchema } from "typebox"; import type { CursorPiToolDisplay } from "./cursor-tool-transcript.js"; -const NATIVE_CURSOR_TOOL_NAMES = ["read", "bash", "ls"] as const; +const NATIVE_CURSOR_TOOL_NAMES = ["read", "bash", "ls", "cursor_edit", "cursor_write"] as const; type NativeCursorToolName = (typeof NATIVE_CURSOR_TOOL_NAMES)[number]; const NATIVE_CURSOR_TOOL_DISPLAY_ENV = "PI_CURSOR_NATIVE_TOOL_DISPLAY"; // Registration-only kill switch for users who want transcript fallback without shadowing read/bash/ls. const NATIVE_CURSOR_TOOL_REGISTRATION_ENV = "PI_CURSOR_REGISTER_NATIVE_TOOLS"; +const cursorReplayToolSchema = Type.Object({}, { additionalProperties: true }); export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { id: string; @@ -22,6 +24,7 @@ export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { const registeredNativeToolNames = new Set(); const nativeToolResults = new Map(); +let currentNativeToolCwd = process.cwd(); function readBooleanEnv(name: string): boolean | undefined { const value = process.env[name]?.trim().toLowerCase(); @@ -77,11 +80,13 @@ export const __testUtils = { reset(): void { registeredNativeToolNames.clear(); nativeToolResults.clear(); + currentNativeToolCwd = process.cwd(); }, }; function wrapNativeCursorTool( definition: ToolDefinition, + getCurrentDefinition: () => ToolDefinition, ): ToolDefinition { return { ...definition, @@ -94,21 +99,151 @@ function wrapNativeCursorTool( terminate: cursorDisplay.terminate ?? true, }; } - return definition.execute(toolCallId, params, signal, onUpdate, ctx); + return getCurrentDefinition().execute(toolCallId, params, signal, onUpdate, ctx); }, }; } -function registerNativeCursorTool(pi: ExtensionAPI, toolName: NativeCursorToolName, cwd: string): void { - if (toolName === "read") { - pi.registerTool(wrapNativeCursorTool(createReadToolDefinition(cwd))); - return; +interface CursorReplayToolDetails { + cursorToolName?: "edit" | "write"; + path?: string; + linesAdded?: number; + linesRemoved?: number; + linesCreated?: number; + fileSize?: number; + diffString?: string; +} + +function asCursorReplayToolDetails(value: unknown): CursorReplayToolDetails | undefined { + return value && typeof value === "object" ? (value as CursorReplayToolDetails) : undefined; +} + +function getCursorReplayPath(args: Record | undefined, details: CursorReplayToolDetails | undefined): string { + const argPath = args?.path; + return details?.path ?? (typeof argPath === "string" && argPath.trim() ? argPath : "unknown"); +} + +type CursorReplayRenderCall = NonNullable["renderCall"]>; +type CursorReplayRenderResult = NonNullable["renderResult"]>; +type CursorReplayRenderTheme = Parameters[1]; + +function formatCursorReplayDiff(diff: string, theme: CursorReplayRenderTheme, maxLines: number): string { + const lines = diff.split("\n"); + const visible = lines.slice(0, maxLines); + const rendered = visible.map((line) => { + if (line.startsWith("+") && !line.startsWith("+++")) return theme.fg("success", line); + if (line.startsWith("-") && !line.startsWith("---")) return theme.fg("error", line); + return theme.fg("muted", line); + }); + if (lines.length > maxLines) rendered.push(theme.fg("muted", `... (${lines.length - maxLines} more diff lines)`)); + return rendered.join("\n"); +} + +function renderCursorReplayCall( + toolName: "cursor_edit" | "cursor_write", + args: Record | undefined, + theme: CursorReplayRenderTheme, + isPartial: boolean, +): Text { + if (!isPartial) return new Text("", 0, 0); + const cursorToolName = toolName === "cursor_edit" ? "edit" : "write"; + let text = theme.fg("toolTitle", theme.bold(`Cursor ${cursorToolName} `)); + text += theme.fg("accent", getCursorReplayPath(args, undefined)); + return new Text(text, 0, 0); +} + +function pluralize(count: number, noun: string): string { + return `${count} ${noun}${count === 1 ? "" : "s"}`; +} + +function hasCursorEditChanges(details: CursorReplayToolDetails): boolean { + return Boolean(details.diffString) || Boolean(details.linesAdded) || Boolean(details.linesRemoved); +} + +function classifyCursorEditOperation(details: CursorReplayToolDetails): "created" | "deleted" | "updated" | "unchanged" { + if (!hasCursorEditChanges(details)) return "unchanged"; + if (details.diffString?.startsWith("--- /dev/null")) return "created"; + if (details.diffString?.includes("\n+++ /dev/null")) return "deleted"; + return "updated"; +} + +function formatCursorEditSummary(details: CursorReplayToolDetails): string { + const operation = classifyCursorEditOperation(details); + if (operation === "unchanged") return "no changes needed"; + if (operation === "created" && details.linesAdded !== undefined) return `created ${pluralize(details.linesAdded, "line")}`; + if (operation === "deleted" && details.linesRemoved !== undefined) return `deleted ${pluralize(details.linesRemoved, "line")}`; + const parts = [ + details.linesAdded ? `added ${pluralize(details.linesAdded, "line")}` : undefined, + details.linesRemoved ? `removed ${pluralize(details.linesRemoved, "line")}` : undefined, + ].filter((part): part is string => Boolean(part)); + return parts.length > 0 ? parts.join(", ") : "updated file"; +} + +function renderCursorReplayResult( + result: Parameters[0], + options: Parameters[1], + theme: Parameters[2], + isError: boolean, +): Text { + if (options.isPartial) return new Text(theme.fg("warning", "Replaying Cursor tool result..."), 0, 0); + const details = asCursorReplayToolDetails(result.details); + const content = result.content[0]; + const text = content?.type === "text" ? content.text : ""; + if (isError) return new Text(theme.fg("error", text.split("\n")[0] || "Cursor replay failed"), 0, 0); + + if (details?.cursorToolName === "edit") { + const summary = formatCursorEditSummary(details); + let rendered = `${theme.fg("toolTitle", theme.bold(`Cursor ${classifyCursorEditOperation(details)}`))} ${theme.fg("accent", getCursorReplayPath(undefined, details))} ${theme.fg("success", summary)}`; + if (details.diffString) rendered += options.expanded ? `\n${formatCursorReplayDiff(details.diffString, theme, 40)}` : theme.fg("muted", " (expand for diff)"); + return new Text(rendered, 0, 0); } - if (toolName === "bash") { - pi.registerTool(wrapNativeCursorTool(createBashToolDefinition(cwd))); - return; + + if (details?.cursorToolName === "write") { + const parts = [ + details.linesCreated !== undefined ? `${details.linesCreated} line${details.linesCreated === 1 ? "" : "s"}` : undefined, + details.fileSize !== undefined ? `${details.fileSize} bytes` : undefined, + ].filter(Boolean); + const summary = parts.length > 0 ? parts.join(", ") : "written"; + return new Text( + `${theme.fg("toolTitle", theme.bold("Cursor write"))} ${theme.fg("accent", getCursorReplayPath(undefined, details))} ${theme.fg("success", summary)}`, + 0, + 0, + ); } - pi.registerTool(wrapNativeCursorTool(createLsToolDefinition(cwd))); + + return new Text(text || theme.fg("success", "Cursor tool result replayed"), 0, 0); +} + +function createCursorReplayOnlyToolDefinition(toolName: "cursor_edit" | "cursor_write"): ToolDefinition { + const cursorToolName = toolName === "cursor_edit" ? "edit" : "write"; + return { + name: toolName, + label: `Cursor ${cursorToolName}`, + description: `Replay display for a Cursor SDK ${cursorToolName} operation. This tool only returns recorded Cursor results and never mutates files directly.`, + parameters: cursorReplayToolSchema, + renderShell: "self", + async execute() { + throw new Error(`No recorded Cursor ${cursorToolName} result was available. This replay-only tool does not execute file mutations.`); + }, + renderCall(args, theme, context) { + return renderCursorReplayCall(toolName, args as Record, theme, context.isPartial); + }, + renderResult(result, options, theme, context) { + return renderCursorReplayResult(result, options, theme, context.isError); + }, + }; +} + +function createNativeCursorToolDefinition(toolName: NativeCursorToolName, cwd: string): ToolDefinition { + if (toolName === "read") return createReadToolDefinition(cwd) as ToolDefinition; + if (toolName === "bash") return createBashToolDefinition(cwd) as ToolDefinition; + if (toolName === "ls") return createLsToolDefinition(cwd) as ToolDefinition; + return createCursorReplayOnlyToolDefinition(toolName) as ToolDefinition; +} + +function registerNativeCursorTool(pi: ExtensionAPI, toolName: NativeCursorToolName): void { + const definition = createNativeCursorToolDefinition(toolName, currentNativeToolCwd); + pi.registerTool(wrapNativeCursorTool(definition, () => createNativeCursorToolDefinition(toolName, currentNativeToolCwd))); } function hasNonBuiltinTool(pi: ExtensionAPI, toolName: NativeCursorToolName): boolean { @@ -122,7 +257,7 @@ function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionCont return; } - const cwd = ctx.cwd; + currentNativeToolCwd = ctx.cwd; const skippedToolNames: string[] = []; for (const toolName of NATIVE_CURSOR_TOOL_NAMES) { if (registeredNativeToolNames.has(toolName)) continue; @@ -130,7 +265,7 @@ function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionCont skippedToolNames.push(toolName); continue; } - registerNativeCursorTool(pi, toolName, cwd); + registerNativeCursorTool(pi, toolName); registeredNativeToolNames.add(toolName); } diff --git a/src/cursor-tool-transcript.ts b/src/cursor-tool-transcript.ts index 9d6fabc..823ba79 100644 --- a/src/cursor-tool-transcript.ts +++ b/src/cursor-tool-transcript.ts @@ -203,7 +203,7 @@ function readFilePreview(path: string, options: TranscriptOptions): string | und try { const realCwd = realpathSync(cwd); const realFilePath = realpathSync(filePath); - if (!isPathWithinCwd(realFilePath, realCwd) || isSensitivePreviewPath(realFilePath)) return undefined; + if (!isPathWithinCwd(realFilePath, realCwd) || isSensitivePreviewPath(filePath) || isSensitivePreviewPath(realFilePath)) return undefined; const stat = statSync(realFilePath); if (!stat.isFile()) return undefined; @@ -544,6 +544,37 @@ export function buildCursorPiToolDisplay(toolCall: unknown, options: TranscriptO }; } + if (name === "edit") { + const value = asRecord(result.value); + return { + toolName: "cursor_edit", + args, + result: textToolResult(formatEdit(args, result, options), { + cursorToolName: "edit", + path: typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined, + linesAdded: getNumber(value, "linesAdded"), + linesRemoved: getNumber(value, "linesRemoved"), + diffString: getString(value, "diffString"), + }), + isError: result.status === "error", + }; + } + + if (name === "write") { + const value = asRecord(result.value); + return { + toolName: "cursor_write", + args, + result: textToolResult(formatWrite(args, result, options), { + cursorToolName: "write", + path: typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined, + linesCreated: getNumber(value, "linesCreated"), + fileSize: getNumber(value, "fileSize"), + }), + isError: result.status === "error", + }; + } + return buildGenericPiToolDisplay(name, args, result, options); } diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index a56c70e..5cafccf 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -46,7 +46,7 @@ const mockedCreate = vi.mocked(Agent.create); const mockedCreateAgentPlatform = vi.mocked(createAgentPlatform); type RegisteredTool = ToolDefinition; -type TestExtensionContext = Pick & { ui: Pick }; +type TestExtensionContext = Pick & { ui: Pick }; type TestEventHandler = (event: unknown, ctx: TestExtensionContext) => Promise | void; function createBuiltinToolInfo(name: string): ToolInfo { @@ -115,7 +115,7 @@ async function registerNativeToolDisplayForTest(registeredTools: RegisteredTool[ }), } as unknown as ExtensionAPI); for (const handler of handlers) { - await handler({ reason: "startup" }, { hasUI: false, ui: { notify: vi.fn() } }); + await handler({ reason: "startup" }, { cwd: process.cwd(), hasUI: false, ui: { notify: vi.fn() } }); } } @@ -522,6 +522,92 @@ describe("streamCursor", () => { expect(replayDone.message.content).toEqual([{ type: "text", text: "Final answer only." }]); }); + it("replays Cursor edit activity through a replay-only native tool", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + const registeredTools: RegisteredTool[] = []; + await registerNativeToolDisplayForTest(registeredTools); + + let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; + const runWait = vi.fn( + () => + new Promise<{ id: string; status: "finished"; result: string }>((resolve) => { + resolveRun = resolve; + }), + ); + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ update: { type: "tool-call-started", toolCall: { type: "edit", args: { path: "src/index.ts" } }, callId: "c1" } }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + type: "edit", + args: { path: "src/index.ts" }, + result: { + status: "success", + value: { linesAdded: 1, linesRemoved: 1, diffString: "--- a/src/index.ts\n+++ b/src/index.ts" }, + }, + }, + callId: "c1", + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "running", + wait: runWait, + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + agentId: "agent-1", + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const firstEvents = await collectEvents(streamCursor(makeModel(), makeContext(), { apiKey: "test-key" })); + const firstDone = firstEvents.find((e: any) => e.type === "done") as any; + const toolCall = firstDone.message.content.find((block: any) => block.type === "toolCall"); + + expect(toolCall.name).toBe("cursor_edit"); + expect(toolCall.arguments).toEqual({ path: "src/index.ts" }); + const cursorEditTool = registeredTools.find((tool) => tool.name === "cursor_edit"); + expect(cursorEditTool).toBeDefined(); + const toolResult = await cursorEditTool!.execute(toolCall.id, toolCall.arguments, undefined, undefined, {}); + expect(toolResult).toMatchObject({ + content: [{ type: "text", text: expect.stringContaining("edit src/index.ts") }], + details: { cursorToolName: "edit" }, + terminate: false, + }); + expect(toolResult.content[0].text).toContain("+1 -1"); + + await expect(cursorEditTool!.execute("not-recorded", { path: "src/index.ts" }, undefined, undefined, {})).rejects.toThrow( + "replay-only tool does not execute file mutations", + ); + + resolveRun({ id: "run-1", status: "finished", result: "Done." }); + + const replayContext = makeContext(); + replayContext.messages = [ + ...replayContext.messages, + firstDone.message, + { + role: "toolResult", + toolCallId: toolCall.id, + toolName: "cursor_edit", + content: toolResult.content, + details: toolResult.details, + isError: false, + timestamp: 2, + }, + ]; + const replayEvents = await collectEvents(streamCursor(makeModel(), replayContext, { apiKey: "test-key" })); + const replayText = replayEvents.filter((e: any) => e.type === "text_delta").map((e: any) => e.delta).join(""); + expect(replayText).toBe("Done."); + expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(0); + }); + it("disposes abandoned native replay runs after the idle timeout", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; cursorProviderTestUtils.setCursorNativeReplayIdleDisposeMs(1); diff --git a/test/cursor-tool-transcript.test.ts b/test/cursor-tool-transcript.test.ts index 555383b..29a1c7a 100644 --- a/test/cursor-tool-transcript.test.ts +++ b/test/cursor-tool-transcript.test.ts @@ -73,6 +73,28 @@ describe("formatCursorToolTranscript", () => { } }); + it("does not fill empty Cursor read results through sensitive workspace symlink names", () => { + const dir = mkdtempSync(join(tmpdir(), "cursor-tool-transcript-")); + try { + writeFileSync(join(dir, "safe-target.txt"), "API_KEY=do-not-show\n"); + symlinkSync(join(dir, "safe-target.txt"), join(dir, ".env")); + + const transcript = formatCursorToolTranscript( + { + name: "read", + args: { path: join(dir, ".env") }, + result: { status: "success", value: { content: "", totalLines: 1, fileSize: 20 } }, + }, + { cwd: dir }, + ); + + expect(transcript).toContain("read .env"); + expect(transcript).not.toContain("do-not-show"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + it("does not fill empty Cursor read results through workspace symlinks to outside files", () => { const dir = mkdtempSync(join(tmpdir(), "cursor-tool-transcript-")); const outsideDir = mkdtempSync(join(tmpdir(), "cursor-tool-transcript-outside-")); @@ -149,6 +171,36 @@ describe("formatCursorToolTranscript", () => { expect(display.result.content[0].text).not.toContain("ls ."); }); + it("builds replay-only native pi display data for Cursor edit and write calls", () => { + const editDisplay = buildCursorPiToolDisplay({ + name: "edit", + args: { path: "src/index.ts" }, + result: { status: "success", value: { linesAdded: 1, linesRemoved: 1, diffString: "--- a/src/index.ts\n+++ b/src/index.ts" } }, + }); + const writeDisplay = buildCursorPiToolDisplay({ + name: "write", + args: { path: "new.txt" }, + result: { status: "success", value: { linesCreated: 1, fileSize: 6 } }, + }); + + expect(editDisplay).toMatchObject({ + toolName: "cursor_edit", + args: { path: "src/index.ts" }, + result: { details: { cursorToolName: "edit" } }, + isError: false, + }); + expect(editDisplay.result.content[0].text).toContain("edit src/index.ts"); + expect(editDisplay.result.content[0].text).toContain("+1 -1"); + expect(writeDisplay).toMatchObject({ + toolName: "cursor_write", + args: { path: "new.txt" }, + result: { details: { cursorToolName: "write" } }, + isError: false, + }); + expect(writeDisplay.result.content[0].text).toContain("write new.txt"); + expect(writeDisplay.result.content[0].text).toContain("Created 1 lines"); + }); + it("builds native pi display data for Cursor read and shell calls", () => { const readDisplay = buildCursorPiToolDisplay({ name: "read", diff --git a/test/index.test.ts b/test/index.test.ts index 5342f57..a924bfe 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -142,8 +142,8 @@ describe("extension factory", () => { "cursor-refresh-models", expect.objectContaining({ description: expect.stringContaining("Refresh the live Cursor model catalog") }), ); - expect(pi.registerTool).toHaveBeenCalledTimes(3); - expect(pi._tools.map((tool) => tool.name)).toEqual(["read", "bash", "ls"]); + expect(pi.registerTool).toHaveBeenCalledTimes(5); + expect(pi._tools.map((tool) => tool.name)).toEqual(["read", "bash", "ls", "cursor_edit", "cursor_write"]); expect(pi.on).toHaveBeenCalledWith("session_start", expect.any(Function)); expect(pi.on).toHaveBeenCalledWith("model_select", expect.any(Function)); expect(mockedDiscover).toHaveBeenCalledOnce(); @@ -331,6 +331,30 @@ describe("extension factory", () => { } }); + it("updates registered native Cursor tool wrappers to the latest pi session cwd", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + mockedDiscover.mockResolvedValueOnce([]); + const firstDir = mkdtempSync(join(tmpdir(), "pi-cursor-native-cwd-first-")); + const secondDir = mkdtempSync(join(tmpdir(), "pi-cursor-native-cwd-second-")); + try { + writeFileSync(join(firstDir, "session-file.txt"), "from first cwd\n"); + writeFileSync(join(secondDir, "session-file.txt"), "from second cwd\n"); + const pi = createMockPi(); + await extensionFactory(pi as unknown as ExtensionAPI); + await runSessionStartHandlers(pi, { cwd: firstDir }); + await runSessionStartHandlers(pi, { cwd: secondDir }); + + const readTool = pi._tools.find((tool) => tool.name === "read"); + const result = await readTool.execute("ordinary-read", { path: "session-file.txt" }, undefined, undefined, {}); + + expect(pi.registerTool).toHaveBeenCalledTimes(5); + expect(result.content).toEqual([{ type: "text", text: "from second cwd\n" }]); + } finally { + rmSync(firstDir, { recursive: true, force: true }); + rmSync(secondDir, { recursive: true, force: true }); + } + }); + it("registered native Cursor tool wrappers return recorded Cursor results without executing built-ins", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; mockedDiscover.mockResolvedValueOnce([]); @@ -400,9 +424,10 @@ describe("extension factory", () => { await extensionFactory(pi as unknown as ExtensionAPI); await runSessionStartHandlers(pi); - expect(pi._tools.map((tool) => tool.name)).toEqual(["bash", "ls"]); + expect(pi._tools.map((tool) => tool.name)).toEqual(["bash", "ls", "cursor_edit", "cursor_write"]); expect(canRenderCursorToolNatively("read")).toBe(false); expect(canRenderCursorToolNatively("bash")).toBe(true); + expect(canRenderCursorToolNatively("cursor_edit")).toBe(true); expect(canRenderCursorToolNatively("ls")).toBe(true); }); }); -- 2.51.2 From 2f0f8c4582d7e0abc946b5d80d39a50d5db781ad Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Fri, 15 May 2026 12:23:01 -0600 Subject: [PATCH 045/359] Release 0.1.10 --- CHANGELOG.md | 21 +++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c7ebd0..2a5cb51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 0.1.10 - 2026-05-15 + +### Added + +- Replay Cursor SDK `edit` and `write` activity through native pi tool-use turns using non-mutating `cursor_edit` and `cursor_write` cards, so Cursor file changes are visible as first-class tool activity without shadowing pi's built-in `edit` and `write` schemas. +- Add a maintainer `npm run refresh:cursor-snapshots` workflow for refreshing the reviewable Cursor fallback model catalog and optional checkpoint-derived context-window snapshot before releases. + +### Changed + +- Improve Cursor edit/write replay card UX with concise created/updated/deleted/unchanged summaries and expanded colored diffs. +- Clarify image follow-up behavior: only latest user-message image bytes are forwarded; earlier images remain transcript placeholders and should be reattached or described. +- Allow `/cursor-refresh-models` to refresh the live Cursor model catalog after auth changes without restarting pi. +- Label local read fallback previews as transcript-time local previews when Cursor read result content is unavailable. + +### Fixed + +- Prevent local read fallback previews from escaping the workspace through symlinks and from bypassing sensitive-path checks through sensitive symlink names. +- Budget oversized prompt history before `Agent.send`, including image-token reservations, while preserving system/tool-boundary instructions and the latest user request. +- Preserve assistant text emitted before native Cursor tool replay. +- Use the pi session cwd for native replay tool registration and update fallback execution to the latest session cwd. + ## 0.1.9 - 2026-05-14 ### Fixed diff --git a/package-lock.json b/package-lock.json index 12df9fd..04b374b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.9", + "version": "0.1.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.9", + "version": "0.1.10", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.13" diff --git a/package.json b/package.json index d825845..dd6a8bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.9", + "version": "0.1.10", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", -- 2.51.2 From 2baeda49be3ccb5840f5a4b0b5f71d0bae2d1742 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 18 May 2026 07:45:24 -0600 Subject: [PATCH 046/359] chore: update pi baseline and ignore cueloop state --- .cueloop/README.md | 233 ------ .cueloop/config.jsonc | 3 - .cueloop/done.jsonc | 366 --------- .cueloop/queue.jsonc | 4 - .gitignore | 11 +- package-lock.json | 1152 ++++------------------------- package.json | 12 +- src/cursor-native-tool-display.ts | 4 + 8 files changed, 169 insertions(+), 1616 deletions(-) delete mode 100644 .cueloop/README.md delete mode 100644 .cueloop/config.jsonc delete mode 100644 .cueloop/done.jsonc delete mode 100644 .cueloop/queue.jsonc diff --git a/.cueloop/README.md b/.cueloop/README.md deleted file mode 100644 index 0e8530b..0000000 --- a/.cueloop/README.md +++ /dev/null @@ -1,233 +0,0 @@ - -# CueLoop runtime files - -This repo is using CueLoop. The `cueloop` executable is the primary command name. This project stores runtime state in `.cueloop/`. New repos default to `.cueloop/`. - -> This file is generated and owned by CueLoop. `cueloop init` and agent-facing write-enabled commands may refresh it when CueLoop ships a newer template. Avoid hand-editing it unless you intentionally accept that local drift may be replaced. - -## Files - -- `.cueloop/config.jsonc` — repo-local configuration. -- `.cueloop/queue.jsonc` — JSONC task queue; source of truth for active work. -- `.cueloop/done.jsonc` — JSONC archive for completed tasks; only `done`/`rejected` statuses are valid. -- `.cueloop/cache/` — runtime cache for plans, completions, sessions, and temporary state. -- `.cueloop/logs/` — debug logs; should stay gitignored. -- `.cueloop/trust.jsonc` — machine-local trust decision; should stay gitignored. - -Do not rename runtime directories manually. Use `cueloop migrate runtime-dir --check` to preview runtime migration status and `cueloop migrate runtime-dir --apply` to move supported old project state when safe. - -## Core commands - -### Bootstrap and health - -- Bootstrap repo files: - - `cueloop init` -- Check this generated README: - - `cueloop init --check` -- Verify environment readiness: - - `cueloop doctor` -- Validate queue state: - - `cueloop queue validate` - -### Queue management - -- Inspect queue: - - `cueloop queue list` - - `cueloop queue next --with-title` -- Get next task ID: - - `cueloop queue next-id` - - `cueloop queue next-id --count 7` -- Show task details: - - `cueloop queue show RQ-0001` -- Archive completed tasks: - - `cueloop queue archive` -- Repair queue issues: - - `cueloop queue repair` -- Remove queue lock: - - `cueloop queue unlock` -- Sort and search tasks: - - `cueloop queue sort` - - `cueloop queue search "authentication"` - - `cueloop queue search "TODO" --status todo` -- Queue reports: - - `cueloop queue stats` - - `cueloop queue history --days 14` - - `cueloop queue burndown --days 30` - - `cueloop queue prune --age 90 --keep-last 100` - -### Task creation and updates - -- Build a task from a request: - - `cueloop task "Add tests for X"` -- Insert fully-shaped tasks atomically: - - `cueloop task insert --input /tmp/task-insert.json` - - `cueloop task insert --dry-run --format json --input /tmp/task-insert.json` -- Update task fields from repo state: - - `cueloop task update RQ-0001` - - `cueloop task update` -- Edit task fields: - - `cueloop task edit title "New title" RQ-0001` - - `cueloop task edit tags "rust, cli" RQ-0001` -- Change task status: - - `cueloop task status doing RQ-0001` -- Show task details: - - `cueloop task show RQ-0001` - -### Execution - -- Open the macOS app (macOS-only): - - `cueloop app open` -- Run one task: - - `cueloop run one` - - `cueloop run one --phases 3` - - `cueloop run one --quick` - - `cueloop run one --include-draft` -- Run a capped loop: - - `cueloop run loop --max-tasks 1` - - `cueloop run loop --phases 2 --max-tasks 1` -- Advanced unlimited loop mode (intentional only): - - `cueloop run loop --max-tasks 0` - -### PRD, context, and scans - -- Convert PRD markdown to tasks: - - `cueloop prd create docs/prd/feature.md` - - `cueloop prd create docs/prd/feature.md --multi` - - `cueloop prd create docs/prd/feature.md --dry-run` -- Generate or update AGENTS.md: - - `cueloop context init` - - `cueloop context update --section troubleshooting` - - `cueloop context validate` -- Seed tasks from a scan: - - `cueloop scan --focus "CI gaps"` - - `cueloop scan --focus "risk audit" --runner claude --model sonnet` - -## Troubleshooting - -### Duplicate task ID error - -If `cueloop queue validate` reports a duplicate task ID, this usually means a new task was added without incrementing the ID. Do not delete tasks. - -1. Run `cueloop queue next-id` to preview the next available ID. -2. Edit `.cueloop/queue.jsonc` and change the colliding task ID. -3. Re-run `cueloop queue validate`. - -Task IDs must be unique across both `queue.jsonc` and `done.jsonc`. - -### Generating multiple task IDs - -Use `--count` to generate IDs in one call: - -```bash -cueloop queue next-id --count 7 -``` - -`next-id` does not reserve IDs. For agent or script queue growth, prefer `cueloop task insert` so CueLoop assigns IDs under the queue lock. Keep `next-id` for manual recovery or one-off JSON surgery. - -## Template variables - -Prompt templates support variable interpolation for environment variables and config values: - -- `${VAR}` — expand environment variable, leaving the literal when unset. -- `${VAR:-default}` — expand with a default value when unset. -- `{{config.agent.runner}}` — current runner. -- `{{config.agent.model}}` — current model. -- `{{config.queue.file}}` — queue file path, for example `.cueloop/queue.jsonc`. -- `{{config.queue.done_file}}` — done archive path, for example `.cueloop/done.jsonc`. -- `{{config.queue.id_prefix}}` — task ID prefix, for example `RQ`. -- `{{config.queue.id_width}}` — task ID width, for example `4`. -- `{{config.project_type}}` — project type. - -Escaping: - -- `$${VAR}` — outputs literal `${VAR}`. -- `\${VAR}` — outputs literal `${VAR}`. - -Standard placeholders like `{{USER_REQUEST}}` are still processed after variable expansion. - -## Prompt overrides - -Default prompts are embedded in the `cueloop` binary. Custom prompt files should live in `.cueloop/prompts/`. - -Useful commands: - -- `cueloop prompt worker --phase 1` -- `cueloop prompt worker --phase 2` -- `cueloop prompt worker --phase 3` -- `cueloop prompt list` -- `cueloop prompt show worker --raw` -- `cueloop prompt diff worker` -- `cueloop prompt export --all` -- `cueloop prompt sync --dry-run` -- `cueloop prompt sync` - -## Runner configuration - -CueLoop can use built-in runner IDs (`codex`, `opencode`, `gemini`, `claude`, `cursor`, `kimi`, `pi`) or plugin runner IDs. - -One-off usage: - -- `cueloop task --runner opencode --model gpt-5.2 "Add tests for X"` -- `cueloop scan --runner gemini --model gemini-3-flash-preview --focus "risk audit"` -- `cueloop task --runner claude --model opus --repo-prompt plan "Add tests for X"` -- `cueloop run one --phases 3` -- `cueloop run one --phases 2` -- `cueloop run one --quick` - -Defaults via config: - -```json -{ - "version": 2, - "agent": { - "runner": "claude", - "model": "sonnet", - "phases": 3, - "iterations": 1, - "ci_gate": { - "enabled": true, - "argv": ["make", "ci"] - } - } -} -``` - -## Three-phase workflow - -CueLoop supports a 3-phase workflow by default: - -1. **Phase 1 (Planning):** generate a detailed plan and cache it in `.cueloop/cache/plans/.md`. -2. **Phase 2 (Implementation + CI):** implement the plan and pass the configured CI gate. -3. **Phase 3 (Code Review + Completion):** review the diff, refine if needed, rerun the CI gate, and complete the task. - -Use `cueloop run one --phases 3` for the full workflow. Use `--quick` as shorthand for `--phases 1`. - -## Security: safeguard dumps and redaction - -When runner operations fail, CueLoop writes safeguard dumps to temp directories for troubleshooting. By default, dumps are redacted before writing. - -Raw, non-redacted dumps require explicit opt-in: - -```bash -CUELOOP_RAW_DUMP=1 cueloop run one -cueloop run one --debug -``` - -Security notes: - -- Never commit safeguard dumps. -- Debug mode writes raw runner output to `.cueloop/logs/debug.log`. -- Temporary safeguard dumps use CueLoop-owned temp paths; inspect the reported path when troubleshooting. - -## Common flags - -- `--quick` — shorthand for `--phases 1`. -- `--include-draft` — include draft tasks when selecting work. -- `--runner ` — override runner. -- `--model ` — override model. -- `--repo-prompt ` / `-rp` — RepoPrompt mode. -- `--git-revert-mode ` — control revert behavior on errors. -- `--git-commit-push-on` / `--git-commit-push-off` — toggle auto commit/push. -- `--debug` — capture raw output and imply raw dumps. -- `--force` — bypass locks or overwrite files where supported. -- `-v`, `--verbose` — increase output verbosity. diff --git a/.cueloop/config.jsonc b/.cueloop/config.jsonc deleted file mode 100644 index 12dc0a6..0000000 --- a/.cueloop/config.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "version": 2 -} \ No newline at end of file diff --git a/.cueloop/done.jsonc b/.cueloop/done.jsonc deleted file mode 100644 index 6fa1b00..0000000 --- a/.cueloop/done.jsonc +++ /dev/null @@ -1,366 +0,0 @@ -{ - "version": 1, - "tasks": [ - { - "id": "RQ-0001", - "status": "done", - "title": "Fix symlink escape in Cursor read transcript fallback", - "priority": "high", - "tags": [ - "security", - "cursor-sdk", - "transcript" - ], - "scope": [ - "src/cursor-tool-transcript.ts", - "test/cursor-tool-transcript.test.ts" - ], - "evidence": [ - "src/cursor-tool-transcript.ts:194-205 re-reads empty Cursor read results after only a textual inside-cwd check.", - "src/cursor-tool-transcript.ts:172-174 checks relative path text, while statSync/openSync follow symlinks.", - "test/cursor-tool-transcript.test.ts:43-68 covers sensitive/direct outside files but not symlink-to-outside paths." - ], - "plan": [ - "Add a regression test for an in-workspace symlink pointing to an outside file.", - "Resolve real paths for cwd and target before inside-cwd and sensitivity checks, or reject symlinks with lstatSync.", - "Run npm test and npm run typecheck." - ], - "notes": [ - "Started by agent to fix symlink escape in Cursor read transcript fallback.", - "Implemented realpath-based preview validation for cwd and target before opening fallback read preview. Added symlink-to-outside regression test. Validated with npm test -- test/cursor-tool-transcript.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." - ], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T15:56:28.437515000Z", - "completed_at": "2026-05-15T15:56:28.437515000Z", - "started_at": "2026-05-15T15:54:25.593372000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, - { - "id": "RQ-0002", - "status": "done", - "title": "Add prompt/context budgeting before Cursor Agent.send", - "priority": "high", - "tags": [ - "context", - "cursor-sdk", - "performance" - ], - "scope": [ - "src/context.ts", - "src/cursor-provider.ts", - "test/context.test.ts", - "test/cursor-provider.test.ts" - ], - "evidence": [ - "src/context.ts:70-100 serializes every pi message/tool result into one Cursor prompt.", - "src/cursor-provider.ts:585-586 estimates prompt tokens after building the full prompt but does not trim against model.contextWindow.", - "Long sessions or large tool outputs can exceed Cursor context despite accurate model metadata." - ], - "plan": [ - "Design a budgeted transcript builder that preserves system instructions and the latest user request.", - "Trim or summarize oldest turns and large tool outputs with an output-token reserve.", - "Add tests for oversized histories and oversized tool results.", - "Run npm test and npm run typecheck." - ], - "notes": [ - "Started by agent to add prompt/context budgeting before Cursor Agent.send.", - "Added model-derived prompt input budgeting before Cursor Agent.send. Preserves system/tool-boundary instructions and latest user request, keeps recent history that fits, omits older oversized history/tool output with an omission notice, and reserves estimated image tokens. Validated with npm test -- test/context.test.ts, npm test -- test/cursor-provider.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings after image-budget fix." - ], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T16:03:54.301207000Z", - "completed_at": "2026-05-15T16:03:54.301207000Z", - "started_at": "2026-05-15T15:56:50.094857000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, - { - "id": "RQ-0003", - "status": "done", - "title": "Preserve assistant text emitted before native Cursor tool replay", - "priority": "medium", - "tags": [ - "cursor-sdk", - "native-tool-replay", - "ux" - ], - "scope": [ - "src/cursor-provider.ts", - "test/cursor-provider.test.ts" - ], - "evidence": [ - "src/cursor-provider.ts:753-759 stores pre-tool textDeltas but does not stream them when native replay is enabled before the first tool.", - "test/cursor-provider.test.ts:476-522 currently expects firstText to be empty after Cursor emits pre-tool text." - ], - "plan": [ - "Queue or emit pre-tool text into the first replay turn before the native tool call.", - "Update tests to assert the pre-tool assistant text is preserved.", - "Verify native replay still mirrors pi toolUse/toolResult turn shape.", - "Run npm test and npm run typecheck." - ], - "notes": [ - "Started by agent to preserve pre-tool assistant text during native Cursor tool replay.", - "Queued buffered pre-tool text deltas into native replay before the first native tool event and cleared the buffer to avoid duplicate fallback replay. Updated native replay test to assert text plus toolCall in the first toolUse turn. Validated with npm test -- test/cursor-provider.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." - ], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T16:06:18.803764000Z", - "completed_at": "2026-05-15T16:06:18.803764000Z", - "started_at": "2026-05-15T16:04:13.282764000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, - { - "id": "RQ-0004", - "status": "done", - "title": "Add live Cursor model catalog refresh after login/auth changes", - "priority": "medium", - "tags": [ - "models", - "auth", - "pi-extension" - ], - "scope": [ - "src/index.ts", - "src/model-discovery.ts", - "README.md", - "docs/cursor-model-ux-spec.md", - "test/index.test.ts", - "test/model-discovery.test.ts" - ], - "evidence": [ - "src/index.ts:10-31 discovers models only during async extension startup.", - "src/model-discovery.ts:583-608 falls back and tells users to /reload or restart after auth is added.", - "Installed pi registerProvider docs state post-load provider registrations take effect immediately without /reload." - ], - "plan": [ - "Add a /cursor-refresh-models command that re-runs discoverModels().", - "Call pi.registerProvider('cursor', ...) with the refreshed model list and existing streamSimple config.", - "Notify users whether a live catalog replaced fallback models or fallback remains active.", - "Update README/spec and tests.", - "Run npm test and npm run typecheck." - ], - "notes": [ - "Started by agent to add live Cursor model catalog refresh.", - "Added /cursor-refresh-models command that re-runs discoverModels and re-registers the cursor provider with refreshed models and streamCursor without /reload. Updated fallback guidance in source, README, UX spec, and tests. Validated with npm test -- test/index.test.ts test/model-discovery.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." - ], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T16:11:11.758300000Z", - "completed_at": "2026-05-15T16:11:11.758300000Z", - "started_at": "2026-05-15T16:06:36.556384000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, - { - "id": "RQ-0005", - "status": "done", - "title": "Improve or explicitly constrain image follow-up behavior", - "priority": "medium", - "tags": [ - "images", - "multimodal", - "ux" - ], - "scope": [ - "src/context.ts", - "test/context.test.ts", - "README.md", - "docs/cursor-model-ux-spec.md" - ], - "evidence": [ - "src/context.ts:21-37 forwards images only from the latest user message.", - "src/context.ts:39-48 replaces historical images with [image omitted from transcript].", - "docs/cursor-model-ux-spec.md:15-16 records this as a pending product decision." - ], - "plan": [ - "Choose a policy: keep current behavior with explicit prompt limitation, or add opt-in recent-image carry-forward with token/privacy budget.", - "Implement the chosen behavior and update tests.", - "Update README/spec so users understand multimodal follow-up semantics.", - "Run npm test and npm run typecheck." - ], - "notes": [ - "Started by agent to clarify image follow-up behavior.", - "Chose explicit-constraining policy for multimodal follow-ups. Added prompt boundary that only latest-user images are available as bytes and Cursor should ask for reattachment/description for prior images. Updated context tests, README, and UX spec. Validated with npm test -- test/context.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." - ], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T16:13:29.234095000Z", - "completed_at": "2026-05-15T16:13:29.234095000Z", - "started_at": "2026-05-15T16:11:20.530130000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, - { - "id": "RQ-0006", - "status": "done", - "title": "Use pi session cwd instead of process.cwd where available", - "priority": "low", - "tags": [ - "pi-extension", - "cwd", - "lifecycle" - ], - "scope": [ - "src/cursor-native-tool-display.ts", - "src/cursor-provider.ts", - "test/cursor-provider.test.ts" - ], - "evidence": [ - "src/cursor-native-tool-display.ts:119-133 registers native replay tools using process.cwd() despite receiving ExtensionContext.", - "src/cursor-provider.ts:573-582 passes process.cwd() to Cursor Agent.create local cwd.", - "Installed pi extension docs expose ctx.cwd as the session working directory." - ], - "plan": [ - "Replace process.cwd() with ctx.cwd for native tool replay registration.", - "Investigate whether provider stream context/options expose session cwd; if not, document limitation or upstream a pi provider option.", - "Add/update tests for non-default cwd where feasible.", - "Run npm test and npm run typecheck." - ], - "notes": [ - "Started by agent to use pi session cwd where available.", - "Native Cursor tool replay now registers wrapped read/bash/ls tools with ExtensionContext.cwd instead of process.cwd. Provider streamCursor remains on process.cwd because pi-ai Context/SimpleStreamOptions do not expose session cwd; documented in code and added follow-up RQ-0009. Added test proving native read resolves relative paths against session cwd. Validated with npm test -- test/index.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings after RQ-0009 follow-up." - ], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T16:20:04.637091000Z", - "completed_at": "2026-05-15T16:20:04.637091000Z", - "started_at": "2026-05-15T16:13:36.989387000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, - { - "id": "RQ-0009", - "status": "rejected", - "title": "Use pi session cwd in Cursor provider stream when pi exposes it", - "priority": "low", - "tags": [ - "pi-extension", - "cwd", - "provider" - ], - "scope": [ - "src/cursor-provider.ts" - ], - "evidence": [ - "RQ-0006 replaced process.cwd() with ctx.cwd for native tool replay where ExtensionContext is available.", - "src/cursor-provider.ts still uses process.cwd() because pi-ai Context/SimpleStreamOptions do not currently expose session cwd to streamSimple.", - "Reviewer noted this provider cwd limitation as optional future debt." - ], - "plan": [ - "Monitor or update against pi provider API if Context/SimpleStreamOptions gains session cwd.", - "Replace process.cwd() in streamCursor Agent.create local cwd with the provider/session cwd when available.", - "Add a provider-level test proving non-process cwd is passed to Cursor Agent.create.", - "Run npm test and npm run typecheck." - ], - "notes": [ - "Started by agent to verify whether provider stream can use pi session cwd.", - "Rejected as not currently actionable: installed pi-ai Context and SimpleStreamOptions expose no session cwd to streamSimple. RQ-0006 already applied ctx.cwd where available for native replay and documented provider-stream limitation in src/cursor-provider.ts. Recreate this task if pi provider APIs later expose session cwd." - ], - "request": "Follow-up from RQ-0006 reviewer finding.", - "created_at": "2026-05-15T16:19:57.688396000Z", - "updated_at": "2026-05-15T16:57:15.431670000Z", - "completed_at": "2026-05-15T16:57:15.431670000Z", - "started_at": "2026-05-15T16:55:33.003869000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, - { - "id": "RQ-0007", - "status": "done", - "title": "Reduce local re-read fallback risk in Cursor tool transcripts", - "priority": "low", - "tags": [ - "transcript", - "correctness", - "cursor-sdk" - ], - "scope": [ - "src/cursor-tool-transcript.ts", - "test/cursor-tool-transcript.test.ts", - "README.md" - ], - "evidence": [ - "src/cursor-tool-transcript.ts:218-253 fills empty Cursor read content by reading the current local file.", - "That preview may show content Cursor did not actually see if the file changed after Cursor's read." - ], - "plan": [ - "Decide whether to remove local read fallback or label it explicitly as a local preview at replay time.", - "Adjust transcript and native display output accordingly.", - "Update tests and docs for empty Cursor read-result behavior.", - "Run npm test and npm run typecheck." - ], - "notes": [ - "Started by agent to reduce local re-read fallback risk in Cursor tool transcripts.", - "Kept safe local fallback read previews but labeled them as transcript-time local previews when Cursor read result content is unavailable. Native read display uses same label. README documents the semantics. Validated with npm test -- test/cursor-tool-transcript.test.ts, npm test, npm run typecheck. Reviewer subagent reported no material findings." - ], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T16:59:05.869384000Z", - "completed_at": "2026-05-15T16:59:05.869384000Z", - "started_at": "2026-05-15T16:57:25.139310000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - }, - { - "id": "RQ-0008", - "status": "done", - "title": "Add maintenance path for Cursor fallback model/context snapshot drift", - "priority": "low", - "tags": [ - "models", - "maintenance", - "release" - ], - "scope": [ - "src/model-discovery.ts", - "src/bundled-context-windows.ts", - "docs/cursor-model-ux-spec.md", - "README.md", - "package.json" - ], - "evidence": [ - "src/model-discovery.ts:22-160 hardcodes fallback Cursor catalog entries.", - "src/bundled-context-windows.ts:1-24 contains a generated context-window snapshot from 2026-05-04.", - "Cursor model IDs, aliases, defaults, and context windows can drift as Cursor changes its catalog." - ], - "plan": [ - "Add a maintainer script to refresh fallback/catalog snapshots from Cursor.models.list() and checkpoint context metadata.", - "Make generated output reviewable and documented; do not store API keys or secrets.", - "Update docs with when to run the script before releases.", - "Run npm test and npm run typecheck." - ], - "notes": [ - "Started by agent to add maintenance path for Cursor fallback model/context snapshot drift.", - "Added npm run refresh:cursor-snapshots maintainer script. Fallback Cursor catalog now lives in reviewable generated source src/cursor-fallback-models.generated.ts imported by model discovery. Script refreshes fallback metadata from Cursor.models.list, scrubs known auth material from SDK errors, and optionally merges checkpoint-derived context windows only when --context-windows is provided. README and UX spec document release maintenance path. Validated with node --check scripts/refresh-cursor-model-snapshots.mjs, npm run refresh:cursor-snapshots -- --help, npm test, npm run typecheck, git diff --check. Reviewer subagent reported no material findings after fixes." - ], - "request": "From 2026-05-15 extension improvement audit requested by user.", - "created_at": "2026-05-15T15:45:25.103158000Z", - "updated_at": "2026-05-15T17:06:14.707937000Z", - "completed_at": "2026-05-15T17:06:14.707937000Z", - "started_at": "2026-05-15T16:59:29.255767000Z", - "depends_on": [], - "blocks": [], - "relates_to": [], - "custom_fields": {} - } - ] -} \ No newline at end of file diff --git a/.cueloop/queue.jsonc b/.cueloop/queue.jsonc deleted file mode 100644 index 1e42945..0000000 --- a/.cueloop/queue.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "version": 1, - "tasks": [] -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index a7d231b..26f82e8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,12 +9,5 @@ recording/ demo-script.md docs/public-demo-script.md -# CueLoop local/runtime artifacts (do not commit) -.cueloop/cache/ -.cueloop/logs/ -.cueloop/workspaces/ -.cueloop/lock/ -.cueloop/undo/ -.cueloop/webhooks/ -.cueloop/trust.json -.cueloop/trust.jsonc +# CueLoop local/runtime state (do not commit) +.cueloop/ diff --git a/package-lock.json b/package-lock.json index 04b374b..c5561c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,17 +12,21 @@ "@cursor/sdk": "^1.0.13" }, "devDependencies": { - "@earendil-works/pi-ai": "^0.74.0", - "@earendil-works/pi-coding-agent": "^0.74.0", + "@earendil-works/pi-ai": "^0.75.3", + "@earendil-works/pi-coding-agent": "^0.75.3", + "@earendil-works/pi-tui": "^0.75.3", + "typebox": "^1.1.38", "typescript": "^6.0.3", "vitest": "^4.1.6" }, "engines": { - "node": ">=18" + "node": ">=22.19.0" }, "peerDependencies": { "@earendil-works/pi-ai": "*", - "@earendil-works/pi-coding-agent": "*" + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*", + "typebox": "*" } }, "node_modules/@anthropic-ai/sdk": { @@ -850,17 +854,6 @@ "node": ">=6.9.0" } }, - "node_modules/@borewit/text-codec": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", - "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/@bufbuild/protobuf": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", @@ -982,23 +975,25 @@ ] }, "node_modules/@earendil-works/pi-agent-core": { - "version": "0.74.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.74.0.tgz", - "integrity": "sha512-6GMR7/wwjEJ1EsXLWEz03QOWin4AMrJ/AZoMpgm5DJ6GHsF6q6GOhQbj5Zip4dow3vo/TmBAVqM+vmGfrjGAFQ==", + "version": "0.75.3", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.75.3.tgz", + "integrity": "sha512-azg09GSrckQa3ffbH09YEZC7DyHgmNSX+vmWEoEhQvp4icbzqbqLfIeMayMNEK/aGusm1SghZC4bPlDdagDALg==", "dev": true, "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "^0.74.0", - "typebox": "^1.1.24" + "@earendil-works/pi-ai": "^0.75.3", + "ignore": "^7.0.5", + "typebox": "^1.1.24", + "yaml": "^2.8.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.19.0" } }, "node_modules/@earendil-works/pi-ai": { - "version": "0.74.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.74.0.tgz", - "integrity": "sha512-7M7qcrZY/KEkH4wFkX3eqzvmKru4O88wezNKoN0KD2m4aAOmp9tdW2xCmUgSTSWlKB7b2Xw9QtAgrzHtg6t6iw==", + "version": "0.75.3", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.75.3.tgz", + "integrity": "sha512-UKccS+ADlkSVJ49a00346jUfXmUi6zzzB+pPWotsyA6SxhKr2ejjkGQksGyR1DyNVrsEP/WWlsOSTUUwVlzNaA==", "dev": true, "license": "MIT", "dependencies": { @@ -1006,75 +1001,66 @@ "@aws-sdk/client-bedrock-runtime": "^3.1030.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "^2.2.0", - "chalk": "^5.6.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "openai": "6.26.0", "partial-json": "^0.1.7", - "proxy-agent": "^6.5.0", - "typebox": "^1.1.24", - "undici": "^7.19.1", - "zod-to-json-schema": "^3.24.6" + "typebox": "^1.1.24" }, "bin": { "pi-ai": "dist/cli.js" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.19.0" } }, "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.74.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.74.0.tgz", - "integrity": "sha512-Q5GikbB5vRBrsrrf/uvet53rPSQ1sn5I5mO+l7sIobdXYpS04/X2oOc2UHFm90fNdkl3yU+ANTZL0zOtHbnqRw==", + "version": "0.75.3", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.75.3.tgz", + "integrity": "sha512-LIi5/CdUBfcLp3BAtpLx1BfnHDLmDOQVdzYfS1H9fjjCw2dcPr9voSI5ncrhvZdgyFSnfHck4BCbNcfZk+TEHQ==", "dev": true, "license": "MIT", "dependencies": { - "@earendil-works/pi-agent-core": "^0.74.0", - "@earendil-works/pi-ai": "^0.74.0", - "@earendil-works/pi-tui": "^0.74.0", + "@earendil-works/pi-agent-core": "^0.75.3", + "@earendil-works/pi-ai": "^0.75.3", + "@earendil-works/pi-tui": "^0.75.3", "@silvia-odwyer/photon-node": "^0.3.4", "chalk": "^5.5.0", - "cli-highlight": "^2.1.11", + "cross-spawn": "^7.0.6", "diff": "^8.0.2", - "extract-zip": "^2.0.1", - "file-type": "^21.1.1", "glob": "^13.0.1", + "highlight.js": "^10.7.3", "hosted-git-info": "^9.0.2", "ignore": "^7.0.5", "jiti": "^2.7.0", - "marked": "^15.0.12", "minimatch": "^10.2.3", "proper-lockfile": "^4.1.2", - "strip-ansi": "^7.1.0", "typebox": "^1.1.24", - "undici": "^7.19.1", - "uuid": "^14.0.0", + "undici": "^8.3.0", "yaml": "^2.8.2" }, "bin": { "pi": "dist/cli.js" }, "engines": { - "node": ">=20.6.0" + "node": ">=22.19.0" }, "optionalDependencies": { - "@mariozechner/clipboard": "^0.3.5" + "@mariozechner/clipboard": "^0.3.6" } }, "node_modules/@earendil-works/pi-tui": { - "version": "0.74.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.74.0.tgz", - "integrity": "sha512-1aIfXZp7D/z+1VlZX8BZcs6pgO8rjmil7kwyhctNDsWvce3Yfl8GVgu4eq+I0Mjhr8Cj+ipBiv9CLIzdoyCOIQ==", + "version": "0.75.3", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.75.3.tgz", + "integrity": "sha512-UbhtCsae+b3Y8/ZxtBPhiOrkD66gOHvJbfvLZwhBBsNtQuvUkZY5t9MQwmb8QcDYkFRnXHaq3FcEy1hjRSfj6w==", "dev": true, "license": "MIT", "dependencies": { - "@types/mime-types": "^2.1.4", - "chalk": "^5.5.0", "get-east-asian-width": "^1.3.0", - "marked": "^15.0.12", - "mime-types": "^3.0.1" + "marked": "^15.0.12" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.19.0" }, "optionalDependencies": { "koffi": "^2.9.0" @@ -1159,9 +1145,9 @@ "license": "MIT" }, "node_modules/@mariozechner/clipboard": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.5.tgz", - "integrity": "sha512-D3F+UrU9CR7roJt0zDLp6Oc+4/KlLDIrN4frH+6V90SJNW2KKUec1oCQIPaaDjCqeOsQyX9dyqYbImIQIM45PA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.6.tgz", + "integrity": "sha512-MXdtr+6+ntlIVHdrZYuZNQydu6o8yZswFJ2Ln81j2O/Y9B/LDHvEaIm95xWNPkjGTWriSOeLnQJRFs6dYb60bg==", "dev": true, "license": "MIT", "optional": true, @@ -1169,22 +1155,22 @@ "node": ">= 10" }, "optionalDependencies": { - "@mariozechner/clipboard-darwin-arm64": "0.3.2", - "@mariozechner/clipboard-darwin-universal": "0.3.2", - "@mariozechner/clipboard-darwin-x64": "0.3.2", - "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", - "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-musl": "0.3.2", - "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", - "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" + "@mariozechner/clipboard-darwin-arm64": "0.3.6", + "@mariozechner/clipboard-darwin-universal": "0.3.6", + "@mariozechner/clipboard-darwin-x64": "0.3.6", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.6", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.6", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.6", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.6", + "@mariozechner/clipboard-linux-x64-musl": "0.3.6", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.6", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.6" } }, "node_modules/@mariozechner/clipboard-darwin-arm64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", - "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.6.tgz", + "integrity": "sha512-HjaisYCAbHi/1+N1yDAQHc8ZXGffufIUT5NSOSVR3f3AuMDusxTtnbK8tZ7JFDkShua1oNGZoNwQHsc8MPtE0Q==", "cpu": [ "arm64" ], @@ -1199,9 +1185,9 @@ } }, "node_modules/@mariozechner/clipboard-darwin-universal": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", - "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.6.tgz", + "integrity": "sha512-8BWtPjOtJOJoykml3w0fx0zRrfWP31mXrJwfoA7xzNprkZw1uolCNfgmjDiVBseoKjp16EGITz7bN+61qn8dWA==", "dev": true, "license": "MIT", "optional": true, @@ -1213,9 +1199,9 @@ } }, "node_modules/@mariozechner/clipboard-darwin-x64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", - "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.6.tgz", + "integrity": "sha512-p9syiZD1kU4I+1ya7f7g+zD1GiUvR8fdlRlNmgsZNWlyjtc8rlV2EjTLd/35x1LsdBq020GVvtzp0ZmPgBI09Q==", "cpu": [ "x64" ], @@ -1230,9 +1216,9 @@ } }, "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", - "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.6.tgz", + "integrity": "sha512-5JFf5rGofrm+V29HNF+wLthXphHdQpMbKDUYJ5tML6/Z5DLlLOV/9Ak4kDPtYyZ+Dzf+kAusE0VsFg4+tfP1IA==", "cpu": [ "arm64" ], @@ -1250,9 +1236,9 @@ } }, "node_modules/@mariozechner/clipboard-linux-arm64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", - "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.6.tgz", + "integrity": "sha512-JlVjxxw0GbGC0djXYWRIqyteO3J1KZ/QG3udlEFaOD5TLOM1FnmXXAPDQBqr+aBVr720ef9K00dirYnJ0LDCtw==", "cpu": [ "arm64" ], @@ -1270,9 +1256,9 @@ } }, "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", - "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.6.tgz", + "integrity": "sha512-4t8BUi5zZ+L77otFQVnVSlaTyAX4TVk9EqQm4syMrEQp96trFEHEwwNHcNEBGzYv5+K7mxay50TthYkz47OWzQ==", "cpu": [ "riscv64" ], @@ -1290,9 +1276,9 @@ } }, "node_modules/@mariozechner/clipboard-linux-x64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", - "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.6.tgz", + "integrity": "sha512-trtPwcNLW37irwQCJLtCxLw757jjJZk3TSnY/MU9bhtWtA3K9b/eLW0e4RGhUXDoFRds9opNWWaUDuFLa8dm0w==", "cpu": [ "x64" ], @@ -1310,9 +1296,9 @@ } }, "node_modules/@mariozechner/clipboard-linux-x64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", - "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.6.tgz", + "integrity": "sha512-WfnzIvOCCWQiN0MmltCEo6cLceUDbYe+I7xyFZjaps5A+2Op/M2CY7Rey+C4ucQhrvmpoHmTSFgY9ODWk7snoA==", "cpu": [ "x64" ], @@ -1330,9 +1316,9 @@ } }, "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", - "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.6.tgz", + "integrity": "sha512-+8+1aHYsBPUjmW3otmWlg+Hijt0iJvoBBs5e0mxFeUd4gDaKMB8Bn6x7c6KVtscg7E5j5NFXnwQqNSIAO4p8zQ==", "cpu": [ "arm64" ], @@ -1347,9 +1333,9 @@ } }, "node_modules/@mariozechner/clipboard-win32-x64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", - "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.6.tgz", + "integrity": "sha512-S4xfPmERC8ZkiLHe3vekZCjdDwNEETCuvCgQK2kP6/TnvmUkq1y2Pk+DjM4t8uh9KMX9bH4zs5ePcKa8GTXmfg==", "cpu": [ "x64" ], @@ -2484,38 +2470,6 @@ "@statsig/client-core": "3.31.0" } }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", @@ -2552,13 +2506,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mime-types": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", - "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "25.6.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", @@ -2576,17 +2523,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@vitest/expect": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", @@ -2720,42 +2656,6 @@ "node": ">= 14" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -2766,19 +2666,6 @@ "node": ">=12" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -2809,16 +2696,6 @@ ], "license": "MIT" }, - "node_modules/basic-ftp": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", - "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -2893,16 +2770,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2942,107 +2809,51 @@ "node": ">=18" } }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "ISC", - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } + "license": "MIT" }, - "node_modules/cli-highlight/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "node": ">= 8" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ansi-regex": "^5.0.1" + "isexe": "^2.0.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" + "bin": { + "node-which": "bin/node-which" }, "engines": { - "node": ">=7.0.0" + "node": ">= 8" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -3095,21 +2906,6 @@ "node": ">=4.0.0" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -3139,13 +2935,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -3172,62 +2961,6 @@ "dev": true, "license": "MIT" }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -3238,16 +2971,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -3281,27 +3004,6 @@ "dev": true, "license": "MIT" }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, "node_modules/fast-xml-builder": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.7.tgz", @@ -3340,16 +3042,6 @@ "fxparser": "src/cli/cli.js" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3392,25 +3084,6 @@ "node": "^12.20 || >= 14.13" } }, - "node_modules/file-type": { - "version": "21.3.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", - "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -3481,20 +3154,10 @@ "node": ">=18" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, "license": "MIT", "engines": { @@ -3504,47 +3167,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -3604,20 +3226,10 @@ "devOptional": true, "license": "ISC" }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3707,26 +3319,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/isexe": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", @@ -3795,9 +3387,9 @@ } }, "node_modules/koffi": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz", - "integrity": "sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.2.tgz", + "integrity": "sha512-owU0MRwv6xkrVqCd+33uw6BaYppkTRXbO/rVdJNI2dvZG0gzyRhYwW25eWtc5pauwK8TGh3AbkFONSezdykfSA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4119,33 +3711,6 @@ "node": ">= 18" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -4217,18 +3782,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", @@ -4254,16 +3807,6 @@ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, - "node_modules/netmask": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", - "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/node-abi": { "version": "3.90.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.90.0.tgz", @@ -4366,16 +3909,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", @@ -4432,64 +3965,6 @@ "node": ">=8" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "license": "MIT" - }, "node_modules/partial-json": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", @@ -4513,6 +3988,16 @@ "node": ">=14.0.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-scurry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", @@ -4537,13 +4022,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4677,43 +4155,6 @@ "node": ">=12.0.0" } }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -4753,16 +4194,6 @@ "node": ">= 6" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -4839,6 +4270,29 @@ "node": ">=10" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -4898,58 +4352,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", - "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5003,60 +4405,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -5079,36 +4427,6 @@ ], "license": "MIT" }, - "node_modules/strtok3": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", - "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tar": { "version": "7.5.15", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", @@ -5159,29 +4477,6 @@ "node": ">=6" } }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5226,25 +4521,6 @@ "node": ">=14.0.0" } }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", @@ -5272,9 +4548,9 @@ } }, "node_modules/typebox": { - "version": "1.1.37", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.37.tgz", - "integrity": "sha512-jb7jp6KvOvvy5sd+11AfJ0/e0F0AS9RcOXd55oGi2ZnRHIGmFvrTaNF+ZidRmGBmmNTkM5KKl0Z37KzxJ+owEQ==", + "version": "1.1.38", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", + "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", "dev": true, "license": "MIT" }, @@ -5292,19 +4568,6 @@ "node": ">=14.17" } }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/undici": { "version": "7.25.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", @@ -5327,20 +4590,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/uuid": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", - "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, "node_modules/vite": { "version": "8.0.12", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz", @@ -5559,47 +4808,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5628,16 +4836,6 @@ } } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -5663,46 +4861,6 @@ "url": "https://github.com/sponsors/eemeli" } }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", diff --git a/package.json b/package.json index dd6a8bb..cb570aa 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ ], "type": "module", "engines": { - "node": ">=18" + "node": ">=22.19.0" }, "scripts": { "typecheck": "tsc --noEmit", @@ -44,11 +44,15 @@ }, "peerDependencies": { "@earendil-works/pi-ai": "*", - "@earendil-works/pi-coding-agent": "*" + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*", + "typebox": "*" }, "devDependencies": { - "@earendil-works/pi-ai": "^0.74.0", - "@earendil-works/pi-coding-agent": "^0.74.0", + "@earendil-works/pi-ai": "^0.75.3", + "@earendil-works/pi-coding-agent": "^0.75.3", + "@earendil-works/pi-tui": "^0.75.3", + "typebox": "^1.1.38", "typescript": "^6.0.3", "vitest": "^4.1.6" }, diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index b311b0e..7c3dc94 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -220,6 +220,10 @@ function createCursorReplayOnlyToolDefinition(toolName: "cursor_edit" | "cursor_ name: toolName, label: `Cursor ${cursorToolName}`, description: `Replay display for a Cursor SDK ${cursorToolName} operation. This tool only returns recorded Cursor results and never mutates files directly.`, + promptSnippet: `Render a recorded Cursor SDK ${cursorToolName} operation without mutating files.`, + promptGuidelines: [ + `Use ${toolName} only for replaying Cursor SDK ${cursorToolName} results that were already produced by Cursor; ${toolName} does not perform file mutations.`, + ], parameters: cursorReplayToolSchema, renderShell: "self", async execute() { -- 2.51.2 From 6bfa416259c3305e84423b24f0844bf17f3cdf88 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 18 May 2026 07:52:30 -0600 Subject: [PATCH 047/359] Release v0.1.11 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a5cb51..56152dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.1.11 - 2026-05-18 + +### Changed +- Updated the local pi package baseline to `@earendil-works/*` `0.75.3`, including the Node.js `>=22.19.0` runtime floor and refreshed npm lockfile. +- Added prompt metadata for the non-mutating Cursor replay tools so pi can describe `cursor_edit` and `cursor_write` more clearly in tool guidance. +- Removed tracked CueLoop runtime state from the repository and ignored local `.cueloop/` artifacts. + + ## 0.1.10 - 2026-05-15 ### Added diff --git a/package-lock.json b/package-lock.json index c5561c0..293d453 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.10", + "version": "0.1.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.10", + "version": "0.1.11", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.13" diff --git a/package.json b/package.json index cb570aa..049f20c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.10", + "version": "0.1.11", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", -- 2.51.2 From 38d0148874508bd315072e1b43b774f0c1108882 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 18 May 2026 08:01:36 -0600 Subject: [PATCH 048/359] Release v0.1.12 --- CHANGELOG.md | 5 +++++ package-lock.json | 4 ++-- package.json | 7 +++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56152dd..38b8107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.1.12 - 2026-05-18 + +### Fixed +- Bundle the audited `@cursor/sdk` dependency tree so `pi install npm:pi-cursor-sdk` preserves patched `sqlite3`, `tar`, and `undici` transitive versions even though npm package-level `overrides` are not applied when the package is installed as a dependency. + ## 0.1.11 - 2026-05-18 ### Changed diff --git a/package-lock.json b/package-lock.json index 293d453..41751c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.11", + "version": "0.1.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.11", + "version": "0.1.12", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.13" diff --git a/package.json b/package.json index 049f20c..a62f48a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.11", + "version": "0.1.12", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", @@ -64,5 +64,8 @@ "overrides": { "undici": "7.25.0", "sqlite3": "6.0.1" - } + }, + "bundledDependencies": [ + "@cursor/sdk" + ] } -- 2.51.2 From 603d3bb4d1d4d5987567a2ee373fd835d621f109 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 18 May 2026 09:17:19 -0600 Subject: [PATCH 049/359] Release v0.1.13 --- CHANGELOG.md | 5 +++++ package-lock.json | 4 ++-- package.json | 7 ++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b8107..c62d18b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.1.13 - 2026-05-18 + +### Fixed +- Restored lightweight GitHub pi install behavior by removing bundled dependency metadata from the published package. The package already uses the latest `@cursor/sdk` `1.0.13`; local and GitHub installs continue to use the repo-level audited lockfile and overrides. + ## 0.1.12 - 2026-05-18 ### Fixed diff --git a/package-lock.json b/package-lock.json index 41751c5..fa69c83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.12", + "version": "0.1.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.12", + "version": "0.1.13", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.13" diff --git a/package.json b/package.json index a62f48a..78df442 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.12", + "version": "0.1.13", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", @@ -64,8 +64,5 @@ "overrides": { "undici": "7.25.0", "sqlite3": "6.0.1" - }, - "bundledDependencies": [ - "@cursor/sdk" - ] + } } -- 2.51.2 From f007daf7741fc134694320686ac01849926f42fe Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Mon, 18 May 2026 11:33:05 -0600 Subject: [PATCH 050/359] Release 0.1.14 --- CHANGELOG.md | 6 + README.md | 37 +- docs/cursor-model-ux-spec.md | 12 +- package-lock.json | 4 +- package.json | 2 +- src/bundled-context-windows.ts | 7 +- src/cursor-fallback-models.generated.ts | 4139 ++++++++++++++++++++++- test/model-discovery.test.ts | 65 +- 8 files changed, 4150 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c62d18b..aef5b96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.1.14 - 2026-05-18 + +### Changed +- Refreshed the Cursor fallback model snapshot and bundled default/non-Max context-window cache from the current `@cursor/sdk` 1.0.13 catalog, including Composer 2.5 (`composer-2.5` and `composer-2-5`) with default fast-mode support. +- Updated README, demo, and maintainer model UX docs to use Composer 2.5 as the primary Composer example. + ## 0.1.13 - 2026-05-18 ### Fixed diff --git a/README.md b/README.md index 704853f..d1db851 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ pi install https://github.com/fitchmultz/pi-cursor-sdk 2. Start pi with a Cursor model: ```bash -pi --model cursor/composer-2 +pi --model cursor/composer-2.5 ``` 3. In pi, run `/login`, choose `Use an API key`, choose `Cursor`, and paste your Cursor API key. @@ -30,7 +30,7 @@ If pi started without a key, run `/cursor-refresh-models` after `/login` to refr ## Requirements -- Node.js 18+ +- Node.js 22.19+ - pi - a Cursor API key saved through `/login`, available as `CURSOR_API_KEY`, or passed with pi's `--api-key` @@ -64,7 +64,7 @@ For development from this repository: ```bash npm install -pi -e . --model cursor/composer-2 +pi -e . --model cursor/composer-2.5 ``` ## Configure your Cursor API key @@ -72,7 +72,7 @@ pi -e . --model cursor/composer-2 Preferred setup: ```bash -pi --model cursor/composer-2 +pi --model cursor/composer-2.5 ``` Then, inside pi: @@ -89,13 +89,13 @@ Environment setup: ```bash export CURSOR_API_KEY="your-key" -pi --model cursor/composer-2 +pi --model cursor/composer-2.5 ``` One-shot setup: ```bash -pi --api-key "your-key" --model cursor/composer-2 --cursor-no-fast -p "Say ok only." +pi --api-key "your-key" --model cursor/composer-2.5 --cursor-no-fast -p "Say ok only." ``` Discovery uses pi's native resolution order for this extension: `--api-key`, the stored `cursor` key in `~/.pi/agent/auth.json`, then `CURSOR_API_KEY`. @@ -118,7 +118,7 @@ Expected behavior: Smoke test: ```bash -pi --model cursor/composer-2 --cursor-no-fast -p "Reply with: ok" +pi --model cursor/composer-2.5 --cursor-no-fast -p "Reply with: ok" ``` ## Choosing a model @@ -126,7 +126,7 @@ pi --model cursor/composer-2 --cursor-no-fast -p "Reply with: ok" Choose Cursor models interactively with `/model`, or pass a model on the command line: ```bash -pi --model cursor/composer-2 +pi --model cursor/composer-2.5 pi --model cursor/gpt-5.5@1m pi --model cursor/gpt-5.5@272k pi --model cursor/claude-opus-4-7@300k @@ -180,10 +180,10 @@ For one run, force fast on or off without changing saved defaults: ```bash pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only" -pi --model cursor/composer-2 --cursor-no-fast -p "Say ok only" +pi --model cursor/composer-2.5 --cursor-no-fast -p "Say ok only" ``` -`composer-2` can default to fast. Use `--cursor-no-fast` for a one-shot no-fast `composer-2` run. In print mode (`-p`), `--cursor-no-fast` is silent and does not write `~/.pi/agent/cursor-sdk.json`. +Composer 2 and Composer 2.5 can default to fast. Use `--cursor-no-fast` for a one-shot no-fast Composer run. In print mode (`-p`), `--cursor-no-fast` is silent and does not write `~/.pi/agent/cursor-sdk.json`. In interactive mode, the footer only shows fast mode when fast is enabled: @@ -199,14 +199,11 @@ Images from the latest user message are forwarded to Cursor. Historical images a ## Fallback models -If no key is available from `/login`, `CURSOR_API_KEY`, or `--api-key`, model discovery fails, or discovery returns no models, the extension registers conservative fallback Cursor models and notifies interactive users when possible: +If no key is available from `/login`, `CURSOR_API_KEY`, or `--api-key`, model discovery fails, or discovery returns no models, the extension registers a bundled fallback snapshot of the latest reviewed Cursor SDK model catalog and notifies interactive users when possible. -- `composer-2` -- `gpt-5.5@1m`, `gpt-5.5@272k` -- `claude-sonnet-4-6@1m`, `claude-sonnet-4-6@200k` -- `claude-opus-4-7@1m`, `claude-opus-4-7@300k` +The fallback snapshot includes Composer 2.5 (`composer-2.5` and `composer-2-5`), Composer 2, GPT, Claude, Gemini, Grok, Kimi, and other model IDs exposed by the reviewed `Cursor.models.list()` output. The exact checked-in snapshot lives in `src/cursor-fallback-models.generated.ts`. -Fallback models are a conservative startup model list. Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-key`. If you add auth after startup, run `/cursor-refresh-models` to refresh the full live Cursor model catalog without restarting pi. +Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-key`. If you add auth after startup, run `/cursor-refresh-models` to refresh the full live Cursor model catalog without restarting pi. ## Limits @@ -229,13 +226,13 @@ You can also restart pi with a key in the same shell or launcher that starts pi: ```bash export CURSOR_API_KEY="your-key" -pi --model cursor/composer-2 +pi --model cursor/composer-2.5 ``` Or run a one-shot command: ```bash -pi --api-key "your-key" --model cursor/composer-2 -p "Say ok only" +pi --api-key "your-key" --model cursor/composer-2.5 -p "Say ok only" ``` ### `pi --list-models cursor` shows no Cursor models @@ -273,7 +270,7 @@ Cursor SDK local agents load MCP servers from Cursor setting sources and inline Cursor native replay is a UI enhancement for interactive TTY sessions. If another extension already owns `read`, `bash`, `ls`, `cursor_edit`, or `cursor_write`, this extension skips only the conflicting native replay wrapper and uses the scrubbed Cursor activity transcript for that tool instead. To disable Cursor native replay registration entirely, start pi with: ```bash -PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2 +PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2.5 ``` `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is also accepted as a registration-only opt-out. @@ -306,7 +303,7 @@ Local development run: ```bash npm install -CURSOR_API_KEY="your-key" pi -e . --model cursor/composer-2 +CURSOR_API_KEY="your-key" pi -e . --model cursor/composer-2.5 ``` Maintainer design notes live in [`docs/cursor-model-ux-spec.md`](docs/cursor-model-ux-spec.md). diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 6eb1793..a1ef4e9 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -149,7 +149,7 @@ cursor/gpt-5.5@1m cursor/gpt-5.5@272k cursor/claude-opus-4-7@1m cursor/claude-opus-4-7@300k -cursor/composer-2 +cursor/composer-2.5 ``` Avoid colon-based context IDs in the first implementation unless this spec is intentionally changed: @@ -428,7 +428,7 @@ Use Cursor default variants: ```text gpt-5.5 -> cursor/gpt-5.5@1m, thinking medium, fast=false -composer-2 -> cursor/composer-2, fast=true +composer-2.5 -> cursor/composer-2.5, fast=true ``` ### Resume Session @@ -492,7 +492,7 @@ These examples document the capability shapes the extension handles, not an exha | Example model shape | Cursor controls | Pi representation | |---|---|---| | plain model, such as `default` or models with no exposed controls | none | plain model | -| `composer-2`-style model | fast | plain model + fast extension state | +| Composer-style model such as `composer-2.5` or `composer-2` | fast | plain model + fast extension state | | GPT-style reasoning model with context variants | context, reasoning, fast when exposed | context variants + native thinking + optional fast state | | Claude-style thinking model with context variants | thinking, context, effort when exposed | context variants + native thinking + optional fast state | | Claude-style thinking model without context variants | thinking and/or effort | plain model + native thinking | @@ -504,12 +504,12 @@ If Cursor later adds `fast`, `context`, `reasoning`, `effort`, or aliases to a m ## Detailed Examples -### `composer-2` +### Composer 2 / 2.5 -Initial Cursor default: +Initial Cursor default for Composer 2.5: ```text -pi model: cursor/composer-2 +pi model: cursor/composer-2.5 Cursor params: fast=true pi thinking: off Cursor status: cursor fast diff --git a/package-lock.json b/package-lock.json index fa69c83..4dc43e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-cursor-sdk", - "version": "0.1.13", + "version": "0.1.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-cursor-sdk", - "version": "0.1.13", + "version": "0.1.14", "license": "MIT", "dependencies": { "@cursor/sdk": "^1.0.13" diff --git a/package.json b/package.json index 78df442..7d8b85d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-cursor-sdk", - "version": "0.1.13", + "version": "0.1.14", "description": "pi provider extension backed by @cursor/sdk local agents", "author": "Mitch Fultz (https://github.com/fitchmultz)", "license": "MIT", diff --git a/src/bundled-context-windows.ts b/src/bundled-context-windows.ts index 66082bf..c6bb008 100644 --- a/src/bundled-context-windows.ts +++ b/src/bundled-context-windows.ts @@ -1,14 +1,16 @@ -// Generated from Cursor SDK checkpoint tokenDetails.maxTokens on 2026-05-04. +// Generated from Cursor SDK checkpoint tokenDetails.maxTokens on 2026-05-18. +// Refresh with: npm run refresh:cursor-snapshots -- --write --context-windows ~/.pi/agent/cursor-sdk-context-windows.json // These are default/non-Max-mode SDK context windows for Cursor models that do not // expose a catalog `context` parameter. Do not replace them with Max Mode values // unless the Cursor SDK exposes an exact Max Mode model selection and the extension // uses that selection for matching pi model IDs. export const BUNDLED_CONTEXT_WINDOWS = { + "default": 200000, "claude-haiku-4-5": 200000, "claude-opus-4-5": 200000, "composer-1.5": 200000, "composer-2": 200000, - default: 200000, + "composer-2.5": 200000, "gemini-2.5-flash": 200000, "gemini-3-flash": 200000, "gemini-3.1-pro": 200000, @@ -22,6 +24,7 @@ export const BUNDLED_CONTEXT_WINDOWS = { "gpt-5.3-codex-spark": 128000, "gpt-5.4-mini": 272000, "gpt-5.4-nano": 272000, + "gpt-5.5@272k": 272000, "grok-4-20": 200000, "kimi-k2.5": 262000, } as const satisfies Record; diff --git a/src/cursor-fallback-models.generated.ts b/src/cursor-fallback-models.generated.ts index 1327cf9..4f7f4c4 100644 --- a/src/cursor-fallback-models.generated.ts +++ b/src/cursor-fallback-models.generated.ts @@ -5,141 +5,4138 @@ import type { ModelListItem } from "@cursor/sdk"; // Do not add secrets; this file stores public model metadata only. export const FALLBACK_MODEL_ITEMS = [ { - id: "composer-2", - displayName: "Cursor Composer 2", + id: "claude-haiku-4-5", + displayName: "Haiku 4.5", + aliases: [ + "haiku-latest", + "haiku", + "haiku-4.5", + "haiku-4-5" + ], parameters: [ { - id: "fast", - displayName: "Fast", - values: [{ value: "false" }, { value: "true" }], - }, + id: "thinking", + displayName: "Thinking", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: ":icon-brain:" + } + ] + } ], variants: [ { - params: [{ id: "fast", value: "true" }], - displayName: "Cursor Composer 2", - isDefault: true, + params: [ + { + id: "thinking", + value: "false" + } + ], + displayName: "Haiku 4.5" }, - ], + { + params: [ + { + id: "thinking", + value: "true" + } + ], + displayName: "Haiku 4.5", + isDefault: true + } + ] }, { - id: "gpt-5.5", - displayName: "GPT-5.5", + id: "claude-opus-4-5", + displayName: "Opus 4.5", + aliases: [ + "opus", + "opus-4.5", + "opus-4-5" + ], parameters: [ { - id: "context", - displayName: "Context", - values: [{ value: "1m" }, { value: "272k" }], - }, - { - id: "reasoning", - displayName: "Reasoning", + id: "thinking", + displayName: "Thinking", values: [ - { value: "none" }, - { value: "low" }, - { value: "medium" }, - { value: "high" }, - { value: "extra-high" }, - ], - }, - { - id: "fast", - displayName: "Fast", - values: [{ value: "false" }, { value: "true" }], - }, + { + value: "false" + }, + { + value: "true", + displayName: ":icon-brain:" + } + ] + } ], variants: [ { params: [ - { id: "context", value: "1m" }, - { id: "reasoning", value: "medium" }, - { id: "fast", value: "false" }, + { + id: "thinking", + value: "false" + } ], - displayName: "GPT-5.5", - isDefault: true, + displayName: "Opus 4.5" }, - ], + { + params: [ + { + id: "thinking", + value: "true" + } + ], + displayName: "Opus 4.5", + isDefault: true + } + ] }, { - id: "claude-sonnet-4-6", - displayName: "Sonnet 4.6", + id: "claude-opus-4-6", + displayName: "Opus 4.6", + aliases: [ + "opus", + "opus-4.6", + "opus-4-6" + ], parameters: [ { id: "thinking", displayName: "Thinking", - values: [{ value: "false" }, { value: "true" }], + values: [ + { + value: "false" + }, + { + value: "true", + displayName: ":icon-brain:" + } + ] }, { id: "context", displayName: "Context", - values: [{ value: "1m" }, { value: "200k" }], + values: [ + { + value: "200k", + displayName: "200K" + }, + { + value: "1m", + displayName: "1M" + } + ] }, { id: "effort", displayName: "Effort", values: [ - { value: "low" }, - { value: "medium" }, - { value: "high" }, - { value: "xhigh" }, - { value: "max" }, - ], + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "max", + displayName: "Max" + } + ] }, { id: "fast", displayName: "Fast", - values: [{ value: "false" }, { value: "true" }], - }, + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } ], variants: [ { params: [ - { id: "thinking", value: "true" }, - { id: "context", value: "1m" }, - { id: "effort", value: "medium" }, - { id: "fast", value: "false" }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "false" + } ], - displayName: "Sonnet 4.6", - isDefault: true, + displayName: "Opus 4.6" }, - ], + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6", + isDefault: true + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.6" + } + ] }, { id: "claude-opus-4-7", displayName: "Opus 4.7", + aliases: [ + "opus-latest", + "opus", + "opus-4.7", + "opus-4-7" + ], parameters: [ { id: "thinking", displayName: "Thinking", - values: [{ value: "false" }, { value: "true" }], + values: [ + { + value: "false" + }, + { + value: "true", + displayName: ":icon-brain:" + } + ] }, { id: "context", displayName: "Context", - values: [{ value: "1m" }, { value: "300k" }], + values: [ + { + value: "300k", + displayName: "300K" + }, + { + value: "1m", + displayName: "1M" + } + ] }, { id: "effort", displayName: "Effort", values: [ - { value: "low" }, - { value: "medium" }, - { value: "high" }, - { value: "xhigh" }, - { value: "max" }, - ], + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "xhigh", + displayName: "Extra High" + }, + { + value: "max", + displayName: "Max" + } + ] }, + { + id: "fast", + displayName: "Fast", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } ], variants: [ { params: [ - { id: "thinking", value: "true" }, - { id: "context", value: "1m" }, - { id: "effort", value: "xhigh" }, + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "false" + } ], - displayName: "Opus 4.7", - isDefault: true, + displayName: "Opus 4.7" }, - ], - }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "xhigh" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "xhigh" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "xhigh" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "xhigh" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "xhigh" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "xhigh" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "300k" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "xhigh" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7", + isDefault: true + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "xhigh" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Opus 4.7" + }, + { + params: [ + { + id: "cyber", + value: "false" + }, + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Opus 4.7" + } + ] + }, + { + id: "claude-sonnet-4", + displayName: "Sonnet 4", + aliases: [ + "sonnet", + "sonnet-4" + ], + parameters: [ + { + id: "thinking", + displayName: "Thinking", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: ":icon-brain:" + } + ] + }, + { + id: "context", + displayName: "Context", + values: [ + { + value: "200k", + displayName: "200K" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + } + ], + displayName: "Sonnet 4", + isDefault: true + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + } + ], + displayName: "Sonnet 4" + } + ] + }, + { + id: "claude-sonnet-4-5", + displayName: "Sonnet 4.5", + aliases: [ + "sonnet", + "sonnet-4.5", + "sonnet-4-5" + ], + parameters: [ + { + id: "thinking", + displayName: "Thinking", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: ":icon-brain:" + } + ] + }, + { + id: "context", + displayName: "Context", + values: [ + { + value: "200k", + displayName: "200K" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + } + ], + displayName: "Sonnet 4.5" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + } + ], + displayName: "Sonnet 4.5", + isDefault: true + } + ] + }, + { + id: "claude-sonnet-4-6", + displayName: "Sonnet 4.6", + aliases: [ + "sonnet-latest", + "sonnet", + "sonnet-4.6", + "sonnet-4-6" + ], + parameters: [ + { + id: "thinking", + displayName: "Thinking", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: ":icon-brain:" + } + ] + }, + { + id: "context", + displayName: "Context", + values: [ + { + value: "200k", + displayName: "200K" + }, + { + value: "1m", + displayName: "1M" + } + ] + }, + { + id: "effort", + displayName: "Effort", + values: [ + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "max", + displayName: "Max" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "low" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "medium" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "high" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "max" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "false" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "low" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "medium" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "high" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "200k" + }, + { + id: "effort", + value: "max" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "low" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "medium" + } + ], + displayName: "Sonnet 4.6", + isDefault: true + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "high" + } + ], + displayName: "Sonnet 4.6" + }, + { + params: [ + { + id: "thinking", + value: "true" + }, + { + id: "context", + value: "1m" + }, + { + id: "effort", + value: "max" + } + ], + displayName: "Sonnet 4.6" + } + ] + }, + { + id: "composer-2", + displayName: "Composer 2", + aliases: [ + "composer-latest", + "composer" + ], + parameters: [ + { + id: "fast", + displayName: "Fast", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "fast", + value: "true" + } + ], + displayName: "Composer 2", + isDefault: true + }, + { + params: [ + { + id: "fast", + value: "false" + } + ], + displayName: "Composer 2" + } + ] + }, + { + id: "composer-2.5", + displayName: "Composer 2.5", + aliases: [ + "composer-2-5" + ], + parameters: [ + { + id: "fast", + displayName: "Fast", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "fast", + value: "true" + } + ], + displayName: "Composer 2.5", + isDefault: true + }, + { + params: [ + { + id: "fast", + value: "false" + } + ], + displayName: "Composer 2.5" + } + ] + }, + { + id: "default", + displayName: "Auto", + aliases: [ + "auto" + ], + variants: [ + { + params: [], + displayName: "Auto", + isDefault: true + } + ] + }, + { + id: "gemini-2.5-flash", + displayName: "Gemini 2.5 Flash", + aliases: [ + "gemini-flash" + ], + variants: [ + { + params: [], + displayName: "Gemini 2.5 Flash", + isDefault: true + } + ] + }, + { + id: "gemini-3-flash", + displayName: "Gemini 3 Flash", + aliases: [ + "gemini-flash-latest", + "gemini-flash" + ], + variants: [ + { + params: [], + displayName: "Gemini 3 Flash", + isDefault: true + } + ] + }, + { + id: "gemini-3.1-pro", + displayName: "Gemini 3.1 Pro", + aliases: [ + "gemini-latest", + "gemini-pro-latest", + "gemini", + "gemini-pro" + ], + variants: [ + { + params: [], + displayName: "Gemini 3.1 Pro", + isDefault: true + } + ] + }, + { + id: "gpt-5-mini", + displayName: "GPT-5 Mini", + aliases: [ + "gpt-mini" + ], + variants: [ + { + params: [], + displayName: "GPT-5 Mini", + isDefault: true + } + ] + }, + { + id: "gpt-5.1", + displayName: "GPT-5.1", + aliases: [ + "gpt" + ], + parameters: [ + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "reasoning", + value: "low" + } + ], + displayName: "GPT-5.1" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + } + ], + displayName: "GPT-5.1", + isDefault: true + }, + { + params: [ + { + id: "reasoning", + value: "high" + } + ], + displayName: "GPT-5.1" + } + ] + }, + { + id: "gpt-5.1-codex-max", + displayName: "Codex 5.1 Max", + aliases: [ + "codex", + "codex-5.1-max" + ], + parameters: [ + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "extra-high", + displayName: "Extra High" + } + ] + }, + { + id: "fast", + displayName: "Fast", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.1 Max" + }, + { + params: [ + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.1 Max" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.1 Max" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.1 Max" + }, + { + params: [ + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.1 Max" + }, + { + params: [ + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.1 Max", + isDefault: true + }, + { + params: [ + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.1 Max" + }, + { + params: [ + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.1 Max" + } + ] + }, + { + id: "gpt-5.1-codex-mini", + displayName: "Codex 5.1 Mini", + aliases: [ + "codex-mini-latest", + "codex-mini" + ], + parameters: [ + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "reasoning", + value: "low" + } + ], + displayName: "Codex 5.1 Mini" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + } + ], + displayName: "Codex 5.1 Mini", + isDefault: true + }, + { + params: [ + { + id: "reasoning", + value: "high" + } + ], + displayName: "Codex 5.1 Mini" + } + ] + }, + { + id: "gpt-5.2", + displayName: "GPT-5.2", + aliases: [ + "gpt" + ], + parameters: [ + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "extra-high", + displayName: "Extra High" + } + ] + }, + { + id: "fast", + displayName: "Fast", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.2" + }, + { + params: [ + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.2" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.2" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.2" + }, + { + params: [ + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.2" + }, + { + params: [ + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.2", + isDefault: true + }, + { + params: [ + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.2" + }, + { + params: [ + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.2" + } + ] + }, + { + id: "gpt-5.2-codex", + displayName: "Codex 5.2", + aliases: [ + "codex", + "codex-5.2" + ], + parameters: [ + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "extra-high", + displayName: "Extra High" + } + ] + }, + { + id: "fast", + displayName: "Fast", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.2" + }, + { + params: [ + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.2" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.2" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.2" + }, + { + params: [ + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.2" + }, + { + params: [ + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.2", + isDefault: true + }, + { + params: [ + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.2" + }, + { + params: [ + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.2" + } + ] + }, + { + id: "gpt-5.3-codex", + displayName: "Codex 5.3", + aliases: [ + "codex-latest", + "codex", + "codex-5.3" + ], + parameters: [ + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "extra-high", + displayName: "Extra High" + } + ] + }, + { + id: "fast", + displayName: "Fast", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.3" + }, + { + params: [ + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.3" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.3" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.3" + }, + { + params: [ + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.3" + }, + { + params: [ + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.3", + isDefault: true + }, + { + params: [ + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "Codex 5.3" + }, + { + params: [ + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "Codex 5.3" + } + ] + }, + { + id: "gpt-5.4", + displayName: "GPT-5.4", + aliases: [ + "gpt" + ], + parameters: [ + { + id: "context", + displayName: "Context", + values: [ + { + value: "272k", + displayName: "272K" + }, + { + value: "1m", + displayName: "1M" + } + ] + }, + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "none", + displayName: "None" + }, + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "extra-high", + displayName: "Extra High" + } + ] + }, + { + id: "fast", + displayName: "Fast", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "none" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "none" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "none" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4", + isDefault: true + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4" + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.4" + } + ] + }, + { + id: "gpt-5.4-mini", + displayName: "GPT-5.4 Mini", + aliases: [ + "gpt-mini-latest", + "gpt-mini" + ], + parameters: [ + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "none", + displayName: "None" + }, + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "xhigh", + displayName: "Extra High" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "reasoning", + value: "none" + } + ], + displayName: "GPT-5.4 Mini" + }, + { + params: [ + { + id: "reasoning", + value: "low" + } + ], + displayName: "GPT-5.4 Mini" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + } + ], + displayName: "GPT-5.4 Mini", + isDefault: true + }, + { + params: [ + { + id: "reasoning", + value: "high" + } + ], + displayName: "GPT-5.4 Mini" + }, + { + params: [ + { + id: "reasoning", + value: "xhigh" + } + ], + displayName: "GPT-5.4 Mini" + } + ] + }, + { + id: "gpt-5.4-nano", + displayName: "GPT-5.4 Nano", + aliases: [ + "gpt-nano-latest", + "gpt-nano" + ], + parameters: [ + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "none", + displayName: "None" + }, + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "xhigh", + displayName: "Extra High" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "reasoning", + value: "none" + } + ], + displayName: "GPT-5.4 Nano" + }, + { + params: [ + { + id: "reasoning", + value: "low" + } + ], + displayName: "GPT-5.4 Nano" + }, + { + params: [ + { + id: "reasoning", + value: "medium" + } + ], + displayName: "GPT-5.4 Nano", + isDefault: true + }, + { + params: [ + { + id: "reasoning", + value: "high" + } + ], + displayName: "GPT-5.4 Nano" + }, + { + params: [ + { + id: "reasoning", + value: "xhigh" + } + ], + displayName: "GPT-5.4 Nano" + } + ] + }, + { + id: "gpt-5.5", + displayName: "GPT-5.5", + aliases: [ + "gpt-latest", + "gpt", + "gpt-5-5" + ], + parameters: [ + { + id: "context", + displayName: "Context", + values: [ + { + value: "272k", + displayName: "272K" + }, + { + value: "1m", + displayName: "1M" + } + ] + }, + { + id: "reasoning", + displayName: "Reasoning", + values: [ + { + value: "none", + displayName: "None" + }, + { + value: "low", + displayName: "Low" + }, + { + value: "medium", + displayName: "Medium" + }, + { + value: "high", + displayName: "High" + }, + { + value: "extra-high", + displayName: "Extra High" + } + ] + }, + { + id: "fast", + displayName: "Fast", + values: [ + { + value: "false" + }, + { + value: "true", + displayName: "Fast" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "none" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "none" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "272k" + }, + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "true" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "none" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "low" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "medium" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5", + isDefault: true + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5" + }, + { + params: [ + { + id: "context", + value: "1m" + }, + { + id: "reasoning", + value: "extra-high" + }, + { + id: "fast", + value: "false" + } + ], + displayName: "GPT-5.5" + } + ] + }, + { + id: "grok-4.3", + displayName: "Grok 4.3", + aliases: [ + "grok-latest", + "grok" + ], + parameters: [ + { + id: "context", + displayName: "Context", + values: [ + { + value: "200k", + displayName: "200K" + }, + { + value: "1m", + displayName: "1M" + } + ] + } + ], + variants: [ + { + params: [ + { + id: "context", + value: "200k" + } + ], + displayName: "Grok 4.3" + }, + { + params: [ + { + id: "context", + value: "1m" + } + ], + displayName: "Grok 4.3", + isDefault: true + } + ] + }, + { + id: "kimi-k2.5", + displayName: "Kimi K2.5", + aliases: [ + "kimi-latest", + "kimi" + ], + variants: [ + { + params: [], + displayName: "Kimi K2.5", + isDefault: true + } + ] + } ] satisfies ModelListItem[]; diff --git a/test/model-discovery.test.ts b/test/model-discovery.test.ts index c3b4498..3425ece 100644 --- a/test/model-discovery.test.ts +++ b/test/model-discovery.test.ts @@ -56,19 +56,25 @@ describe("discoverModels", () => { vi.clearAllMocks(); }); - it("returns context-qualified fallback models when no API key", async () => { + it("returns generated fallback models when no API key", async () => { delete process.env.CURSOR_API_KEY; const issues: CursorModelFallbackIssue[] = []; const models = await discoverModels({ onFallback: (issue) => issues.push(issue) }); - expect(models.map((model) => model.id)).toEqual([ - "claude-opus-4-7@1m", - "claude-opus-4-7@300k", - "claude-sonnet-4-6@1m", - "claude-sonnet-4-6@200k", - "composer-2", - "gpt-5.5@1m", - "gpt-5.5@272k", - ]); + const modelIds = models.map((model) => model.id); + expect(modelIds).toEqual( + expect.arrayContaining([ + "claude-opus-4-7@1m", + "claude-opus-4-7@300k", + "claude-sonnet-4-6@1m", + "claude-sonnet-4-6@200k", + "composer-2", + "composer-2.5", + "composer-2-5", + "gpt-5.5@1m", + "gpt-5.5@272k", + ]), + ); + expect(modelIds.length).toBeGreaterThan(20); expect(issues).toEqual([ expect.objectContaining({ reason: "missing-api-key", @@ -750,20 +756,39 @@ describe("discoverModels", () => { }); }); - it("keeps fallback model IDs aligned with the documented fallback list", async () => { + it("keeps the fallback snapshot aligned with the current Composer 2.5 catalog shape", async () => { delete process.env.CURSOR_API_KEY; const models = await discoverModels(); + const modelIds = models.map((model) => model.id); - expect(models.map((model) => model.id)).toEqual([ - "claude-opus-4-7@1m", - "claude-opus-4-7@300k", - "claude-sonnet-4-6@1m", - "claude-sonnet-4-6@200k", - "composer-2", - "gpt-5.5@1m", - "gpt-5.5@272k", - ]); + expect(modelIds).toEqual(expect.arrayContaining(["composer-2.5", "composer-2-5", "composer-2"])); + expect(getCursorModelMetadata("composer-2.5")).toEqual( + expect.objectContaining({ + baseModelId: "composer-2.5", + selectionModelId: "composer-2.5", + contextWindow: 200000, + supportsFast: true, + defaultFast: true, + }), + ); + expect(getCursorModelMetadata("composer-2-5")).toEqual( + expect.objectContaining({ + baseModelId: "composer-2.5", + selectionModelId: "composer-2-5", + contextWindow: 200000, + supportsFast: true, + defaultFast: true, + }), + ); + expect(buildCursorModelSelection("composer-2.5", "off")).toEqual({ + id: "composer-2.5", + params: [{ id: "fast", value: "true" }], + }); + expect(buildCursorModelSelection("composer-2.5", "off", false)).toEqual({ + id: "composer-2.5", + params: [{ id: "fast", value: "false" }], + }); }); it("falls back and reports discovery failure when Cursor.models.list throws", async () => { -- 2.51.2 From 1adfa25a1ddf234993cd7785d2cc4207feec25f5 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Tue, 19 May 2026 08:38:54 -0600 Subject: [PATCH 051/359] Implement session CWD management for Cursor integration; update Cursor model version in documentation. Add tests for session CWD functionality and ensure proper handling during session start events. --- AGENTS.md | 2 +- CHANGELOG.md | 6 + src/cursor-native-tool-display.ts | 8 +- src/cursor-provider.ts | 7 +- src/cursor-session-cwd.ts | 33 ++++ src/index.ts | 3 + test/cursor-provider.test.ts | 2 + test/cursor-session-cwd.test.ts | 67 ++++++++ test/index-session-cwd-integration.test.ts | 175 +++++++++++++++++++++ test/index.test.ts | 2 + 10 files changed, 296 insertions(+), 9 deletions(-) create mode 100644 src/cursor-session-cwd.ts create mode 100644 test/cursor-session-cwd.test.ts create mode 100644 test/index-session-cwd-integration.test.ts diff --git a/AGENTS.md b/AGENTS.md index 4649ba8..2e73521 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,7 +29,7 @@ This repository is a pi provider extension that registers Cursor SDK-backed mode - Run tests: `npm test` - Typecheck: `npm run typecheck` - Watch tests while developing: `npm run test:watch` -- Local development run, requires a Cursor key: `CURSOR_API_KEY="your-key" pi -e . --model cursor/composer-2` +- Local development run, requires a Cursor key: `CURSOR_API_KEY="your-key" pi -e . --model cursor/composer-2.5` - List Cursor models, requires pi and usually a Cursor key: `pi --list-models cursor` There is no lint or format script in `package.json` at this time. diff --git a/CHANGELOG.md b/CHANGELOG.md index aef5b96..8467c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixed + +- Use the pi session cwd for Cursor `Agent.create`, not only native tool replay display. Completes the 0.1.10 cwd work that previously updated replay registration but left the Cursor agent runtime on `process.cwd()`. + ## 0.1.14 - 2026-05-18 ### Changed diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index 7c3dc94..20f30ba 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -8,6 +8,7 @@ import { } from "@earendil-works/pi-coding-agent"; import { Text } from "@earendil-works/pi-tui"; import { Type, type TSchema } from "typebox"; +import { getCursorSessionCwd } from "./cursor-session-cwd.js"; import type { CursorPiToolDisplay } from "./cursor-tool-transcript.js"; const NATIVE_CURSOR_TOOL_NAMES = ["read", "bash", "ls", "cursor_edit", "cursor_write"] as const; @@ -24,7 +25,6 @@ export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay { const registeredNativeToolNames = new Set(); const nativeToolResults = new Map(); -let currentNativeToolCwd = process.cwd(); function readBooleanEnv(name: string): boolean | undefined { const value = process.env[name]?.trim().toLowerCase(); @@ -80,7 +80,6 @@ export const __testUtils = { reset(): void { registeredNativeToolNames.clear(); nativeToolResults.clear(); - currentNativeToolCwd = process.cwd(); }, }; @@ -246,8 +245,8 @@ function createNativeCursorToolDefinition(toolName: NativeCursorToolName, cwd: s } function registerNativeCursorTool(pi: ExtensionAPI, toolName: NativeCursorToolName): void { - const definition = createNativeCursorToolDefinition(toolName, currentNativeToolCwd); - pi.registerTool(wrapNativeCursorTool(definition, () => createNativeCursorToolDefinition(toolName, currentNativeToolCwd))); + const definition = createNativeCursorToolDefinition(toolName, getCursorSessionCwd()); + pi.registerTool(wrapNativeCursorTool(definition, () => createNativeCursorToolDefinition(toolName, getCursorSessionCwd()))); } function hasNonBuiltinTool(pi: ExtensionAPI, toolName: NativeCursorToolName): boolean { @@ -261,7 +260,6 @@ function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionCont return; } - currentNativeToolCwd = ctx.cwd; const skippedToolNames: string[] = []; for (const toolName of NATIVE_CURSOR_TOOL_NAMES) { if (registeredNativeToolNames.has(toolName)) continue; diff --git a/src/cursor-provider.ts b/src/cursor-provider.ts index c25aa60..3be3f19 100644 --- a/src/cursor-provider.ts +++ b/src/cursor-provider.ts @@ -10,6 +10,7 @@ import { import { Agent, createAgentPlatform } from "@cursor/sdk"; import type { InteractionUpdate, SDKAgent, SettingSource } from "@cursor/sdk"; import { buildCursorPrompt, type CursorPrompt } from "./context.js"; +import { getCursorSessionCwd } from "./cursor-session-cwd.js"; import { getEffectiveFastForModelId } from "./cursor-state.js"; import { buildCursorModelSelection } from "./model-discovery.js"; import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js"; @@ -575,9 +576,9 @@ export function streamCursor( if (!apiKey) throw new Error(MISSING_API_KEY_MESSAGE); resolvedApiKey = apiKey; - // pi-ai Context/SimpleStreamOptions do not currently expose ExtensionContext.cwd; - // provider calls use the process cwd until pi exposes a session cwd to streamSimple. - const cwd = process.cwd(); + // pi-ai Context/SimpleStreamOptions do not expose ExtensionContext.cwd; bridge via session_start + // until pi threads session cwd into streamSimple (cwd can change without a new session event). + const cwd = getCursorSessionCwd(); const fastEnabled = getEffectiveFastForModelId(model.id); const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled); const settingSources = resolveCursorSettingSources(); diff --git a/src/cursor-session-cwd.ts b/src/cursor-session-cwd.ts new file mode 100644 index 0000000..a42e1ac --- /dev/null +++ b/src/cursor-session-cwd.ts @@ -0,0 +1,33 @@ +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +const state = { + sessionCwd: process.cwd(), +}; + +/** + * Pi session cwd when known; falls back to process.cwd() before session_start. + * Updated on session_start only until pi threads cwd into streamSimple—mid-session cwd + * changes without a new session_start event are not reflected here. + */ +export function getCursorSessionCwd(): string { + return state.sessionCwd; +} + +function setCursorSessionCwd(cwd: string): void { + state.sessionCwd = cwd; +} + +function resetCursorSessionCwd(): void { + state.sessionCwd = process.cwd(); +} + +export function registerCursorSessionCwd(pi: ExtensionAPI): void { + pi.on("session_start", (_event, ctx) => { + setCursorSessionCwd(ctx.cwd); + }); +} + +export const __testUtils = { + set: setCursorSessionCwd, + reset: resetCursorSessionCwd, +}; diff --git a/src/index.ts b/src/index.ts index ea3e3fd..ffa6fe8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import type { ExtensionAPI, ProviderConfig, ProviderModelConfig } from "@earendi import { discoverModels, type CursorModelFallbackIssue } from "./model-discovery.js"; import { registerCursorFastControls } from "./cursor-state.js"; import { registerCursorNativeToolDisplay } from "./cursor-native-tool-display.js"; +import { registerCursorSessionCwd } from "./cursor-session-cwd.js"; import { streamCursor } from "./cursor-provider.js"; function createCursorProviderConfig(models: ProviderModelConfig[]): ProviderConfig { @@ -20,6 +21,8 @@ function registerCursorProvider(pi: ExtensionAPI, models: ProviderModelConfig[]) } export default async function (pi: ExtensionAPI) { + // Session cwd must register before other session_start listeners that depend on it. + registerCursorSessionCwd(pi); registerCursorFastControls(pi); registerCursorNativeToolDisplay(pi); let fallbackIssue: CursorModelFallbackIssue | undefined; diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 5cafccf..0a33dee 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -32,6 +32,7 @@ vi.mock("@cursor/sdk", () => { }); import { Agent, createAgentPlatform } from "@cursor/sdk"; +import { __testUtils as cursorSessionCwdTestUtils } from "../src/cursor-session-cwd.js"; import { streamCursor, __testUtils as cursorProviderTestUtils } from "../src/cursor-provider.js"; import { __testUtils as modelDiscoveryTestUtils } from "../src/model-discovery.js"; import { __testUtils as contextWindowCacheTestUtils } from "../src/context-window-cache.js"; @@ -200,6 +201,7 @@ describe("streamCursor", () => { delete process.env.PI_CURSOR_SETTING_SOURCES; expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(0); cursorProviderTestUtils.resetCursorNativeReplayIdleDisposeMs(); + cursorSessionCwdTestUtils.reset(); nativeToolDisplayTestUtils.reset(); modelDiscoveryTestUtils.registerModelItems(cursorModelItems); // Re-setup default mock return after clearing diff --git a/test/cursor-session-cwd.test.ts b/test/cursor-session-cwd.test.ts new file mode 100644 index 0000000..678d070 --- /dev/null +++ b/test/cursor-session-cwd.test.ts @@ -0,0 +1,67 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import { + __testUtils as cursorSessionCwdTestUtils, + getCursorSessionCwd, + registerCursorSessionCwd, +} from "../src/cursor-session-cwd.js"; + +function createMockPi() { + const handlers = new Map void>>(); + return { + on: vi.fn((event: string, handler: (event: unknown, ctx: { cwd: string }) => void) => { + const list = handlers.get(event) ?? []; + list.push(handler); + handlers.set(event, list); + }), + emitSessionStart(cwd: string) { + for (const handler of handlers.get("session_start") ?? []) { + handler({ reason: "startup" }, { cwd }); + } + }, + } satisfies Pick & { emitSessionStart(cwd: string): void }; +} + +describe("cursor-session-cwd", () => { + afterEach(() => { + cursorSessionCwdTestUtils.reset(); + }); + + it("falls back to process.cwd() before session_start", () => { + expect(getCursorSessionCwd()).toBe(process.cwd()); + }); + + it("syncs cwd from session_start", () => { + const sessionDir = mkdtempSync(join(tmpdir(), "pi-cursor-session-cwd-")); + try { + const pi = createMockPi(); + registerCursorSessionCwd(pi as unknown as ExtensionAPI); + pi.emitSessionStart(sessionDir); + + expect(getCursorSessionCwd()).toBe(sessionDir); + } finally { + rmSync(sessionDir, { recursive: true, force: true }); + } + }); + + it("updates cwd on subsequent session_start events", () => { + const firstDir = mkdtempSync(join(tmpdir(), "pi-cursor-session-cwd-a-")); + const secondDir = mkdtempSync(join(tmpdir(), "pi-cursor-session-cwd-b-")); + try { + const pi = createMockPi(); + registerCursorSessionCwd(pi as unknown as ExtensionAPI); + + pi.emitSessionStart(firstDir); + expect(getCursorSessionCwd()).toBe(firstDir); + + pi.emitSessionStart(secondDir); + expect(getCursorSessionCwd()).toBe(secondDir); + } finally { + rmSync(firstDir, { recursive: true, force: true }); + rmSync(secondDir, { recursive: true, force: true }); + } + }); +}); diff --git a/test/index-session-cwd-integration.test.ts b/test/index-session-cwd-integration.test.ts new file mode 100644 index 0000000..d024a8d --- /dev/null +++ b/test/index-session-cwd-integration.test.ts @@ -0,0 +1,175 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { Context, Model } from "@earendil-works/pi-ai"; +import type { ExtensionAPI, ExtensionContext, ProviderConfig } from "@earendil-works/pi-coding-agent"; + +vi.mock("../src/model-discovery.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + discoverModels: vi.fn(), + }; +}); + +function createMockAgentRun() { + return { + id: "run-1", + agentId: "agent-1", + status: "finished", + wait: vi.fn().mockResolvedValue({ id: "run-1", status: "finished" }), + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; +} + +function createMockAgent() { + const mockSend = vi.fn().mockResolvedValue(createMockAgentRun()); + return { + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }; +} + +vi.mock("@cursor/sdk", () => ({ + Agent: { + create: vi.fn().mockResolvedValue(createMockAgent()), + }, + createAgentPlatform: vi.fn().mockResolvedValue({ + checkpointStore: { loadLatest: vi.fn().mockResolvedValue(undefined) }, + }), +})); + +import { Agent } from "@cursor/sdk"; +import type { AssistantMessageEventStream } from "@earendil-works/pi-ai"; +import extensionFactory from "../src/index.js"; +import { discoverModels } from "../src/model-discovery.js"; +import { streamCursor, __testUtils as cursorProviderTestUtils } from "../src/cursor-provider.js"; +import { __testUtils as cursorSessionCwdTestUtils } from "../src/cursor-session-cwd.js"; + +const mockedDiscover = vi.mocked(discoverModels); +const mockedAgentCreate = vi.mocked(Agent.create); + +type TestExtensionContext = Pick & { + ui: Pick; + sessionManager: Pick; +}; + +function createMockPi() { + const registered: Array<{ name: string; config: ProviderConfig }> = []; + const handlers = new Map void>>(); + return { + registerProvider: vi.fn((name: string, config: ProviderConfig) => { + registered.push({ name, config }); + }), + registerFlag: vi.fn(), + registerCommand: vi.fn(), + registerTool: vi.fn(), + getAllTools: vi.fn(() => []), + sendMessage: vi.fn(), + on: vi.fn((event: string, handler: (event: unknown, ctx: TestExtensionContext) => void) => { + handlers.set(event, [...(handlers.get(event) ?? []), handler]); + }), + getFlag: vi.fn().mockReturnValue(false), + appendEntry: vi.fn(), + _registered: registered, + _handlers: handlers, + }; +} + +async function runSessionStartHandlers( + pi: ReturnType, + ctxOverrides: Partial = {}, +): Promise { + const ctx: TestExtensionContext = { + cwd: process.cwd(), + hasUI: false, + ui: { notify: vi.fn(), setStatus: vi.fn() }, + sessionManager: { getBranch: vi.fn(() => []) }, + ...ctxOverrides, + }; + for (const handler of pi._handlers.get("session_start") ?? []) { + await handler({ reason: "startup" }, ctx); + } +} + +function makeModel(): Model<"cursor-sdk"> { + return { + id: "composer-2.5", + name: "Cursor Composer 2.5", + api: "cursor-sdk", + provider: "cursor", + baseUrl: "", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }; +} + +function makeContext(): Context { + return { + systemPrompt: "Be helpful.", + messages: [{ role: "user", content: "Hello", timestamp: 1 }], + }; +} + +async function collectEvents(stream: AssistantMessageEventStream) { + const events: unknown[] = []; + for await (const event of stream) { + events.push(event); + } + return events; +} + +describe("extension session cwd integration", () => { + beforeEach(() => { + vi.clearAllMocks(); + delete process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY; + delete process.env.PI_CURSOR_REGISTER_NATIVE_TOOLS; + expect(cursorProviderTestUtils.pendingCursorNativeRunCount()).toBe(0); + cursorSessionCwdTestUtils.reset(); + mockedAgentCreate.mockResolvedValue(createMockAgent()); + mockedDiscover.mockResolvedValue([ + { + id: "composer-2.5", + name: "Cursor Composer 2.5", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + ]); + }); + + afterEach(() => { + cursorSessionCwdTestUtils.reset(); + }); + + it("passes pi session cwd from extension registration through streamSimple to Agent.create", async () => { + const sessionDir = mkdtempSync(join(tmpdir(), "pi-cursor-index-agent-cwd-")); + try { + const pi = createMockPi(); + await extensionFactory(pi as unknown as ExtensionAPI); + await runSessionStartHandlers(pi, { cwd: sessionDir }); + + expect(pi.registerProvider).toHaveBeenCalledOnce(); + const streamSimple = pi._registered[0]?.config.streamSimple; + expect(streamSimple).toBe(streamCursor); + + await collectEvents(streamSimple!(makeModel(), makeContext(), { apiKey: "test-key" })); + + expect(mockedAgentCreate).toHaveBeenCalledWith( + expect.objectContaining({ + local: { cwd: sessionDir }, + }), + ); + } finally { + rmSync(sessionDir, { recursive: true, force: true }); + } + }); +}); diff --git a/test/index.test.ts b/test/index.test.ts index a924bfe..46de33d 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -21,6 +21,7 @@ import { canRenderCursorToolNatively, recordCursorNativeToolDisplay, } from "../src/cursor-native-tool-display.js"; +import { __testUtils as cursorSessionCwdTestUtils } from "../src/cursor-session-cwd.js"; const mockedDiscover = vi.mocked(discoverModels); const mockedStreamCursor = vi.mocked(streamCursor); @@ -104,6 +105,7 @@ describe("extension factory", () => { vi.clearAllMocks(); delete process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY; delete process.env.PI_CURSOR_REGISTER_NATIVE_TOOLS; + cursorSessionCwdTestUtils.reset(); nativeToolDisplayTestUtils.reset(); }); -- 2.51.2 From b5cf881402e6376a9f1a5d00ab3e499b95295c90 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Tue, 19 May 2026 14:19:45 -0600 Subject: [PATCH 052/359] Replay Cursor grep and glob through native bash cards. Cursor SDK search tools previously fell back to plain transcript text in the TUI; route them through pi bash replay with synthesized command labels and human-readable empty results. Co-authored-by: Cursor --- CHANGELOG.md | 5 ++ README.md | 2 +- docs/cursor-model-ux-spec.md | 2 +- src/cursor-tool-transcript.ts | 80 ++++++++++++++++++++----- test/cursor-provider.test.ts | 90 +++++++++++++++++++++++++++++ test/cursor-tool-transcript.test.ts | 48 +++++++++++++++ 6 files changed, 212 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8467c6d..e3fd218 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,13 @@ ## Unreleased +### Added + +- Replay Cursor SDK `grep` and `glob` activity through native pi `bash` cards with synthesized command labels, so search activity matches shell/read card UX in interactive TTY sessions. + ### Fixed +- Format zero-match Cursor grep results as `(no matches)` instead of raw `{ "totalMatches": 0 }` JSON in native replay and transcript output. - Use the pi session cwd for Cursor `Agent.create`, not only native tool replay display. Completes the 0.1.10 cwd work that previously updated replay registration but left the Cursor agent runtime on `process.cwd()`. ## 0.1.14 - 2026-05-18 diff --git a/README.md b/README.md index d1db851..f04e7fe 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-k ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, `ls`, `edit`, and `write` activity through pi's native tool-call path with recorded results (for example green `read`, `$ ...`, and Cursor edit/write cards) without forcing Cursor to call pi tools or rerun commands. Cursor edit/write activity uses replay-only `cursor_edit` and `cursor_write` tool cards because Cursor's file-editing schema is not the same as pi's built-in `edit`/`write` schemas; those replay tools only display recorded Cursor results and never mutate files directly. If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths; that preview is explicitly labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, `ls`, `edit`, and `write` activity through pi's native tool-call path with recorded results (for example green `read`, `$ ...`, and Cursor edit/write cards) without forcing Cursor to call pi tools or rerun commands. Cursor `grep` and `glob` activity is replayed through native `bash` cards with synthesized command labels. Cursor edit/write activity uses replay-only `cursor_edit` and `cursor_write` tool cards because Cursor's file-editing schema is not the same as pi's built-in `edit`/`write` schemas; those replay tools only display recorded Cursor results and never mutate files directly. If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths; that preview is explicitly labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Cursor setting sources are opt-in.** The extension does not pass `local.settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. To load configured Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities, start pi with `PI_CURSOR_SETTING_SOURCES=all`. To narrow loading, set a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index a1ef4e9..90e5ba7 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -18,7 +18,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. Users can opt in with `PI_CURSOR_SETTING_SOURCES=all` or narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, `ls`, `edit`, and `write` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands/file edits. Cursor edit/write activity is replayed through `cursor_edit` and `cursor_write` cards rather than pi's built-in `edit`/`write` names because Cursor's edit/write schemas differ from pi's schemas; these replay-only tools display recorded Cursor results and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, `ls`, `edit`, and `write` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands/file edits. Cursor `grep` and `glob` activity is replayed through native `bash` cards with synthesized command labels. Cursor edit/write activity is replayed through `cursor_edit` and `cursor_write` cards rather than pi's built-in `edit`/`write` names because Cursor's edit/write schemas differ from pi's schemas; these replay-only tools display recorded Cursor results and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. When native replay splits one Cursor SDK run into multiple pi turns, prompt input is counted once for the run; later synthetic replay turns report `input: 0` and only their own output estimate. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.13 documentation says the SDK may enable Max Mode automatically when a selected model requires it, but the public local-agent `ModelSelection` path still does not expose a manual Max Mode selector. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-tool-transcript.ts b/src/cursor-tool-transcript.ts index 823ba79..665b068 100644 --- a/src/cursor-tool-transcript.ts +++ b/src/cursor-tool-transcript.ts @@ -87,6 +87,7 @@ function normalizeToolName(name: string): string { case "run_terminal_cmd": case "terminal": case "bash": + case "shell": return "shell"; case "grep_search": case "search": @@ -304,17 +305,13 @@ function formatLs(args: Record, result: NormalizedResult, optio } function formatGlob(args: Record, result: NormalizedResult, options: TranscriptOptions): string { - const pattern = typeof args.globPattern === "string" ? args.globPattern : "*"; - const targetDirectory = typeof args.targetDirectory === "string" ? formatDisplayPath(args.targetDirectory, options.cwd) : undefined; - const header = targetDirectory ? `glob ${pattern} in ${targetDirectory}` : `glob ${pattern}`; + const header = `$ ${synthesizeGlobBashCommand(args, options)}`; if (result.status === "error") return joinSections(header, formatError(result.error)); + return joinSections(header, getGlobBody(result, options)); +} - const value = asRecord(result.value); - const files = getArray(value, "files")?.filter((entry): entry is string => typeof entry === "string") ?? []; - if (files.length === 0) return joinSections(header, stringifyUnknown(result.value)); - const limited = limitItems(files, options); - const body = limited.omitted > 0 ? `${limited.items.join("\n")}\n... (${limited.omitted} more files truncated)` : limited.items.join("\n"); - return joinSections(header, body); +function formatSearchCount(totalMatches: number): string { + return totalMatches === 1 ? "1 match" : `${totalMatches} matches`; } function collectSearchResults(value: unknown): string[] { @@ -350,23 +347,60 @@ function collectSearchResults(value: unknown): string[] { lines.push(`${getString(countRecord, "file") ?? ""}: ${getNumber(countRecord, "count") ?? 0}`.trim()); } } else { + const totalMatches = getNumber(outputRecord, "totalMatches"); + if (totalMatches !== undefined) { + if (totalMatches === 0) continue; + lines.push(formatSearchCount(totalMatches)); + continue; + } lines.push(stringifyUnknown(outputValue)); } } + + const topLevelTotalMatches = getNumber(record, "totalMatches"); + if (lines.length === 0 && topLevelTotalMatches !== undefined) { + return topLevelTotalMatches === 0 ? ["(no matches)"] : [formatSearchCount(topLevelTotalMatches)]; + } return lines.filter(Boolean); } -function formatGrep(args: Record, result: NormalizedResult, options: TranscriptOptions): string { +function synthesizeGrepBashCommand(args: Record, options: TranscriptOptions): string { const pattern = typeof args.pattern === "string" ? args.pattern : ""; const path = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined; const glob = typeof args.glob === "string" ? args.glob : undefined; - const header = ["grep", pattern && JSON.stringify(pattern), path ?? glob].filter(Boolean).join(" "); - if (result.status === "error") return joinSections(header, formatError(result.error)); + return ["grep", pattern && JSON.stringify(pattern), path ?? glob].filter(Boolean).join(" "); +} +function synthesizeGlobBashCommand(args: Record, options: TranscriptOptions): string { + const pattern = typeof args.globPattern === "string" ? args.globPattern : "*"; + const targetDirectory = typeof args.targetDirectory === "string" ? formatDisplayPath(args.targetDirectory, options.cwd) : undefined; + return targetDirectory ? `glob ${pattern} in ${targetDirectory}` : `glob ${pattern}`; +} + +function getGrepBody(result: NormalizedResult, options: TranscriptOptions): string { const lines = collectSearchResults(result.value); const limited = limitItems(lines, options); const body = limited.omitted > 0 ? `${limited.items.join("\n")}\n... (${limited.omitted} more matches truncated)` : limited.items.join("\n"); - return joinSections(header, limitText(body || stringifyUnknown(result.value), options)); + return limitText(body || stringifyUnknown(result.value), options); +} + +function getGlobBody(result: NormalizedResult, options: TranscriptOptions): string { + const value = asRecord(result.value); + const files = getArray(value, "files")?.filter((entry): entry is string => typeof entry === "string") ?? []; + if (files.length === 0) { + const totalMatches = getNumber(value, "totalMatches"); + if (totalMatches === 0) return "(no files)"; + return stringifyUnknown(result.value); + } + const limited = limitItems(files, options); + const body = limited.omitted > 0 ? `${limited.items.join("\n")}\n... (${limited.omitted} more files truncated)` : limited.items.join("\n"); + return limitText(body, options); +} + +function formatGrep(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const header = `$ ${synthesizeGrepBashCommand(args, options)}`; + if (result.status === "error") return joinSections(header, formatError(result.error)); + return joinSections(header, getGrepBody(result, options)); } function formatWrite(args: Record, result: NormalizedResult, options: TranscriptOptions): string { @@ -535,6 +569,26 @@ export function buildCursorPiToolDisplay(toolCall: unknown, options: TranscriptO }; } + if (name === "grep") { + const isError = result.status === "error"; + return { + toolName: "bash", + args: { command: synthesizeGrepBashCommand(args, options) }, + result: textToolResult(isError ? formatError(result.error) : getGrepBody(result, options)), + isError, + }; + } + + if (name === "glob") { + const isError = result.status === "error"; + return { + toolName: "bash", + args: { command: synthesizeGlobBashCommand(args, options) }, + result: textToolResult(isError ? formatError(result.error) : getGlobBody(result, options)), + isError, + }; + } + if (name === "ls") { return { toolName: "ls", diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 0a33dee..31680ff 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -524,6 +524,96 @@ describe("streamCursor", () => { expect(replayDone.message.content).toEqual([{ type: "text", text: "Final answer only." }]); }); + it("replays Cursor grep activity through native bash display", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + const registeredTools: RegisteredTool[] = []; + await registerNativeToolDisplayForTest(registeredTools); + + let resolveRun: (result: { id: string; status: "finished"; result: string }) => void = () => {}; + const runWait = vi.fn( + () => + new Promise<{ id: string; status: "finished"; result: string }>((resolve) => { + resolveRun = resolve; + }), + ); + const mockSend = vi.fn().mockImplementation(async (_msg: unknown, opts: { onDelta: (a: unknown) => void }) => { + opts.onDelta({ + update: { + type: "tool-call-started", + toolCall: { type: "grep", args: { pattern: "sem_reindex", path: "src" } }, + callId: "c1", + }, + }); + opts.onDelta({ + update: { + type: "tool-call-completed", + toolCall: { + type: "grep", + args: { pattern: "sem_reindex", path: "src" }, + result: { + status: "success", + value: { + workspaceResults: { + src: { + type: "files", + output: { files: ["src/tools/reindex.ts"] }, + }, + }, + }, + }, + }, + callId: "c1", + }, + }); + return { + id: "run-1", + agentId: "agent-1", + status: "running", + wait: runWait, + cancel: vi.fn(), + supports: () => true, + unsupportedReason: () => undefined, + }; + }); + mockedCreate.mockResolvedValue({ + agentId: "agent-1", + send: mockSend, + [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined), + }); + + const firstEvents = await collectEvents(streamCursor(makeModel(), makeContext(), { apiKey: "test-key" })); + const firstDone = firstEvents.find((e: any) => e.type === "done") as any; + const toolCall = firstDone.message.content.find((block: any) => block.type === "toolCall"); + const trace = firstEvents.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); + + expect(firstDone.reason).toBe("toolUse"); + expect(toolCall.name).toBe("bash"); + expect(toolCall.arguments).toEqual({ command: 'grep "sem_reindex" src' }); + expect(trace).not.toContain("src/tools/reindex.ts"); + + const bashTool = registeredTools.find((tool) => tool.name === "bash"); + const toolResult = await bashTool!.execute(toolCall.id, toolCall.arguments, undefined, undefined, {}); + expect(toolResult.content[0].text).toContain("src/tools/reindex.ts"); + + resolveRun({ id: "run-1", status: "finished", result: "Done." }); + + const replayContext = makeContext(); + replayContext.messages = [ + ...replayContext.messages, + firstDone.message, + { + role: "toolResult", + toolCallId: toolCall.id, + toolName: "bash", + content: toolResult.content, + details: toolResult.details, + isError: false, + timestamp: 2, + }, + ]; + await collectEvents(streamCursor(makeModel(), replayContext, { apiKey: "test-key" })); + }); + it("replays Cursor edit activity through a replay-only native tool", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; const registeredTools: RegisteredTool[] = []; diff --git a/test/cursor-tool-transcript.test.ts b/test/cursor-tool-transcript.test.ts index 29a1c7a..811698d 100644 --- a/test/cursor-tool-transcript.test.ts +++ b/test/cursor-tool-transcript.test.ts @@ -227,6 +227,54 @@ describe("formatCursorToolTranscript", () => { }); }); + it("builds native pi bash display data for Cursor grep and glob calls", () => { + const grepDisplay = buildCursorPiToolDisplay({ + type: "grep", + args: { pattern: "getActiveTools|sem_reindex", path: "src" }, + result: { + status: "success", + value: { + workspaceResults: { + src: { + type: "files", + output: { files: ["src/tools/reindex.ts", "src/tools/status.ts"] }, + }, + }, + }, + }, + }); + const globDisplay = buildCursorPiToolDisplay({ + type: "glob", + args: { globPattern: "**/*.ts", targetDirectory: "src" }, + result: { status: "success", value: { files: ["src/index.ts", "src/context.ts"] } }, + }); + const emptyGrepDisplay = buildCursorPiToolDisplay({ + type: "grep", + args: { pattern: "missing", path: "src" }, + result: { status: "success", value: { totalMatches: 0 } }, + }); + const emptyGlobDisplay = buildCursorPiToolDisplay({ + type: "glob", + args: { globPattern: "**/*.missing", targetDirectory: "src" }, + result: { status: "success", value: { files: [], totalMatches: 0 } }, + }); + + expect(grepDisplay).toMatchObject({ + toolName: "bash", + args: { command: 'grep "getActiveTools|sem_reindex" src' }, + result: { content: [{ type: "text", text: "src/tools/reindex.ts\nsrc/tools/status.ts" }] }, + isError: false, + }); + expect(globDisplay).toMatchObject({ + toolName: "bash", + args: { command: "glob **/*.ts in src" }, + result: { content: [{ type: "text", text: "src/index.ts\nsrc/context.ts" }] }, + isError: false, + }); + expect(emptyGrepDisplay.result.content[0].text).toBe("(no matches)"); + expect(emptyGlobDisplay.result.content[0].text).toBe("(no files)"); + }); + it("labels native read display local previews when Cursor read content is unavailable", () => { const dir = mkdtempSync(join(tmpdir(), "cursor-tool-display-")); try { -- 2.51.2 From 73c6ee7021961e7df30c66a5dd2e74bb22574f91 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Tue, 19 May 2026 15:44:15 -0600 Subject: [PATCH 053/359] Polish Cursor native tool replay Replay Cursor read, shell, and grep activity through pi-native tool cards with safer formatting. Keep native replay tools active after session registration and document the UX behavior. --- CHANGELOG.md | 5 +- README.md | 4 +- docs/cursor-model-ux-spec.md | 2 +- src/cursor-native-tool-display.ts | 22 ++++++++- src/cursor-tool-transcript.ts | 75 ++++++++++++++++++++++++----- test/cursor-provider.test.ts | 19 +++++--- test/cursor-tool-transcript.test.ts | 30 +++++++++--- test/index.test.ts | 33 +++++++++++-- 8 files changed, 153 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3fd218..cbac8e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,14 @@ ### Added -- Replay Cursor SDK `grep` and `glob` activity through native pi `bash` cards with synthesized command labels, so search activity matches shell/read card UX in interactive TTY sessions. +- Replay Cursor SDK `grep` activity through native pi `grep` cards and `glob` activity through native pi `bash` cards with synthesized command labels, so search activity matches built-in tool UX in interactive TTY sessions. ### Fixed - Format zero-match Cursor grep results as `(no matches)` instead of raw `{ "totalMatches": 0 }` JSON in native replay and transcript output. +- Strip trailing colons from Cursor grep file-list replay output. +- Make native Cursor read replay closer to pi's built-in read cards by displaying session-relative paths and 20-line continuation hints. +- Convert Cursor SDK shell timeouts from milliseconds to seconds in native bash replay cards instead of rendering `30000ms` as `30000s`. - Use the pi session cwd for Cursor `Agent.create`, not only native tool replay display. Completes the 0.1.10 cwd work that previously updated replay registration but left the Cursor agent runtime on `process.cwd()`. ## 0.1.14 - 2026-05-18 diff --git a/README.md b/README.md index f04e7fe..72a1051 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-k ## Limits - **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, `ls`, `edit`, and `write` activity through pi's native tool-call path with recorded results (for example green `read`, `$ ...`, and Cursor edit/write cards) without forcing Cursor to call pi tools or rerun commands. Cursor `grep` and `glob` activity is replayed through native `bash` cards with synthesized command labels. Cursor edit/write activity uses replay-only `cursor_edit` and `cursor_write` tool cards because Cursor's file-editing schema is not the same as pi's built-in `edit`/`write` schemas; those replay tools only display recorded Cursor results and never mutate files directly. If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths; that preview is explicitly labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. +- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, `grep`, `ls`, `edit`, and `write` activity through pi's native tool-call path with recorded results (for example green `read`, `grep`, `$ ...`, and Cursor edit/write cards) without forcing Cursor to call pi tools or rerun commands. Cursor `glob` activity is replayed through native `bash` cards with synthesized command labels. Cursor edit/write activity uses replay-only `cursor_edit` and `cursor_write` tool cards because Cursor's file-editing schema is not the same as pi's built-in `edit`/`write` schemas; those replay tools only display recorded Cursor results and never mutate files directly. If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths; that preview is explicitly labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. - **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. - **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. - **Cursor setting sources are opt-in.** The extension does not pass `local.settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. To load configured Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities, start pi with `PI_CURSOR_SETTING_SOURCES=all`. To narrow loading, set a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. @@ -267,7 +267,7 @@ Cursor SDK local agents load MCP servers from Cursor setting sources and inline ### Cursor native tool cards conflict with another extension -Cursor native replay is a UI enhancement for interactive TTY sessions. If another extension already owns `read`, `bash`, `ls`, `cursor_edit`, or `cursor_write`, this extension skips only the conflicting native replay wrapper and uses the scrubbed Cursor activity transcript for that tool instead. To disable Cursor native replay registration entirely, start pi with: +Cursor native replay is a UI enhancement for interactive TTY sessions. If another extension already owns `read`, `bash`, `grep`, `ls`, `cursor_edit`, or `cursor_write`, this extension skips only the conflicting native replay wrapper and uses the scrubbed Cursor activity transcript for that tool instead. To disable Cursor native replay registration entirely, start pi with: ```bash PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2.5 diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 90e5ba7..19e66ad 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -18,7 +18,7 @@ Current implementation notes: - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. - Local agents do not pass `settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. Users can opt in with `PI_CURSOR_SETTING_SOURCES=all` or narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, `ls`, `edit`, and `write` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands/file edits. Cursor `grep` and `glob` activity is replayed through native `bash` cards with synthesized command labels. Cursor edit/write activity is replayed through `cursor_edit` and `cursor_write` cards rather than pi's built-in `edit`/`write` names because Cursor's edit/write schemas differ from pi's schemas; these replay-only tools display recorded Cursor results and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. +- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, `grep`, `ls`, `edit`, and `write` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands/file edits. Cursor `glob` activity is replayed through native `bash` cards with synthesized command labels. Cursor edit/write activity is replayed through `cursor_edit` and `cursor_write` cards rather than pi's built-in `edit`/`write` names because Cursor's edit/write schemas differ from pi's schemas; these replay-only tools display recorded Cursor results and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. When native replay splits one Cursor SDK run into multiple pi turns, prompt input is counted once for the run; later synthetic replay turns report `input: 0` and only their own output estimate. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.13 documentation says the SDK may enable Max Mode automatically when a selected model requires it, but the public local-agent `ModelSelection` path still does not expose a manual Max Mode selector. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. diff --git a/src/cursor-native-tool-display.ts b/src/cursor-native-tool-display.ts index 20f30ba..b8a8b00 100644 --- a/src/cursor-native-tool-display.ts +++ b/src/cursor-native-tool-display.ts @@ -1,5 +1,6 @@ import { createBashToolDefinition, + createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, type ExtensionAPI, @@ -11,7 +12,7 @@ import { Type, type TSchema } from "typebox"; import { getCursorSessionCwd } from "./cursor-session-cwd.js"; import type { CursorPiToolDisplay } from "./cursor-tool-transcript.js"; -const NATIVE_CURSOR_TOOL_NAMES = ["read", "bash", "ls", "cursor_edit", "cursor_write"] as const; +const NATIVE_CURSOR_TOOL_NAMES = ["read", "bash", "grep", "ls", "cursor_edit", "cursor_write"] as const; type NativeCursorToolName = (typeof NATIVE_CURSOR_TOOL_NAMES)[number]; const NATIVE_CURSOR_TOOL_DISPLAY_ENV = "PI_CURSOR_NATIVE_TOOL_DISPLAY"; // Registration-only kill switch for users who want transcript fallback without shadowing read/bash/ls. @@ -240,6 +241,7 @@ function createCursorReplayOnlyToolDefinition(toolName: "cursor_edit" | "cursor_ function createNativeCursorToolDefinition(toolName: NativeCursorToolName, cwd: string): ToolDefinition { if (toolName === "read") return createReadToolDefinition(cwd) as ToolDefinition; if (toolName === "bash") return createBashToolDefinition(cwd) as ToolDefinition; + if (toolName === "grep") return createGrepToolDefinition(cwd) as ToolDefinition; if (toolName === "ls") return createLsToolDefinition(cwd) as ToolDefinition; return createCursorReplayOnlyToolDefinition(toolName) as ToolDefinition; } @@ -254,7 +256,21 @@ function hasNonBuiltinTool(pi: ExtensionAPI, toolName: NativeCursorToolName): bo return existingTool !== undefined && existingTool.sourceInfo.source !== "builtin"; } -function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionContext): void { +type NativeRegistrationContext = { hasUI: boolean; ui: Pick }; + +function activateRegisteredNativeCursorTools(pi: ExtensionAPI): void { + if (registeredNativeToolNames.size === 0) return; + const activeToolNames = new Set(pi.getActiveTools()); + let changed = false; + for (const toolName of registeredNativeToolNames) { + if (activeToolNames.has(toolName)) continue; + activeToolNames.add(toolName); + changed = true; + } + if (changed) pi.setActiveTools([...activeToolNames]); +} + +function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: NativeRegistrationContext): void { if (!isCursorNativeToolRegistrationRequested()) { registeredNativeToolNames.clear(); return; @@ -271,6 +287,8 @@ function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionCont registeredNativeToolNames.add(toolName); } + activateRegisteredNativeCursorTools(pi); + if (skippedToolNames.length > 0 && readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV) === true && ctx.hasUI) { ctx.ui.notify( `Cursor native tool replay skipped for ${skippedToolNames.join(", ")} because another extension already provides ${skippedToolNames.length === 1 ? "that tool" : "those tools"}. Cursor will use scrubbed activity transcripts for skipped tools.`, diff --git a/src/cursor-tool-transcript.ts b/src/cursor-tool-transcript.ts index 665b068..1d47bfc 100644 --- a/src/cursor-tool-transcript.ts +++ b/src/cursor-tool-transcript.ts @@ -6,6 +6,7 @@ const DEFAULT_MAX_TRANSCRIPT_LINES = 800; const DEFAULT_MAX_LIST_ITEMS = 200; const DEFAULT_READ_TRANSCRIPT_CHARS = 4000; const DEFAULT_READ_TRANSCRIPT_LINES = 12; +const DEFAULT_NATIVE_READ_DISPLAY_LINES = 20; const LOCAL_READ_PREVIEW_NOTICE = "[local file preview at transcript time; Cursor read result content was unavailable]"; @@ -256,6 +257,30 @@ function formatRead(args: Record, result: NormalizedResult, opt return joinSections(`read ${path}`, limitText(getReadContent(args, result, options), readOptions, totalLines)); } +function buildReadDisplayArgs(args: Record, options: TranscriptOptions): Record { + const rawPath = typeof args.path === "string" ? args.path : undefined; + return rawPath ? { ...args, path: formatDisplayPath(rawPath, options.cwd) } : args; +} + +function formatNativeReadDisplayContent(args: Record, result: NormalizedResult, options: TranscriptOptions): string { + const value = asRecord(result.value); + const totalLines = getNumber(value, "totalLines"); + const readOptions = { + ...options, + maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS, + maxLines: options.maxLines ?? DEFAULT_NATIVE_READ_DISPLAY_LINES, + }; + const content = getReadContent(args, result, readOptions); + if (totalLines === undefined) return limitText(content, readOptions); + + const maxLines = readOptions.maxLines ?? DEFAULT_NATIVE_READ_DISPLAY_LINES; + const lines = content.split("\n"); + const visible = lines.slice(0, maxLines).join("\n"); + if (totalLines <= maxLines && lines.length <= maxLines) return visible; + if (visible.length > (readOptions.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS)) return limitText(content, readOptions, totalLines); + return `${visible}\n\n[${Math.max(totalLines - maxLines, 0)} more lines in file. Use offset=${maxLines + 1} to continue.]`; +} + function getShellOutput(result: NormalizedResult): { text: string; exitCode: number | undefined } { const value = asRecord(result.value); const stdout = getString(value, "stdout") ?? ""; @@ -268,6 +293,16 @@ function getShellOutput(result: NormalizedResult): { text: string; exitCode: num return { text: outputParts.filter(Boolean).join("\n\n") || "(no output)", exitCode }; } +function buildShellDisplayArgs(args: Record): Record { + const command = typeof args.command === "string" ? args.command : undefined; + const timeoutMs = getNumber(args, "timeout"); + const displayArgs: Record = command ? { command } : { ...args }; + if (timeoutMs !== undefined) { + displayArgs.timeout = timeoutMs / 1000; + } + return displayArgs; +} + function formatShell(args: Record, result: NormalizedResult, options: TranscriptOptions): string { const command = typeof args.command === "string" ? args.command : stringifyUnknown(args).trim(); if (result.status === "error") return joinSections(`$ ${command || "shell"}`, formatError(result.error)); @@ -314,6 +349,10 @@ function formatSearchCount(totalMatches: number): string { return totalMatches === 1 ? "1 match" : `${totalMatches} matches`; } +function formatSearchFile(file: string): string { + return file.endsWith(":") ? file.slice(0, -1) : file; +} + function collectSearchResults(value: unknown): string[] { const record = asRecord(value); const outputs: unknown[] = []; @@ -339,7 +378,7 @@ function collectSearchResults(value: unknown): string[] { } } else if (type === "files") { const files = getArray(output, "files") ?? []; - lines.push(...files.filter((entry): entry is string => typeof entry === "string")); + lines.push(...files.filter((entry): entry is string => typeof entry === "string").map(formatSearchFile)); } else if (type === "count") { const counts = getArray(output, "counts") ?? []; for (const count of counts) { @@ -371,6 +410,23 @@ function synthesizeGrepBashCommand(args: Record, options: Trans return ["grep", pattern && JSON.stringify(pattern), path ?? glob].filter(Boolean).join(" "); } +function buildGrepDisplayArgs(args: Record, options: TranscriptOptions): Record { + const displayArgs: Record = {}; + const pattern = typeof args.pattern === "string" ? args.pattern : undefined; + const path = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined; + const glob = typeof args.glob === "string" ? args.glob : undefined; + const ignoreCase = getBoolean(args, "caseInsensitive"); + const context = getNumber(args, "context") ?? getNumber(args, "contextBefore") ?? getNumber(args, "contextAfter"); + const limit = getNumber(args, "headLimit"); + if (pattern !== undefined) displayArgs.pattern = pattern; + if (path !== undefined) displayArgs.path = path; + if (glob !== undefined) displayArgs.glob = glob; + if (ignoreCase !== undefined) displayArgs.ignoreCase = ignoreCase; + if (context !== undefined) displayArgs.context = context; + if (limit !== undefined) displayArgs.limit = limit; + return Object.keys(displayArgs).length > 0 ? displayArgs : args; +} + function synthesizeGlobBashCommand(args: Record, options: TranscriptOptions): string { const pattern = typeof args.globPattern === "string" ? args.globPattern : "*"; const targetDirectory = typeof args.targetDirectory === "string" ? formatDisplayPath(args.targetDirectory, options.cwd) : undefined; @@ -543,17 +599,10 @@ export function buildCursorPiToolDisplay(toolCall: unknown, options: TranscriptO if (name === "read") { const isError = result.status === "error"; - const value = asRecord(result.value); - const totalLines = getNumber(value, "totalLines"); - const readOptions = { - ...options, - maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS, - maxLines: options.maxLines ?? DEFAULT_READ_TRANSCRIPT_LINES, - }; return { toolName: "read", - args, - result: textToolResult(isError ? formatError(result.error) : limitText(getReadContent(args, result, options), readOptions, totalLines)), + args: buildReadDisplayArgs(args, options), + result: textToolResult(isError ? formatError(result.error) : formatNativeReadDisplayContent(args, result, options)), isError, }; } @@ -563,7 +612,7 @@ export function buildCursorPiToolDisplay(toolCall: unknown, options: TranscriptO const isError = result.status === "error" || (shellOutput.exitCode !== undefined && shellOutput.exitCode !== 0); return { toolName: "bash", - args, + args: buildShellDisplayArgs(args), result: textToolResult(result.status === "error" ? formatError(result.error) : limitText(shellOutput.text, options)), isError, }; @@ -572,8 +621,8 @@ export function buildCursorPiToolDisplay(toolCall: unknown, options: TranscriptO if (name === "grep") { const isError = result.status === "error"; return { - toolName: "bash", - args: { command: synthesizeGrepBashCommand(args, options) }, + toolName: "grep", + args: buildGrepDisplayArgs(args, options), result: textToolResult(isError ? formatError(result.error) : getGrepBody(result, options)), isError, }; diff --git a/test/cursor-provider.test.ts b/test/cursor-provider.test.ts index 31680ff..d2687dc 100644 --- a/test/cursor-provider.test.ts +++ b/test/cursor-provider.test.ts @@ -91,6 +91,7 @@ async function collectEvents(stream: ReturnType) { async function registerNativeToolDisplayForTest(registeredTools: RegisteredTool[]): Promise { const handlers: TestEventHandler[] = []; + let activeToolNames = ["read", "bash", "edit", "write"]; registerCursorNativeToolDisplay({ on: vi.fn((event: string, handler: TestEventHandler) => { if (event === "session_start") handlers.push(handler); @@ -100,7 +101,7 @@ async function registerNativeToolDisplayForTest(registeredTools: RegisteredTool[ }), getAllTools: vi.fn(() => { const toolsByName = new Map(); - for (const name of ["read", "bash", "ls"]) { + for (const name of ["read", "bash", "grep", "ls", "edit", "write"]) { const tool = createBuiltinToolInfo(name); toolsByName.set(tool.name, tool); } @@ -114,6 +115,10 @@ async function registerNativeToolDisplayForTest(registeredTools: RegisteredTool[ } return [...toolsByName.values()]; }), + getActiveTools: vi.fn(() => [...activeToolNames]), + setActiveTools: vi.fn((toolNames: string[]) => { + activeToolNames = [...toolNames]; + }), } as unknown as ExtensionAPI); for (const handler of handlers) { await handler({ reason: "startup" }, { cwd: process.cwd(), hasUI: false, ui: { notify: vi.fn() } }); @@ -524,7 +529,7 @@ describe("streamCursor", () => { expect(replayDone.message.content).toEqual([{ type: "text", text: "Final answer only." }]); }); - it("replays Cursor grep activity through native bash display", async () => { + it("replays Cursor grep activity through native grep display", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; const registeredTools: RegisteredTool[] = []; await registerNativeToolDisplayForTest(registeredTools); @@ -587,12 +592,12 @@ describe("streamCursor", () => { const trace = firstEvents.filter((e: any) => e.type === "thinking_delta").map((e: any) => e.delta).join(""); expect(firstDone.reason).toBe("toolUse"); - expect(toolCall.name).toBe("bash"); - expect(toolCall.arguments).toEqual({ command: 'grep "sem_reindex" src' }); + expect(toolCall.name).toBe("grep"); + expect(toolCall.arguments).toEqual({ pattern: "sem_reindex", path: "src" }); expect(trace).not.toContain("src/tools/reindex.ts"); - const bashTool = registeredTools.find((tool) => tool.name === "bash"); - const toolResult = await bashTool!.execute(toolCall.id, toolCall.arguments, undefined, undefined, {}); + const grepTool = registeredTools.find((tool) => tool.name === "grep"); + const toolResult = await grepTool!.execute(toolCall.id, toolCall.arguments, undefined, undefined, {}); expect(toolResult.content[0].text).toContain("src/tools/reindex.ts"); resolveRun({ id: "run-1", status: "finished", result: "Done." }); @@ -604,7 +609,7 @@ describe("streamCursor", () => { { role: "toolResult", toolCallId: toolCall.id, - toolName: "bash", + toolName: "grep", content: toolResult.content, details: toolResult.details, isError: false, diff --git a/test/cursor-tool-transcript.test.ts b/test/cursor-tool-transcript.test.ts index 811698d..79313bf 100644 --- a/test/cursor-tool-transcript.test.ts +++ b/test/cursor-tool-transcript.test.ts @@ -209,7 +209,7 @@ describe("formatCursorToolTranscript", () => { }); const shellDisplay = buildCursorPiToolDisplay({ name: "run_terminal_cmd", - args: { command: "date" }, + args: { command: "date", timeout: 30000 }, result: { status: "success", value: { stdout: "Sat May 9\n", stderr: "", exitCode: 0 } }, }); @@ -221,13 +221,31 @@ describe("formatCursorToolTranscript", () => { }); expect(shellDisplay).toMatchObject({ toolName: "bash", - args: { command: "date" }, + args: { command: "date", timeout: 30 }, result: { content: [{ type: "text", text: "Sat May 9" }] }, isError: false, }); }); - it("builds native pi bash display data for Cursor grep and glob calls", () => { + it("normalizes native Cursor read display paths and uses pi-like continuation text", () => { + const cwd = "/repo"; + const content = Array.from({ length: 25 }, (_, index) => `line ${index + 1}`).join("\n"); + const display = buildCursorPiToolDisplay( + { + name: "read", + args: { path: "/repo/README.md" }, + result: { status: "success", value: { content, totalLines: 25, fileSize: content.length } }, + }, + { cwd }, + ); + + expect(display.args).toEqual({ path: "README.md" }); + expect(display.result.content[0].text).toBe( + `${Array.from({ length: 20 }, (_, index) => `line ${index + 1}`).join("\n")}\n\n[5 more lines in file. Use offset=21 to continue.]`, + ); + }); + + it("builds native pi grep display data for Cursor grep calls and bash display data for Cursor glob calls", () => { const grepDisplay = buildCursorPiToolDisplay({ type: "grep", args: { pattern: "getActiveTools|sem_reindex", path: "src" }, @@ -237,7 +255,7 @@ describe("formatCursorToolTranscript", () => { workspaceResults: { src: { type: "files", - output: { files: ["src/tools/reindex.ts", "src/tools/status.ts"] }, + output: { files: ["src/tools/reindex.ts:", "src/tools/status.ts:"] }, }, }, }, @@ -260,8 +278,8 @@ describe("formatCursorToolTranscript", () => { }); expect(grepDisplay).toMatchObject({ - toolName: "bash", - args: { command: 'grep "getActiveTools|sem_reindex" src' }, + toolName: "grep", + args: { pattern: "getActiveTools|sem_reindex", path: "src" }, result: { content: [{ type: "text", text: "src/tools/reindex.ts\nsrc/tools/status.ts" }] }, isError: false, }); diff --git a/test/index.test.ts b/test/index.test.ts index 46de33d..886ec88 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -62,7 +62,8 @@ function createMockPi(existingTools?: ToolInfo[]) { const commands = new Map(); const tools: RegisteredTool[] = []; const handlers = new Map(); - const initialTools = existingTools ?? ["read", "bash", "ls"].map(createBuiltinToolInfo); + let activeToolNames = ["read", "bash", "edit", "write"]; + const initialTools = existingTools ?? ["read", "bash", "grep", "ls", "edit", "write"].map(createBuiltinToolInfo); return { registerProvider: vi.fn((name: string, config: ProviderConfig) => { registered.push({ name, config }); @@ -87,6 +88,10 @@ function createMockPi(existingTools?: ToolInfo[]) { } return [...toolsByName.values()]; }), + getActiveTools: vi.fn(() => [...activeToolNames]), + setActiveTools: vi.fn((toolNames: string[]) => { + activeToolNames = [...toolNames]; + }), sendMessage: vi.fn(), on: vi.fn((event: string, handler: TestEventHandler) => { handlers.set(event, [...(handlers.get(event) ?? []), handler]); @@ -97,6 +102,7 @@ function createMockPi(existingTools?: ToolInfo[]) { _commands: commands, _tools: tools, _handlers: handlers, + _activeToolNames: () => activeToolNames, }; } @@ -144,8 +150,9 @@ describe("extension factory", () => { "cursor-refresh-models", expect.objectContaining({ description: expect.stringContaining("Refresh the live Cursor model catalog") }), ); - expect(pi.registerTool).toHaveBeenCalledTimes(5); - expect(pi._tools.map((tool) => tool.name)).toEqual(["read", "bash", "ls", "cursor_edit", "cursor_write"]); + expect(pi.registerTool).toHaveBeenCalledTimes(6); + expect(pi._tools.map((tool) => tool.name)).toEqual(["read", "bash", "grep", "ls", "cursor_edit", "cursor_write"]); + expect(pi.setActiveTools).toHaveBeenCalledWith(["read", "bash", "edit", "write", "grep", "ls", "cursor_edit", "cursor_write"]); expect(pi.on).toHaveBeenCalledWith("session_start", expect.any(Function)); expect(pi.on).toHaveBeenCalledWith("model_select", expect.any(Function)); expect(mockedDiscover).toHaveBeenCalledOnce(); @@ -314,6 +321,20 @@ describe("extension factory", () => { expect(notify).not.toHaveBeenCalled(); }); + it("defers native Cursor tool wrapper registration until session_start", async () => { + process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; + mockedDiscover.mockResolvedValueOnce([]); + const pi = createMockPi(); + pi.getAllTools.mockImplementation(() => { + throw new Error("runtime tool actions are unavailable during extension load"); + }); + + await extensionFactory(pi as unknown as ExtensionAPI); + + expect(pi.registerTool).not.toHaveBeenCalled(); + expect(canRenderCursorToolNatively("grep")).toBe(false); + }); + it("registers native Cursor tool wrappers with the pi session cwd", async () => { process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY = "1"; mockedDiscover.mockResolvedValueOnce([]); @@ -349,7 +370,7 @@ describe("extension factory", () => { const readTool = pi._tools.find((tool) => tool.name === "read"); const result = await readTool.execute("ordinary-read", { path: "session-file.txt" }, undefined, undefined, {}); - expect(pi.registerTool).toHaveBeenCalledTimes(5); + expect(pi.registerTool).toHaveBeenCalledTimes(6); expect(result.content).toEqual([{ type: "text", text: "from second cwd\n" }]); } finally { rmSync(firstDir, { recursive: true, force: true }); @@ -421,14 +442,16 @@ describe("extension factory", () => { }, }, createBuiltinToolInfo("bash"), + createBuiltinToolInfo("grep"), createBuiltinToolInfo("ls"), ]); await extensionFactory(pi as unknown as ExtensionAPI); await runSessionStartHandlers(pi); - expect(pi._tools.map((tool) => tool.name)).toEqual(["bash", "ls", "cursor_edit", "cursor_write"]); + expect(pi._tools.map((tool) => tool.name)).toEqual(["bash", "grep", "ls", "cursor_edit", "cursor_write"]); expect(canRenderCursorToolNatively("read")).toBe(false); expect(canRenderCursorToolNatively("bash")).toBe(true); + expect(canRenderCursorToolNatively("grep")).toBe(true); expect(canRenderCursorToolNatively("cursor_edit")).toBe(true); expect(canRenderCursorToolNatively("ls")).toBe(true); }); -- 2.51.2 From 0a8908d82d1c96fc375f0a2ab84e6a7e51c24f45 Mon Sep 17 00:00:00 2001 From: Mitch Fultz <57411549+fitchmultz@users.noreply.github.com> Date: Thu, 21 May 2026 13:20:16 -0600 Subject: [PATCH 054/359] Add Cursor local pi tool bridge Release 0.1.15. Highlights: - Add the default-on Cursor local pi MCP tool bridge and Cursor question tool. - Improve native Cursor tool replay parity for grep/glob/read/shell/tool errors. - Fix PR9 regression findings around path-only write replay, abort races, prompt literal preservation, bridge replay suppression, and Cursor SDK startup/skills log containment. Validated locally: - npm test - npm run typecheck - npm publish --dry-run - npm pack --dry-run - git diff --check --- .gitignore | 11 + AGENTS.md | 14 +- CHANGELOG.md | 18 +- README.md | 68 +- docs/cursor-model-ux-spec.md | 20 +- docs/cursor-native-tool-replay.md | 88 + docs/cursor-native-tool-visual-audit.md | 183 ++ .../pr9-review-deep-dive-2026-05-21.md | 127 ++ package-lock.json | 1039 ++++++++- package.json | 7 +- src/context.ts | 45 +- src/cursor-mcp-timeout-override.ts | 111 + src/cursor-native-tool-display.ts | 429 +++- src/cursor-pi-tool-bridge.ts | 637 ++++++ src/cursor-provider.ts | 551 ++++- src/cursor-question-tool.ts | 247 +++ src/cursor-tool-names.ts | 67 + src/cursor-tool-transcript.ts | 654 +++++- src/index.ts | 4 + test/context.test.ts | 116 +- test/cursor-mcp-timeout-override.test.ts | 141 ++ test/cursor-pi-tool-bridge.test.ts | 327 +++ test/cursor-provider.test.ts | 1947 ++++++++++++++++- test/cursor-tool-transcript.test.ts | 397 +++- test/index-session-cwd-integration.test.ts | 11 +- test/index.test.ts | 516 ++++- 26 files changed, 7401 insertions(+), 374 deletions(-) create mode 100644 docs/cursor-native-tool-replay.md create mode 100644 docs/cursor-native-tool-visual-audit.md create mode 100644 docs/investigations/pr9-review-deep-dive-2026-05-21.md create mode 100644 src/cursor-mcp-timeout-override.ts create mode 100644 src/cursor-pi-tool-bridge.ts create mode 100644 src/cursor-question-tool.ts create mode 100644 src/cursor-tool-names.ts create mode 100644 test/cursor-mcp-timeout-override.test.ts create mode 100644 test/cursor-pi-tool-bridge.test.ts diff --git a/.gitignore b/.gitignore index 26f82e8..96d46ae 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,20 @@ coverage/ .env.* *.tgz .pi/ +demos/ recording/ demo-script.md docs/public-demo-script.md +# Debug Mode local/runtime logs (do not commit) +.debug/ + +# Cursor local/runtime logs and prompt exports (do not commit) +.cursor/debug-*.log +prompt-exports/ + +# Cursor native replay smoke-test artifacts (do not commit) +.pi-tool-demo/ + # CueLoop local/runtime state (do not commit) .cueloop/ diff --git a/AGENTS.md b/AGENTS.md index 2e73521..d2c9665 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,12 +6,16 @@ This repository is a pi provider extension that registers Cursor SDK-backed mode ## Repository map -- `src/index.ts` registers the pi extension, provider, fallback warnings, and Cursor fast controls. +- `src/index.ts` registers the pi extension, provider, fallback warnings, Cursor fast controls, native replay wrappers, question tool, and pi tool bridge hooks. - `src/model-discovery.ts` discovers Cursor models, builds pi model metadata, stores per-model metadata, and defines fallback models. -- `src/cursor-provider.ts` streams through local `@cursor/sdk` agents and sanitizes Cursor SDK errors. +- `src/cursor-provider.ts` streams through local `@cursor/sdk` agents, injects local MCP bridge config, resumes live bridge runs, and sanitizes Cursor SDK errors. +- `src/cursor-pi-tool-bridge.ts` exposes active pi tools to local Cursor agents through a per-run loopback MCP bridge. +- `src/cursor-question-tool.ts` owns the bridge-exposed `cursor_ask_question` pi UI tool. +- `src/cursor-native-tool-display.ts`, `src/cursor-tool-transcript.ts`, and `src/cursor-tool-names.ts` handle display-only Cursor native tool replay and transcript labels. +- `src/cursor-mcp-timeout-override.ts` owns Cursor SDK MCP call timeout overrides for long-running local MCP tools. - `src/cursor-state.ts` owns `/cursor-fast`, `--cursor-fast`, `--cursor-no-fast`, session state, and global fast defaults. - `src/context.ts`, `src/context-window-cache.ts`, and `src/bundled-context-windows.ts` handle prompt conversion and context-window caches. -- `test/**/*.test.ts` contains Vitest coverage for provider registration, discovery, state, context, and streaming behavior. +- `test/**/*.test.ts` contains Vitest coverage for provider registration, discovery, state, context, bridge, replay, and streaming behavior. - `docs/cursor-model-ux-spec.md` is the maintainer design source of truth for Cursor model UX. Keep it aligned with behavior changes. ## Operating rules @@ -28,6 +32,7 @@ This repository is a pi provider extension that registers Cursor SDK-backed mode - Install dependencies: `npm install` - Run tests: `npm test` - Typecheck: `npm run typecheck` +- Package-readiness check: `npm pack --dry-run` - Watch tests while developing: `npm run test:watch` - Local development run, requires a Cursor key: `CURSOR_API_KEY="your-key" pi -e . --model cursor/composer-2.5` - List Cursor models, requires pi and usually a Cursor key: `pi --list-models cursor` @@ -49,6 +54,7 @@ Done means: - The intended behavior or documentation change is complete. - `npm test` and `npm run typecheck` pass, unless the change is docs-only and the user asked for minimal validation. +- `npm pack --dry-run` passes when package metadata, publishable docs, dependencies, or ignored artifacts change. - Related README/docs/tests are updated when behavior, commands, user-visible model IDs, flags, or troubleshooting change. - No secrets, local API keys, or noisy local state are added. @@ -66,7 +72,7 @@ Use a short written plan before multi-file behavior changes, SDK integration cha - NEVER store Cursor API keys in repo files, `~/.pi/agent/cursor-sdk.json`, tests, logs, snapshots, or docs examples. - Scrub Cursor SDK errors and output that may contain API keys, bearer tokens, cookies, sessions, or auth headers. -- Do not enable ambient Cursor settings/rules loading unless the SDK no longer writes terminal logs that corrupt pi's TUI and the docs/spec are updated. +- Ambient Cursor settings/rules loading is enabled by default through `PI_CURSOR_SETTING_SOURCES=all`; keep SDK startup log filtering intact so settings/skills output does not corrupt pi's TUI. - Live `pi`/Cursor smoke tests may call external services and require `CURSOR_API_KEY`; run them only when needed and report if skipped. ## Progress updates and handoff diff --git a/CHANGELOG.md b/CHANGELOG.md index cbac8e2..970e210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,17 +2,33 @@ ## Unreleased +## 0.1.15 - 2026-05-21 + ### Added -- Replay Cursor SDK `grep` activity through native pi `grep` cards and `glob` activity through native pi `bash` cards with synthesized command labels, so search activity matches built-in tool UX in interactive TTY sessions. +- Add the default-on local pi MCP tool bridge, which exposes bridgeable active pi tools to local Cursor agents while executing calls through pi's normal tool path. +- Add `cursor_ask_question` through the bridge so Cursor can ask users through pi UI as `pi__cursor_ask_question`. +- Add `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` for opting in to overlapping built-in pi tools that are hidden from the Cursor bridge by default. +- Add Cursor SDK MCP tool-call timeout overrides via `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS` and `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` for long-running local MCP tools, including bridged pi tools. +- Replay Cursor SDK `grep` activity through native pi `grep` cards and `glob` activity through native pi `find` cards, so search activity matches built-in tool UX in interactive TTY sessions. + +### Changed + +- Load Cursor setting sources with `PI_CURSOR_SETTING_SOURCES=all` by default while filtering direct Cursor SDK startup logs so settings, rules, plugins, and configured Cursor MCP servers are available without corrupting pi's TUI. ### Fixed +- Replay recorded Cursor tool errors, including nonzero shell exits and timeout-backgrounded shell commands, as native pi tool errors instead of successful green cards. - Format zero-match Cursor grep results as `(no matches)` instead of raw `{ "totalMatches": 0 }` JSON in native replay and transcript output. - Strip trailing colons from Cursor grep file-list replay output. - Make native Cursor read replay closer to pi's built-in read cards by displaying session-relative paths and 20-line continuation hints. - Convert Cursor SDK shell timeouts from milliseconds to seconds in native bash replay cards instead of rendering `30000ms` as `30000s`. - Use the pi session cwd for Cursor `Agent.create`, not only native tool replay display. Completes the 0.1.10 cwd work that previously updated replay registration but left the Cursor agent runtime on `process.cwd()`. +- Replay path-only Cursor `write` activity through neutral recorded Cursor activity instead of invalid native pi `write` calls. +- Preserve literal `cursor_edit`, `cursor_write`, and `cursor_mcp` text in user messages, assistant text, tool args, and tool results while still relabeling structured replay tool names. +- Avoid hiding unrelated MCP activity whose result payload merely contains a bridge tool name, while still suppressing real bridge-owned Cursor MCP replay by invocation identity and call ID. +- Clean up pending native replay waits when abort signals are already aborted or abort before listener registration. +- Suppress direct Cursor SDK settings/skills startup noise, including late `managed_skills.removed` lines, without swallowing unrelated non-startup stdout/stderr output. ## 0.1.14 - 2026-05-18 diff --git a/README.md b/README.md index 72a1051..83f0fab 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ For Claude models with both `thinking` and `effort`, pi thinking `off` sends `th In `pi --list-models`, `thinking=no` means pi cannot control the model's thinking level with `--thinking`, a final `:medium` model suffix, or shift+tab. It does not mean the Cursor model cannot think. -Some Cursor SDK models do not expose a `reasoning`, `effort`, or `thinking` parameter for the extension to set. Cursor thinking is still enabled/supported by the model, and Cursor may still emit thinking deltas. The extension does not disable Cursor's default reasoning behavior. +Some Cursor SDK models do not expose a `reasoning`, `effort`, or `thinking` parameter for the extension to set. Cursor thinking is still enabled/supported by the model, and Cursor may still emit thinking deltas. The extension surfaces those deltas through pi's native thinking rendering when the SDK emits them. ## Fast mode @@ -197,6 +197,31 @@ If you do not see `cursor fast`, fast mode is off. Images from the latest user message are forwarded to Cursor. Historical images are kept out of the transcript and appear only as `[image omitted from transcript]` placeholders, so follow-up questions about an earlier image should reattach the image or include a textual description. The extension advertises `text` and `image` input for Cursor models because Cursor's SDK accepts image messages and Cursor models are expected to support them. + +## Tools and local pi bridge + +Cursor runs use local Cursor SDK agents. Cursor's own local-agent tools, Cursor settings, plugins, and configured Cursor MCP servers remain available through the SDK. + +In addition, pi-cursor-sdk exposes the current pi session's bridgeable active tools to Cursor through a local loopback MCP bridge by default. The bridge snapshots `pi.getActiveTools()` and `pi.getAllTools()` for each Cursor run, excludes internal Cursor replay activity names, and hides overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`) by default because Cursor local agents already have native equivalents. Non-overlapping active tools and extension/custom tools present in pi's active tool registry are exposed as MCP tools with collision-safe names such as `pi__sem_reindex`, and calls map back to the real pi tool names. When Cursor calls a bridged tool, pi executes it through the normal pi tool path, so confirmations, tool hooks, renderers, session history, and abort behavior stay pi-native. The bridge also exposes `cursor_ask_question` as `pi__cursor_ask_question` when enabled, allowing Cursor to ask the user through pi UI instead of silently choosing a default. The bridge does not call tool `execute()` handlers directly. + +Bridge controls: + +```bash +# Roll back to Cursor SDK tools/settings/MCP only; do not expose active pi tools through the bridge. +PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/composer-2.5 + +# Opt in to also expose overlapping pi tool names through the bridge. +PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/composer-2.5 + +# Override Cursor SDK MCP tool-call timeout, including bridged pi tools and configured Cursor MCP servers. +PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS=7200 pi --model cursor/composer-2.5 +PI_CURSOR_MCP_TOOL_TIMEOUT_MS=7200000 pi --model cursor/composer-2.5 +``` + +`PI_CURSOR_PI_TOOL_BRIDGE=0` is the supported rollback flag and disables the bridge entirely. The bridge also treats `false`, `off`, `none`, `no`, and `disabled` as off; `1`, `true`, `on`, `yes`, and `enabled` as on. `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` opts in to exposing overlapping pi tool names that Cursor already has native equivalents for (`read`, `bash`, `write`, `edit`, `grep`, `find`, and `ls`). By default those names are hidden even when pi's Cursor replay wrapper has registered them as extension tools. The Cursor MCP timeout override defaults to 3600 seconds because the installed Cursor SDK has a 60-second MCP request default that is too short for some local MCP tools, including bridged pi tools and configured Cursor MCP servers. + +Cursor-native tool replay is separate from the bridge. Replay only displays completed Cursor SDK tool activity as pi-native-looking cards with recorded results, using pi's normal success/error card shell for neutral Cursor activity too. It never re-runs Cursor-side commands, reapplies Cursor edits, calls MCP servers, or mutates pi state. See [Cursor native tool replay](docs/cursor-native-tool-replay.md). + ## Fallback models If no key is available from `/login`, `CURSOR_API_KEY`, or `--api-key`, model discovery fails, or discovery returns no models, the extension registers a bundled fallback snapshot of the latest reviewed Cursor SDK model catalog and notifies interactive users when possible. @@ -207,11 +232,11 @@ Actual Cursor runs still need a key from `/login`, `CURSOR_API_KEY`, or `--api-k ## Limits -- **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. -- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, `grep`, `ls`, `edit`, and `write` activity through pi's native tool-call path with recorded results (for example green `read`, `grep`, `$ ...`, and Cursor edit/write cards) without forcing Cursor to call pi tools or rerun commands. Cursor `glob` activity is replayed through native `bash` cards with synthesized command labels. Cursor edit/write activity uses replay-only `cursor_edit` and `cursor_write` tool cards because Cursor's file-editing schema is not the same as pi's built-in `edit`/`write` schemas; those replay tools only display recorded Cursor results and never mutate files directly. If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths; that preview is explicitly labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. -- **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor. -- **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls. -- **Cursor setting sources are opt-in.** The extension does not pass `local.settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. To load configured Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities, start pi with `PI_CURSOR_SETTING_SOURCES=all`. To narrow loading, set a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. +- **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents. Cloud pi tool bridging is out of scope because it needs a separate auth, transport, lifetime, and remote trust design. +- **The pi tool bridge is local and MCP-backed.** Bridgeable active pi tools are exposed to local Cursor agents through a tokenized `127.0.0.1` MCP endpoint; internal Cursor replay activity names are excluded, and overlapping built-in pi tools are hidden by default. Set `PI_CURSOR_PI_TOOL_BRIDGE=0` to disable it or `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` to expose overlapping built-ins too. +- **Cursor native tool replay is display-only.** Replay renders recorded Cursor SDK activity and never re-runs Cursor-side commands, reapplies Cursor edits, calls MCP servers, or mutates pi state. Workflow tools such as Cursor `SwitchMode` and Cursor todo state are not pi workflow controls. See [Cursor native tool replay](docs/cursor-native-tool-replay.md) for supported replay cards, ordering, conflict handling, and opt-out flags. +- **Cursor run state can span tool-use turns.** A new Cursor SDK agent starts for a new provider run. When Cursor calls bridged pi tools or emits replayed Cursor tool activity, the same Cursor SDK run can stay alive across pi `toolUse` turns so results resume in the original Cursor run. +- **Cursor setting sources default to all.** The extension passes `local.settingSources: ["all"]` by default so configured Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities are available like they are in Cursor. To narrow loading, set a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. To disable ambient setting sources, set `PI_CURSOR_SETTING_SOURCES=none`. Direct Cursor SDK startup logs are suppressed so setting/skill loading messages do not pollute the TUI. - **Max Mode is not a manual pi variant.** Cursor's SDK may enable Max Mode automatically for models that require it. This extension only advertises exact context-window variants that the SDK catalog exposes and otherwise uses conservative SDK-derived default/non-Max context windows. - **Output token limits are conservative.** Cursor SDK model metadata does not currently expose output token limits directly. - **Token usage is approximate in pi.** Cursor SDK usage events include internal agent/tool/cache work, so the extension reports an approximate replayable pi prompt/output size for context display and compaction decisions. @@ -251,7 +276,7 @@ pi install npm:pi-cursor-sdk ### `pi --list-models` shows `thinking=no` -That does not mean the model cannot think. It means the Cursor SDK does not expose a pi-controllable thinking parameter for that model. The model may still think internally and may still emit thinking deltas. +That does not mean the model cannot think. It means the Cursor SDK does not expose a pi-controllable thinking parameter for that model. The model may still think internally and may still emit thinking deltas that pi renders natively. ### I do not see `cursor fast` in the footer @@ -259,21 +284,38 @@ Fast mode is currently off. The footer only shows `cursor fast` when fast mode i ### My Cursor app settings or rules do not seem to apply -Cursor setting sources are not loaded by default because the Cursor SDK can print settings/skills loading output directly to the terminal. Start pi with `PI_CURSOR_SETTING_SOURCES=all`, or choose a narrower list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. +Cursor setting sources are loaded with `PI_CURSOR_SETTING_SOURCES=all` by default. To narrow loading, set `PI_CURSOR_SETTING_SOURCES=project,user,plugins` or another comma-separated list. If you explicitly disabled sources with `PI_CURSOR_SETTING_SOURCES=none`, remove that override. ### Cursor does not call my web search MCP/tool -Cursor SDK local agents load MCP servers from Cursor setting sources and inline SDK config. This extension leaves Cursor setting sources off by default to avoid startup log noise, so a web search tool needs to be configured in Cursor and settings sources need to be enabled with `PI_CURSOR_SETTING_SOURCES=all` or a narrower list. +Cursor SDK local agents load MCP servers from Cursor setting sources and inline SDK config. This extension enables all Cursor setting sources by default, so a missing web search tool usually means it is not configured in Cursor or the run was started with a narrowing/disable override such as `PI_CURSOR_SETTING_SOURCES=none`. -### Cursor native tool cards conflict with another extension +### Cursor does not call my pi extension tool + +The local pi bridge only exposes tools that are active in the current pi session and present in pi's tool registry at Cursor run start. By default, it does not expose overlapping pi tool names that Cursor already has native equivalents for (`read`, `bash`, `write`, `edit`, `grep`, `find`, and `ls`). Opt in if you intentionally want Cursor to see both the Cursor-native tool and an overlapping built-in pi tool: + +```bash +PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/composer-2.5 +``` + +To disable the bridge for rollback or isolation, start pi with: -Cursor native replay is a UI enhancement for interactive TTY sessions. If another extension already owns `read`, `bash`, `grep`, `ls`, `cursor_edit`, or `cursor_write`, this extension skips only the conflicting native replay wrapper and uses the scrubbed Cursor activity transcript for that tool instead. To disable Cursor native replay registration entirely, start pi with: +```bash +PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/composer-2.5 +``` + +### A Cursor MCP tool times out + +The extension raises Cursor SDK's MCP tool-call timeout from 60 seconds to 3600 seconds by default for Cursor SDK MCP `callTool` requests, including the local pi bridge and configured Cursor MCP servers. For longer local MCP tools, set one override: ```bash -PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2.5 +PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS=7200 pi --model cursor/composer-2.5 +PI_CURSOR_MCP_TOOL_TIMEOUT_MS=7200000 pi --model cursor/composer-2.5 ``` -`PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is also accepted as a registration-only opt-out. +### Cursor native tool cards conflict with another extension + +Cursor native replay is a UI enhancement for interactive TTY sessions. See [Cursor native tool replay](docs/cursor-native-tool-replay.md) for conflict behavior and opt-out flags. ## Development diff --git a/docs/cursor-model-ux-spec.md b/docs/cursor-model-ux-spec.md index 19e66ad..ab19d98 100644 --- a/docs/cursor-model-ux-spec.md +++ b/docs/cursor-model-ux-spec.md @@ -16,10 +16,16 @@ Current implementation notes: - Image payload forwarding sends images only from the latest user message. If the latest user turn is plain text after an earlier image turn, the transcript keeps an `[image omitted from transcript]` placeholder but no image bytes are sent to Cursor. The prompt explicitly tells Cursor that prior image bytes are unavailable and to ask the user to reattach or describe a prior image when needed. Carrying images forward across turns remains a future product decision because it affects token cost, privacy, stale visual context, and expected multimodal follow-up behavior. - `@cursor/sdk` is a package dependency of this extension; users should not need a global SDK install. - Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults. -- Local agents do not pass `settingSources` by default because the Cursor SDK can print settings/skills loading output directly to the terminal during startup. Users can opt in with `PI_CURSOR_SETTING_SOURCES=all` or narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`. +- Local agents pass `settingSources: ["all"]` by default so Cursor MCP servers, plugin tools, project/user settings, and related Cursor-native capabilities are available. Users can narrow loading with a comma-separated list such as `PI_CURSOR_SETTING_SOURCES=project,user,plugins`, or disable ambient setting sources with `PI_CURSOR_SETTING_SOURCES=none`. The provider suppresses direct Cursor SDK startup writes around agent creation so setting/skill loading logs do not pollute pi's TUI. - Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model. -- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, `grep`, `ls`, `edit`, and `write` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands/file edits. Cursor `glob` activity is replayed through native `bash` cards with synthesized command labels. Cursor edit/write activity is replayed through `cursor_edit` and `cursor_write` cards rather than pi's built-in `edit`/`write` names because Cursor's edit/write schemas differ from pi's schemas; these replay-only tools display recorded Cursor results and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active. +- Cursor-side thinking remains visible through pi's native thinking rendering when the Cursor SDK emits thinking or summary deltas. +- Local Cursor agents get two tool paths. First, Cursor keeps the Cursor SDK local-agent tool surface plus configured Cursor settings, plugins, and Cursor MCP servers. Second, pi-cursor-sdk exposes active pi tools through a default-on, tokenized loopback MCP bridge when `pi.getActiveTools()` and `pi.getAllTools()` contain exposable tools. Cursor sees collision-safe MCP names such as `pi__sem_reindex`, while pi emits and executes the real pi tool name. Overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`) are hidden by default because Cursor local agents already have native equivalents; `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` opts into exposing them too. The provider also registers `cursor_ask_question` for Cursor models when the bridge is enabled; Cursor sees it as `pi__cursor_ask_question`, and pi executes it through the normal tool path so interactive users can choose options from pi UI. In non-UI modes it reports that UI is unavailable so Cursor can state a default assumption instead. The bridge queues MCP calls, emits provider `toolcall_*` events, waits for matching pi `toolResult` messages by `toolCallId`, resolves the result back to the same Cursor SDK run, and never calls tool `execute()` handlers directly. `PI_CURSOR_PI_TOOL_BRIDGE=0` disables this local bridge, including question bridging. Cloud Cursor agents remain out of scope for the bridge. +- Cursor SDK MCP tool calls use a guarded timeout override because installed `@cursor/sdk` 1.0.13 has a 60-second MCP request default with no public per-server timeout option. The extension extends that Cursor SDK MCP `callTool` timeout path to 3600 seconds by default. Users can override it with `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` or `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS`. +- Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, `grep`, `find`, `ls`, `edit`, `write`, diagnostics, delete, todo/plan, task, image generation, and MCP activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native-looking cards without rerunning Cursor's reads/shell commands/file edits. Cursor `glob` activity is replayed through native `find` cards. Cursor write activity is replayed through native-looking `write` cards, and Cursor StrReplace/edit activity uses native-looking `edit` only when recorded arguments truthfully satisfy pi's `edit` schema; path-only Cursor edit and notebook edit replay falls back to neutral Cursor activity before pi validation. Diagnostics, delete, todos/plans, task, image, and MCP activity use neutral Cursor activity cards with pi's default success/error shell. Neutral Cursor activity calls include `activityTitle` and, when available, `activitySummary` so partial/collapsed cards preserve identity such as `Cursor plan`, `Cursor todos`, `Cursor MCP`, or `Cursor edit`. Replay-only tools display recorded Cursor results, normalize workspace-local paths/diff headers for display, use pi diff colors for edit previews and path-inferred syntax highlighting for write previews, and fail closed if called without a recorded result. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. Cursor workflow tools such as `SwitchMode` and Cursor todo state are not pi workflow controls; reported todo/plan events are displayed as Cursor activity only. Plan/todo replay cards can be followed by Cursor's final plan text, selected from `run.wait().result` when Cursor provides one and trimmed against already-emitted text. Started Cursor SDK tool calls that never receive a completion event are discarded without synthetic replay errors; explicit failures remain visible when Cursor reports them through completed tool calls or step results. `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` disables native replay, and `PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is a registration-only opt-out that keeps the transcript fallback without shadowing pi tool names. When bridge or native replay cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK activity arrives: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later tool batches as further `toolUse` turns, then Cursor's final assistant answer. For shell replay, completed `stdout` / `stderr` are primary; unambiguous `shell-output-delta` data is used only as display-only fallback for empty successful shell completions, and overlapping shell calls drop ambiguous deltas instead of guessing. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when no live-run turn split is active. +- Synthetic replay names are internal compatibility details. New model-facing prompt text and user-visible cards use native tool names when renderer-compatible, or neutral Cursor activity labels when not. Legacy sessions containing old internal replay names are sanitized before prompt/display. Bridge MCP names such as `pi__sem_reindex` are MCP-only; pi session output uses real pi tool names. - Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage. When native replay splits one Cursor SDK run into multiple pi turns, prompt input is counted once for the run; later synthetic replay turns report `input: 0` and only their own output estimate. +- Audit observation, 2026-05-19, superseded by the 2026-05-21 replay pass: a missing-file read with Composer 2.5 emitted `tool-call-started` for Cursor `read`, then streamed final text `Error: File not found`, but did not emit `tool-call-completed` or an `onStep` `toolCall` error result. Leftover started calls are now discarded at run completion instead of becoming synthetic replay errors. Cursor-reported completed/step errors remain visible. +- Maintainer visual verification for replay-card changes should follow [Cursor Native Tool Visual Audit Workflow](./cursor-native-tool-visual-audit.md): offscreen PTY-driven pi run, xterm.js/Playwright screenshot rendering, and JSONL inspection before accepting commits or PRs. - For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup. - Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.13 documentation says the SDK may enable Max Mode automatically when a selected model requires it, but the public local-agent `ModelSelection` path still does not expose a manual Max Mode selector. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends. - `@cursor/sdk` 1.0.13 adds latest-style `ModelListItem.aliases`. The extension registers only unambiguous aliases as pi model IDs (with the same context suffixes when applicable) and sends the alias back in `ModelSelection.id`, while sharing Cursor-only state such as fast defaults with the underlying catalog `id`. Aliases shared by multiple base models, such as generic family aliases, are skipped because the pi row metadata would otherwise imply one base model while Cursor may resolve the alias to another. @@ -236,7 +242,7 @@ Important distinction: - **Cursor thinking support** applies to all Cursor SDK models. The extension should assume Cursor models can think and may emit thinking deltas. - **Pi-controllable thinking** means Cursor exposes a `reasoning`, `effort`, or `thinking` parameter that the extension can set from pi's native thinking level. These models register `reasoning: true` and show `thinking=yes` in `pi --list-models`. -- **Cursor SDK thinking-control gap** means the model can still think, but the SDK does not expose a user-controllable thinking parameter for that model. These models register `reasoning: false` and show `thinking=no` in `pi --list-models` because pi cannot control a level for them. The extension still parses Cursor `thinking-delta` events if they are emitted. +- **Cursor SDK thinking-control gap** means the model can still think, but the SDK does not expose a user-controllable thinking parameter for that model. These models register `reasoning: false` and show `thinking=no` in `pi --list-models` because pi cannot control a level for them. The extension still surfaces Cursor `thinking-delta` and summary events through pi's native thinking rendering when they are emitted. Do not mark a model `reasoning: true` only because it can think. That would make pi show controls such as `--thinking`, `:medium`, and shift+tab even though the extension cannot translate them into Cursor SDK params. @@ -655,3 +661,11 @@ Before calling done: - `pi --model cursor/gpt-5.5@272k --thinking xhigh -p "Say ok only"` - `pi --model cursor/gpt-5.5@1m --cursor-fast -p "Say ok only"` - confirm requests use selected context, pi thinking, and fast flag state + +4. Tool bridge and replay: + - `npm test -- test/cursor-pi-tool-bridge.test.ts test/cursor-provider.test.ts test/cursor-mcp-timeout-override.test.ts` + - confirm `Agent.create()` gets `mcpServers.pi_tools` when active pi tools exist and omits it when `PI_CURSOR_PI_TOOL_BRIDGE=0` or the active snapshot is empty + - confirm bridged MCP requests emit real pi tool calls and resolve matching pi tool results back to the same Cursor SDK run + - confirm bridge MCP activity is suppressed from Cursor replay while non-bridge Cursor MCP activity remains visible + - confirm `PI_CURSOR_MCP_TOOL_TIMEOUT_MS` and `PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS` override the Cursor SDK MCP callTool timeout seam + - run the visual audit workflow when replay card visuals or bridge card visuals change; JSONL should show real pi tool names for bridged calls and no duplicate MCP replay for bridge calls diff --git a/docs/cursor-native-tool-replay.md b/docs/cursor-native-tool-replay.md new file mode 100644 index 0000000..32339bc --- /dev/null +++ b/docs/cursor-native-tool-replay.md @@ -0,0 +1,88 @@ +# Cursor native tool replay + +pi-cursor-sdk has two separate tool paths: + +1. **Local pi MCP bridge:** default-on for local Cursor agents. It exposes the current pi session's bridgeable active tools to Cursor through a tokenized `127.0.0.1` MCP endpoint, excluding internal Cursor replay activity names and, by default, overlapping built-in pi tools (`read`, `bash`, `write`, `edit`, `grep`, `find`, `ls`). When Cursor calls one of those MCP tools, pi executes the real pi tool through the normal pi tool path. +2. **Cursor native tool replay:** display-only. It renders completed Cursor SDK tool activity as pi-native-looking cards using recorded Cursor results. + +This document is about replay. Replay is not execution and is not the local pi bridge. + +## Local pi bridge summary + +The bridge is enabled by default when bridgeable active pi tools exist. Cursor sees bridge-owned MCP names such as `pi__sem_reindex`, while pi history and tool cards use the real pi tool name such as `sem_reindex`. The bridge hides overlapping built-in pi tools by default because Cursor already has native equivalents; extension/custom tools and non-overlapping active tools present in pi's active tool registry normally remain exposed. pi-cursor-sdk also registers `cursor_ask_question` for Cursor models when the bridge is enabled, exposed to Cursor as `pi__cursor_ask_question`, so Cursor can ask the user to choose instead of silently defaulting when the pi UI is available. The bridge does not call pi tool `execute()` handlers directly; it queues the request, emits a real pi `toolCall`, waits for the matching pi `toolResult`, and resolves the Cursor MCP call back into the same Cursor SDK run. + +Rollback and timeout controls: + +```bash +PI_CURSOR_PI_TOOL_BRIDGE=0 pi --model cursor/composer-2.5 +PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1 pi --model cursor/composer-2.5 +PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS=7200 pi --model cursor/composer-2.5 +PI_CURSOR_MCP_TOOL_TIMEOUT_MS=7200000 pi --model cursor/composer-2.5 +``` + +`PI_CURSOR_PI_TOOL_BRIDGE=0` disables the bridge, including `pi__cursor_ask_question`. `PI_CURSOR_EXPOSE_BUILTIN_TOOLS=1` opts in to exposing overlapping pi tool names that Cursor already has native equivalents for (`read`, `bash`, `write`, `edit`, `grep`, `find`, and `ls`). By default those names are hidden even when pi's Cursor replay wrapper has registered them as extension tools; non-overlapping active built-ins remain bridgeable by default. Cursor-native tools, Cursor settings, plugins, and configured Cursor MCP servers still come from the Cursor SDK local agent path. Cloud Cursor agents are out of scope for this bridge. + +## What gets replayed + +When Cursor reports completed tool activity, the extension can display recorded results for: + +- `read` +- `bash` +- `grep` +- `find` +- `ls` +- `edit` +- `write` +- diagnostics +- delete +- todos and plans +- tasks +- image generation +- MCP activity + +Cursor `glob` activity is displayed through native `find` cards. + +Edit and write activity replays through pi-facing `edit` and `write` cards only when replay arguments truthfully satisfy the matching pi schema, but still uses recorded Cursor results only. The adapter passes through truthful Cursor paths, content when Cursor reported it, and recorded diff/details; it does not pretend Cursor's editing schema is pi's schema and it fails closed if a recorded replay result is missing. Cursor `StrReplace` with recorded replacement text displays as native-looking `edit`; path-only Cursor `edit` and notebook edit activity fall back to neutral Cursor activity so pi does not reject the replay before recorded-result handling. Cursor `write` displays as native-looking `write`. Diagnostics, delete, todos/plans, task, image, and MCP activity use neutral Cursor activity cards with pi's default success/error tool shell. Neutral Cursor activity cards carry display metadata such as `activityTitle` and `activitySummary`, so partial/collapsed cards can say `Cursor plan`, `Cursor todos`, `Cursor MCP`, or `Cursor edit` instead of only `Cursor activity`. These replay tools only display recorded Cursor results; they never mutate files or execute tool work directly. Replay paths are normalized to workspace-relative paths when possible. Collapsed replay cards include bounded previews for diffs and text details so small edits, todos, task output, and MCP results are visible without expanding; edit previews omit raw unified diff headers and show compact numbered changed/context lines using pi's native diff added/removed/context colors, and write previews use syntax highlighting when pi can infer a language from the path. Image generation replay cards show the saved image path in the collapsed summary and render the image inline when pi terminal image display is enabled and the generated file is still readable. + +## What replay does not do + +Native replay is display-only: + +- pi does not re-run Cursor-side commands. +- pi does not apply Cursor-side edits or deletes. +- pi does not call Cursor-side MCP servers. +- replay-only cards do not update pi state or generate images. +- replay does not expose pi tool schemas to Cursor; the local pi MCP bridge is the separate path that exposes active pi tools. +- Cursor workflow tools such as `SwitchMode` and Cursor todo state are not pi workflow controls; reported todo/plan events are displayed as Cursor activity only. Plan/todo replay cards do not drive pi plan-mode state. + +If a Cursor read completion reports no content, the extension may include a bounded local file preview for safe in-workspace paths. That preview is labeled as a local preview captured at transcript time, not guaranteed Cursor-observed content. + +Other unsupported Cursor SDK tools may still be described through a bounded scrubbed activity transcript when the SDK reports completed tool-call data. Started Cursor SDK tool calls that never receive a completion event are discarded without a synthetic replay error; missing completion is not itself treated as a Cursor tool failure. Explicit failures remain visible when Cursor reports an error through a completed tool call or step result. Some Cursor-internal workflow actions may only appear in Cursor's own thinking stream or not be reported as replayable SDK tool completions. + +## Ordering and non-interactive output + +As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results, then continuing with live post-tool Cursor thinking/text, later Cursor tool batches, or Cursor's final answer as the next assistant turn. For plan-mode runs, neutral Cursor plan/todo cards can therefore appear before the final Cursor plan text. + +Bridged pi tool calls follow the same visible pi `toolUse` turn shape, but they are real pi tool executions rather than replayed Cursor results. + +For shell replay, completed `stdout` / `stderr` remain the primary source. If a successful completed shell result is empty and Cursor emitted unambiguous `shell-output-delta` data while exactly one shell call was active, the replay card uses that delta as display-only fallback data. Overlapping shell calls make delta attribution ambiguous, so those fallback deltas are dropped rather than guessed. `(no output)` is kept only when no completed output or safe delta fallback is available. + +Non-interactive and session consumers still receive bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text. + +## Synthetic-name policy + +Synthetic replay names are internal compatibility details. New model-facing prompt text and user-visible cards use native tool names when renderer-compatible, or neutral Cursor activity labels when not. Legacy sessions that already contain old internal replay names are rewritten to safe labels in prompt text and display surfaces. + +Bridge MCP names are also not pi tool names. Cursor may see names such as `pi__sem_reindex` inside the local MCP bridge, but pi session output uses the real pi tool name. + +## Conflicts and opt out + +Native replay wrappers are registered only for tool names not already owned by another extension. If another extension already owns a wrapper name needed for replay, pi-cursor-sdk skips only the conflicting wrapper and uses the scrubbed Cursor activity transcript for that tool instead. Legacy replay wrappers remain registered for old sessions, but their model-facing and user-visible labels are sanitized. + +Disable native replay registration entirely: + +```bash +PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2.5 +``` + +`PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is also accepted as a registration-only opt-out. diff --git a/docs/cursor-native-tool-visual-audit.md b/docs/cursor-native-tool-visual-audit.md new file mode 100644 index 0000000..997553e --- /dev/null +++ b/docs/cursor-native-tool-visual-audit.md @@ -0,0 +1,183 @@ +# Cursor Native Tool Visual Audit Workflow + +This workflow verifies Cursor SDK tool replay the way a human sees it in pi's interactive TUI, without stealing macOS focus. + +Use it before accepting replay-card commits or PRs. Text logs and JSONL are necessary, but they are not enough when the claim is visual parity: always keep before/after PNGs for the exact prompt. + +## When to use this + +Use this workflow when changing or reviewing: + +- Cursor native tool replay cards. +- Tool-call turn ordering. +- Tool-result error styling. +- Truncation, continuation hints, timeout labels, or path display. +- Any PR claiming native TUI parity. + +Do not use this for ordinary unit-only logic changes. + +## Why this workflow exists + +Earlier manual verification used a visible Terminal window plus `screencapture`. That worked, but it stole system focus and made it easy for the user to type into the audit window by accident. + +The preferred workflow is now offscreen: + +1. Spawn `pi` in a pseudo-terminal at a fixed size. +2. Feed the prompt programmatically. +3. Save raw ANSI output and plain text output. +4. Render the terminal buffer through xterm.js in headless Playwright. +5. Save a PNG screenshot. +6. Inspect the session JSONL for exact persisted `toolCall` / `toolResult` data. + +This gives human-like visual evidence without activating Terminal, iTerm, or a browser window. + +## Tool stack + +Install the harness outside this repo so generated assets and temporary dependencies do not pollute commits: + +```bash +HARNESS=/tmp/pi-visual-harness +rm -rf "$HARNESS" +mkdir -p "$HARNESS" +cd "$HARNESS" +npm init -y +npm install node-pty @xterm/xterm playwright +npm rebuild node-pty +``` + +`npm rebuild node-pty` is useful after Node upgrades; without it, `node-pty` may fail with `posix_spawnp failed`. + +## Runner contract + +A runner script should: + +- Spawn `pi -e --model cursor/composer-2.5` with: + - `PI_CURSOR_NATIVE_TOOL_DISPLAY=1` + - `TERM=xterm-256color` + - fixed PTY size, for example `150x45` + - cwd set to the target audit repo. +- Wait for startup. +- Write the exact prompt and carriage return to the PTY. +- Wait a bounded amount of time. +- Save: + - `