pi-cursor-sdk 0.1.18 → 0.1.20

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 (49) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +59 -1
  3. package/docs/cursor-live-smoke-checklist.md +4 -1
  4. package/docs/cursor-model-ux-spec.md +7 -5
  5. package/docs/cursor-native-tool-replay.md +99 -3
  6. package/docs/cursor-testing-lessons.md +234 -5
  7. package/package.json +10 -2
  8. package/scripts/debug-provider-events.mjs +403 -0
  9. package/scripts/debug-sdk-events.mjs +413 -0
  10. package/scripts/lib/cursor-probe-utils.mjs +52 -0
  11. package/scripts/lib/cursor-sdk-output-filter.mjs +86 -0
  12. package/scripts/probe-mcp-coldstart.mjs +244 -0
  13. package/scripts/validate-smoke-jsonl.mjs +27 -3
  14. package/src/context.ts +45 -32
  15. package/src/cursor-agent-message-web-tools.ts +172 -0
  16. package/src/cursor-agents-context.ts +176 -0
  17. package/src/cursor-incomplete-tool-visibility.ts +124 -0
  18. package/src/cursor-live-run-coordinator.ts +18 -7
  19. package/src/cursor-mcp-timeout-override.ts +66 -11
  20. package/src/cursor-model.ts +12 -0
  21. package/src/cursor-native-tool-display-registration.ts +1 -4
  22. package/src/cursor-native-tool-display-replay.ts +65 -6
  23. package/src/cursor-native-tool-display-tools.ts +20 -0
  24. package/src/cursor-pi-tool-bridge-diagnostics.ts +11 -1
  25. package/src/cursor-pi-tool-bridge-run.ts +16 -1
  26. package/src/cursor-pi-tool-bridge-types.ts +3 -0
  27. package/src/cursor-provider-errors.ts +96 -0
  28. package/src/cursor-provider-live-run-drain.ts +181 -62
  29. package/src/cursor-provider-turn-coordinator.ts +220 -33
  30. package/src/cursor-provider.ts +302 -93
  31. package/src/cursor-question-tool.ts +1 -4
  32. package/src/cursor-sdk-abort-error-guard.ts +109 -0
  33. package/src/cursor-sdk-event-debug-constants.ts +40 -0
  34. package/src/cursor-sdk-event-debug-session.ts +163 -0
  35. package/src/cursor-sdk-event-debug.ts +602 -0
  36. package/src/cursor-sensitive-text.ts +27 -7
  37. package/src/cursor-session-agent.ts +279 -82
  38. package/src/cursor-session-send-policy.ts +43 -0
  39. package/src/cursor-setting-sources.ts +29 -0
  40. package/src/cursor-state.ts +1 -5
  41. package/src/cursor-tool-lifecycle.ts +85 -0
  42. package/src/cursor-tool-names.ts +39 -0
  43. package/src/cursor-tool-transcript.ts +4 -2
  44. package/src/cursor-tool-visibility.ts +63 -0
  45. package/src/cursor-transcript-tool-formatters.ts +228 -5
  46. package/src/cursor-transcript-tool-specs.ts +135 -24
  47. package/src/cursor-transcript-utils.ts +12 -0
  48. package/src/cursor-web-tool-activity.ts +84 -0
  49. package/src/index.ts +4 -1
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Maintainer-only Cursor SDK event capture probe.
4
+ * Captures timestamped run.stream(), onDelta, and onStep surfaces for one run.
5
+ */
6
+ import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
+ import { createRequire } from "node:module";
8
+ import { dirname, join, resolve } from "node:path";
9
+ import {
10
+ CURSOR_SETTING_SOURCES_ENV,
11
+ resolveCursorSettingSources,
12
+ scrubSensitiveText,
13
+ } from "./lib/cursor-probe-utils.mjs";
14
+ import { installCursorSdkOutputFilter, suppressCursorSdkOutput } from "./lib/cursor-sdk-output-filter.mjs";
15
+
16
+ const require = createRequire(import.meta.url);
17
+ const packageJson = require("../package.json");
18
+
19
+ const ARTIFACTS = {
20
+ metadata: "metadata.json",
21
+ streamEvents: "stream-events.jsonl",
22
+ onDelta: "on-delta.jsonl",
23
+ onStep: "on-step.jsonl",
24
+ waitResult: "wait-result.json",
25
+ conversation: "conversation.json",
26
+ summary: "summary.json",
27
+ };
28
+
29
+ const DEFAULT_MODEL = "composer-2.5";
30
+ const RAW_ARTIFACT_WARNING =
31
+ "Raw artifact files may contain local paths, project text, tool args/results, or secrets from the workspace. Do not commit or share them.";
32
+
33
+ function readSdkVersion() {
34
+ try {
35
+ const sdkEntry = require.resolve("@cursor/sdk");
36
+ const sdkPackagePath = join(dirname(sdkEntry), "../../package.json");
37
+ return JSON.parse(readFileSync(sdkPackagePath, "utf8")).version;
38
+ } catch {
39
+ return "unknown";
40
+ }
41
+ }
42
+
43
+ function artifactPath(artifactDir, name) {
44
+ return join(artifactDir, ARTIFACTS[name]);
45
+ }
46
+
47
+ function printHelp() {
48
+ console.log(`Capture timestamped Cursor SDK event timelines for one local run.
49
+
50
+ Usage:
51
+ CURSOR_API_KEY=... npm run debug:sdk-events -- [options]
52
+ node scripts/debug-sdk-events.mjs [options]
53
+
54
+ Options:
55
+ --cwd <path> Agent working directory. Default: process.cwd().
56
+ --model <id> Cursor model id. Default: ${DEFAULT_MODEL}.
57
+ --prompt <text> Required user prompt for the run.
58
+ --out <dir> Artifact directory. Default: /tmp/pi-cursor-sdk-sdk-events-<timestamp>.
59
+ --setting-sources <value> Comma-separated Cursor setting sources, or all/none.
60
+ Default: PI_CURSOR_SETTING_SOURCES env, otherwise all.
61
+ --include-conversation Also capture run.conversation() when supported.
62
+ --api-key <key> Cursor API key. Prefer CURSOR_API_KEY to avoid shell history.
63
+ -h, --help Show this help.
64
+
65
+ Stdout:
66
+ Prints artifact paths and summary counts only. Raw payloads stay on disk under:
67
+ ${ARTIFACTS.streamEvents} (run.stream()), ${ARTIFACTS.onDelta} (onDelta), ${ARTIFACTS.onStep} (onStep).
68
+
69
+ Exit codes:
70
+ 0 capture completed
71
+ 1 invalid arguments, missing auth, or Cursor SDK failure
72
+
73
+ Safety:
74
+ - Never prints CURSOR_API_KEY or --api-key values.
75
+ - Default artifact root is outside the repo (/tmp/...).
76
+ - ${RAW_ARTIFACT_WARNING}
77
+ - Verify Cursor SDK behavior against the installed @cursor/sdk package and/or
78
+ https://cursor.com/docs/sdk/typescript before drawing integration conclusions.`);
79
+ }
80
+
81
+ function fail(message, secrets = []) {
82
+ const scrubbed = scrubSensitiveText(message, secrets[0]);
83
+ console.error(`debug-sdk-events: ${scrubbed}`);
84
+ process.exit(1);
85
+ }
86
+
87
+ export function parseDebugSdkEventsArgs(argv, env = process.env) {
88
+ const args = {
89
+ cwd: process.cwd(),
90
+ model: DEFAULT_MODEL,
91
+ prompt: undefined,
92
+ out: undefined,
93
+ settingSources: resolveCursorSettingSources(env[CURSOR_SETTING_SOURCES_ENV]),
94
+ includeConversation: false,
95
+ apiKey: env.CURSOR_API_KEY?.trim() || undefined,
96
+ help: false,
97
+ };
98
+ for (let index = 0; index < argv.length; index++) {
99
+ const arg = argv[index];
100
+ if (arg === "-h" || arg === "--help") {
101
+ args.help = true;
102
+ continue;
103
+ }
104
+ if (arg === "--include-conversation") {
105
+ args.includeConversation = true;
106
+ continue;
107
+ }
108
+ if (arg === "--cwd") {
109
+ const value = argv[++index];
110
+ if (!value || value.startsWith("--")) fail("--cwd requires a path");
111
+ args.cwd = resolve(value);
112
+ continue;
113
+ }
114
+ if (arg.startsWith("--cwd=")) {
115
+ args.cwd = resolve(arg.slice("--cwd=".length));
116
+ continue;
117
+ }
118
+ if (arg === "--model") {
119
+ const value = argv[++index];
120
+ if (!value || value.startsWith("--")) fail("--model requires a value");
121
+ args.model = value.trim();
122
+ continue;
123
+ }
124
+ if (arg.startsWith("--model=")) {
125
+ args.model = arg.slice("--model=".length).trim();
126
+ continue;
127
+ }
128
+ if (arg === "--prompt") {
129
+ const value = argv[++index];
130
+ if (!value || value.startsWith("--")) fail("--prompt requires a value");
131
+ args.prompt = value;
132
+ continue;
133
+ }
134
+ if (arg.startsWith("--prompt=")) {
135
+ args.prompt = arg.slice("--prompt=".length);
136
+ continue;
137
+ }
138
+ if (arg === "--out") {
139
+ const value = argv[++index];
140
+ if (!value || value.startsWith("--")) fail("--out requires a directory path");
141
+ args.out = resolve(value);
142
+ continue;
143
+ }
144
+ if (arg.startsWith("--out=")) {
145
+ args.out = resolve(arg.slice("--out=".length));
146
+ continue;
147
+ }
148
+ if (arg === "--setting-sources") {
149
+ const value = argv[++index];
150
+ if (!value || value.startsWith("--")) fail("--setting-sources requires a value");
151
+ args.settingSources = resolveCursorSettingSources(value);
152
+ continue;
153
+ }
154
+ if (arg.startsWith("--setting-sources=")) {
155
+ args.settingSources = resolveCursorSettingSources(arg.slice("--setting-sources=".length));
156
+ continue;
157
+ }
158
+ if (arg === "--api-key") {
159
+ const value = argv[++index];
160
+ if (!value || value.startsWith("--")) fail("--api-key requires a value");
161
+ args.apiKey = value.trim();
162
+ continue;
163
+ }
164
+ if (arg.startsWith("--api-key=")) {
165
+ args.apiKey = arg.slice("--api-key=".length).trim();
166
+ continue;
167
+ }
168
+ fail(`unknown argument: ${arg}`);
169
+ }
170
+ return args;
171
+ }
172
+
173
+ function defaultOutDir() {
174
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
175
+ return join("/tmp", `pi-cursor-sdk-sdk-events-${stamp}`);
176
+ }
177
+
178
+ function eventType(value) {
179
+ if (value && typeof value === "object" && typeof value.type === "string") return value.type;
180
+ return "unknown";
181
+ }
182
+
183
+ export function createTimingTracker() {
184
+ return {
185
+ eventCount: 0,
186
+ firstMs: undefined,
187
+ lastMs: undefined,
188
+ maxGapMs: undefined,
189
+ record(elapsedMs) {
190
+ if (this.eventCount === 0) {
191
+ this.firstMs = elapsedMs;
192
+ } else {
193
+ this.maxGapMs = Math.max(this.maxGapMs ?? 0, elapsedMs - (this.lastMs ?? elapsedMs));
194
+ }
195
+ this.eventCount += 1;
196
+ this.lastMs = elapsedMs;
197
+ },
198
+ snapshot() {
199
+ return {
200
+ eventCount: this.eventCount,
201
+ firstMs: this.firstMs,
202
+ lastMs: this.lastMs,
203
+ maxGapMs: this.maxGapMs,
204
+ };
205
+ },
206
+ };
207
+ }
208
+
209
+ export function createEventJsonlSink(artifactDir, startedAt) {
210
+ const paths = {
211
+ streamEvents: artifactPath(artifactDir, "streamEvents"),
212
+ onDelta: artifactPath(artifactDir, "onDelta"),
213
+ onStep: artifactPath(artifactDir, "onStep"),
214
+ };
215
+ for (const path of Object.values(paths)) {
216
+ writeFileSync(path, "");
217
+ }
218
+ const counts = {
219
+ stream: {},
220
+ onDelta: {},
221
+ onStep: {},
222
+ };
223
+ const timing = {
224
+ stream: createTimingTracker(),
225
+ onDelta: createTimingTracker(),
226
+ onStep: createTimingTracker(),
227
+ };
228
+
229
+ function append(pathKey, countKey, recordKey, value) {
230
+ const elapsedMs = Date.now() - startedAt;
231
+ const record = {
232
+ ts: new Date().toISOString(),
233
+ elapsedMs,
234
+ [recordKey]: value,
235
+ };
236
+ appendFileSync(paths[pathKey], `${JSON.stringify(record)}\n`);
237
+ const type = eventType(value);
238
+ counts[countKey][type] = (counts[countKey][type] ?? 0) + 1;
239
+ timing[countKey].record(elapsedMs);
240
+ return record;
241
+ }
242
+
243
+ return {
244
+ appendStream: (event) => append("streamEvents", "stream", "event", event),
245
+ appendDelta: (update) => append("onDelta", "onDelta", "update", update),
246
+ appendStep: (step) => append("onStep", "onStep", "step", step),
247
+ getSummaryState() {
248
+ return {
249
+ counts: {
250
+ stream: { ...counts.stream },
251
+ onDelta: { ...counts.onDelta },
252
+ onStep: { ...counts.onStep },
253
+ },
254
+ timing: {
255
+ stream: timing.stream.snapshot(),
256
+ onDelta: timing.onDelta.snapshot(),
257
+ onStep: timing.onStep.snapshot(),
258
+ },
259
+ };
260
+ },
261
+ close() {
262
+ return Promise.resolve();
263
+ },
264
+ };
265
+ }
266
+
267
+ function summarizeConversation(conversation) {
268
+ if (!conversation) return undefined;
269
+ if (Array.isArray(conversation)) return { turnCount: conversation.length };
270
+ return conversation;
271
+ }
272
+
273
+ export function buildSummary({
274
+ artifactDir,
275
+ counts,
276
+ timing,
277
+ waitResult,
278
+ conversation,
279
+ includeConversation,
280
+ }) {
281
+ return {
282
+ artifactDir,
283
+ files: {
284
+ metadata: artifactPath(artifactDir, "metadata"),
285
+ streamEvents: artifactPath(artifactDir, "streamEvents"),
286
+ onDelta: artifactPath(artifactDir, "onDelta"),
287
+ onStep: artifactPath(artifactDir, "onStep"),
288
+ waitResult: artifactPath(artifactDir, "waitResult"),
289
+ conversation: includeConversation ? artifactPath(artifactDir, "conversation") : undefined,
290
+ },
291
+ counts,
292
+ timing,
293
+ wait: waitResult
294
+ ? {
295
+ status: waitResult.status,
296
+ durationMs: waitResult.durationMs,
297
+ hasResultText: Boolean(waitResult.result?.trim()),
298
+ }
299
+ : undefined,
300
+ conversation: summarizeConversation(conversation),
301
+ warnings: [RAW_ARTIFACT_WARNING],
302
+ };
303
+ }
304
+
305
+ function printStdoutSummary(summary) {
306
+ console.log(JSON.stringify(summary, null, 2));
307
+ }
308
+
309
+ async function captureEvents(args) {
310
+ const artifactDir = args.out ?? defaultOutDir();
311
+ mkdirSync(artifactDir, { recursive: true });
312
+ const startedAt = Date.now();
313
+ const metadata = {
314
+ capturedAt: new Date(startedAt).toISOString(),
315
+ cwd: args.cwd,
316
+ model: args.model,
317
+ settingSources: args.settingSources ?? null,
318
+ prompt: args.prompt,
319
+ packageVersion: packageJson.version,
320
+ sdkVersion: readSdkVersion(),
321
+ includeConversation: args.includeConversation,
322
+ warnings: [RAW_ARTIFACT_WARNING],
323
+ };
324
+ writeFileSync(artifactPath(artifactDir, "metadata"), `${JSON.stringify(metadata, null, 2)}\n`);
325
+
326
+ const restoreOutputFilter = installCursorSdkOutputFilter();
327
+ const eventSink = createEventJsonlSink(artifactDir, startedAt);
328
+ let agent;
329
+ try {
330
+ const { Agent } = await suppressCursorSdkOutput(() => import("@cursor/sdk"));
331
+ agent = await suppressCursorSdkOutput(() =>
332
+ Agent.create({
333
+ apiKey: args.apiKey,
334
+ model: { id: args.model },
335
+ local: args.settingSources ? { cwd: args.cwd, settingSources: args.settingSources } : { cwd: args.cwd },
336
+ }),
337
+ );
338
+
339
+ const run = await suppressCursorSdkOutput(() =>
340
+ agent.send(
341
+ { text: args.prompt },
342
+ {
343
+ onDelta: ({ update }) => eventSink.appendDelta(update),
344
+ onStep: ({ step }) => eventSink.appendStep(step),
345
+ },
346
+ ),
347
+ );
348
+
349
+ await suppressCursorSdkOutput(async () => {
350
+ for await (const event of run.stream()) {
351
+ eventSink.appendStream(event);
352
+ }
353
+ });
354
+
355
+ const waitResult = await suppressCursorSdkOutput(() => run.wait());
356
+ writeFileSync(artifactPath(artifactDir, "waitResult"), `${JSON.stringify(waitResult, null, 2)}\n`);
357
+
358
+ let conversation;
359
+ if (args.includeConversation) {
360
+ if (run.supports("conversation")) {
361
+ conversation = await suppressCursorSdkOutput(() => run.conversation());
362
+ } else {
363
+ conversation = {
364
+ skipped: true,
365
+ reason: run.unsupportedReason("conversation") ?? "conversation unsupported",
366
+ };
367
+ }
368
+ writeFileSync(artifactPath(artifactDir, "conversation"), `${JSON.stringify(conversation, null, 2)}\n`);
369
+ }
370
+
371
+ const summary = buildSummary({
372
+ artifactDir,
373
+ ...eventSink.getSummaryState(),
374
+ waitResult,
375
+ conversation,
376
+ includeConversation: args.includeConversation,
377
+ });
378
+ writeFileSync(artifactPath(artifactDir, "summary"), `${JSON.stringify(summary, null, 2)}\n`);
379
+ printStdoutSummary(summary);
380
+ } catch (error) {
381
+ const message = error instanceof Error ? error.message : String(error);
382
+ fail(message, [args.apiKey]);
383
+ } finally {
384
+ await eventSink.close().catch(() => {});
385
+ try {
386
+ agent?.close();
387
+ } finally {
388
+ restoreOutputFilter();
389
+ }
390
+ }
391
+ }
392
+
393
+ async function main(argv = process.argv.slice(2), env = process.env) {
394
+ const args = parseDebugSdkEventsArgs(argv, env);
395
+ if (args.help) {
396
+ printHelp();
397
+ process.exit(0);
398
+ }
399
+ if (!args.prompt?.trim()) {
400
+ fail("--prompt is required");
401
+ }
402
+ if (!args.apiKey) {
403
+ fail("Cursor API key is required. Set CURSOR_API_KEY or pass --api-key.");
404
+ }
405
+ await captureEvents(args);
406
+ }
407
+
408
+ if (import.meta.url === new URL(process.argv[1], "file:").href) {
409
+ main().catch((error) => {
410
+ const message = error instanceof Error ? error.message : String(error);
411
+ fail(message);
412
+ });
413
+ }
@@ -0,0 +1,52 @@
1
+ export const CURSOR_SETTING_SOURCES_ENV = "PI_CURSOR_SETTING_SOURCES";
2
+
3
+ function escapeRegExp(value) {
4
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ }
6
+
7
+ export function resolveCursorSettingSources(raw) {
8
+ const trimmed = raw?.trim();
9
+ if (!trimmed) return ["all"];
10
+ const normalized = trimmed.toLowerCase();
11
+ if (["0", "false", "off", "none", "omit", "disabled"].includes(normalized)) return undefined;
12
+ if (["1", "true", "on", "all"].includes(normalized)) return ["all"];
13
+ return trimmed
14
+ .split(",")
15
+ .map((entry) => entry.trim())
16
+ .filter(Boolean);
17
+ }
18
+
19
+ const BRIDGE_ENDPOINT_ROOT = "/cursor-pi-tool-bridge";
20
+ const BRIDGE_ENDPOINT_TOKEN_PATTERN = "[^/\\s\"'<>]+";
21
+ const BRIDGE_LOOPBACK_HOST_PATTERN = "127\\.0\\.0\\.1(?::\\d+)?";
22
+ const BRIDGE_ENDPOINT_PATH_PATTERN = `${escapeRegExp(BRIDGE_ENDPOINT_ROOT)}/${BRIDGE_ENDPOINT_TOKEN_PATTERN}/mcp`;
23
+
24
+ function scrubBridgeEndpointMaterial(text) {
25
+ return text
26
+ .replace(
27
+ new RegExp(`https?://${BRIDGE_LOOPBACK_HOST_PATTERN}${BRIDGE_ENDPOINT_PATH_PATTERN}`, "gi"),
28
+ "[redacted-bridge-endpoint]",
29
+ )
30
+ .replace(
31
+ new RegExp(`${BRIDGE_LOOPBACK_HOST_PATTERN}${BRIDGE_ENDPOINT_PATH_PATTERN}`, "gi"),
32
+ "[redacted-bridge-endpoint]",
33
+ )
34
+ .replace(new RegExp(BRIDGE_ENDPOINT_PATH_PATTERN, "gi"), "[redacted-bridge-endpoint]");
35
+ }
36
+
37
+ export function scrubSensitiveText(text, apiKey) {
38
+ let scrubbed = text;
39
+ const trimmedKey = apiKey?.trim();
40
+ if (trimmedKey) {
41
+ scrubbed = scrubbed.replace(new RegExp(escapeRegExp(trimmedKey), "g"), "[redacted]");
42
+ }
43
+ return scrubBridgeEndpointMaterial(
44
+ scrubbed
45
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")
46
+ .replace(/((?:^|[\s,{])cookie["']?\s*[:=]\s*["']?)[^\n]+/gi, "$1[redacted]")
47
+ .replace(
48
+ /((?:authorization|api[_-]?key|apiKey|token|session(?:[_-]?id)?)["']?\s*[:=]\s*["']?)[^"'\s,;}]+/gi,
49
+ "$1[redacted]",
50
+ ),
51
+ );
52
+ }
@@ -0,0 +1,86 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
3
+ export const CURSOR_SDK_STARTUP_NOISE_PATTERNS = [
4
+ "[hooks]",
5
+ "managed_skills.",
6
+ "CursorPluginsAgentSkillsService load completed",
7
+ "LocalCursorRulesService load completed",
8
+ "AgentSkillsCursorRulesService load completed",
9
+ "Error initializing ignore mapping for",
10
+ "Ripgrep path not configured. Call configureRipgrepPath() at startup.",
11
+ ];
12
+
13
+ const cursorSdkOutputSuppression = new AsyncLocalStorage();
14
+
15
+ export function isCursorSdkOutputSuppressed() {
16
+ return cursorSdkOutputSuppression.getStore() === true;
17
+ }
18
+
19
+ export function suppressCursorSdkOutput(operation) {
20
+ return cursorSdkOutputSuppression.run(true, operation);
21
+ }
22
+
23
+ export function isCursorSdkStartupNoise(text) {
24
+ return CURSOR_SDK_STARTUP_NOISE_PATTERNS.some((pattern) => text.includes(pattern));
25
+ }
26
+
27
+ function createFilteredProcessWrite(write, stream) {
28
+ return (chunk, encodingOrCallback, callback) => {
29
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
30
+ if (isCursorSdkOutputSuppressed() || isCursorSdkStartupNoise(text)) {
31
+ const done = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
32
+ done?.();
33
+ return true;
34
+ }
35
+ return write.call(stream, chunk, encodingOrCallback, callback);
36
+ };
37
+ }
38
+
39
+ function createFilteredConsoleMethod(method) {
40
+ return (...args) => {
41
+ const text = args.map((arg) => (typeof arg === "string" ? arg : String(arg))).join(" ");
42
+ if (isCursorSdkOutputSuppressed() || isCursorSdkStartupNoise(text)) return;
43
+ method(...args);
44
+ };
45
+ }
46
+
47
+ let activeOutputFilterInstalls = 0;
48
+ let outputFilterOriginals;
49
+
50
+ export function installCursorSdkOutputFilter() {
51
+ if (activeOutputFilterInstalls === 0) {
52
+ outputFilterOriginals = {
53
+ stdoutWrite: process.stdout.write,
54
+ stderrWrite: process.stderr.write,
55
+ consoleLog: console.log,
56
+ consoleInfo: console.info,
57
+ consoleWarn: console.warn,
58
+ consoleError: console.error,
59
+ consoleDebug: console.debug,
60
+ };
61
+ process.stdout.write = createFilteredProcessWrite(outputFilterOriginals.stdoutWrite, process.stdout);
62
+ process.stderr.write = createFilteredProcessWrite(outputFilterOriginals.stderrWrite, process.stderr);
63
+ console.log = createFilteredConsoleMethod(outputFilterOriginals.consoleLog);
64
+ console.info = createFilteredConsoleMethod(outputFilterOriginals.consoleInfo);
65
+ console.warn = createFilteredConsoleMethod(outputFilterOriginals.consoleWarn);
66
+ console.error = createFilteredConsoleMethod(outputFilterOriginals.consoleError);
67
+ console.debug = createFilteredConsoleMethod(outputFilterOriginals.consoleDebug);
68
+ }
69
+ activeOutputFilterInstalls += 1;
70
+
71
+ let restored = false;
72
+ return () => {
73
+ if (restored) return;
74
+ restored = true;
75
+ activeOutputFilterInstalls = Math.max(activeOutputFilterInstalls - 1, 0);
76
+ if (activeOutputFilterInstalls > 0 || !outputFilterOriginals) return;
77
+ process.stdout.write = outputFilterOriginals.stdoutWrite;
78
+ process.stderr.write = outputFilterOriginals.stderrWrite;
79
+ console.log = outputFilterOriginals.consoleLog;
80
+ console.info = outputFilterOriginals.consoleInfo;
81
+ console.warn = outputFilterOriginals.consoleWarn;
82
+ console.error = outputFilterOriginals.consoleError;
83
+ console.debug = outputFilterOriginals.consoleDebug;
84
+ outputFilterOriginals = undefined;
85
+ };
86
+ }