pi-cursor-sdk 0.1.16 → 0.1.17

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/README.md +1 -1
  3. package/docs/cursor-live-smoke-checklist.md +35 -39
  4. package/docs/cursor-model-ux-spec.md +3 -2
  5. package/package.json +11 -5
  6. package/scripts/steering-rpc-smoke.mjs +238 -0
  7. package/scripts/tmux-live-smoke.sh +418 -0
  8. package/scripts/validate-smoke-jsonl.mjs +152 -0
  9. package/src/cursor-edit-diff.ts +11 -0
  10. package/src/cursor-env-boolean.ts +22 -0
  11. package/src/cursor-live-run-coordinator.ts +483 -0
  12. package/src/cursor-native-tool-display-registration.ts +93 -0
  13. package/src/cursor-native-tool-display-replay.ts +465 -0
  14. package/src/cursor-native-tool-display-state.ts +78 -0
  15. package/src/cursor-native-tool-display-tools.ts +102 -0
  16. package/src/cursor-native-tool-display.ts +10 -648
  17. package/src/cursor-partial-content-emitter.ts +121 -0
  18. package/src/cursor-pi-tool-bridge-abort.ts +133 -0
  19. package/src/cursor-pi-tool-bridge-diagnostics.ts +179 -0
  20. package/src/cursor-pi-tool-bridge-mcp.ts +118 -0
  21. package/src/cursor-pi-tool-bridge-run.ts +384 -0
  22. package/src/cursor-pi-tool-bridge-server.ts +182 -0
  23. package/src/cursor-pi-tool-bridge-snapshot.ts +88 -0
  24. package/src/cursor-pi-tool-bridge-types.ts +80 -0
  25. package/src/cursor-pi-tool-bridge.ts +42 -1104
  26. package/src/cursor-provider-live-run-drain.ts +379 -0
  27. package/src/cursor-provider-turn-coordinator.ts +456 -0
  28. package/src/cursor-provider.ts +72 -1103
  29. package/src/cursor-record-utils.ts +26 -0
  30. package/src/cursor-sdk-output-filter.ts +100 -0
  31. package/src/cursor-sensitive-text.ts +37 -0
  32. package/src/cursor-tool-transcript.ts +28 -1229
  33. package/src/cursor-transcript-tool-formatters.ts +641 -0
  34. package/src/cursor-transcript-tool-specs.ts +441 -0
  35. package/src/cursor-transcript-utils.ts +276 -0
