pi-cursor-sdk 0.1.31 → 0.1.33
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 +22 -0
- package/README.md +21 -1
- package/docs/cursor-testing-lessons.md +1 -1
- package/docs/platform-smoke.md +42 -19
- package/package.json +2 -3
- package/platform-smoke.config.mjs +7 -2
- package/scripts/platform-smoke/crabbox-runner.mjs +5 -4
- package/scripts/platform-smoke/doctor.mjs +54 -26
- package/scripts/platform-smoke/platform-build-windows.ps1 +0 -4
- package/scripts/platform-smoke/targets.mjs +2 -2
- package/scripts/platform-smoke.mjs +24 -19
- package/src/context-window-cache.ts +23 -7
- package/src/cursor-live-run-coordinator.ts +13 -1
- package/src/cursor-provider-errors.ts +3 -1
- package/src/cursor-sdk-process-error-guard.ts +4 -2
- package/src/cursor-state.ts +21 -12
- package/src/model-list-cache.ts +74 -10
- package/docs/crabbox-platform-testing-lessons.md +0 -508
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.33 - 2026-06-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- 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).
|
|
10
|
+
- 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).
|
|
11
|
+
|
|
12
|
+
## 0.1.32 - 2026-06-02
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Add a production typing-safety regression test that blocks broad TypeScript escape hatches such as `as unknown as`, `as any`, `as never`, explicit `any`, and production `@ts-ignore` / `@ts-expect-error` usage.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Replace repeated native replay render-test `as never` casts with typed test render fixture helpers.
|
|
21
|
+
- Use the maintained Homebrew Crabbox binary on `PATH` for platform smoke with a `0.24.0` minimum version, keeping `PLATFORM_SMOKE_CRABBOX` as an explicit override only.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Harden local Cursor cache/config JSON parsing so model-list, context-window, and fast-default files are validated from `unknown` before trusted values are used.
|
|
26
|
+
|
|
5
27
|
## 0.1.31 - 2026-06-01
|
|
6
28
|
|
|
7
29
|
### 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
|
|
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
|
|
|
@@ -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
|
|
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
|
|
package/docs/platform-smoke.md
CHANGED
|
@@ -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.
|
|
@@ -52,12 +54,12 @@ The runner uses one supported Crabbox build.
|
|
|
52
54
|
Current baseline:
|
|
53
55
|
|
|
54
56
|
```text
|
|
55
|
-
install: brew install crabbox
|
|
56
|
-
version: 0.
|
|
57
|
-
binary:
|
|
57
|
+
install: brew install openclaw/tap/crabbox
|
|
58
|
+
version: 0.26.0 or newer
|
|
59
|
+
binary: Homebrew `crabbox` on PATH (`/opt/homebrew/bin/crabbox` on Apple Silicon Homebrew installs)
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
Use the Homebrew Crabbox binary on PATH for normal release gates. `PLATFORM_SMOKE_CRABBOX=/path/to/crabbox` is only an explicit override for testing a non-default binary. `smoke:platform:doctor` verifies the configured binary and fails when it is older than the configured minimum version.
|
|
61
63
|
|
|
62
64
|
Required Crabbox providers:
|
|
63
65
|
|
|
@@ -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
|
|
|
@@ -188,21 +194,29 @@ export default {
|
|
|
188
194
|
"cursor-abort-cleanup",
|
|
189
195
|
],
|
|
190
196
|
requiredCrabbox: {
|
|
191
|
-
install: "
|
|
192
|
-
|
|
197
|
+
install: "Homebrew package or PLATFORM_SMOKE_CRABBOX override",
|
|
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
|
|
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
|
|
219
|
+
# Optional override; by default the gate uses Homebrew `crabbox` from PATH.
|
|
206
220
|
PLATFORM_SMOKE_CRABBOX=/opt/homebrew/bin/crabbox
|
|
207
221
|
|
|
208
222
|
PLATFORM_SMOKE_MAC_HOST=localhost
|
|
@@ -210,11 +224,13 @@ PLATFORM_SMOKE_MAC_USER="$USER"
|
|
|
210
224
|
PLATFORM_SMOKE_MAC_WORK_ROOT="/Users/$USER/crabbox/pi-cursor-sdk"
|
|
211
225
|
PLATFORM_SMOKE_UBUNTU_IMAGE="cimg/node:24.16"
|
|
212
226
|
|
|
213
|
-
|
|
227
|
+
# Optional Parallels overrides; defaults come from platform-smoke.config.mjs.
|
|
228
|
+
PLATFORM_SMOKE_WINDOWS_VM="pi-extension-windows-template"
|
|
214
229
|
PLATFORM_SMOKE_WINDOWS_SNAPSHOT="crabbox-ready"
|
|
215
230
|
PLATFORM_SMOKE_WINDOWS_USER="<windows-ssh-user>"
|
|
216
231
|
PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT="C:\\crabbox\\pi-cursor-sdk"
|
|
217
232
|
|
|
233
|
+
# Required for live suites; doctor fails before spending Cursor tokens if absent.
|
|
218
234
|
CURSOR_API_KEY="..."
|
|
219
235
|
```
|
|
220
236
|
|
|
@@ -277,7 +293,13 @@ Required:
|
|
|
277
293
|
|
|
278
294
|
### Windows template VM
|
|
279
295
|
|
|
280
|
-
The user's daily Windows VM is not the long-term test target.
|
|
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
|
+
```
|
|
281
303
|
|
|
282
304
|
Template requirements:
|
|
283
305
|
|
|
@@ -292,8 +314,9 @@ Template requirements:
|
|
|
292
314
|
- `node-pty` self-test passes in native Windows.
|
|
293
315
|
- Source VM is powered off.
|
|
294
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.
|
|
295
318
|
|
|
296
|
-
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.
|
|
297
320
|
|
|
298
321
|
### Windows native
|
|
299
322
|
|
|
@@ -312,13 +335,13 @@ tar --version
|
|
|
312
335
|
|
|
313
336
|
Doctor checks:
|
|
314
337
|
|
|
315
|
-
1. Required
|
|
316
|
-
2. `PLATFORM_SMOKE_CRABBOX`
|
|
338
|
+
1. Required auth is present and optional target overrides resolve against config defaults.
|
|
339
|
+
2. Homebrew `crabbox` is available on PATH, or `PLATFORM_SMOKE_CRABBOX` points at an executable override.
|
|
317
340
|
3. Crabbox build matches the configured baseline.
|
|
318
341
|
4. Crabbox provider registry includes `local-container`, `ssh`, and `parallels`.
|
|
319
|
-
5. `crabbox doctor --provider local-container --json` passes.
|
|
342
|
+
5. `crabbox doctor --provider local-container --target linux --json` passes.
|
|
320
343
|
6. Docker runtime is active.
|
|
321
|
-
7. macOS SSH
|
|
344
|
+
7. Crabbox macOS static SSH doctor with `--doctor-probe-ssh` passes, and the localhost SSH probe sees Node, npm, Git, rsync, and tar.
|
|
322
345
|
8. `prlctl` exists.
|
|
323
346
|
9. Windows source VM exists.
|
|
324
347
|
10. Windows source snapshot exists.
|
|
@@ -357,7 +380,7 @@ Per target, `platform-build` must:
|
|
|
357
380
|
|
|
358
381
|
1. Record `node --version` and assert the target Node major is at least `nodeValidationMajor`.
|
|
359
382
|
2. Run `npm ci` in `extensionSourceRoot`.
|
|
360
|
-
3. Run `npm run check:platform-smoke` on the target so smoke harness syntax and invariant tests fail before live Cursor calls
|
|
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.
|
|
361
384
|
4. Run `npm test` on the target with the same target-local release-tag guard bypass.
|
|
362
385
|
5. Run `npm run typecheck`.
|
|
363
386
|
6. Run `npm pack`.
|
|
@@ -378,7 +401,7 @@ Purpose:
|
|
|
378
401
|
- fail before spending Cursor tokens;
|
|
379
402
|
- produce the packed extension used by later suites.
|
|
380
403
|
|
|
381
|
-
The host `smoke:platform:all` entrypoint enforces doctor first
|
|
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.
|
|
382
405
|
|
|
383
406
|
### `cursor-native-visual-matrix`
|
|
384
407
|
|
|
@@ -595,7 +618,7 @@ cursor-abort-cleanup: 1
|
|
|
595
618
|
|
|
596
619
|
Maximum per target: `3` Cursor invocations.
|
|
597
620
|
|
|
598
|
-
Maximum full gate: `
|
|
621
|
+
Maximum full gate: `9` Cursor invocations.
|
|
599
622
|
|
|
600
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`.
|
|
601
624
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cursor-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
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",
|
|
@@ -13,9 +13,14 @@ export default {
|
|
|
13
13
|
"cursor-abort-cleanup",
|
|
14
14
|
],
|
|
15
15
|
requiredCrabbox: {
|
|
16
|
-
install: "
|
|
17
|
-
|
|
16
|
+
install: "Homebrew package or PLATFORM_SMOKE_CRABBOX override",
|
|
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
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
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",
|
|
@@ -45,17 +45,32 @@ function shell(cmd, opts = {}) {
|
|
|
45
45
|
catch { return null; }
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function commandPath(command) {
|
|
49
|
+
return shell(`command -v ${command}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
function parseLeaseId(output) {
|
|
49
53
|
return output.match(/\bleased\s+(\S+)/)?.[1]
|
|
50
54
|
?? output.match(/\blease=(\S+)/)?.[1]
|
|
51
55
|
?? null;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
function
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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;
|
|
59
74
|
return [
|
|
60
75
|
"--provider", "parallels",
|
|
61
76
|
"--target", "windows",
|
|
@@ -87,9 +102,9 @@ function crabbox(cbox, args, timeout = 300_000) {
|
|
|
87
102
|
}
|
|
88
103
|
}
|
|
89
104
|
|
|
90
|
-
function disposableWindowsSshProbe(cbox) {
|
|
105
|
+
function disposableWindowsSshProbe(cbox, config = {}) {
|
|
91
106
|
const slug = "pi-cursor-sdk-doctor-windows";
|
|
92
|
-
const baseArgs = windowsCrabboxBaseArgs();
|
|
107
|
+
const baseArgs = windowsCrabboxBaseArgs(config);
|
|
93
108
|
const warm = crabbox(cbox, ["warmup", ...baseArgs, "--slug", slug, "--keep", "--reclaim"], 300_000);
|
|
94
109
|
const leaseId = parseLeaseId(warm.stdout) ?? parseLeaseId(warm.stderr) ?? slug;
|
|
95
110
|
try {
|
|
@@ -126,37 +141,50 @@ function runChecks(config) {
|
|
|
126
141
|
// ── Phase 1: environment variables ──
|
|
127
142
|
console.log("\n── Environment variables ──");
|
|
128
143
|
const requiredVars = [
|
|
129
|
-
"PLATFORM_SMOKE_CRABBOX",
|
|
130
144
|
"CURSOR_API_KEY",
|
|
131
|
-
"PLATFORM_SMOKE_WINDOWS_VM",
|
|
132
|
-
"PLATFORM_SMOKE_WINDOWS_SNAPSHOT",
|
|
133
|
-
"PLATFORM_SMOKE_WINDOWS_USER",
|
|
134
|
-
"PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT",
|
|
135
145
|
];
|
|
136
146
|
const optionalVars = [
|
|
147
|
+
"PLATFORM_SMOKE_CRABBOX",
|
|
137
148
|
"PLATFORM_SMOKE_MAC_HOST",
|
|
138
149
|
"PLATFORM_SMOKE_MAC_USER",
|
|
139
150
|
"PLATFORM_SMOKE_MAC_WORK_ROOT",
|
|
140
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",
|
|
141
156
|
];
|
|
142
157
|
for (const name of requiredVars) {
|
|
143
158
|
const v = env(name);
|
|
144
159
|
v ? ok(`${name} = ${name === "CURSOR_API_KEY" ? "(present, redacted)" : (v.length > 50 ? v.slice(0, 50) + "..." : v)}`)
|
|
145
160
|
: fail(`${name} missing`);
|
|
146
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
|
+
};
|
|
147
169
|
for (const name of optionalVars) {
|
|
148
170
|
const v = env(name);
|
|
149
|
-
|
|
171
|
+
const fallback = optionalDefaults[name] ? `(default: ${optionalDefaults[name]})` : "(default)";
|
|
172
|
+
ok(`${name} = ${v || fallback}`);
|
|
150
173
|
}
|
|
151
174
|
|
|
152
175
|
// ── Phase 2: Crabbox binary ──
|
|
153
176
|
console.log("\n── Crabbox binary ──");
|
|
154
|
-
const cbox = env("PLATFORM_SMOKE_CRABBOX");
|
|
155
|
-
|
|
156
|
-
|
|
177
|
+
const cbox = env("PLATFORM_SMOKE_CRABBOX") || "crabbox";
|
|
178
|
+
const cboxPath = env("PLATFORM_SMOKE_CRABBOX") || commandPath("crabbox");
|
|
179
|
+
if (!cboxPath) {
|
|
180
|
+
fail(`crabbox not found on PATH; install with ${config.requiredCrabbox?.install ?? "Homebrew"} or set PLATFORM_SMOKE_CRABBOX`);
|
|
157
181
|
} else {
|
|
158
|
-
|
|
159
|
-
|
|
182
|
+
if (env("PLATFORM_SMOKE_CRABBOX")) {
|
|
183
|
+
try { accessSync(cboxPath, constants.X_OK); ok(`binary: ${cboxPath} (env override)`); }
|
|
184
|
+
catch { fail(`${cboxPath} not executable`); }
|
|
185
|
+
} else {
|
|
186
|
+
ok(`binary: ${cboxPath} (PATH)`);
|
|
187
|
+
}
|
|
160
188
|
const ver = silent(cbox, ["--version"]);
|
|
161
189
|
const actualVersion = ver?.split("\n")[0]?.trim();
|
|
162
190
|
if (actualVersion) ok(`version: ${actualVersion}`);
|
|
@@ -174,9 +202,9 @@ function runChecks(config) {
|
|
|
174
202
|
}
|
|
175
203
|
const requiredCommit = config.requiredCrabbox?.commit;
|
|
176
204
|
if (!requiredVersion && !minimumVersion && requiredCommit) {
|
|
177
|
-
const gitRoot = findGitRoot(dirname(
|
|
205
|
+
const gitRoot = findGitRoot(dirname(cboxPath));
|
|
178
206
|
const actualCommit = gitRoot ? silent("git", ["-C", gitRoot, "rev-parse", "HEAD"]) : null;
|
|
179
|
-
if (!actualCommit) fail(`could not verify Crabbox source commit for ${
|
|
207
|
+
if (!actualCommit) fail(`could not verify Crabbox source commit for ${cboxPath}`);
|
|
180
208
|
else if (actualCommit !== requiredCommit) fail(`Crabbox commit mismatch: expected ${requiredCommit}, got ${actualCommit}`);
|
|
181
209
|
else ok(`commit: ${actualCommit}`);
|
|
182
210
|
}
|
|
@@ -184,7 +212,7 @@ function runChecks(config) {
|
|
|
184
212
|
|
|
185
213
|
// ── Phase 3: Crabbox providers ──
|
|
186
214
|
console.log("\n── Crabbox providers ──");
|
|
187
|
-
if (
|
|
215
|
+
if (cboxPath) {
|
|
188
216
|
const providerList = silent(cbox, ["providers"]);
|
|
189
217
|
if (providerList) {
|
|
190
218
|
for (const provider of ["ssh", "local-container", "parallels"]) {
|
|
@@ -196,7 +224,7 @@ function runChecks(config) {
|
|
|
196
224
|
fail("crabbox providers failed");
|
|
197
225
|
}
|
|
198
226
|
const ubuntuImage = env("PLATFORM_SMOKE_UBUNTU_IMAGE") || config?.ubuntuContainerImage || "cimg/node:24.16";
|
|
199
|
-
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"]);
|
|
200
228
|
if (lcDoc) {
|
|
201
229
|
try {
|
|
202
230
|
const d = JSON.parse(lcDoc);
|
|
@@ -214,7 +242,7 @@ function runChecks(config) {
|
|
|
214
242
|
"doctor", "--provider", "ssh", "--target", "macos",
|
|
215
243
|
"--static-host", sshHost, "--static-user", sshUser,
|
|
216
244
|
"--static-port", "22", "--static-work-root", sshRoot,
|
|
217
|
-
"--json",
|
|
245
|
+
"--doctor-probe-ssh", "--json",
|
|
218
246
|
]);
|
|
219
247
|
if (sshDoc) {
|
|
220
248
|
try {
|
|
@@ -256,7 +284,7 @@ function runChecks(config) {
|
|
|
256
284
|
fail("prlctl not found");
|
|
257
285
|
} else {
|
|
258
286
|
ok("prlctl found");
|
|
259
|
-
const vmName = env("PLATFORM_SMOKE_WINDOWS_VM") ||
|
|
287
|
+
const vmName = env("PLATFORM_SMOKE_WINDOWS_VM") || windowsParallelsDefaults(config).vm;
|
|
260
288
|
const list = shell("prlctl list -a --no-header 2>/dev/null");
|
|
261
289
|
if (list) {
|
|
262
290
|
const vms = list.split("\n").filter(Boolean);
|
|
@@ -270,7 +298,7 @@ function runChecks(config) {
|
|
|
270
298
|
fail(`VM "${vmName}" state: ${status} — source VM must be stopped for linked clones`);
|
|
271
299
|
}
|
|
272
300
|
|
|
273
|
-
const snapName = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") ||
|
|
301
|
+
const snapName = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || windowsParallelsDefaults(config).snapshot;
|
|
274
302
|
const snapsJson = shell(`prlctl snapshot-list "${vmName}" -j 2>/dev/null`);
|
|
275
303
|
let snapshotFound = false;
|
|
276
304
|
let snapshotPowerOff = false;
|
|
@@ -314,7 +342,7 @@ function runChecks(config) {
|
|
|
314
342
|
} else {
|
|
315
343
|
ok(`template "${vmName}" has no IP; verifying Windows SSH/tools through a disposable Crabbox clone`);
|
|
316
344
|
if (cbox && snapshotFound && snapshotPowerOff) {
|
|
317
|
-
const probe = disposableWindowsSshProbe(cbox);
|
|
345
|
+
const probe = disposableWindowsSshProbe(cbox, config);
|
|
318
346
|
probe.ok ? ok(`disposable Windows clone SSH/tool probe OK: ${probe.message}`) : fail(probe.message);
|
|
319
347
|
} else {
|
|
320
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('
|
|
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('
|
|
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'));
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { resolve, dirname } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { accessSync, constants
|
|
7
|
-
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { accessSync, constants } from "node:fs";
|
|
8
7
|
|
|
9
8
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
10
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -47,11 +46,11 @@ Environment:
|
|
|
47
46
|
PLATFORM_SMOKE_MAC_HOST macOS SSH host (default: localhost)
|
|
48
47
|
PLATFORM_SMOKE_MAC_USER macOS SSH user (default: \$USER)
|
|
49
48
|
PLATFORM_SMOKE_MAC_WORK_ROOT macOS work root
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT Windows native work root
|
|
49
|
+
PLATFORM_SMOKE_UBUNTU_IMAGE Ubuntu container image
|
|
50
|
+
PLATFORM_SMOKE_WINDOWS_VM Parallels source VM override (default from config)
|
|
51
|
+
PLATFORM_SMOKE_WINDOWS_SNAPSHOT Snapshot override (default from config)
|
|
52
|
+
PLATFORM_SMOKE_WINDOWS_USER Windows SSH user override (default: \$USER)
|
|
53
|
+
PLATFORM_SMOKE_WINDOWS_NATIVE_WORK_ROOT Windows native work root override (default from config)
|
|
55
54
|
`);
|
|
56
55
|
}
|
|
57
56
|
|
|
@@ -90,17 +89,17 @@ function parseArgs(argv) {
|
|
|
90
89
|
return args;
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
function validateSelections(targets, suites) {
|
|
93
|
+
const allowedTargets = new Set(config.requiredTargets ?? []);
|
|
94
|
+
const allowedSuites = new Set(config.requiredSuites ?? []);
|
|
95
|
+
const badTargets = targets.filter((target) => !allowedTargets.has(target));
|
|
96
|
+
const badSuites = suites.filter((suite) => !allowedSuites.has(suite));
|
|
97
|
+
if (badTargets.length > 0) {
|
|
98
|
+
throw new Error(`unknown target(s): ${badTargets.join(", ")}; allowed: ${[...allowedTargets].join(", ")}`);
|
|
99
|
+
}
|
|
100
|
+
if (badSuites.length > 0) {
|
|
101
|
+
throw new Error(`unknown suite(s): ${badSuites.join(", ")}; allowed: ${[...allowedSuites].join(", ")}`);
|
|
102
|
+
}
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
// ── commands ───────────────────────────────────────────────────────────────
|
|
@@ -158,7 +157,6 @@ async function main() {
|
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
if (args.command === "run") {
|
|
161
|
-
assertHostReleaseVersionGuard();
|
|
162
160
|
const targets = args.target
|
|
163
161
|
? args.target.split(",").map((s) => s.trim()).filter(Boolean)
|
|
164
162
|
: config.requiredTargets;
|
|
@@ -167,6 +165,13 @@ async function main() {
|
|
|
167
165
|
? [args.suite]
|
|
168
166
|
: config.requiredSuites;
|
|
169
167
|
|
|
168
|
+
try {
|
|
169
|
+
validateSelections(targets, suites);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(err.message);
|
|
172
|
+
process.exit(2);
|
|
173
|
+
}
|
|
174
|
+
|
|
170
175
|
const targetRuns = targets.map(async (targetName) => {
|
|
171
176
|
console.log(`\n=== Target: ${targetName} ===`);
|
|
172
177
|
const result = args.suite
|