pi-cursor-sdk 0.2.0 → 0.3.6

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 (168) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +54 -50
  3. package/dist/bundled-context-windows.js +39 -0
  4. package/dist/context-window-cache.js +92 -0
  5. package/dist/context.js +338 -0
  6. package/dist/cursor-active-tools.js +3 -0
  7. package/dist/cursor-agent-message-web-tools.js +165 -0
  8. package/dist/cursor-agents-context-registration.js +17 -0
  9. package/dist/cursor-agents-context.js +113 -0
  10. package/dist/cursor-api-key.js +34 -0
  11. package/dist/cursor-bridge-contract.js +27 -0
  12. package/dist/cursor-cloud-lifecycle.js +650 -0
  13. package/dist/cursor-cloud-local-state.js +460 -0
  14. package/dist/cursor-cloud-options.js +145 -0
  15. package/dist/cursor-cloud-reporting.js +222 -0
  16. package/dist/cursor-compact-tool-summary.js +75 -0
  17. package/dist/cursor-config.js +613 -0
  18. package/dist/cursor-context-tools.js +4 -0
  19. package/dist/cursor-display-only-trace.js +11 -0
  20. package/dist/cursor-display-text.js +18 -0
  21. package/dist/cursor-durable-fs.js +53 -0
  22. package/dist/cursor-edit-diff.js +13 -0
  23. package/dist/cursor-env-boolean.js +19 -0
  24. package/dist/cursor-fallback-models.generated.js +7760 -0
  25. package/dist/cursor-fallback-warning.js +15 -0
  26. package/dist/cursor-http1.js +45 -0
  27. package/dist/cursor-incomplete-tool-visibility.js +83 -0
  28. package/dist/cursor-live-run-accounting.js +55 -0
  29. package/dist/cursor-live-run-coordinator.js +406 -0
  30. package/dist/cursor-mcp-timeout-override.js +116 -0
  31. package/dist/cursor-model-lifecycle.js +32 -0
  32. package/dist/cursor-model.js +5 -0
  33. package/dist/cursor-native-replay-routing.js +35 -0
  34. package/dist/cursor-native-replay-trace.js +28 -0
  35. package/dist/cursor-native-tool-display-registration.js +102 -0
  36. package/dist/cursor-native-tool-display-replay.js +428 -0
  37. package/dist/cursor-native-tool-display-state.js +70 -0
  38. package/dist/cursor-native-tool-display-tools.js +123 -0
  39. package/dist/cursor-native-tool-names.js +11 -0
  40. package/dist/cursor-partial-content-emitter.js +118 -0
  41. package/dist/cursor-pi-tool-bridge-abort.js +114 -0
  42. package/dist/cursor-pi-tool-bridge-constants.js +6 -0
  43. package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
  44. package/dist/cursor-pi-tool-bridge-env.js +18 -0
  45. package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
  46. package/dist/cursor-pi-tool-bridge-run.js +355 -0
  47. package/dist/cursor-pi-tool-bridge-server.js +163 -0
  48. package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
  49. package/dist/cursor-pi-tool-bridge-types.js +1 -0
  50. package/dist/cursor-pi-tool-bridge.js +123 -0
  51. package/dist/cursor-provider-errors.js +311 -0
  52. package/dist/cursor-provider-lazy.js +39 -0
  53. package/dist/cursor-provider-live-run-drain.js +412 -0
  54. package/dist/cursor-provider-overflow.js +90 -0
  55. package/dist/cursor-provider-run-finalizer.js +176 -0
  56. package/dist/cursor-provider-run-outcome.js +76 -0
  57. package/dist/cursor-provider-turn-coordinator.js +275 -0
  58. package/dist/cursor-provider-turn-display-router.js +158 -0
  59. package/dist/cursor-provider-turn-emit.js +35 -0
  60. package/dist/cursor-provider-turn-finalize.js +154 -0
  61. package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
  62. package/dist/cursor-provider-turn-prepare.js +374 -0
  63. package/dist/cursor-provider-turn-runner.js +146 -0
  64. package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
  65. package/dist/cursor-provider-turn-send.js +138 -0
  66. package/dist/cursor-provider-turn-shell-output.js +117 -0
  67. package/dist/cursor-provider-turn-tool-ledger.js +99 -0
  68. package/dist/cursor-provider-turn-types.js +1 -0
  69. package/dist/cursor-provider.js +72 -0
  70. package/dist/cursor-question-tool.js +194 -0
  71. package/dist/cursor-record-utils.js +61 -0
  72. package/dist/cursor-replay-activity-builders.js +114 -0
  73. package/dist/cursor-replay-source-names.js +27 -0
  74. package/dist/cursor-replay-summary-args.js +105 -0
  75. package/dist/cursor-replay-tool-details.js +167 -0
  76. package/dist/cursor-ripgrep-path.js +27 -0
  77. package/dist/cursor-run-final-text.js +63 -0
  78. package/dist/cursor-runtime-state.js +380 -0
  79. package/dist/cursor-sdk-billed-usage.js +86 -0
  80. package/dist/cursor-sdk-event-debug-constants.js +33 -0
  81. package/dist/cursor-sdk-event-debug-session.js +107 -0
  82. package/dist/cursor-sdk-event-debug.js +461 -0
  83. package/dist/cursor-sdk-output-filter.js +1 -0
  84. package/dist/cursor-sdk-process-error-guard.js +221 -0
  85. package/dist/cursor-sdk-runtime.js +3 -0
  86. package/dist/cursor-sensitive-text.js +23 -0
  87. package/dist/cursor-session-agent-cleanup.js +311 -0
  88. package/dist/cursor-session-agent-lifecycle.js +32 -0
  89. package/dist/cursor-session-agent-lineage.js +103 -0
  90. package/dist/cursor-session-agent-resume.js +417 -0
  91. package/dist/cursor-session-agent.js +507 -0
  92. package/dist/cursor-session-compaction-prep.js +21 -0
  93. package/dist/cursor-session-scope.js +113 -0
  94. package/dist/cursor-session-send-policy.js +18 -0
  95. package/dist/cursor-session-store.js +105 -0
  96. package/dist/cursor-session-turn-queue.js +50 -0
  97. package/dist/cursor-setting-sources.js +15 -0
  98. package/dist/cursor-skill-tool.js +230 -0
  99. package/dist/cursor-state.js +519 -0
  100. package/dist/cursor-task-presentation.js +59 -0
  101. package/dist/cursor-tool-lifecycle.js +94 -0
  102. package/dist/cursor-tool-manifest.js +34 -0
  103. package/dist/cursor-tool-presentation-registry.js +305 -0
  104. package/dist/cursor-tool-result-display-readers.js +158 -0
  105. package/dist/cursor-tool-transcript.js +32 -0
  106. package/dist/cursor-tool-visibility.js +28 -0
  107. package/dist/cursor-transcript-tool-formatters.js +618 -0
  108. package/dist/cursor-transcript-tool-specs.js +284 -0
  109. package/dist/cursor-transcript-utils.js +170 -0
  110. package/dist/cursor-usage-accounting.js +166 -0
  111. package/dist/cursor-web-tool-activity.js +23 -0
  112. package/dist/cursor-web-tool-args.js +12 -0
  113. package/dist/index.js +82 -0
  114. package/dist/model-discovery.js +305 -0
  115. package/dist/model-list-cache.js +156 -0
  116. package/docs/cursor-dogfood-checklist.md +2 -1
  117. package/docs/cursor-live-smoke-checklist.md +13 -12
  118. package/docs/cursor-model-ux-spec.md +25 -6
  119. package/docs/cursor-native-tool-replay.md +13 -13
  120. package/docs/cursor-native-tool-visual-audit.md +6 -3
  121. package/docs/cursor-testing-lessons.md +7 -7
  122. package/docs/cursor-tool-surfaces.md +5 -5
  123. package/docs/platform-smoke.md +3 -3
  124. package/package.json +19 -7
  125. package/platform-smoke.config.mjs +1 -1
  126. package/scripts/build.mjs +126 -0
  127. package/scripts/cloud-runtime-smoke.mjs +3 -1
  128. package/scripts/debug-provider-events.mjs +3 -1
  129. package/scripts/debug-sdk-events.mjs +1 -1
  130. package/scripts/isolated-cursor-smoke.sh +5 -4
  131. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
  132. package/scripts/lib/cursor-visual-render.mjs +1 -1
  133. package/scripts/lib/ensure-built.mjs +16 -0
  134. package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
  135. package/scripts/local-resume-smoke.mjs +3 -1
  136. package/scripts/platform-smoke/card-detect.mjs +1 -1
  137. package/scripts/platform-smoke/live-suite-runner.mjs +1 -1
  138. package/scripts/platform-smoke/render-ansi.mjs +1 -1
  139. package/scripts/platform-smoke/targets.mjs +2 -2
  140. package/scripts/prepare.mjs +102 -0
  141. package/scripts/steering-rpc-smoke.mjs +4 -2
  142. package/scripts/tmux-live-smoke.sh +11 -11
  143. package/scripts/visual-tui-smoke-self-test.mjs +4 -1
  144. package/scripts/visual-tui-smoke.mjs +51 -7
  145. package/src/bundled-context-windows.ts +4 -1
  146. package/src/cursor-agents-context-registration.ts +2 -2
  147. package/src/cursor-agents-context.ts +0 -2
  148. package/src/cursor-api-key.ts +5 -4
  149. package/src/cursor-cloud-reporting.ts +23 -2
  150. package/src/cursor-fallback-models.generated.ts +592 -204
  151. package/src/cursor-live-run-coordinator.ts +1 -0
  152. package/src/cursor-native-tool-display-registration.ts +10 -11
  153. package/src/cursor-provider-errors.ts +1 -1
  154. package/src/cursor-provider-lazy.ts +5 -4
  155. package/src/cursor-provider-live-run-drain.ts +1 -0
  156. package/src/cursor-provider-run-finalizer.ts +1 -0
  157. package/src/cursor-provider-turn-finalize.ts +12 -0
  158. package/src/cursor-provider-turn-types.ts +2 -0
  159. package/src/cursor-sdk-billed-usage.ts +107 -0
  160. package/src/cursor-sdk-process-error-guard.ts +6 -4
  161. package/src/cursor-session-agent-lifecycle.ts +8 -9
  162. package/src/cursor-session-agent-resume.ts +23 -0
  163. package/src/cursor-session-compaction-prep.ts +4 -0
  164. package/src/cursor-tool-manifest.ts +1 -1
  165. package/src/cursor-usage-accounting.ts +70 -9
  166. package/src/index.ts +1 -1
  167. package/tsconfig.build.json +8 -0
  168. package/tsconfig.json +16 -0