@@ -0,0 +1,441 @@
1
+ import { CURSOR_REPLAY_ACTIVITY_TOOL_NAME, getCursorReplayDisplayLabel, type CursorReplayLegacyToolName } from "./cursor-tool-names.js";
2
+ import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
3
+ import {
4
+ asRecord,
5
+ firstNonEmptyLine,
6
+ formatDisplayPath,
7
+ formatDiffString,
8
+ formatError,
9
+ getNumber,
10
+ getString,
11
+ limitText,
12
+ stringifyUnknown,
13
+ truncateArg,
14
+ type CursorPiToolDisplay,
15
+ type NormalizedResult,
16
+ type PiToolDisplayResult,
17
+ type TranscriptOptions,
18
+ } from "./cursor-transcript-utils.js";
19
+ import {
20
+ buildCursorEditActivityDisplayArgs,
21
+ buildFindDisplayArgs,
22
+ buildGrepDisplayArgs,
23
+ buildNativeEditDisplayArgs,
24
+ buildReadDisplayArgs,
25
+ buildShellDisplayArgs,
26
+ buildWriteDisplayArgs,
27
+ collectTaskText,
28
+ formatDelete,
29
+ formatEdit,
30
+ formatFallback,
31
+ formatGenerateImage,
32
+ formatGlob,
33
+ formatGrep,
34
+ formatLs,
35
+ formatMcp,
36
+ formatPlan,
37
+ formatRead,
38
+ formatReadLints,
39
+ formatShell,
40
+ formatTask,
41
+ formatTodos,
42
+ formatWrite,
43
+ formatNativeReadDisplayContent,
44
+ getCursorWriteArgContent,
45
+ getGenerateImageDisplayPath,
46
+ getGenerateImagePath,
47
+ getGlobBody,
48
+ getGrepBody,
49
+ getLsBody,
50
+ getReadLintDiagnostics,
51
+ getReadLintPaths,
52
+ getShellOutput,
53
+ getTaskDescription,
54
+ getTodoItems,
55
+ getTodoTotalCount,
56
+ inferImageMimeType,
57
+ summarizePlan,
58
+ summarizeTask,
59
+ summarizeTodos,
60
+ } from "./cursor-transcript-tool-formatters.js";
61
+
62
+ export interface ToolDisplayContext {
63
+ rawName: string;
64
+ name: string;
65
+ args: Record<string, unknown>;
66
+ result: NormalizedResult;
67
+ options: TranscriptOptions;
68
+ }
69
+
70
+ interface ActivityReplaySpec {
71
+ labelKey: CursorReplayLegacyToolName;
72
+ buildActivityArgs: (context: ToolDisplayContext) => Record<string, unknown>;
73
+ buildActivitySummary: (context: ToolDisplayContext) => string | undefined;
74
+ buildDetails: (context: ToolDisplayContext, contentText: string) => Record<string, unknown>;
75
+ }
76
+
77
+ interface ToolDisplaySpec {
78
+ formatTranscript: (context: ToolDisplayContext) => string;
79
+ buildPiToolDisplay: (context: ToolDisplayContext) => CursorPiToolDisplay;
80
+ activityReplay?: ActivityReplaySpec;
81
+ }
82
+
83
+ function textToolResult(text: string, details?: unknown): PiToolDisplayResult {
84
+ return { content: [{ type: "text", text }], details };
85
+ }
86
+
87
+ function buildCursorActivityDisplayArgs(
88
+ args: Record<string, unknown>,
89
+ activityTitle: string,
90
+ activitySummary: string | undefined,
91
+ ): Record<string, unknown> {
92
+ const trimmedSummary = activitySummary?.trim();
93
+ return {
94
+ ...args,
95
+ activityTitle,
96
+ ...(trimmedSummary ? { activitySummary: trimmedSummary } : {}),
97
+ };
98
+ }
99
+
100
+ function buildReplaySummaryDisplay(
101
+ toolName: string,
102
+ args: Record<string, unknown>,
103
+ result: NormalizedResult,
104
+ contentText: string,
105
+ details: Record<string, unknown>,
106
+ ): CursorPiToolDisplay {
107
+ const isError = result.status === "error";
108
+ const summary = isError ? formatError(result.error) : firstNonEmptyLine(contentText);
109
+ return {
110
+ toolName,
111
+ args,
112
+ result: textToolResult(contentText, {
113
+ ...details,
114
+ summary: details.summary ?? summary,
115
+ expandedText: details.expandedText ?? contentText,
116
+ }),
117
+ isError,
118
+ };
119
+ }
120
+
121
+ function buildActivityReplayDisplay(cursorToolName: string, spec: ToolDisplaySpec, context: ToolDisplayContext): CursorPiToolDisplay {
122
+ const activity = spec.activityReplay;
123
+ if (!activity) throw new Error(`Missing activity replay spec for ${cursorToolName}`);
124
+ const activityTitle = getCursorReplayDisplayLabel(activity.labelKey);
125
+ const activitySummary = activity.buildActivitySummary(context);
126
+ const activityArgs = buildCursorActivityDisplayArgs(
127
+ activity.buildActivityArgs(context),
128
+ activityTitle,
129
+ activitySummary,
130
+ );
131
+ const contentText = spec.formatTranscript(context).trimEnd();
132
+ const details = activity.buildDetails(context, contentText);
133
+ return buildReplaySummaryDisplay(
134
+ CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
135
+ activityArgs,
136
+ context.result,
137
+ contentText,
138
+ {
139
+ cursorToolName,
140
+ title: activityTitle,
141
+ summary: context.result.status === "error" ? undefined : details.summary ?? activitySummary,
142
+ ...details,
143
+ },
144
+ );
145
+ }
146
+
147
+ function buildGenericPiToolDisplay(context: ToolDisplayContext): CursorPiToolDisplay {
148
+ const { name, args, result, options } = context;
149
+ const isError = result.status === "error";
150
+ return {
151
+ toolName: name,
152
+ args,
153
+ result: textToolResult(isError ? formatError(result.error) : limitText(stringifyUnknown(result.value), options)),
154
+ isError,
155
+ };
156
+ }
157
+
158
+ function buildEditPiToolDisplay(context: ToolDisplayContext): CursorPiToolDisplay {
159
+ const { rawName, args, result, options } = context;
160
+ const value = asRecord(result.value);
161
+ const rawDiff = resolveCursorEditDiff(value);
162
+ const normalizedDiff = formatDiffString(rawDiff, options);
163
+ const nativeEditArgs = buildNativeEditDisplayArgs(rawName, args, options);
164
+ const baseActivityArgs = buildCursorEditActivityDisplayArgs(args, options);
165
+ const displayPath = typeof baseActivityArgs.path === "string" ? baseActivityArgs.path : undefined;
166
+ const activityTitle = getCursorReplayDisplayLabel("cursor_edit");
167
+ const activityArgs = buildCursorActivityDisplayArgs(baseActivityArgs, activityTitle, displayPath);
168
+ const contentText = formatEdit(activityArgs, result, options);
169
+ const details = {
170
+ cursorToolName: "edit",
171
+ path: displayPath,
172
+ linesAdded: getNumber(value, "linesAdded"),
173
+ linesRemoved: getNumber(value, "linesRemoved"),
174
+ diffString: normalizedDiff,
175
+ diff: normalizedDiff,
176
+ firstChangedLine: getNumber(value, "firstChangedLine"),
177
+ };
178
+ if (nativeEditArgs) {
179
+ return {
180
+ toolName: "edit",
181
+ args: nativeEditArgs,
182
+ result: textToolResult(contentText, details),
183
+ isError: result.status === "error",
184
+ };
185
+ }
186
+ return buildReplaySummaryDisplay(
187
+ CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
188
+ activityArgs,
189
+ result,
190
+ contentText.trimEnd(),
191
+ {
192
+ ...details,
193
+ title: activityTitle,
194
+ summary: result.status === "error" ? undefined : displayPath ?? "replayed",
195
+ },
196
+ );
197
+ }
198
+
199
+ function buildWritePiToolDisplay(context: ToolDisplayContext): CursorPiToolDisplay {
200
+ const { args, result, options } = context;
201
+ const value = asRecord(result.value);
202
+ const content = getCursorWriteArgContent(args);
203
+ const displayArgs = buildWriteDisplayArgs(args, options);
204
+ const displayPath = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
205
+ const contentText = formatWrite(args, result, options).trimEnd();
206
+ const details = {
207
+ cursorToolName: "write",
208
+ path: displayPath,
209
+ linesCreated: getNumber(value, "linesCreated"),
210
+ fileSize: getNumber(value, "fileSize"),
211
+ fileContentAfterWrite: getString(value, "fileContentAfterWrite"),
212
+ expandedText: contentText,
213
+ };
214
+ if (content === undefined) {
215
+ const activityTitle = getCursorReplayDisplayLabel("cursor_write");
216
+ return buildReplaySummaryDisplay(
217
+ CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
218
+ buildCursorActivityDisplayArgs(displayArgs, activityTitle, displayPath ?? "file"),
219
+ result,
220
+ contentText,
221
+ {
222
+ ...details,
223
+ title: activityTitle,
224
+ summary: result.status === "error" ? undefined : displayPath ?? "wrote file",
225
+ },
226
+ );
227
+ }
228
+ return {
229
+ toolName: "write",
230
+ args: displayArgs,
231
+ result: textToolResult(contentText, details),
232
+ isError: result.status === "error",
233
+ };
234
+ }
235
+
236
+ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
237
+ read: {
238
+ formatTranscript: ({ args, result, options }) => formatRead(args, result, options),
239
+ buildPiToolDisplay: ({ args, result, options }) => {
240
+ const isError = result.status === "error";
241
+ return {
242
+ toolName: "read",
243
+ args: buildReadDisplayArgs(args, options),
244
+ result: textToolResult(isError ? formatError(result.error) : formatNativeReadDisplayContent(args, result, options)),
245
+ isError,
246
+ };
247
+ },
248
+ },
249
+ shell: {
250
+ formatTranscript: ({ args, result, options }) => formatShell(args, result, options),
251
+ buildPiToolDisplay: ({ args, result, options }) => {
252
+ const shellOutput = getShellOutput(result, args);
253
+ const isError = result.status === "error" || shellOutput.timedOut || (shellOutput.exitCode !== undefined && shellOutput.exitCode !== 0);
254
+ return {
255
+ toolName: "bash",
256
+ args: buildShellDisplayArgs(args),
257
+ result: textToolResult(result.status === "error" ? formatError(result.error) : limitText(shellOutput.text, options)),
258
+ isError,
259
+ };
260
+ },
261
+ },
262
+ grep: {
263
+ formatTranscript: ({ args, result, options }) => formatGrep(args, result, options),
264
+ buildPiToolDisplay: ({ args, result, options }) => {
265
+ const isError = result.status === "error";
266
+ return {
267
+ toolName: "grep",
268
+ args: buildGrepDisplayArgs(args, options),
269
+ result: textToolResult(isError ? formatError(result.error) : getGrepBody(result, options)),
270
+ isError,
271
+ };
272
+ },
273
+ },
274
+ glob: {
275
+ formatTranscript: ({ args, result, options }) => formatGlob(args, result, options),
276
+ buildPiToolDisplay: ({ args, result, options }) => {
277
+ const isError = result.status === "error";
278
+ return {
279
+ toolName: "find",
280
+ args: buildFindDisplayArgs(args, options),
281
+ result: textToolResult(isError ? formatError(result.error) : getGlobBody(result, options)),
282
+ isError,
283
+ };
284
+ },
285
+ },
286
+ ls: {
287
+ formatTranscript: ({ args, result, options }) => formatLs(args, result, options),
288
+ buildPiToolDisplay: ({ args, result, options }) => ({
289
+ toolName: "ls",
290
+ args,
291
+ result: textToolResult(result.status === "error" ? formatError(result.error) : getLsBody(result, options).trim()),
292
+ isError: result.status === "error",
293
+ }),
294
+ },
295
+ edit: {
296
+ formatTranscript: ({ args, result, options }) => formatEdit(args, result, options),
297
+ buildPiToolDisplay: buildEditPiToolDisplay,
298
+ },
299
+ write: {
300
+ formatTranscript: ({ args, result, options }) => formatWrite(args, result, options),
301
+ buildPiToolDisplay: buildWritePiToolDisplay,
302
+ },
303
+ delete: {
304
+ formatTranscript: ({ args, result, options }) => formatDelete(args, result, options),
305
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("delete", TOOL_DISPLAY_SPECS.delete, context),
306
+ activityReplay: {
307
+ labelKey: "cursor_delete",
308
+ buildActivityArgs: ({ args, options }) => {
309
+ const displayPath = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
310
+ return displayPath ? { path: displayPath } : {};
311
+ },
312
+ buildActivitySummary: ({ args, options }) => {
313
+ const displayPath = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
314
+ return displayPath ?? "file";
315
+ },
316
+ buildDetails: ({ args, result, options }) => {
317
+ const displayPath = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
318
+ const value = asRecord(result.value);
319
+ return {
320
+ path: displayPath,
321
+ fileSize: getNumber(value, "fileSize"),
322
+ summary: result.status === "error" ? undefined : displayPath ? `deleted ${displayPath}` : "deleted file",
323
+ };
324
+ },
325
+ },
326
+ },
327
+ readLints: {
328
+ formatTranscript: ({ args, result, options }) => formatReadLints(args, result, options),
329
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("readLints", TOOL_DISPLAY_SPECS.readLints, context),
330
+ activityReplay: {
331
+ labelKey: "cursor_read_lints",
332
+ buildActivityArgs: ({ args, result, options }) => {
333
+ const paths = getReadLintPaths(args, result, options);
334
+ const diagnosticCount = getReadLintDiagnostics(result, options).length;
335
+ return { paths, diagnosticCount };
336
+ },
337
+ buildActivitySummary: ({ args, result, options }) => {
338
+ const paths = getReadLintPaths(args, result, options);
339
+ const diagnosticCount = getReadLintDiagnostics(result, options).length;
340
+ return `${diagnosticCount} diagnostic${diagnosticCount === 1 ? "" : "s"}${paths.length > 0 ? ` in ${paths.join(", ")}` : ""}`;
341
+ },
342
+ buildDetails: () => ({}),
343
+ },
344
+ },
345
+ updateTodos: {
346
+ formatTranscript: ({ args, result, options }) => formatTodos(args, result, options, "updateTodos"),
347
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("updateTodos", TOOL_DISPLAY_SPECS.updateTodos, context),
348
+ activityReplay: {
349
+ labelKey: "cursor_update_todos",
350
+ buildActivityArgs: ({ args, result }) => {
351
+ const todos = getTodoItems(args, result);
352
+ return { totalCount: getTodoTotalCount(args, result, todos) };
353
+ },
354
+ buildActivitySummary: ({ args, result }) => summarizeTodos(args, result),
355
+ buildDetails: () => ({}),
356
+ },
357
+ },
358
+ createPlan: {
359
+ formatTranscript: ({ args, result, options }) => formatPlan(args, result, options),
360
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("createPlan", TOOL_DISPLAY_SPECS.createPlan, context),
361
+ activityReplay: {
362
+ labelKey: "cursor_create_plan",
363
+ buildActivityArgs: ({ args, result }) => {
364
+ const todos = getTodoItems(args, result);
365
+ return { totalCount: getTodoTotalCount(args, result, todos) };
366
+ },
367
+ buildActivitySummary: ({ args, result }) => summarizePlan(args, result),
368
+ buildDetails: () => ({}),
369
+ },
370
+ },
371
+ task: {
372
+ formatTranscript: ({ args, result, options }) => formatTask(args, result, options),
373
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("task", TOOL_DISPLAY_SPECS.task, context),
374
+ activityReplay: {
375
+ labelKey: "cursor_task",
376
+ buildActivityArgs: ({ args, result }) => {
377
+ const description = getTaskDescription(args, result);
378
+ return { description: truncateArg(description) };
379
+ },
380
+ buildActivitySummary: ({ args, result }) => {
381
+ const description = getTaskDescription(args, result);
382
+ return summarizeTask(description, collectTaskText(result));
383
+ },
384
+ buildDetails: () => ({}),
385
+ },
386
+ },
387
+ generateImage: {
388
+ formatTranscript: ({ args, result, options }) => formatGenerateImage(args, result, options),
389
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("generateImage", TOOL_DISPLAY_SPECS.generateImage, context),
390
+ activityReplay: {
391
+ labelKey: "cursor_generate_image",
392
+ buildActivityArgs: ({ args }) => {
393
+ const prompt = getString(args, "prompt") ?? getString(args, "description") ?? "image";
394
+ return { prompt: truncateArg(prompt) };
395
+ },
396
+ buildActivitySummary: ({ args, result, options }) => {
397
+ const prompt = getString(args, "prompt") ?? getString(args, "description") ?? "image";
398
+ const imageDisplayPath = getGenerateImageDisplayPath(args, result, options);
399
+ return imageDisplayPath ?? truncateArg(prompt);
400
+ },
401
+ buildDetails: ({ args, result, options }, contentText) => {
402
+ const imagePath = getGenerateImagePath(args, result);
403
+ const imageDisplayPath = getGenerateImageDisplayPath(args, result, options);
404
+ return {
405
+ imagePath,
406
+ imageDisplayPath,
407
+ imageMimeType: inferImageMimeType(imagePath),
408
+ summary: result.status === "error" ? undefined : imageDisplayPath ? `saved ${imageDisplayPath}` : "image generated",
409
+ expandedText: contentText,
410
+ };
411
+ },
412
+ },
413
+ },
414
+ mcp: {
415
+ formatTranscript: ({ args, result, options }) => formatMcp(args, result, options),
416
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("mcp", TOOL_DISPLAY_SPECS.mcp, context),
417
+ activityReplay: {
418
+ labelKey: "cursor_mcp",
419
+ buildActivityArgs: ({ args }) => {
420
+ const toolName = getString(args, "toolName") ?? "mcp";
421
+ return { toolName: truncateArg(toolName) };
422
+ },
423
+ buildActivitySummary: ({ args }) => truncateArg(getString(args, "toolName") ?? "mcp"),
424
+ buildDetails: ({ result }, contentText) => ({
425
+ summary: result.status === "error" ? undefined : firstNonEmptyLine(contentText) ?? "MCP result captured",
426
+ }),
427
+ },
428
+ },
429
+ };
430
+
431
+ export function formatCursorToolTranscriptFromSpec(context: ToolDisplayContext): string {
432
+ const spec = TOOL_DISPLAY_SPECS[context.name];
433
+ if (spec) return spec.formatTranscript(context);
434
+ return formatFallback(context.name, context.args, context.result, context.options);
435
+ }
436
+
437
+ export function buildCursorPiToolDisplayFromSpec(context: ToolDisplayContext): CursorPiToolDisplay {
438
+ const spec = TOOL_DISPLAY_SPECS[context.name];
439
+ if (spec) return spec.buildPiToolDisplay(context);
440
+ return buildGenericPiToolDisplay(context);
441
+ }