pi-cursor-sdk 0.1.13 → 0.1.15

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.
@@ -0,0 +1,183 @@
1
+ # Cursor Native Tool Visual Audit Workflow
2
+
3
+ This workflow verifies Cursor SDK tool replay the way a human sees it in pi's interactive TUI, without stealing macOS focus.
4
+
5
+ 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.
6
+
7
+ ## When to use this
8
+
9
+ Use this workflow when changing or reviewing:
10
+
11
+ - Cursor native tool replay cards.
12
+ - Tool-call turn ordering.
13
+ - Tool-result error styling.
14
+ - Truncation, continuation hints, timeout labels, or path display.
15
+ - Any PR claiming native TUI parity.
16
+
17
+ Do not use this for ordinary unit-only logic changes.
18
+
19
+ ## Why this workflow exists
20
+
21
+ 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.
22
+
23
+ The preferred workflow is now offscreen:
24
+
25
+ 1. Spawn `pi` in a pseudo-terminal at a fixed size.
26
+ 2. Feed the prompt programmatically.
27
+ 3. Save raw ANSI output and plain text output.
28
+ 4. Render the terminal buffer through xterm.js in headless Playwright.
29
+ 5. Save a PNG screenshot.
30
+ 6. Inspect the session JSONL for exact persisted `toolCall` / `toolResult` data.
31
+
32
+ This gives human-like visual evidence without activating Terminal, iTerm, or a browser window.
33
+
34
+ ## Tool stack
35
+
36
+ Install the harness outside this repo so generated assets and temporary dependencies do not pollute commits:
37
+
38
+ ```bash
39
+ HARNESS=/tmp/pi-visual-harness
40
+ rm -rf "$HARNESS"
41
+ mkdir -p "$HARNESS"
42
+ cd "$HARNESS"
43
+ npm init -y
44
+ npm install node-pty @xterm/xterm playwright
45
+ npm rebuild node-pty
46
+ ```
47
+
48
+ `npm rebuild node-pty` is useful after Node upgrades; without it, `node-pty` may fail with `posix_spawnp failed`.
49
+
50
+ ## Runner contract
51
+
52
+ A runner script should:
53
+
54
+ - Spawn `pi -e <extension-dir> --model cursor/composer-2.5` with:
55
+ - `PI_CURSOR_NATIVE_TOOL_DISPLAY=1`
56
+ - `TERM=xterm-256color`
57
+ - fixed PTY size, for example `150x45`
58
+ - cwd set to the target audit repo.
59
+ - Wait for startup.
60
+ - Write the exact prompt and carriage return to the PTY.
61
+ - Wait a bounded amount of time.
62
+ - Save:
63
+ - `<label>.ansi` raw terminal bytes.
64
+ - `<label>.txt` stripped text for quick search.
65
+ - `<label>.png` rendered xterm screenshot.
66
+ - `<label>.jsonl.path` pointing to the latest pi session JSONL.
67
+ - Kill the PTY child after capture.
68
+ - Check for leftover commands when prompts can background work, especially shell timeout tests.
69
+
70
+ Example invocation shape:
71
+
72
+ ```bash
73
+ node /tmp/pi-visual-harness/run-pi-visual.mjs \
74
+ --label after-shell-nonzero \
75
+ --ext /path/to/pi-cursor-sdk \
76
+ --cwd /path/to/test-workspace \
77
+ --prompt "Run \`printf 'cursor-shell-stderr\\n' >&2; exit 7\` using only the shell/terminal tool. Do not use read, grep, glob, find, ls, edit, or write. Print the command result exactly, then stop." \
78
+ --wait-ms 30000 \
79
+ --out-dir /tmp/pi-visual-harness/review-current
80
+ ```
81
+
82
+ Keep the runner in `/tmp` unless the project explicitly decides to check in a maintained audit harness.
83
+
84
+ ## Before/after comparison
85
+
86
+ Use a clean worktree for the baseline and the active worktree for the candidate change:
87
+
88
+ ```bash
89
+ BASE=/tmp/pi-cursor-visual-review
90
+ BEFORE_WT=$BASE/before-main
91
+ AFTER_WT=/path/to/pi-cursor-sdk
92
+ TARGET=/path/to/test-workspace
93
+
94
+ rm -rf "$BASE"
95
+ git fetch origin main
96
+ BASE_COMMIT=$(git merge-base origin/main HEAD)
97
+ git worktree add --detach "$BEFORE_WT" "$BASE_COMMIT"
98
+
99
+ # Optional speedup when the before worktree has no install of its own.
100
+ ln -s "$AFTER_WT/node_modules" "$BEFORE_WT/node_modules"
101
+ ```
102
+
103
+ Then run the same prompt against both extension dirs:
104
+
105
+ ```bash
106
+ node /tmp/pi-visual-harness/run-pi-visual.mjs \
107
+ --label before-glob-single \
108
+ --ext "$BEFORE_WT" \
109
+ --cwd "$TARGET" \
110
+ --prompt "Find files matching \`src/tools/reindex.ts\` using only the glob/file-search tool. Do not use shell, bash, grep, read, or ls. Print the matched files exactly as found, then stop." \
111
+ --wait-ms 16000 \
112
+ --out-dir /tmp/pi-visual-harness/review-current
113
+
114
+ node /tmp/pi-visual-harness/run-pi-visual.mjs \
115
+ --label after-glob-single \
116
+ --ext "$AFTER_WT" \
117
+ --cwd "$TARGET" \
118
+ --prompt "Find files matching \`src/tools/reindex.ts\` using only the glob/file-search tool. Do not use shell, bash, grep, read, or ls. Print the matched files exactly as found, then stop." \
119
+ --wait-ms 16000 \
120
+ --out-dir /tmp/pi-visual-harness/review-current
121
+ ```
122
+
123
+ For review, create a simple HTML/PNG gallery that places `before-*.png` and `after-*.png` side by side. Keep the generated gallery in `/tmp` unless explicitly asked to commit visual artifacts.
124
+
125
+ ## JSONL inspection
126
+
127
+ For each visual claim, inspect the JSONL path written by the runner. Confirm at least:
128
+
129
+ - `toolCall.name` is the expected pi-facing replay tool name.
130
+ - `toolCall.arguments` show the expected user-facing args.
131
+ - `toolResult.toolName` matches the call.
132
+ - `toolResult.content[0].text` contains the recorded body expected in the card.
133
+ - `toolResult.isError` matches the visual card state.
134
+
135
+ For local pi MCP bridge claims, also confirm:
136
+
137
+ - Bridged calls appear as the real pi tool name (for example `sem_reindex`), not the MCP bridge name (for example `pi__sem_reindex`; or `read`/`pi__read` when overlapping built-ins are explicitly exposed).
138
+ - The JSONL has no second Cursor MCP replay card for the same bridged call.
139
+ - Non-bridge Cursor MCP activity, if present, still renders as neutral Cursor activity instead of being suppressed.
140
+
141
+ Small helper pattern:
142
+
143
+ ```bash
144
+ python3 - <<'PY'
145
+ import json, pathlib
146
+ path = pathlib.Path('/tmp/pi-visual-harness/review-current/after-shell-nonzero.jsonl.path').read_text().strip()
147
+ for line in pathlib.Path(path).read_text().splitlines():
148
+ obj = json.loads(line)
149
+ msg = obj.get('message', {})
150
+ if msg.get('role') == 'assistant':
151
+ for part in msg.get('content', []):
152
+ if part.get('type') == 'toolCall':
153
+ print('CALL', part.get('name'), part.get('arguments'))
154
+ if msg.get('role') == 'toolResult':
155
+ text = msg.get('content', [{}])[0].get('text', '')
156
+ print('RESULT', msg.get('toolName'), 'isError=', msg.get('isError'), repr(text[:160]))
157
+ PY
158
+ ```
159
+
160
+ ## Safety rules
161
+
162
+ - Prefer the offscreen PTY renderer. Do not use `osascript`, visible Terminal windows, or `screencapture` unless a user explicitly asks for a real desktop screenshot.
163
+ - Keep generated screenshots, HTML galleries, ANSI logs, and temporary harness dependencies out of the repo by default.
164
+ - Use short, deterministic prompts with bounded wait times.
165
+ - For timeout/background prompts, always check for leftovers:
166
+
167
+ ```bash
168
+ ps -axo pid,etime,command | rg "sleep 2|should-not-print|<audit-session-label>" || true
169
+ ```
170
+
171
+ - If the model uses a different tool than requested, record it as model/provider behavior unless JSONL shows replay lost or misrendered a completed Cursor tool event.
172
+ - Visual output can differ slightly from macOS Terminal fonts because xterm.js renders offscreen. Treat this workflow as evidence for card class, color state, labels, ordering, truncation, and content. Use a real terminal screenshot only for pixel-level terminal-specific bugs.
173
+
174
+ ## Required evidence before commit or merge
175
+
176
+ Before accepting a replay-card change, provide:
177
+
178
+ - Before and after PNG paths.
179
+ - The prompt used for each pair.
180
+ - JSONL paths for each run.
181
+ - A short statement of what changed visually.
182
+ - The relevant JSONL `toolCall` / `toolResult` facts.
183
+ - `npm test` and `npm run typecheck` results, unless the change is documentation-only.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cursor-sdk",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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",
@@ -26,6 +26,8 @@
26
26
  "scripts/refresh-cursor-model-snapshots.mjs",