@@ -0,0 +1,27 @@
1
+ import { accessSync, constants } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+ const RIPGREP_ENV = "CURSOR_RIPGREP_PATH";
5
+ export function resolveBundledCursorRipgrepPath(fromModuleUrl = import.meta.url) {
6
+ try {
7
+ const require = createRequire(fromModuleUrl);
8
+ const platformPackage = `@cursor/sdk-${process.platform}-${process.arch}`;
9
+ const sdkEntry = require.resolve("@cursor/sdk");
10
+ const packageDirectory = dirname(require.resolve(`${platformPackage}/package.json`, { paths: [dirname(sdkEntry)] }));
11
+ const ripgrepPath = join(packageDirectory, "bin", process.platform === "win32" ? "rg.exe" : "rg");
12
+ accessSync(ripgrepPath, constants.X_OK);
13
+ return ripgrepPath;
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ export function ensureCursorRipgrepPath() {
20
+ const configuredPath = process.env[RIPGREP_ENV];
21
+ if (configuredPath && isAbsolute(configuredPath))
22
+ return configuredPath;
23
+ const bundledPath = resolveBundledCursorRipgrepPath();
24
+ if (bundledPath)
25
+ process.env[RIPGREP_ENV] = bundledPath;
26
+ return bundledPath;
27
+ }
@@ -0,0 +1,63 @@
1
+ import { asRecord, hasUsableText } from "./cursor-record-utils.js";
2
+ function isCursorTextBoundary(text, index) {
3
+ if (index <= 0 || index >= text.length)
4
+ return true;
5
+ const before = text[index - 1];
6
+ const after = text[index];
7
+ return !/[\p{L}\p{N}_]/u.test(before) || !/[\p{L}\p{N}_]/u.test(after);
8
+ }
9
+ function trimAlreadyEmittedCursorText(text, emittedText, options) {
10
+ if (!text || !emittedText)
11
+ return text;
12
+ if (text === emittedText)
13
+ return "";
14
+ if (text.startsWith(emittedText) && (options?.allowPartialPrefix || isCursorTextBoundary(text, emittedText.length))) {
15
+ return text.slice(emittedText.length);
16
+ }
17
+ if (emittedText.endsWith(text) && isCursorTextBoundary(emittedText, emittedText.length - text.length))
18
+ return "";
19
+ const trimmedText = text.trim();
20
+ const trimmedEmittedText = emittedText.trim();
21
+ if (trimmedText === trimmedEmittedText)
22
+ return "";
23
+ if (trimmedText && trimmedEmittedText.endsWith(trimmedText)) {
24
+ const suffixStart = trimmedEmittedText.length - trimmedText.length;
25
+ if (isCursorTextBoundary(trimmedEmittedText, suffixStart))
26
+ return "";
27
+ }
28
+ return text;
29
+ }
30
+ export function trimCurrentTurnAlreadyEmittedCursorText(text, currentTurnEmittedText, emittedText = currentTurnEmittedText) {
31
+ if (!currentTurnEmittedText)
32
+ return trimAlreadyEmittedCursorText(text, emittedText);
33
+ const currentTurnTrimmedText = trimAlreadyEmittedCursorText(text, currentTurnEmittedText, { allowPartialPrefix: true });
34
+ if (currentTurnTrimmedText !== text)
35
+ return currentTurnTrimmedText;
36
+ if (emittedText.endsWith(currentTurnEmittedText)) {
37
+ const emittedTextTrimmedText = trimAlreadyEmittedCursorText(text, emittedText, { allowPartialPrefix: true });
38
+ if (emittedTextTrimmedText !== text)
39
+ return emittedTextTrimmedText;
40
+ }
41
+ return trimAlreadyEmittedCursorText(text, emittedText);
42
+ }
43
+ export function getFinalAssistantText(message) {
44
+ for (let index = message.content.length - 1; index >= 0; index--) {
45
+ const block = asRecord(message.content[index]);
46
+ if (block?.type !== "text" || typeof block.text !== "string")
47
+ continue;
48
+ if (hasUsableText(block.text))
49
+ return block.text;
50
+ }
51
+ return "";
52
+ }
53
+ export function selectCursorFinalText(resultText, textDeltas, emittedText, fallbackText, options) {
54
+ const candidates = [typeof resultText === "string" ? resultText : undefined, fallbackText, textDeltas.join("")];
55
+ for (const candidate of candidates) {
56
+ if (!hasUsableText(candidate))
57
+ continue;
58
+ const trimmedCandidate = trimAlreadyEmittedCursorText(candidate, emittedText, options);
59
+ if (hasUsableText(trimmedCandidate))
60
+ return trimmedCandidate;
61
+ }
62
+ return "";
63
+ }
@@ -0,0 +1,380 @@
1
+ import { registerCursorCloudLifecycleLedger, runCursorCloudLifecycleCommand, } from "./cursor-cloud-lifecycle.js";
2
+ import { CURSOR_AUTO_REVIEW_ENV, CURSOR_CLOUD_ACK_ENV, CURSOR_CLOUD_ALLOW_LOCAL_STATE_ENV, CURSOR_CLOUD_AUTO_CREATE_PR_ENV, CURSOR_CLOUD_BRANCH_ENV, CURSOR_CLOUD_CONTEXT_ENV, CURSOR_CLOUD_DIRECT_PUSH_ENV, CURSOR_CLOUD_ENV_ENV, CURSOR_CLOUD_ENV_FROM_FILES_ENV, CURSOR_CLOUD_ENV_NAME_ENV, CURSOR_CLOUD_ENV_TYPE_ENV, CURSOR_CLOUD_REPO_ENV, CURSOR_CLOUD_SKIP_REVIEWER_REQUEST_ENV, CURSOR_LOCAL_FORCE_ENV, CURSOR_LOCAL_RESUME_ENV, CURSOR_RUNTIME_ENV, CURSOR_SANDBOX_ENV, getCursorSdkProjectConfigPath, getCursorSdkUserConfigPath, loadCursorSdkConfig, mergeCursorSdkConfigForUpdate, parseExplicitCursorCloudEnvNames, resolveCursorSdkConfig, updateCursorSdkConfig, } from "./cursor-config.js";
3
+ import { asRecord } from "./cursor-record-utils.js";
4
+ import { getResolvedSessionCursorHttp1Enabled } from "./cursor-http1.js";
5
+ import { getCursorSessionCwd, getCursorSessionProjectTrusted } from "./cursor-session-scope.js";
6
+ export const CURSOR_RUNTIME_ENTRY_TYPE = "cursor-runtime-state";
7
+ export const CURSOR_CLOUD_ACK_DISCLOSURE = [
8
+ "Cursor Cloud executes this work remotely.",
9
+ "Fresh context is used by default; prior Pi context is included only with explicit bootstrap opt-in.",
10
+ "Pi-local tools and the Pi bridge are unavailable, and Pi environment variables are not forwarded.",
11
+ "Cursor may create branches, commit, push, and open pull requests.",
12
+ "Cloud agents remain until you archive or delete them.",
13
+ "Cloud Agents run in Max Mode, are billed at Cursor API pricing, and may require spend-limit setup.",
14
+ ].join("\n\n");
15
+ let cliCursorSnapshot = { config: {} };
16
+ let cliLocalForceConsumed = false;
17
+ let envLocalForceConsumed = false;
18
+ let sessionCursorRuntime;
19
+ let sessionCursorCloudAcknowledged = false;
20
+ function isCursorRuntimeEntryData(value) {
21
+ const record = asRecord(value);
22
+ if (!record)
23
+ return false;
24
+ return (record.runtime === "local" || record.runtime === "cloud")
25
+ && (record.cloudAcknowledged === undefined || typeof record.cloudAcknowledged === "boolean");
26
+ }
27
+ function stringFlagValue(value) {
28
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
29
+ }
30
+ export function getCursorCliConfig() {
31
+ const config = structuredClone(cliCursorSnapshot.config);
32
+ const envNames = parseExplicitCursorCloudEnvNames(cliCursorSnapshot.cloudEnvNames, "--cursor-cloud-env");
33
+ if (envNames)
34
+ config.cloud = { ...config.cloud, envNames };
35
+ return config;
36
+ }
37
+ export function getCursorSessionConfig() {
38
+ const useHttp1ForAgent = getResolvedSessionCursorHttp1Enabled();
39
+ return {
40
+ ...(sessionCursorRuntime
41
+ ? {
42
+ runtime: sessionCursorRuntime,
43
+ ...(sessionCursorCloudAcknowledged ? { cloud: { acknowledged: true } } : {}),
44
+ }
45
+ : {}),
46
+ ...(useHttp1ForAgent === undefined ? {} : { local: { useHttp1ForAgent } }),
47
+ };
48
+ }
49
+ export function resolveEffectiveCursorConfig(options) {
50
+ const loadedConfig = loadCursorSdkConfig({ cwd: options.cwd, projectTrusted: options.projectTrusted === true });
51
+ return resolveCursorSdkConfig({
52
+ cli: getCursorCliConfig(),
53
+ session: getCursorSessionConfig(),
54
+ user: loadedConfig.user,
55
+ project: loadedConfig.project,
56
+ });
57
+ }
58
+ export function resolveEffectiveCursorConfigForContext(ctx) {
59
+ return resolveEffectiveCursorConfig({
60
+ cwd: ctx.cwd,
61
+ projectTrusted: getCursorSessionCwd() === ctx.cwd && getCursorSessionProjectTrusted(),
62
+ });
63
+ }
64
+ export function resolveCursorStatusRuntime(ctx) {
65
+ try {
66
+ const config = resolveEffectiveCursorConfigForContext(ctx);
67
+ return {
68
+ kind: "valid",
69
+ runtime: config.runtime,
70
+ useHttp1ForAgent: config.local.useHttp1ForAgent,
71
+ };
72
+ }
73
+ catch (error) {
74
+ return { kind: "invalid", message: error instanceof Error ? error.message : String(error) };
75
+ }
76
+ }
77
+ export function formatResolvedCursorRuntime(runtime) {
78
+ const cap = runtime.cappedBy;
79
+ return cap
80
+ ? `${runtime.value} (source: ${runtime.source} safety cap over ${cap.cappedSource} ${cap.cappedValue})`
81
+ : `${runtime.value} (source: ${runtime.source})`;
82
+ }
83
+ export function formatCursorStatus(runtime, fast, mode, useHttp1ForAgent = false) {
84
+ const parts = [`cursor:${runtime}`, fast === true ? "fast:on" : fast === false ? "fast:off" : "fast:n/a"];
85
+ if (runtime === "local" && useHttp1ForAgent)
86
+ parts.push("http1");
87
+ if (mode === "invalid")
88
+ parts.push("mode invalid");
89
+ else if (mode === "plan")
90
+ parts.push("plan");
91
+ return parts.join(" · ");
92
+ }
93
+ export function consumeCursorLocalForceOverride(resolved) {
94
+ if (!resolved.value)
95
+ return false;
96
+ if (resolved.source === "cli" && !cliLocalForceConsumed) {
97
+ if (cliCursorSnapshot.config.local) {
98
+ const { force: _, ...local } = cliCursorSnapshot.config.local;
99
+ cliCursorSnapshot.config.local = local;
100
+ }
101
+ cliLocalForceConsumed = true;
102
+ return true;
103
+ }
104
+ if (resolved.source === "environment" && !envLocalForceConsumed) {
105
+ envLocalForceConsumed = true;
106
+ return true;
107
+ }
108
+ return false;
109
+ }
110
+ export function restoreSessionCursorRuntimeState(branch) {
111
+ sessionCursorRuntime = undefined;
112
+ sessionCursorCloudAcknowledged = false;
113
+ for (const entry of branch) {
114
+ if (entry.type !== "custom" || entry.customType !== CURSOR_RUNTIME_ENTRY_TYPE)
115
+ continue;
116
+ if (isCursorRuntimeEntryData(entry.data)) {
117
+ sessionCursorRuntime = entry.data.runtime;
118
+ sessionCursorCloudAcknowledged ||= entry.data.cloudAcknowledged === true;
119
+ }
120
+ }
121
+ }
122
+ export function restoreCursorCliState(pi) {
123
+ const runtime = stringFlagValue(pi.getFlag("cursor-runtime"));
124
+ const repo = stringFlagValue(pi.getFlag("cursor-cloud-repo"));
125
+ const branch = stringFlagValue(pi.getFlag("cursor-cloud-branch"));
126
+ const contextHandoff = stringFlagValue(pi.getFlag("cursor-cloud-context"));
127
+ const environmentType = stringFlagValue(pi.getFlag("cursor-cloud-env-type"));
128
+ const environmentName = stringFlagValue(pi.getFlag("cursor-cloud-env-name"));
129
+ const cloudEnvNames = stringFlagValue(pi.getFlag("cursor-cloud-env"));
130
+ const cloud = {
131
+ ...(repo ? { repo } : {}),
132
+ ...(branch ? { branch } : {}),
133
+ ...(contextHandoff ? { contextHandoff } : {}),
134
+ ...(pi.getFlag("cursor-cloud-direct-push") === true ? { directPush: true } : {}),
135
+ ...(pi.getFlag("cursor-cloud-auto-create-pr") === true ? { autoCreatePR: true } : {}),
136
+ ...(pi.getFlag("cursor-cloud-skip-reviewer-request") === true ? { skipReviewerRequest: true } : {}),
137
+ ...(pi.getFlag("cursor-cloud-allow-local-state") === true ? { allowLocalState: true } : {}),
138
+ ...(pi.getFlag("cursor-cloud-env-from-files") === true ? { envFromFiles: true } : {}),
139
+ ...(environmentType || environmentName
140
+ ? { environment: { ...(environmentType ? { type: environmentType } : {}), ...(environmentName ? { name: environmentName } : {}) } }
141
+ : {}),
142
+ ...(pi.getFlag("cursor-cloud-ack") === true ? { acknowledged: true } : {}),
143
+ };
144
+ const local = {
145
+ ...(pi.getFlag("cursor-auto-review") === true ? { autoReview: true } : {}),
146
+ ...(pi.getFlag("cursor-sandbox") === true ? { sandboxOptions: { enabled: true } } : {}),
147
+ ...(!cliLocalForceConsumed && pi.getFlag("cursor-local-force") === true ? { force: true } : {}),
148
+ ...(pi.getFlag("cursor-no-local-resume") === true
149
+ ? { resume: false }
150
+ : pi.getFlag("cursor-local-resume") === true
151
+ ? { resume: true }
152
+ : {}),
153
+ };
154
+ cliCursorSnapshot = {
155
+ config: {
156
+ ...(runtime ? { runtime } : {}),
157
+ ...(Object.keys(cloud).length ? { cloud } : {}),
158
+ ...(Object.keys(local).length ? { local } : {}),
159
+ },
160
+ ...(cloudEnvNames ? { cloudEnvNames } : {}),
161
+ };
162
+ }
163
+ function persistCursorRuntimePreference(pi, runtime, cloudAcknowledged = false) {
164
+ const acknowledged = sessionCursorCloudAcknowledged || cloudAcknowledged;
165
+ pi.appendEntry(CURSOR_RUNTIME_ENTRY_TYPE, {
166
+ runtime,
167
+ ...(acknowledged ? { cloudAcknowledged: true } : {}),
168
+ });
169
+ sessionCursorRuntime = runtime;
170
+ sessionCursorCloudAcknowledged = acknowledged;
171
+ }
172
+ function registerCursorRuntimeFlags(pi) {
173
+ pi.registerFlag("cursor-runtime", {
174
+ description: `Select Cursor runtime for this run: local or cloud (or set ${CURSOR_RUNTIME_ENV})`,
175
+ type: "string",
176
+ default: "",
177
+ });
178
+ pi.registerFlag("cursor-cloud-repo", {
179
+ description: `Set Cursor cloud repository URL for this run (or set ${CURSOR_CLOUD_REPO_ENV})`,
180
+ type: "string",
181
+ default: "",
182
+ });
183
+ pi.registerFlag("cursor-cloud-branch", {
184
+ description: `Set Cursor cloud branch/ref for this run (or set ${CURSOR_CLOUD_BRANCH_ENV})`,
185
+ type: "string",
186
+ default: "",
187
+ });
188
+ pi.registerFlag("cursor-cloud-context", {
189
+ description: `Set Cursor cloud context handoff: never, fresh, or bootstrap (or set ${CURSOR_CLOUD_CONTEXT_ENV})`,
190
+ type: "string",
191
+ default: "",
192
+ });
193
+ pi.registerFlag("cursor-cloud-direct-push", {
194
+ description: `Allow Cursor cloud direct push for this run (or set ${CURSOR_CLOUD_DIRECT_PUSH_ENV}=1)`,
195
+ type: "boolean",
196
+ default: false,
197
+ });
198
+ pi.registerFlag("cursor-cloud-auto-create-pr", {
199
+ description: `Ask Cursor cloud to create a pull request for this run (or set ${CURSOR_CLOUD_AUTO_CREATE_PR_ENV}=1)`,
200
+ type: "boolean",
201
+ default: false,
202
+ });
203
+ pi.registerFlag("cursor-cloud-skip-reviewer-request", {
204
+ description: `Ask Cursor cloud not to request you as a reviewer for this run (or set ${CURSOR_CLOUD_SKIP_REVIEWER_REQUEST_ENV}=1)`,
205
+ type: "boolean",
206
+ default: false,
207
+ });
208
+ pi.registerFlag("cursor-cloud-allow-local-state", {
209
+ description: `Allow Cursor cloud to proceed with local-only state for this run (or set ${CURSOR_CLOUD_ALLOW_LOCAL_STATE_ENV}=1)`,
210
+ type: "boolean",
211
+ default: false,
212
+ });
213
+ pi.registerFlag("cursor-cloud-env", {
214
+ description: `Reserved Cursor cloud env var names; env forwarding is not implemented yet (or set ${CURSOR_CLOUD_ENV_ENV})`,
215
+ type: "string",
216
+ default: "",
217
+ });
218
+ pi.registerFlag("cursor-cloud-env-from-files", {
219
+ description: `Reserved Cursor cloud env-file forwarding flag; not implemented yet (or set ${CURSOR_CLOUD_ENV_FROM_FILES_ENV}=1)`,
220
+ type: "boolean",
221
+ default: false,
222
+ });
223
+ pi.registerFlag("cursor-cloud-env-type", {
224
+ description: `Select Cursor-managed cloud environment type: cloud, pool, or machine (or set ${CURSOR_CLOUD_ENV_TYPE_ENV})`,
225
+ type: "string",
226
+ default: "",
227
+ });
228
+ pi.registerFlag("cursor-cloud-env-name", {
229
+ description: `Select Cursor-managed cloud environment name (or set ${CURSOR_CLOUD_ENV_NAME_ENV})`,
230
+ type: "string",
231
+ default: "",
232
+ });
233
+ pi.registerFlag("cursor-cloud-ack", {
234
+ description: `Acknowledge first-use Cursor cloud runtime risks for this run (or set ${CURSOR_CLOUD_ACK_ENV}=1)`,
235
+ type: "boolean",
236
+ default: false,
237
+ });
238
+ pi.registerFlag("cursor-auto-review", {
239
+ description: `Enable Cursor SDK local Auto-review for this run (or set ${CURSOR_AUTO_REVIEW_ENV}=1)`,
240
+ type: "boolean",
241
+ default: false,
242
+ });
243
+ pi.registerFlag("cursor-sandbox", {
244
+ description: `Enable Cursor SDK local sandboxing for this run (or set ${CURSOR_SANDBOX_ENV}=1)`,
245
+ type: "boolean",
246
+ default: false,
247
+ });
248
+ pi.registerFlag("cursor-local-force", {
249
+ description: `Force-expire a stuck local Cursor SDK run before sending this run (or set ${CURSOR_LOCAL_FORCE_ENV}=1)`,
250
+ type: "boolean",
251
+ default: false,
252
+ });
253
+ pi.registerFlag("cursor-local-resume", {
254
+ description: `Resume recorded local Cursor SDK agents for matching pi session branches (default; or set ${CURSOR_LOCAL_RESUME_ENV}=1)`,
255
+ type: "boolean",
256
+ default: false,
257
+ });
258
+ pi.registerFlag("cursor-no-local-resume", {
259
+ description: `Disable local Cursor SDK agent resume for this run (or set ${CURSOR_LOCAL_RESUME_ENV}=0)`,
260
+ type: "boolean",
261
+ default: false,
262
+ });
263
+ }
264
+ async function confirmCloudAcknowledgement(ctx, alreadyAcknowledged) {
265
+ if (alreadyAcknowledged)
266
+ return true;
267
+ if (!ctx.hasUI) {
268
+ ctx.ui.notify(`Cursor cloud runtime requires first-use acknowledgement. Pass --cursor-cloud-ack or set ${CURSOR_CLOUD_ACK_ENV}=1, then retry.`, "error");
269
+ return false;
270
+ }
271
+ const confirmed = await ctx.ui.confirm("Enable Cursor Cloud runtime?", CURSOR_CLOUD_ACK_DISCLOSURE);
272
+ if (!confirmed)
273
+ ctx.ui.notify("Cursor cloud runtime change cancelled; no settings were written.", "info");
274
+ return confirmed;
275
+ }
276
+ function registerCursorRuntimeCommand(pi, refreshStatus) {
277
+ pi.registerCommand("cursor-runtime", {
278
+ description: "Set Cursor runtime for this session: local or cloud",
279
+ handler: async (args, ctx) => {
280
+ const usage = "Usage: /cursor-runtime local|cloud [--save-user|--save-project]";
281
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
282
+ const raw = tokens[0];
283
+ const saveUser = tokens.includes("--save-user");
284
+ const saveProject = tokens.includes("--save-project");
285
+ const extra = tokens.slice(1).filter((token) => token !== "--save-user" && token !== "--save-project");
286
+ const currentResolution = resolveCursorStatusRuntime(ctx);
287
+ if (!raw) {
288
+ ctx.ui.notify(currentResolution.kind === "invalid"
289
+ ? `${currentResolution.message} ${usage}`
290
+ : `Cursor runtime is ${formatResolvedCursorRuntime(currentResolution.runtime)}. ${usage}`, currentResolution.kind === "invalid" ? "error" : "info");
291
+ return;
292
+ }
293
+ if ((raw !== "local" && raw !== "cloud") || extra.length > 0 || (saveUser && saveProject)) {
294
+ ctx.ui.notify(`Invalid Cursor runtime arguments. ${usage}`, "error");
295
+ return;
296
+ }
297
+ if (currentResolution.kind === "invalid") {
298
+ ctx.ui.notify(`${currentResolution.message} Fix the explicit override before changing the session runtime.`, "error");
299
+ return;
300
+ }
301
+ if (saveProject && (getCursorSessionCwd() !== ctx.cwd || !getCursorSessionProjectTrusted())) {
302
+ ctx.ui.notify("Cannot save Cursor project config without explicit project-trust provenance. Ensure .pi/settings.json or another Pi project resource exists, trust the project, then restart pi; or restart with --approve. Project-local package installs must use --approve on every run that reads or writes .pi/cursor-sdk.json.", "error");
303
+ return;
304
+ }
305
+ const cloudAcknowledged = raw === "cloud" && await confirmCloudAcknowledgement(ctx, resolveEffectiveCursorConfigForContext(ctx).cloud.acknowledged.value);
306
+ if (raw === "cloud" && !cloudAcknowledged)
307
+ return;
308
+ if (saveUser || saveProject) {
309
+ try {
310
+ if (saveUser) {
311
+ updateCursorSdkConfig(getCursorSdkUserConfigPath(), (current) => mergeCursorSdkConfigForUpdate(current, {
312
+ runtime: raw,
313
+ ...(cloudAcknowledged ? { cloud: { acknowledged: true } } : {}),
314
+ }), { newFileMode: 0o600 });
315
+ }
316
+ else {
317
+ updateCursorSdkConfig(getCursorSdkProjectConfigPath(ctx.cwd), (current) => mergeCursorSdkConfigForUpdate(current, { runtime: raw }));
318
+ }
319
+ }
320
+ catch (error) {
321
+ const effectiveResolution = resolveCursorStatusRuntime(ctx);
322
+ refreshStatus(ctx);
323
+ const effective = effectiveResolution.kind === "valid"
324
+ ? ` Effective runtime remains ${formatResolvedCursorRuntime(effectiveResolution.runtime)}.`
325
+ : ` ${effectiveResolution.message}`;
326
+ ctx.ui.notify(`Failed to save Cursor runtime preference to ${saveUser ? "user" : "project"} config: ${error instanceof Error ? error.message : String(error)}.${effective}`, "error");
327
+ return;
328
+ }
329
+ }
330
+ try {
331
+ persistCursorRuntimePreference(pi, raw, cloudAcknowledged);
332
+ }
333
+ catch (error) {
334
+ const effectiveResolution = resolveCursorStatusRuntime(ctx);
335
+ refreshStatus(ctx);
336
+ const effective = effectiveResolution.kind === "valid"
337
+ ? ` Effective runtime is ${formatResolvedCursorRuntime(effectiveResolution.runtime)}.`
338
+ : ` ${effectiveResolution.message}`;
339
+ const persisted = saveUser
340
+ ? "User config was saved, but persisting the session runtime entry failed."
341
+ : saveProject
342
+ ? "Project config was saved, but persisting the session runtime entry failed."
343
+ : "Persisting the session runtime entry failed.";
344
+ ctx.ui.notify(`${persisted} ${error instanceof Error ? error.message : String(error)}.${effective}`, "error");
345
+ return;
346
+ }
347
+ const effectiveResolution = resolveCursorStatusRuntime(ctx);
348
+ refreshStatus(ctx);
349
+ const saved = saveUser
350
+ ? " Saved to user config."
351
+ : saveProject
352
+ ? " Saved to project config; cloud acknowledgement remains session/user-scoped."
353
+ : "";
354
+ const effective = effectiveResolution.kind === "valid"
355
+ ? ` Effective runtime is ${formatResolvedCursorRuntime(effectiveResolution.runtime)}.`
356
+ : ` ${effectiveResolution.message}`;
357
+ ctx.ui.notify(raw === "cloud"
358
+ ? `Cursor runtime request saved for this session and cloud risk acknowledged.${effective}${saved}`
359
+ : `Cursor runtime request saved for this session.${effective}${saved}`, "info");
360
+ },
361
+ });
362
+ }
363
+ export function registerCursorCloudRuntimeControls(pi, options) {
364
+ registerCursorCloudLifecycleLedger(pi);
365
+ registerCursorRuntimeFlags(pi);
366
+ registerCursorRuntimeCommand(pi, options.refreshStatus);
367
+ pi.registerCommand("cursor-cloud", {
368
+ description: "List, archive, or delete recorded Cursor cloud agents for this session branch",
369
+ handler: async (args, ctx) => {
370
+ await runCursorCloudLifecycleCommand(pi, args, ctx);
371
+ },
372
+ });
373
+ }
374
+ export function resetCursorRuntimeStateForTests() {
375
+ cliCursorSnapshot = { config: {} };
376
+ cliLocalForceConsumed = false;
377
+ envLocalForceConsumed = false;
378
+ sessionCursorRuntime = undefined;
379
+ sessionCursorCloudAcknowledged = false;
380
+ }
@@ -0,0 +1,86 @@
1
+ import { asRecord, getArray, getString } from "./cursor-record-utils.js";
2
+ import { readCursorSdkTurnUsage } from "./cursor-usage-accounting.js";
3
+ const BILLED_USAGE_TIMEOUT_MS = 5000;
4
+ // ponytail: process-lifetime watermark of billed usage UUIDs per agentId; reset on process exit.
5
+ // Upgrade to session-scoped storage if multi-day processes retain enough agent IDs to matter.
6
+ const seenBilledRunIdsByAgent = new Map();
7
+ export function isCursorSdkClientMintedRunId(runId) {
8
+ return runId.startsWith("run-");
9
+ }
10
+ export function sumCursorSdkTurnUsage(usages) {
11
+ if (usages.length === 0)
12
+ return undefined;
13
+ return usages.reduce((total, usage) => ({
14
+ inputTokens: total.inputTokens + usage.inputTokens,
15
+ outputTokens: total.outputTokens + usage.outputTokens,
16
+ cacheReadTokens: total.cacheReadTokens + usage.cacheReadTokens,
17
+ cacheWriteTokens: total.cacheWriteTokens + usage.cacheWriteTokens,
18
+ }));
19
+ }
20
+ export function peekCursorBilledUsageRunIds(agentId) {
21
+ return seenBilledRunIdsByAgent.get(agentId) ?? new Set();
22
+ }
23
+ export function rememberCursorBilledUsageRunIds(agentId, runIds) {
24
+ if (runIds.length === 0)
25
+ return;
26
+ let seen = seenBilledRunIdsByAgent.get(agentId);
27
+ if (!seen) {
28
+ seen = new Set();
29
+ seenBilledRunIdsByAgent.set(agentId, seen);
30
+ }
31
+ for (const runId of runIds)
32
+ seen.add(runId);
33
+ }
34
+ function withTimeout(promise, timeoutMs = BILLED_USAGE_TIMEOUT_MS) {
35
+ let timer;
36
+ const timeout = new Promise((resolve) => {
37
+ timer = setTimeout(() => resolve(undefined), timeoutMs);
38
+ timer.unref?.();
39
+ });
40
+ return Promise.race([promise.catch(() => undefined), timeout]).finally(() => {
41
+ if (timer)
42
+ clearTimeout(timer);
43
+ });
44
+ }
45
+ export function selectCursorBilledTurnUsage(agentUsage, options) {
46
+ const runs = (getArray(asRecord(agentUsage), "runs") ?? []).flatMap((item) => {
47
+ const record = asRecord(item);
48
+ const runId = getString(record, "runId");
49
+ const usage = readCursorSdkTurnUsage(record?.usage);
50
+ return runId && usage ? [{ runId, usage }] : [];
51
+ });
52
+ if (options.runtime === "cloud" && options.runId) {
53
+ const match = runs.find((run) => run.runId === options.runId);
54
+ return match ? { turn: match.usage, runIds: [match.runId] } : { runIds: [] };
55
+ }
56
+ const unseen = runs.filter((run) => !options.seenRunIds?.has(run.runId));
57
+ return { turn: sumCursorSdkTurnUsage(unseen.map((run) => run.usage)), runIds: unseen.map((run) => run.runId) };
58
+ }
59
+ export async function fetchCursorSdkAgentUsage(agent, options) {
60
+ if (typeof agent.getUsage !== "function")
61
+ return undefined;
62
+ const query = options.runtime === "cloud" && options.runId && isCursorSdkClientMintedRunId(options.runId)
63
+ ? { runId: options.runId }
64
+ : undefined;
65
+ return withTimeout(Promise.resolve(agent.getUsage(query)));
66
+ }
67
+ export async function attachCursorSdkBilledTurnUsage(options) {
68
+ const agentUsage = await fetchCursorSdkAgentUsage(options.agent, {
69
+ runtime: options.runtime,
70
+ runId: options.runId,
71
+ });
72
+ if (!agentUsage)
73
+ return {};
74
+ const selected = selectCursorBilledTurnUsage(agentUsage, {
75
+ runtime: options.runtime,
76
+ runId: options.runtime === "cloud" ? options.runId : undefined,
77
+ seenRunIds: peekCursorBilledUsageRunIds(options.agentId),
78
+ });
79
+ rememberCursorBilledUsageRunIds(options.agentId, selected.runIds);
80
+ return { agentUsage, turn: selected.turn };
81
+ }
82
+ export const __testUtils = {
83
+ reset() {
84
+ seenBilledRunIdsByAgent.clear();
85
+ },
86
+ };
@@ -0,0 +1,33 @@
1
+ import { resolve } from "node:path";
2
+ import { CURSOR_SDK_EVENT_DEBUG_DIR_ENV, CURSOR_SDK_EVENT_DEBUG_ENV, CURSOR_SDK_EVENT_DEBUG_RUN_DIR_ENV, CURSOR_SDK_EVENT_DEBUG_SESSION_DIR_ENV, CURSOR_SDK_EVENT_DEBUG_STDERR_ENV, } from "../shared/cursor-sdk-event-debug-env.mjs";
3
+ export { CURSOR_SDK_EVENT_DEBUG_DIR_ENV, CURSOR_SDK_EVENT_DEBUG_ENV, CURSOR_SDK_EVENT_DEBUG_RUN_DIR_ENV, CURSOR_SDK_EVENT_DEBUG_SESSION_DIR_ENV, CURSOR_SDK_EVENT_DEBUG_STDERR_ENV, };
4
+ export const CURSOR_SDK_EVENT_DEBUG_LOG_PREFIX = "[pi-cursor-sdk:sdk-events]";
5
+ export const SESSION_MANIFEST = "session.json";
6
+ export const SESSION_PI_SESSION_SNAPSHOT = "pi-session.jsonl";
7
+ export const ARTIFACTS = {
8
+ metadata: "metadata.json",
9
+ sendPayload: "send-payload.json",
10
+ contextSnapshot: "context-snapshot.json",
11
+ onDelta: "on-delta.jsonl",
12
+ onStep: "on-step.jsonl",
13
+ streamEvents: "stream-events.jsonl",
14
+ piStreamEvents: "pi-stream-events.jsonl",
15
+ providerEvents: "provider-events.jsonl",
16
+ liveRunEvents: "live-run-events.jsonl",
17
+ bridgeEvents: "bridge-events.jsonl",
18
+ bridgeRaw: "bridge-raw.jsonl",
19
+ displayDecisions: "display-decisions.jsonl",
20
+ coordinatorEvents: "coordinator-events.jsonl",
21
+ drainEvents: "drain-events.jsonl",
22
+ timeline: "timeline.jsonl",
23
+ piSessionSnapshot: "pi-session-snapshot.jsonl",
24
+ finalPartial: "final-partial.json",
25
+ errors: "errors.jsonl",
26
+ waitResult: "wait-result.json",
27
+ conversation: "conversation.json",
28
+ summary: "summary.json",
29
+ };
30
+ export function resolveCursorSdkEventDebugBaseDir(cwd, env = process.env) {
31
+ const raw = env[CURSOR_SDK_EVENT_DEBUG_DIR_ENV]?.trim();
32
+ return resolve(cwd, raw || ".debug/cursor-sdk-events");
33
+ }