pi-cursor-sdk 0.1.32 → 0.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.34 - 2026-06-04
6
+
7
+ ### Changed
8
+
9
+ - Update the local pi validation baseline to `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` `0.78.1` after reviewing the Pi 0.78.1 changelog and extension/provider docs. Pi core peer dependency ranges now follow current pi package guidance with `"*"` ranges, and docs call pi 0.78.1 the recommended validated baseline rather than a hard pin.
10
+ - Gate Cursor native replay tool registration on Pi 0.78.1's precise `ctx.mode === "tui"` instead of treating all dialog-capable UI modes as safe for terminal replay rendering; RPC/JSON/print modes keep bridge/question tools without TUI-only replay wrappers.
11
+
12
+ ### Fixed
13
+
14
+ - Align `cursor_ask_question` and `cursor_activate_skill` failure paths with Pi's current custom-tool contract by throwing on invalid input, unavailable UI, missing skills, and skill load failures instead of returning successful tool results with ignored `isError` fields.
15
+
16
+ ## 0.1.33 - 2026-06-04
17
+
18
+ ### Fixed
19
+
20
+ - Prevent connect-node-only Cursor SDK network resets such as `ConnectError: [aborted] read ECONNRESET` from escaping as process-level uncaught exceptions during active Cursor turns, while keeping provenance-free generic ConnectRPC errors unsuppressed (#121).
21
+ - Suppress expected Cursor SDK abort `ConnectError` / `AbortError` shapes during abandoned live-run cancellation so idle-resume and interrupt cleanup paths keep pi alive for later prompts (#120).
22
+
5
23
  ## 0.1.32 - 2026-06-02
6
24
 
7
25
  ### Added
package/README.md CHANGED
@@ -2,7 +2,27 @@
2
2
 
3
3
  A pi provider extension that lets pi use Cursor models through the local `@cursor/sdk` agent runtime.
4
4
 
5
- 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.
5
+ Use this extension if you primarily use Cursor models inside pi and want Cursor's local SDK agent loop preserved while pi adds native model selection, auth, thinking/context controls, session behavior, replay UI, and optional pi tool bridging.
6
+
7
+ ## Why use this instead of an OpenAI-compatible Cursor endpoint?
8
+
9
+ Use `pi-cursor-sdk` when you primarily want to use Cursor models **inside pi**.
10
+
11
+ This extension runs Cursor models through the local `@cursor/sdk` agent runtime and keeps Cursor's agent loop intact. pi integrates around that loop: model discovery, model selection, context-window variants, thinking controls where Cursor exposes them, fast/slow aliases, Cursor mode, session handling, native replay cards, and the optional pi tool bridge.
12
+
13
+ OpenAI-compatible Cursor proxies are useful when you want a generic `/v1/chat/completions` or `/v1/responses` endpoint for many clients such as curl, the OpenAI SDK, OpenCode, or other tools. That compatibility comes from translating Cursor behavior into OpenAI-shaped requests, responses, and tool calls.
14
+
15
+ For pi users, that translation is usually the wrong abstraction. `pi-cursor-sdk` is pi-specific on purpose: it lets Cursor remain Cursor while making it feel native in pi.
16
+
17
+ | If you want... | Prefer |
18
+ | --- | --- |
19
+ | First-class Cursor usage inside pi | `pi-cursor-sdk` |
20
+ | Cursor's local SDK agent loop preserved, not replaced by an OpenAI-shaped adapter | `pi-cursor-sdk` |
21
+ | pi model picker, `/login`, `/model`, sessions, context display, footer/status UX | `pi-cursor-sdk` |
22
+ | Cursor SDK local-agent tools, settings, MCP, and native replay surfaced in pi | `pi-cursor-sdk` |
23
+ | pi extension tools exposed to Cursor through a local MCP bridge | `pi-cursor-sdk` |
24
+ | A generic OpenAI-compatible localhost `/v1` API for non-pi clients | An OpenAI-compatible Cursor proxy |
25
+ | One Cursor-ish endpoint shared across several unrelated tools | An OpenAI-compatible Cursor proxy |
6
26
 
7
27
  ## Quick start
8
28
 
@@ -31,10 +51,10 @@ If pi started without a key, run `/cursor-refresh-models` after `/login` to refr
31
51
  ## Requirements
32
52
 
33
53
  - Node.js 22.19+
34
- - pi 0.76.0 or newer
54
+ - pi 0.78.1 or newer recommended; pi core peer metadata is intentionally unpinned so newer pi releases are not blocked
35
55
  - a Cursor SDK API key saved through `/login`, available as `CURSOR_API_KEY`, or passed with pi's `--api-key`
36
56
 
37
- No global `@cursor/sdk` install is required. This package depends on exact `@cursor/sdk@1.0.17`, so normal package installation brings in the SDK version this extension was built and tested against. The Cursor SDK currently depends on `sqlite3@^5.1.7`, whose install path can print deprecated transitive `node-gyp@8` dependency warnings such as `inflight`, `rimraf`, `glob`, `npmlog`, `gauge`, `are-we-there-yet`, and `tar@6`. Those warnings are non-fatal and come from the closed-source Cursor SDK dependency boundary; this package cannot force npm overrides into consumer projects. If you install from a root `package.json` you control, you may choose a root-level override such as `"overrides": { "sqlite3": "6.0.1" }`; pi package installs will still follow npm's normal transitive dependency rules. This package declares a pi **minimum** of 0.76.0 with no maximum peer version, so users who update pi before this extension is republished are not blocked from trying the existing extension. The current validation baseline is pi 0.78.0 plus Cursor SDK 1.0.17; older pi or Cursor SDK compatibility paths are not maintained.
57
+ No global `@cursor/sdk` install is required. This package depends on exact `@cursor/sdk@1.0.17`, so normal package installation brings in the SDK version this extension was built and tested against. The Cursor SDK currently depends on `sqlite3@^5.1.7`, whose install path can print deprecated transitive `node-gyp@8` dependency warnings such as `inflight`, `rimraf`, `glob`, `npmlog`, `gauge`, `are-we-there-yet`, and `tar@6`. Those warnings are non-fatal and come from the closed-source Cursor SDK dependency boundary; this package cannot force npm overrides into consumer projects. If you install from a root `package.json` you control, you may choose a root-level override such as `"overrides": { "sqlite3": "6.0.1" }`; pi package installs will still follow npm's normal transitive dependency rules. This package follows pi package guidance by declaring pi core package peers with `"*"` ranges, so users who update pi before this extension is republished are not blocked by peer metadata. The current recommended and validated pi baseline is 0.78.1 plus Cursor SDK 1.0.17; older pi compatibility paths are best-effort and older Cursor SDK compatibility paths are not maintained.
38
58
 
39
59
  ## Install
40
60
 
@@ -67,8 +67,8 @@ The replay scan flags only error `toolResult` / error assistant messages with `T
67
67
 
68
68
  Pass criteria:
69
69
 
70
- - `pi --version` reports pi 0.78.0 for this cutover baseline.
71
- - `npm ls` shows `@cursor/sdk@1.0.17` and local `@earendil-works/*@0.78.0` packages.
70
+ - `pi --version` reports pi 0.78.1 for this cutover baseline.
71
+ - `npm ls` shows `@cursor/sdk@1.0.17` and local `@earendil-works/*@0.78.1` packages.
72
72
  - `cursor/composer-2-5` appears in the model list.
73
73
  - No Cursor key or auth token is printed.
74
74
  - If neither `~/.pi/agent/auth.json` cursor auth nor `CURSOR_API_KEY` is available, stop and report the live smoke as blocked.
@@ -125,7 +125,7 @@ Observe with `tmux capture-pane -pt "$SESSION"` or attach manually.
125
125
  Pass criteria:
126
126
 
127
127
  - Footer shows `(cursor) composer-2-5`. With `--cursor-no-fast`, Cursor fast mode is off and the Cursor extension status should not show `cursor fast`; ignore unrelated status text from other extensions.
128
- - The run uses pi 0.78.0 `--session-id` successfully.
128
+ - The run uses pi 0.78.1 `--session-id` successfully.
129
129
  - Assistant answer appears correctly.
130
130
  - `/session` shows one user and one assistant message for the simple run.
131
131
  - Persisted JSONL has one assistant message. If the screen appears duplicated, inspect JSONL before deciding whether it is a rendering bug.
@@ -133,7 +133,7 @@ Pass criteria:
133
133
 
134
134
  ## 4. Focused visual card/color rendering check
135
135
 
136
- This is the canonical inner-loop visual debug path for Cursor provider/runtime changes. It requires offscreen TUI visual inspection, not only JSONL or code review. Use pi 0.78.0, `@cursor/sdk@1.0.17`, a fresh temporary session dir, Cursor SDK `plan` mode, native replay enabled, and the checked-in visual runner. The runner resolves `pi` by directly walking the parent `PATH`, uses `process.execPath` for Node, and prepends that Node directory for both prereq checks and tmux launches so `#!/usr/bin/env node` shims use the validated Node. The default matrix is native replay only: native replay registration is forced on, settings sources are `none`, the pi bridge is off, overlapping built-in pi tools are not exposed, and inherited Cursor SDK event-debug artifact env is cleared. With `--event-debug`, debug capture writes to a deterministic directory under `VISUAL_DIR`.
136
+ This is the canonical inner-loop visual debug path for Cursor provider/runtime changes. It requires offscreen TUI visual inspection, not only JSONL or code review. Use pi 0.78.1, `@cursor/sdk@1.0.17`, a fresh temporary session dir, Cursor SDK `plan` mode, native replay enabled, and the checked-in visual runner. The runner resolves `pi` by directly walking the parent `PATH`, uses `process.execPath` for Node, and prepends that Node directory for both prereq checks and tmux launches so `#!/usr/bin/env node` shims use the validated Node. The default matrix is native replay only: native replay registration is forced on, settings sources are `none`, the pi bridge is off, overlapping built-in pi tools are not exposed, and inherited Cursor SDK event-debug artifact env is cleared. With `--event-debug`, debug capture writes to a deterministic directory under `VISUAL_DIR`.
137
137
 
138
138
  ```bash
139
139
  VISUAL_DIR="$(mktemp -d /tmp/pi-cursor-sdk-1016-visual.XXXXXX)"
@@ -15,7 +15,7 @@ Current implementation notes:
15
15
  - Cursor status uses one coordinated `ctx.ui.setStatus("cursor", ...)` value for fast and non-default plan mode; the default pi footer remains intact.
16
16
  - Installed `@cursor/sdk` user messages accept images, and Cursor models are treated as image-capable; registered input metadata is `text` plus `image`.
17
17
  - 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.
18
- - Exact `@cursor/sdk@1.0.17` is a package dependency of this extension; users should not need a global SDK install. pi 0.78.0 is the current validation baseline, while published pi peer dependencies are minimum-only `>=0.76.0` ranges with no upper bound. Newer pi versions are allowed to attempt loading this extension before a matching extension release exists; compatibility is best-effort until validated.
18
+ - Exact `@cursor/sdk@1.0.17` is a package dependency of this extension; users should not need a global SDK install. pi 0.78.1 is the current recommended validation baseline, while published pi core peer dependencies use `"*"` ranges per current pi package guidance. Newer pi versions are allowed to attempt loading this extension before a matching extension release exists; compatibility is best-effort until validated.
19
19
  - 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.
20
20
  - 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 bootstrap stdout/stderr/console noise (including late first-send workspace loading such as hook compatibility warnings) so it does not pollute pi's TUI.
21
21
  - On `cursor/*` models, pi-cursor-sdk removes only pi-generated `<project_instructions>` blocks that overlap the effective Cursor `settingSources`: `user` for `~/.pi/agent/AGENTS.md`; `project` for discovered repo/parent `AGENTS.md` and `CLAUDE.md` (verified Cursor behavior: local agents load project `AGENTS.md` and `CLAUDE.md`). `~/.pi/agent/CLAUDE.md` is not removed (Cursor user layer uses `~/.claude/CLAUDE.md`). Blocks are removed by exact pi serialization match from structured `contextFiles` via the `before_agent_start` hook, not in `buildCursorPrompt` sanitization. Suppression is skipped with `-nc`, `PI_CURSOR_SETTING_SOURCES=none`, narrowed sources such as `plugins` that omit the matching layer, or `PI_CURSOR_PRESERVE_PI_AGENTS_MD=1`. Switching away from a Cursor model restores pi's full context block on the next user message.
@@ -6,16 +6,16 @@ This workflow is the canonical repo path for verifying Cursor SDK tool replay th
6
6
 
7
7
  Use it before accepting replay-card commits or PRs, and for every Cursor provider/runtime release where TUI card/color behavior could regress. Text logs and JSONL are necessary, but they are not enough when the claim is visual parity: always keep PNGs for the exact prompt, and keep before/after PNGs when reviewing a rendering change.
8
8
 
9
- Current validation baseline: pi 0.78.0, exact `@cursor/sdk@1.0.17`, local validation packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` at 0.78.0. Published peer dependencies remain minimum-only at pi 0.76.0+ with no upper bound, so newer pi installs can try the extension before a matching validation release exists.
9
+ Current validation baseline: pi 0.78.1, exact `@cursor/sdk@1.0.17`, local validation packages `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` at 0.78.1. Published pi core peer dependencies use `"*"` ranges per current pi package guidance, so newer pi installs can try the extension before a matching validation release exists.
10
10
 
11
- ## Cursor SDK 1.0.17 / pi 0.78.0 cutover visual record
11
+ ## Cursor SDK 1.0.17 / pi 0.78.1 cutover visual record
12
12
 
13
13
  Record the required cutover validation here or in the final release handoff. The default matrix is native replay only: the runner forces native replay registration on, forces Cursor setting sources off, disables the pi bridge, disables overlapping built-in pi tool exposure, and clears inherited Cursor SDK event-debug artifact env. With `--event-debug`, debug capture writes to a deterministic directory under the visual output directory. Do not commit raw ANSI logs, screenshots, terminal recordings, debug artifacts, or `.debug/visual-smoke` scratch files.
14
14
 
15
15
  | Field | Required value / evidence |
16
16
  | --- | --- |
17
17
  | Command/session used | `npm run smoke:visual -- --ext "$PWD" --cwd "$PWD" --mode plan --out-dir <fresh /tmp dir> --label <matrix label> --prompt <matrix prompt>` with default native-replay isolation |
18
- | Baseline versions | `pi --version` = 0.78.0; `npm ls` = `@cursor/sdk@1.0.17` and local `@earendil-works/*@0.78.0` |
18
+ | Baseline versions | `pi --version` = 0.78.1; `npm ls` = `@cursor/sdk@1.0.17` and local `@earendil-works/*@0.78.1` |
19
19
  | Card categories checked | Claim only categories proven by both PNG and JSONL. Required cutover categories are read, grep/search, find/glob, shell success, write, edit/diff, and true read failure. Direct `ls`/list is tracked as excluded from the current one-prompt platform matrix because composer-2-5 does not route it through native `ls` reliably; source-enumeration coverage is gated through find/glob. Neutral Cursor plan/todo/task/mode activity is optional/opportunistic and only counts when JSONL contains a completed Cursor workflow event. |
20
20
  | Observed status/card colors | Confirm native-looking cards use native pi styling; neutral Cursor activity is not red; true errors are distinct; diff previews show red/green; plan status is readable |
21
21
  | Screenshot/ANSI evidence location | External path only, for example `/tmp/pi-cursor-sdk-1016-visual.*/read-package.{ansi,txt,html,png,jsonl.path}` |
@@ -1,6 +1,6 @@
1
1
  # Cursor Testing Lessons
2
2
 
3
- > **Platform Smoke (new):** The required cross-platform release gate is `npm run smoke:platform:doctor && npm run smoke:platform:all`. See [docs/platform-smoke.md](./platform-smoke.md). For portable lessons other pi extension projects can adapt without sharing repo-specific state, see [Crabbox Platform Testing Lessons](./crabbox-platform-testing-lessons.md). The live smoke checklist remains useful for inner-loop development but is not the release gate.
3
+ > **Platform Smoke (new):** The required cross-platform release gate is `npm run smoke:platform:doctor && npm run smoke:platform:all`. See [docs/platform-smoke.md](./platform-smoke.md). For portable lessons other pi extension projects can adapt without sharing repo-specific state, see the generic Crabbox platform testing guide at `/Users/mitchfultz/Projects/crabbox/docs/pi-extension-platform-testing.md`. The live smoke checklist remains useful for inner-loop development but is not the release gate.
4
4
 
5
5
  ## Purpose
6
6
 
@@ -243,7 +243,7 @@ The script writes timestamped artifacts under `--out` (default `/tmp/pi-cursor-s
243
243
 
244
244
  Stdout prints artifact paths and summary counts only. Raw payloads stay on disk and may contain local paths, project text, tool args/results, or secrets — do not commit or share them.
245
245
 
246
- Hard repo rule: Cursor SDK behavior claims must come from the installed `@cursor/sdk` package and/or https://cursor.com/docs/sdk/typescript, not from memory or ad-hoc probes alone. Current cutover validation targets exact `@cursor/sdk@1.0.17` and pi 0.78.0 local packages.
246
+ Hard repo rule: Cursor SDK behavior claims must come from the installed `@cursor/sdk` package and/or https://cursor.com/docs/sdk/typescript, not from memory or ad-hoc probes alone. Current cutover validation targets exact `@cursor/sdk@1.0.17` and pi 0.78.1 local packages.
247
247
 
248
248
  ## Pi provider SDK event capture
249
249
 
@@ -6,6 +6,8 @@ Branch introduced by: `feat/crabbox-platform-smoke`
6
6
 
7
7
  Oracle review incorporated: this gate resolves the packed-install workspace conflict, Cursor budget contradiction, Windows shell drift, artifact-on-failure gap, render-location ambiguity, provider-debug ambiguity, and registry-classification gap called out during review.
8
8
 
9
+ Crabbox best-practice baseline applied from `~/Projects/crabbox`: Crabbox owns lease, sync, run, evidence transport, and cleanup; this repo owns target policy, package setup, scenario meaning, assertions, artifacts, auth forwarding, redaction, and release criteria.
10
+
9
11
  ## Decision
10
12
 
11
13
  Crabbox is the required platform smoke runner for `pi-cursor-sdk` releases that touch Cursor provider/runtime behavior.
@@ -53,7 +55,7 @@ Current baseline:
53
55
 
54
56
  ```text
55
57
  install: brew install openclaw/tap/crabbox
56
- version: 0.24.0 or newer
58
+ version: 0.26.0 or newer
57
59
  binary: Homebrew `crabbox` on PATH (`/opt/homebrew/bin/crabbox` on Apple Silicon Homebrew installs)
58
60
  ```
59
61
 
@@ -75,6 +77,8 @@ scenario + target capability + artifact contract
75
77
 
76
78
  not a one-off shell script.
77
79
 
80
+ Crabbox is deliberately kept as the transport/lifecycle layer. It must not be treated as proof that the pi extension behavior passed; every suite still fails or passes from project-owned assertions and artifact manifests.
81
+
78
82
  High-level flow:
79
83
 
80
84
  ```text
@@ -145,19 +149,21 @@ scripts/platform-smoke/artifacts.mjs
145
149
  scripts/platform-smoke/card-detect.mjs
146
150
  scripts/platform-smoke/crabbox-runner.mjs
147
151
  scripts/platform-smoke/doctor.mjs
152
+ scripts/platform-smoke/jsonl-text.mjs
148
153
  scripts/platform-smoke/live-suite-runner.mjs
149
154
  scripts/platform-smoke/platform-build-windows.ps1
150
155
  scripts/platform-smoke/pty-capture.mjs
151
156
  scripts/platform-smoke/render-ansi.mjs
152
157
  scripts/platform-smoke/scenarios.mjs
153
158
  scripts/platform-smoke/targets.mjs
159
+ scripts/platform-smoke/visual-evidence.mjs
154
160
  ```
155
161
 
156
162
  Package scripts:
157
163
 
158
164
  ```json
159
165
  {
160
- "check:platform-smoke": "node --check <platform smoke scripts> && vitest run test/smoke-tooling.test.ts",
166
+ "check:platform-smoke": "node --check platform-smoke.config.mjs && node --check <platform smoke scripts> && vitest run test/smoke-tooling.test.ts",
161
167
  "smoke:platform": "node scripts/platform-smoke.mjs",
162
168
  "smoke:platform:doctor": "node scripts/platform-smoke.mjs doctor",
163
169
  "smoke:platform:macos": "node scripts/platform-smoke.mjs run --target macos",
@@ -167,7 +173,7 @@ Package scripts:
167
173
  }
168
174
  ```
169
175
 
170
- Add `.artifacts/`, `.crabbox/`, and `.platform-smoke-runs/` to `.gitignore`.
176
+ Add `.artifacts/`, `.crabbox/`, `.debug/`, and `.platform-smoke-runs/` to `.gitignore`.
171
177
 
172
178
  ## Configuration source
173
179
 
@@ -189,18 +195,25 @@ export default {
189
195
  ],
190
196
  requiredCrabbox: {
191
197
  install: "Homebrew package or PLATFORM_SMOKE_CRABBOX override",
192
- minVersion: "0.24.0",
198
+ minVersion: "0.26.0",
193
199
  },
194
200
  ubuntuContainerImage: "cimg/node:24.16",
195
201
  nodeValidationMajor: 24,
202
+ windowsParallels: {
203
+ sourceVm: "pi-extension-windows-template",
204
+ snapshot: "crabbox-ready",
205
+ workRoot: "C:\\crabbox\\pi-cursor-sdk",
206
+ },
196
207
  };
197
208
  ```
198
209
 
199
210
  `ubuntuContainerImage` defaults the local-container Ubuntu target to an Ubuntu 24.04 Node 24 image with a current glibc baseline for native test dependencies; Crabbox still bootstraps SSH/Git/rsync/curl as needed. `nodeValidationMajor: 24` is the release-smoke validation baseline. It does not change the package engine by itself. A separate compatibility lane can test Node 22.19 later; this required gate validates Node 24 on every target.
200
211
 
212
+ `windowsParallels` records this repo's default shared Windows template contract. Environment overrides may point at a temporary candidate template during infrastructure work, but release runs should use the shared `pi-extension-windows-template` / `crabbox-ready` baseline unless this document is updated.
213
+
201
214
  ## Required local environment
202
215
 
203
- The doctor fails if any required value is missing.
216
+ The config owns reusable defaults. Environment variables are local-machine knobs and one-off overrides, not a second source of truth. The doctor fails if required auth or target readiness is missing.
204
217
 
205
218
  ```bash
206
219
  # Optional override; by default the gate uses Homebrew `crabbox` from PATH.
@@ -211,11 +224,13 @@ PLATFORM_SMOKE_MAC_USER="$USER"
211
224
  PLATFORM_SMOKE_MAC_WORK_ROOT="/Users/$USER/crabbox/pi-cursor-sdk"
212
225
  PLATFORM_SMOKE_UBUNTU_IMAGE="cimg/node:24.16"
213
226
 
214
- PLATFORM_SMOKE_WINDOWS_VM="Windows 11"
227
+ # Optional Parallels overrides; defaults come from platform-smoke.config.mjs.
228
+ PLATFORM_SMOKE_WINDOWS_VM="pi-extension-windows-template"
215
229
  PLATFORM_SMOKE_WINDOWS_SNAPSHOT="crabbox-ready"
216
230
  PLATFORM_SMOKE_WINDOWS_USER="<windows-ssh-user>"
217
231
  PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT="C:\\crabbox\\pi-cursor-sdk"
218
232
 
233
+ # Required for live suites; doctor fails before spending Cursor tokens if absent.
219
234
  CURSOR_API_KEY="..."
220
235
  ```
221
236
 
@@ -278,7 +293,13 @@ Required:
278
293
 
279
294
  ### Windows template VM
280
295
 
281
- The user's daily Windows VM is not the long-term test target. A dedicated template VM and snapshot are required.
296
+ The user's daily Windows VM is not the long-term test target. Use the shared pi-extension Parallels template unless this project documents a replacement with equal evidence:
297
+
298
+ ```text
299
+ source VM: pi-extension-windows-template
300
+ snapshot: crabbox-ready
301
+ work root: C:\\crabbox\\pi-cursor-sdk
302
+ ```
282
303
 
283
304
  Template requirements:
284
305
 
@@ -293,8 +314,9 @@ Template requirements:
293
314
  - `node-pty` self-test passes in native Windows.
294
315
  - Source VM is powered off.
295
316
  - Snapshot named `crabbox-ready` exists.
317
+ - The template contains reusable platform tools only; no repo checkout, `.pi` state, Cursor API key, browser auth, smoke artifacts, or temp files.
296
318
 
297
- Crabbox Parallels creates linked clones from the powered-off snapshot. The source template VM is never used directly for smoke runs.
319
+ Crabbox Parallels creates linked clones from the powered-off snapshot. The source template VM is never used directly for smoke runs. If a run has to install a missing global tool or browser on every Windows clone, treat that as template drift and refresh the shared template instead of making the per-run fallback normal.
298
320
 
299
321
  ### Windows native
300
322
 
@@ -313,13 +335,13 @@ tar --version
313
335
 
314
336
  Doctor checks:
315
337
 
316
- 1. Required env vars exist.
338
+ 1. Required auth is present and optional target overrides resolve against config defaults.
317
339
  2. Homebrew `crabbox` is available on PATH, or `PLATFORM_SMOKE_CRABBOX` points at an executable override.
318
340
  3. Crabbox build matches the configured baseline.
319
341
  4. Crabbox provider registry includes `local-container`, `ssh`, and `parallels`.
320
- 5. `crabbox doctor --provider local-container --json` passes.
342
+ 5. `crabbox doctor --provider local-container --target linux --json` passes.
321
343
  6. Docker runtime is active.
322
- 7. macOS SSH localhost probe passes and sees Node, npm, Git, rsync, and tar.
344
+ 7. Crabbox macOS static SSH doctor with `--doctor-probe-ssh` passes, and the localhost SSH probe sees Node, npm, Git, rsync, and tar.
323
345
  8. `prlctl` exists.
324
346
  9. Windows source VM exists.
325
347
  10. Windows source snapshot exists.
@@ -358,7 +380,7 @@ Per target, `platform-build` must:
358
380
 
359
381
  1. Record `node --version` and assert the target Node major is at least `nodeValidationMajor`.
360
382
  2. Run `npm ci` in `extensionSourceRoot`.
361
- 3. Run `npm run check:platform-smoke` on the target so smoke harness syntax and invariant tests fail before live Cursor calls, with only the target-local release-tag guard bypassed because Crabbox worktrees may not have release tags.
383
+ 3. Run `npm run check:platform-smoke` on the target so config syntax, smoke harness syntax, invalid target/suite guards, and invariant tests fail before live Cursor calls.
362
384
  4. Run `npm test` on the target with the same target-local release-tag guard bypass.
363
385
  5. Run `npm run typecheck`.
364
386
  6. Run `npm pack`.
@@ -379,7 +401,7 @@ Purpose:
379
401
  - fail before spending Cursor tokens;
380
402
  - produce the packed extension used by later suites.
381
403
 
382
- The host `smoke:platform:all` entrypoint enforces doctor first and then the release-version reuse guard before running targets, using local git tags and `package.json`. Required artifacts include `node-version.txt`, `npm-version.txt`, stdout/stderr for `npm ci`, `npm run check:platform-smoke`, `npm test`, `npm run typecheck`, `npm pack`, packed npm install, `pi install`, and `pi list`, plus `packed-tarball.txt`, `summary.json`, `artifact-manifest.json`, `assertions.json`, and `failures.md` on failed assertions.
404
+ The host `smoke:platform:all` entrypoint enforces doctor first before running targets. Required artifacts include `node-version.txt`, `npm-version.txt`, stdout/stderr for `npm ci`, `npm run check:platform-smoke`, `npm test`, `npm run typecheck`, `npm pack`, packed npm install, `pi install`, and `pi list`, plus `packed-tarball.txt`, `summary.json`, `artifact-manifest.json`, `assertions.json`, and `failures.md` on failed assertions.
383
405
 
384
406
  ### `cursor-native-visual-matrix`
385
407
 
@@ -596,7 +618,7 @@ cursor-abort-cleanup: 1
596
618
 
597
619
  Maximum per target: `3` Cursor invocations.
598
620
 
599
- Maximum full gate: `12` Cursor invocations.
621
+ Maximum full gate: `9` Cursor invocations.
600
622
 
601
623
  The merge gate is `npm run smoke:platform:all`; that script runs doctor first and then the matrix to preserve this budget. No suite adds a new Cursor invocation without updating this plan and `platform-smoke.config.mjs`.
602
624
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cursor-sdk",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "pi provider extension backed by @cursor/sdk local agents",
5
5
  "author": "Mitch Fultz (https://github.com/fitchmultz)",
6
6
  "license": "MIT",
@@ -69,7 +69,6 @@
69
69
  "docs/cursor-tool-surfaces.md",
70
70
  "docs/cursor-live-smoke-checklist.md",
71
71
  "docs/cursor-testing-lessons.md",
72
- "docs/crabbox-platform-testing-lessons.md",
73
72
  "docs/cursor-dogfood-checklist.md",
74
73
  "docs/cursor-native-tool-replay.md",
75
74
  "docs/cursor-native-tool-visual-audit.md",
@@ -98,7 +97,7 @@
98
97
  "debug:sdk-events": "node scripts/debug-sdk-events.mjs",
99
98
  "debug:provider-events": "node scripts/debug-provider-events.mjs",
100
99
  "debug:mcp-coldstart": "node scripts/probe-mcp-coldstart.mjs",
101
- "check:platform-smoke": "node --check scripts/platform-smoke.mjs && node --check scripts/platform-smoke/assertions.mjs && node --check scripts/platform-smoke/artifacts.mjs && node --check scripts/platform-smoke/card-detect.mjs && node --check scripts/platform-smoke/crabbox-runner.mjs && node --check scripts/platform-smoke/doctor.mjs && node --check scripts/platform-smoke/jsonl-text.mjs && node --check scripts/platform-smoke/live-suite-runner.mjs && node --check scripts/platform-smoke/pty-capture.mjs && node --check scripts/platform-smoke/render-ansi.mjs && node --check scripts/platform-smoke/scenarios.mjs && node --check scripts/platform-smoke/targets.mjs && node --check scripts/platform-smoke/visual-evidence.mjs && vitest run test/smoke-tooling.test.ts",
100
+ "check:platform-smoke": "node --check platform-smoke.config.mjs && node --check scripts/platform-smoke.mjs && node --check scripts/platform-smoke/assertions.mjs && node --check scripts/platform-smoke/artifacts.mjs && node --check scripts/platform-smoke/card-detect.mjs && node --check scripts/platform-smoke/crabbox-runner.mjs && node --check scripts/platform-smoke/doctor.mjs && node --check scripts/platform-smoke/jsonl-text.mjs && node --check scripts/platform-smoke/live-suite-runner.mjs && node --check scripts/platform-smoke/pty-capture.mjs && node --check scripts/platform-smoke/render-ansi.mjs && node --check scripts/platform-smoke/scenarios.mjs && node --check scripts/platform-smoke/targets.mjs && node --check scripts/platform-smoke/visual-evidence.mjs && vitest run test/smoke-tooling.test.ts",
102
101
  "smoke:platform": "node scripts/platform-smoke.mjs",
103
102
  "smoke:platform:doctor": "node scripts/platform-smoke.mjs doctor",
104
103
  "smoke:platform:macos": "node scripts/platform-smoke.mjs run --target macos",
@@ -111,15 +110,15 @@
111
110
  "@modelcontextprotocol/sdk": "^1.29.0"
112
111
  },
113
112
  "peerDependencies": {
114
- "@earendil-works/pi-ai": ">=0.76.0",
115
- "@earendil-works/pi-coding-agent": ">=0.76.0",
116
- "@earendil-works/pi-tui": ">=0.76.0",
113
+ "@earendil-works/pi-ai": "*",
114
+ "@earendil-works/pi-coding-agent": "*",
115
+ "@earendil-works/pi-tui": "*",
117
116
  "typebox": "*"
118
117
  },
119
118
  "devDependencies": {
120
- "@earendil-works/pi-ai": "0.78.0",
121
- "@earendil-works/pi-coding-agent": "0.78.0",
122
- "@earendil-works/pi-tui": "0.78.0",
119
+ "@earendil-works/pi-ai": "0.78.1",
120
+ "@earendil-works/pi-coding-agent": "0.78.1",
121
+ "@earendil-works/pi-tui": "0.78.1",
123
122
  "@xterm/xterm": "^6.0.0",
124
123
  "node-pty": "^1.1.0",
125
124
  "playwright": "^1.60.0",
@@ -14,8 +14,13 @@ export default {
14
14
  ],
15
15
  requiredCrabbox: {
16
16
  install: "Homebrew package or PLATFORM_SMOKE_CRABBOX override",
17
- minVersion: "0.24.0",
17
+ minVersion: "0.26.0",
18
18
  },
19
19
  ubuntuContainerImage: "cimg/node:24.16",
20
20
  nodeValidationMajor: 24,
21
+ windowsParallels: {
22
+ sourceVm: "pi-extension-windows-template",
23
+ snapshot: "crabbox-ready",
24
+ workRoot: "C:\\crabbox\\pi-cursor-sdk",
25
+ },
21
26
  };
@@ -98,10 +98,11 @@ export function buildTargetBaseArgs(targetName, config = {}) {
98
98
  ];
99
99
  }
100
100
  case "windows-native": {
101
- const vm = env("PLATFORM_SMOKE_WINDOWS_VM") || "pi-extension-windows-template";
102
- const snap = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || "crabbox-ready";
103
- const user = env("PLATFORM_SMOKE_WINDOWS_USER") || env("USER");
104
- const workRoot = env("PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT") || "C:\\crabbox\\pi-cursor-sdk";
101
+ const windows = config.windowsParallels ?? {};
102
+ const vm = env("PLATFORM_SMOKE_WINDOWS_VM") || windows.sourceVm || "pi-extension-windows-template";
103
+ const snap = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || windows.snapshot || "crabbox-ready";
104
+ const user = env("PLATFORM_SMOKE_WINDOWS_USER") || windows.user || env("USER");
105
+ const workRoot = env("PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT") || windows.workRoot || "C:\\crabbox\\pi-cursor-sdk";
105
106
  return [
106
107
  "--provider", "parallels",
107
108
  "--target", "windows",
@@ -55,11 +55,22 @@ function parseLeaseId(output) {
55
55
  ?? null;
56
56
  }
57
57
 
58
- function windowsCrabboxBaseArgs() {
59
- const vm = env("PLATFORM_SMOKE_WINDOWS_VM") || "pi-extension-windows-template";
60
- const snap = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || "crabbox-ready";
61
- const user = env("PLATFORM_SMOKE_WINDOWS_USER") || env("USER");
62
- const workRoot = env("PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT") || "C:\\crabbox\\pi-cursor-sdk";
58
+ function windowsParallelsDefaults(config = {}) {
59
+ const windows = config?.windowsParallels ?? {};
60
+ return {
61
+ vm: windows.sourceVm || "pi-extension-windows-template",
62
+ snapshot: windows.snapshot || "crabbox-ready",
63
+ user: windows.user || env("USER"),
64
+ workRoot: windows.workRoot || "C:\\crabbox\\pi-cursor-sdk",
65
+ };
66
+ }
67
+
68
+ function windowsCrabboxBaseArgs(config = {}) {
69
+ const defaults = windowsParallelsDefaults(config);
70
+ const vm = env("PLATFORM_SMOKE_WINDOWS_VM") || defaults.vm;
71
+ const snap = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || defaults.snapshot;
72
+ const user = env("PLATFORM_SMOKE_WINDOWS_USER") || defaults.user;
73
+ const workRoot = env("PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT") || defaults.workRoot;
63
74
  return [
64
75
  "--provider", "parallels",
65
76
  "--target", "windows",
@@ -91,9 +102,9 @@ function crabbox(cbox, args, timeout = 300_000) {
91
102
  }
92
103
  }
93
104
 
94
- function disposableWindowsSshProbe(cbox) {
105
+ function disposableWindowsSshProbe(cbox, config = {}) {
95
106
  const slug = "pi-cursor-sdk-doctor-windows";
96
- const baseArgs = windowsCrabboxBaseArgs();
107
+ const baseArgs = windowsCrabboxBaseArgs(config);
97
108
  const warm = crabbox(cbox, ["warmup", ...baseArgs, "--slug", slug, "--keep", "--reclaim"], 300_000);
98
109
  const leaseId = parseLeaseId(warm.stdout) ?? parseLeaseId(warm.stderr) ?? slug;
99
110
  try {
@@ -131,10 +142,6 @@ function runChecks(config) {
131
142
  console.log("\n── Environment variables ──");
132
143
  const requiredVars = [
133
144
  "CURSOR_API_KEY",
134
- "PLATFORM_SMOKE_WINDOWS_VM",
135
- "PLATFORM_SMOKE_WINDOWS_SNAPSHOT",
136
- "PLATFORM_SMOKE_WINDOWS_USER",
137
- "PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT",
138
145
  ];
139
146
  const optionalVars = [
140
147
  "PLATFORM_SMOKE_CRABBOX",
@@ -142,15 +149,27 @@ function runChecks(config) {
142
149
  "PLATFORM_SMOKE_MAC_USER",
143
150
  "PLATFORM_SMOKE_MAC_WORK_ROOT",
144
151
  "PLATFORM_SMOKE_UBUNTU_IMAGE",
152
+ "PLATFORM_SMOKE_WINDOWS_VM",
153
+ "PLATFORM_SMOKE_WINDOWS_SNAPSHOT",
154
+ "PLATFORM_SMOKE_WINDOWS_USER",
155
+ "PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT",
145
156
  ];
146
157
  for (const name of requiredVars) {
147
158
  const v = env(name);
148
159
  v ? ok(`${name} = ${name === "CURSOR_API_KEY" ? "(present, redacted)" : (v.length > 50 ? v.slice(0, 50) + "..." : v)}`)
149
160
  : fail(`${name} missing`);
150
161
  }
162
+ const windowsDefaults = windowsParallelsDefaults(config);
163
+ const optionalDefaults = {
164
+ PLATFORM_SMOKE_WINDOWS_VM: windowsDefaults.vm,
165
+ PLATFORM_SMOKE_WINDOWS_SNAPSHOT: windowsDefaults.snapshot,
166
+ PLATFORM_SMOKE_WINDOWS_USER: windowsDefaults.user,
167
+ PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT: windowsDefaults.workRoot,
168
+ };
151
169
  for (const name of optionalVars) {
152
170
  const v = env(name);
153
- ok(`${name} = ${v || "(default)"}`);
171
+ const fallback = optionalDefaults[name] ? `(default: ${optionalDefaults[name]})` : "(default)";
172
+ ok(`${name} = ${v || fallback}`);
154
173
  }
155
174
 
156
175
  // ── Phase 2: Crabbox binary ──
@@ -205,7 +224,7 @@ function runChecks(config) {
205
224
  fail("crabbox providers failed");
206
225
  }
207
226
  const ubuntuImage = env("PLATFORM_SMOKE_UBUNTU_IMAGE") || config?.ubuntuContainerImage || "cimg/node:24.16";
208
- const lcDoc = silent(cbox, ["doctor", "--provider", "local-container", "--local-container-image", ubuntuImage, "--json"]);
227
+ const lcDoc = silent(cbox, ["doctor", "--provider", "local-container", "--target", "linux", "--local-container-image", ubuntuImage, "--json"]);
209
228
  if (lcDoc) {
210
229
  try {
211
230
  const d = JSON.parse(lcDoc);
@@ -223,7 +242,7 @@ function runChecks(config) {
223
242
  "doctor", "--provider", "ssh", "--target", "macos",
224
243
  "--static-host", sshHost, "--static-user", sshUser,
225
244
  "--static-port", "22", "--static-work-root", sshRoot,
226
- "--json",
245
+ "--doctor-probe-ssh", "--json",
227
246
  ]);
228
247
  if (sshDoc) {
229
248
  try {
@@ -265,7 +284,7 @@ function runChecks(config) {
265
284
  fail("prlctl not found");
266
285
  } else {
267
286
  ok("prlctl found");
268
- const vmName = env("PLATFORM_SMOKE_WINDOWS_VM") || "pi-extension-windows-template";
287
+ const vmName = env("PLATFORM_SMOKE_WINDOWS_VM") || windowsParallelsDefaults(config).vm;
269
288
  const list = shell("prlctl list -a --no-header 2>/dev/null");
270
289
  if (list) {
271
290
  const vms = list.split("\n").filter(Boolean);
@@ -279,7 +298,7 @@ function runChecks(config) {
279
298
  fail(`VM "${vmName}" state: ${status} — source VM must be stopped for linked clones`);
280
299
  }
281
300
 
282
- const snapName = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || "crabbox-ready";
301
+ const snapName = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || windowsParallelsDefaults(config).snapshot;
283
302
  const snapsJson = shell(`prlctl snapshot-list "${vmName}" -j 2>/dev/null`);
284
303
  let snapshotFound = false;
285
304
  let snapshotPowerOff = false;
@@ -323,7 +342,7 @@ function runChecks(config) {
323
342
  } else {
324
343
  ok(`template "${vmName}" has no IP; verifying Windows SSH/tools through a disposable Crabbox clone`);
325
344
  if (cbox && snapshotFound && snapshotPowerOff) {
326
- const probe = disposableWindowsSshProbe(cbox);
345
+ const probe = disposableWindowsSshProbe(cbox, config);
327
346
  probe.ok ? ok(`disposable Windows clone SSH/tool probe OK: ${probe.message}`) : fail(probe.message);
328
347
  } else {
329
348
  fail(`Windows SSH probe could not run because "${vmName}" has no IP and no verified snapshot was available`);
@@ -62,19 +62,15 @@ Write-SectionFile "NPM_CI_STDOUT" $NpmCiOut
62
62
  Write-SectionFile "NPM_CI_STDERR" $NpmCiErr
63
63
 
64
64
  Write-Output "=== check:platform-smoke ==="
65
- $env:PI_CURSOR_SKIP_RELEASE_VERSION_GUARD = "1"
66
65
  & npm.cmd run check:platform-smoke 1> $CheckPlatformSmokeOut 2> $CheckPlatformSmokeErr
67
66
  $CHECK_PLATFORM_SMOKE_EXIT = Exit-CodeFromLastCommand
68
- Remove-Item Env:\PI_CURSOR_SKIP_RELEASE_VERSION_GUARD -ErrorAction SilentlyContinue
69
67
  Write-Output "PLATFORM_CHECK_PLATFORM_SMOKE_EXIT=$CHECK_PLATFORM_SMOKE_EXIT"
70
68
  Write-SectionFile "CHECK_PLATFORM_SMOKE_STDOUT" $CheckPlatformSmokeOut
71
69
  Write-SectionFile "CHECK_PLATFORM_SMOKE_STDERR" $CheckPlatformSmokeErr
72
70
 
73
71
  Write-Output "=== npm test ==="
74
- $env:PI_CURSOR_SKIP_RELEASE_VERSION_GUARD = "1"
75
72
  & npm.cmd test 1> $NpmTestOut 2> $NpmTestErr
76
73
  $TEST_EXIT = Exit-CodeFromLastCommand
77
- Remove-Item Env:\PI_CURSOR_SKIP_RELEASE_VERSION_GUARD -ErrorAction SilentlyContinue
78
74
  Write-Output "PLATFORM_NPM_TEST_EXIT=$TEST_EXIT"
79
75
  Write-SectionFile "NPM_TEST_STDOUT" $NpmTestOut
80
76
  Write-SectionFile "NPM_TEST_STDERR" $NpmTestErr
@@ -422,14 +422,14 @@ export function buildPlatformBuildCommand(targetName, packageName = "pi-cursor-s
422
422
  lines.push(...posixSection("NPM_CI_STDERR", 'cat "$PACK_DIR/npm-ci.stderr.txt" 2>/dev/null || true'));
423
423
  lines.push("");
424
424
  lines.push('echo "=== check:platform-smoke ==="');
425
- lines.push('PI_CURSOR_SKIP_RELEASE_VERSION_GUARD=1 npm run check:platform-smoke >"$PACK_DIR/check-platform-smoke.stdout.txt" 2>"$PACK_DIR/check-platform-smoke.stderr.txt"');
425
+ lines.push('npm run check:platform-smoke >"$PACK_DIR/check-platform-smoke.stdout.txt" 2>"$PACK_DIR/check-platform-smoke.stderr.txt"');
426
426
  lines.push("CHECK_PLATFORM_SMOKE_EXIT=$?");
427
427
  lines.push('echo "PLATFORM_CHECK_PLATFORM_SMOKE_EXIT=$CHECK_PLATFORM_SMOKE_EXIT"');
428
428
  lines.push(...posixSection("CHECK_PLATFORM_SMOKE_STDOUT", 'cat "$PACK_DIR/check-platform-smoke.stdout.txt" 2>/dev/null || true'));
429
429
  lines.push(...posixSection("CHECK_PLATFORM_SMOKE_STDERR", 'cat "$PACK_DIR/check-platform-smoke.stderr.txt" 2>/dev/null || true'));
430
430
  lines.push("");
431
431
  lines.push('echo "=== npm test ==="');
432
- lines.push('PI_CURSOR_SKIP_RELEASE_VERSION_GUARD=1 npm test >"$PACK_DIR/npm-test.stdout.txt" 2>"$PACK_DIR/npm-test.stderr.txt"');
432
+ lines.push('npm test >"$PACK_DIR/npm-test.stdout.txt" 2>"$PACK_DIR/npm-test.stderr.txt"');
433
433
  lines.push("TEST_EXIT=$?");
434
434
  lines.push('echo "PLATFORM_NPM_TEST_EXIT=$TEST_EXIT"');
435
435
  lines.push(...posixSection("NPM_TEST_STDOUT", 'cat "$PACK_DIR/npm-test.stdout.txt" 2>/dev/null || true'));