27
27
  "README.md",
28
28
  "docs/cursor-model-ux-spec.md",
29
+ "docs/cursor-native-tool-replay.md",
30
+ "docs/cursor-native-tool-visual-audit.md",
29
31
  "LICENSE",
30
32
  "CHANGELOG.md"
31
33
  ],
@@ -40,7 +42,8 @@
40
42
  "refresh:cursor-snapshots": "node scripts/refresh-cursor-model-snapshots.mjs"
41
43
  },
42
44
  "dependencies": {
43
- "@cursor/sdk": "^1.0.13"
45
+ "@cursor/sdk": "^1.0.13",
46
+ "@modelcontextprotocol/sdk": "^1.29.0"
44
47
  },
45
48
  "peerDependencies": {
46
49
  "@earendil-works/pi-ai": "*",
@@ -1,14 +1,16 @@
1
- // Generated from Cursor SDK checkpoint tokenDetails.maxTokens on 2026-05-04.
1
+ // Generated from Cursor SDK checkpoint tokenDetails.maxTokens on 2026-05-18.
2
+ // Refresh with: npm run refresh:cursor-snapshots -- --write --context-windows ~/.pi/agent/cursor-sdk-context-windows.json
2
3
  // These are default/non-Max-mode SDK context windows for Cursor models that do not
3
4
  // expose a catalog `context` parameter. Do not replace them with Max Mode values
4
5
  // unless the Cursor SDK exposes an exact Max Mode model selection and the extension
5
6
  // uses that selection for matching pi model IDs.
6
7
  export const BUNDLED_CONTEXT_WINDOWS = {
8
+ "default": 200000,
7
9
  "claude-haiku-4-5": 200000,
8
10
  "claude-opus-4-5": 200000,
9
11
  "composer-1.5": 200000,
10
12
  "composer-2": 200000,
11
- default: 200000,
13
+ "composer-2.5": 200000,
12
14
  "gemini-2.5-flash": 200000,
13
15
  "gemini-3-flash": 200000,
14
16
  "gemini-3.1-pro": 200000,
@@ -22,6 +24,7 @@ export const BUNDLED_CONTEXT_WINDOWS = {
22
24
  "gpt-5.3-codex-spark": 128000,
23
25
  "gpt-5.4-mini": 272000,
24
26
  "gpt-5.4-nano": 272000,
27
+ "gpt-5.5@272k": 272000,
25
28
  "grok-4-20": 200000,
26
29
  "kimi-k2.5": 262000,
27
30
  } as const satisfies Record<string, number>;
package/src/context.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Context, Message, ToolCall } from "@earendil-works/pi-ai";
2
2
  import type { SDKImage } from "@cursor/sdk";
3
+ import { getCursorReplayPromptLabel } from "./cursor-tool-names.js";
3
4
 
4
5
  export interface CursorPrompt {
5
6
  text: string;
@@ -58,8 +59,26 @@ function formatContentBlocks(content: string | { type: string; text?: string; da
58
59
  }
59
60
 
60
61
  function formatToolCall(toolCall: ToolCall): string {
61
- const args = JSON.stringify(toolCall.arguments);
62
- return `Tool call (${toolCall.name}, call ${toolCall.id}): ${args}`;
62
+ const args = JSON.stringify(toolCall.arguments) ?? "";
63
+ return `Tool call (${getCursorReplayPromptLabel(toolCall.name)}, call ${toolCall.id}): ${args}`;
64
+ }
65
+
66
+ function sanitizeSystemPromptForCursor(systemPrompt: string): string {
67
+ let sanitized = systemPrompt;
68
+ sanitized = sanitized.replace(
69
+ /Available tools:\n[\s\S]*?\n\nIn addition to the tools above, you may have access to other custom tools depending on the project\.\n\n/g,
70
+ "Pi tool catalog omitted: Cursor can call only Cursor SDK tools exposed in this run.\n\n",
71
+ );
72
+ sanitized = sanitized.replace(
73
+ /Guidelines:\n[\s\S]*?\n\nPi documentation /g,
74
+ "Guidelines:\n- Be concise in your responses.\n- Show file paths clearly when working with files.\n\nPi documentation ",
75
+ );
76
+ sanitized = sanitized.replace(
77
+ /\n\nThe following skills provide specialized instructions for specific tasks\.[\s\S]*?<\/available_skills>/g,
78
+ "",
79
+ );
80
+ sanitized = sanitized.replace(/\n+Semantic code intelligence priority:[\s\S]*$/g, "");
81
+ return sanitized.trim();
63
82
  }
64
83
 
65
84
  function formatMessage(msg: Message): string | undefined {
@@ -84,7 +103,7 @@ function formatMessage(msg: Message): string | undefined {
84
103
  case "toolResult": {
85
104
  const text = formatContentBlocks(msg.content);
86
105
  const label = msg.isError ? "Tool error" : "Tool result";
87
- return `${label} (${msg.toolName}, call ${msg.toolCallId}): ${text}`;
106
+ return `${label} (${getCursorReplayPromptLabel(msg.toolName)}, call ${msg.toolCallId}): ${text}`;
88
107
  }
89
108
  }
90
109
  }
@@ -152,15 +171,17 @@ export function buildCursorPrompt(context: Context, options: CursorPromptOptions
152
171
  const sectionsBeforeMessages: string[] = [
153
172
  [
154
173
  "Cursor SDK tool boundary:",
155
- "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.",
156
- "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.",
157
- "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.",
158
- "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.",
174
+ "You can call only tools actually exposed by Cursor SDK in this run. Pi tool names, replay tool names, and transcript tool names are context only, not callable capabilities.",
175
+ "If asked to list or exercise available tools, list and exercise Cursor SDK tools only; do not claim access to pi-side tools from the system prompt unless Cursor exposes an equivalent tool that runs.",
176
+ "Use pi__cursor_ask_question for material choices if exposed.",
177
+ "Web: use Cursor web/search/browser/MCP or say web search is not configured; do not claim WebSearch/WebFetch unless Cursor executes them.",
178
+ "Replay: pi may display recorded Cursor tool activity as pi-style cards, but replay is display-only and not a capability to invoke.",
179
+ "Images: only latest user images are sent; ask to reattach or describe prior images.",
159
180
  ].join("\n"),
160
181
  ];
161
182
 
162
183
  if (context.systemPrompt) {
163
- sectionsBeforeMessages.push(`System instructions from pi:\n${context.systemPrompt}`);
184
+ sectionsBeforeMessages.push(`System instructions from pi:\n${sanitizeSystemPromptForCursor(context.systemPrompt)}`);
164
185
  }
165
186
 
166
187
  const messageSections = context.messages
@@ -171,8 +192,8 @@ export function buildCursorPrompt(context: Context, options: CursorPromptOptions
171
192
  .filter((section): section is { index: number; text: string } => section !== undefined);
172
193
  const sectionsAfterMessages = [
173
194
  [
174
- "Answer the latest user request above using your capabilities. Do not assume access to pi tools.",
175
- "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.",
195
+ "Answer the latest user request above using Cursor SDK capabilities only. Do not list, promise, or call pi-only tools from the system prompt as if they were available.",
196
+ "If web research is requested, do not claim it unless a Cursor web/search/browser/MCP tool ran.",
176
197
  ].join("\n"),
177
198
  ];
178
199
  const images = extractLatestImages(context.messages);
@@ -188,6 +209,8 @@ export function buildCursorPrompt(context: Context, options: CursorPromptOptions
188
209
  getLatestUserMessageIndex(context.messages),
189
210
  budgetOptions,
190
211
  );
212
+ const text = parts.join(SECTION_SEPARATOR);
213
+
191
214
 
192
- return { text: parts.join(SECTION_SEPARATOR), images };
215
+ return { text, images };
193
216
  }