@emmaneugene/pi-cursor-sdk 0.3.7 → 0.4.0

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 (79) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +99 -126
  3. package/dist/cursor-agents-context-registration.js +1 -3
  4. package/dist/cursor-agents-context.js +2 -2
  5. package/dist/cursor-config.js +2 -290
  6. package/dist/cursor-provider-errors.js +3 -58
  7. package/dist/cursor-provider-live-run-drain.js +0 -3
  8. package/dist/cursor-provider-run-finalizer.js +4 -7
  9. package/dist/cursor-provider-run-outcome.js +1 -1
  10. package/dist/cursor-provider-turn-finalize.js +3 -55
  11. package/dist/cursor-provider-turn-prepare.js +8 -144
  12. package/dist/cursor-provider-turn-runner.js +9 -23
  13. package/dist/cursor-provider-turn-send.js +8 -35
  14. package/dist/cursor-runtime-state.js +6 -268
  15. package/dist/cursor-sdk-billed-usage.js +3 -18
  16. package/dist/cursor-sdk-platform-package.js +88 -0
  17. package/dist/cursor-session-agent.js +18 -14
  18. package/dist/cursor-skill-tool.js +12 -22
  19. package/dist/cursor-state.js +5 -8
  20. package/dist/cursor-usage-accounting.js +1 -1
  21. package/docs/cursor-dogfood-checklist.md +4 -4
  22. package/docs/cursor-live-smoke-checklist.md +23 -23
  23. package/docs/cursor-model-ux-spec.md +27 -30
  24. package/docs/cursor-native-tool-replay.md +4 -4
  25. package/docs/cursor-native-tool-visual-audit.md +7 -7
  26. package/docs/cursor-testing-lessons.md +23 -16
  27. package/docs/cursor-tool-surfaces.md +2 -4
  28. package/docs/platform-smoke-implementation.md +5 -5
  29. package/docs/platform-smoke.md +27 -72
  30. package/package.json +3 -17
  31. package/platform-smoke.config.mjs +2 -1
  32. package/scripts/lib/local-resume-smoke-harness.mjs +0 -18
  33. package/scripts/platform-smoke/card-detect.mjs +1 -1
  34. package/scripts/platform-smoke/local-resume-runner.mjs +1 -1
  35. package/scripts/platform-smoke/scenarios.mjs +1 -1
  36. package/scripts/platform-smoke/targets.mjs +14 -5
  37. package/src/cursor-agents-context-registration.ts +0 -5
  38. package/src/cursor-agents-context.ts +1 -3
  39. package/src/cursor-config.ts +8 -379
  40. package/src/cursor-provider-errors.ts +2 -62
  41. package/src/cursor-provider-live-run-drain.ts +0 -3
  42. package/src/cursor-provider-run-finalizer.ts +4 -12
  43. package/src/cursor-provider-run-outcome.ts +0 -3
  44. package/src/cursor-provider-turn-finalize.ts +3 -61
  45. package/src/cursor-provider-turn-prepare.ts +8 -165
  46. package/src/cursor-provider-turn-runner.ts +15 -31
  47. package/src/cursor-provider-turn-send.ts +7 -38
  48. package/src/cursor-provider-turn-types.ts +9 -30
  49. package/src/cursor-runtime-state.ts +6 -345
  50. package/src/cursor-sdk-billed-usage.ts +3 -24
  51. package/src/cursor-sdk-platform-package.ts +106 -0
  52. package/src/cursor-session-agent.ts +16 -12
  53. package/src/cursor-skill-tool.ts +10 -26
  54. package/src/cursor-state.ts +5 -10
  55. package/src/cursor-usage-accounting.ts +1 -3
  56. package/dist/cursor-cloud-lifecycle.js +0 -650
  57. package/dist/cursor-cloud-local-state.js +0 -460
  58. package/dist/cursor-cloud-options.js +0 -145
  59. package/dist/cursor-cloud-reporting.js +0 -222
  60. package/dist/cursor-ripgrep-path.js +0 -27
  61. package/scripts/cloud-runtime-smoke.d.mts +0 -42
  62. package/scripts/cloud-runtime-smoke.mjs +0 -623
  63. package/scripts/lib/cloud-smoke-artifacts.d.mts +0 -16
  64. package/scripts/lib/cloud-smoke-artifacts.mjs +0 -94
  65. package/scripts/lib/cloud-smoke-cleanup-evidence.d.mts +0 -209
  66. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +0 -585
  67. package/scripts/lib/cloud-smoke-github.d.mts +0 -78
  68. package/scripts/lib/cloud-smoke-github.mjs +0 -331
  69. package/scripts/lib/cloud-smoke-pi-runner.d.mts +0 -42
  70. package/scripts/lib/cloud-smoke-pi-runner.mjs +0 -214
  71. package/scripts/lib/cloud-smoke-shutdown.d.mts +0 -67
  72. package/scripts/lib/cloud-smoke-shutdown.mjs +0 -133
  73. package/shared/cursor-cloud-lifecycle-constants.d.mts +0 -3
  74. package/shared/cursor-cloud-lifecycle-constants.mjs +0 -7
  75. package/src/cursor-cloud-lifecycle.ts +0 -733
  76. package/src/cursor-cloud-local-state.ts +0 -536
  77. package/src/cursor-cloud-options.ts +0 -182
  78. package/src/cursor-cloud-reporting.ts +0 -267
  79. package/src/cursor-ripgrep-path.ts +0 -32
@@ -1,182 +0,0 @@
1
- import type { AgentModeOption, AgentOptions, ModelSelection } from "@cursor/sdk";
2
- import { isCursorCloudEnvironmentType, type CursorResolvedSdkConfig } from "./cursor-config.js";
3
- import {
4
- normalizeCursorCloudStartingRef,
5
- parseCursorCloudRepositoryUrl,
6
- type CursorCloudLocalState,
7
- type CursorCloudLocalStateUnknownReason,
8
- } from "./cursor-cloud-local-state.js";
9
-
10
- export interface CursorCloudPreflightIssue {
11
- code:
12
- | "cloud_ack_required"
13
- | "context_handoff_required"
14
- | "local_state_not_allowed"
15
- | "env_forwarding_not_implemented"
16
- | "cloud_environment_type_invalid"
17
- | "cloud_environment_type_required"
18
- | "cloud_environment_repo_conflict"
19
- | "cloud_branch_repo_required"
20
- | "cloud_branch_invalid"
21
- | "cloud_repo_invalid";
22
- message: string;
23
- }
24
-
25
- export interface CursorCloudPreflightResult {
26
- ok: boolean;
27
- issues: CursorCloudPreflightIssue[];
28
- }
29
-
30
- const CLOUD_REPO_URL_MESSAGE = "Cursor cloud repository must be an HTTPS repository URL without embedded credentials, query parameters, or fragments.";
31
- const CLOUD_STARTING_REF_MESSAGE = "Cursor cloud branch/ref must be a valid Git branch name, refs/heads/<branch>, or full commit SHA; invalid branch names and other refs/* are unsupported.";
32
- const CLOUD_LOCAL_STATE_REASON_LABELS: Record<CursorCloudLocalStateUnknownReason["code"], string> = {
33
- bare_repo: "the repository is bare",
34
- repository_detection_failed: "Git could not determine whether the working directory is a repository",
35
- status_failed: "Git could not determine whether the worktree or index is clean",
36
- index_failed: "Git index inspection failed",
37
- hidden_index_state: "the index hides worktree changes",
38
- history_probe_failed: "Git could not inspect replacement or graft history overrides",
39
- history_overrides: "replacement refs or graft metadata are present",
40
- head_unavailable: "local HEAD is unavailable",
41
- unverified_target: "the cloud target has no locally verified tracking ref",
42
- target_probe_failed: "Git failed while resolving the cloud target",
43
- comparison_failed: "the local HEAD comparison failed",
44
- };
45
-
46
- export function buildCursorCloudAgentOptions(options: {
47
- apiKey: string;
48
- modelSelection: ModelSelection;
49
- agentMode: AgentModeOption;
50
- resolvedConfig: CursorResolvedSdkConfig;
51
- name?: string;
52
- }): AgentOptions {
53
- const { resolvedConfig } = options;
54
- const environment = resolvedConfig.cloud.environment.value;
55
- const environmentType = environment?.type;
56
- const environmentName = environment?.name;
57
- const configuredRepo = resolvedConfig.cloud.repo.value;
58
- const repo = parseCursorCloudRepositoryUrl(configuredRepo);
59
- if (configuredRepo && !repo) throw new Error(CLOUD_REPO_URL_MESSAGE);
60
- const startingRef = normalizeCursorCloudStartingRef(resolvedConfig.cloud.branch.value);
61
- if (startingRef.kind === "unsupported") throw new Error(CLOUD_STARTING_REF_MESSAGE);
62
- const cloud = {
63
- ...(isCursorCloudEnvironmentType(environmentType)
64
- ? {
65
- env: {
66
- type: environmentType,
67
- ...(environmentName ? { name: environmentName } : {}),
68
- },
69
- }
70
- : {}),
71
- ...(repo
72
- ? {
73
- repos: [
74
- {
75
- url: repo,
76
- ...(startingRef.kind === "branch" || startingRef.kind === "commit" ? { startingRef: startingRef.value } : {}),
77
- },
78
- ],
79
- }
80
- : {}),
81
- ...(resolvedConfig.cloud.directPush.value ? { workOnCurrentBranch: true } : {}),
82
- ...(resolvedConfig.cloud.autoCreatePR.source !== "builtin"
83
- ? { autoCreatePR: resolvedConfig.cloud.autoCreatePR.value }
84
- : {}),
85
- ...(resolvedConfig.cloud.skipReviewerRequest.source !== "builtin"
86
- ? { skipReviewerRequest: resolvedConfig.cloud.skipReviewerRequest.value }
87
- : {}),
88
- };
89
- return {
90
- apiKey: options.apiKey,
91
- model: options.modelSelection,
92
- mode: options.agentMode,
93
- ...(options.name ? { name: options.name } : {}),
94
- cloud,
95
- };
96
- }
97
-
98
- export function preflightCursorCloudRuntime(options: {
99
- resolvedConfig: CursorResolvedSdkConfig;
100
- localState: CursorCloudLocalState;
101
- hasPriorContext?: boolean;
102
- }): CursorCloudPreflightResult {
103
- const { resolvedConfig } = options;
104
- const issues: CursorCloudPreflightIssue[] = [];
105
- if (!resolvedConfig.cloud.acknowledged.value) {
106
- issues.push({
107
- code: "cloud_ack_required",
108
- message: "Cursor cloud runtime requires first-use acknowledgement; run /cursor-runtime cloud in an interactive session or pass --cursor-cloud-ack / PI_CURSOR_CLOUD_ACK=1.",
109
- });
110
- }
111
- if (options.hasPriorContext && resolvedConfig.cloud.contextHandoff.value === "never") {
112
- issues.push({
113
- code: "context_handoff_required",
114
- message: "Cursor cloud runtime needs --cursor-cloud-context=fresh or --cursor-cloud-context=bootstrap for sessions with prior pi context.",
115
- });
116
- }
117
- if (
118
- options.localState.insideGitRepo !== false &&
119
- (options.localState.dirty !== false || options.localState.comparison !== "contains_head") &&
120
- !resolvedConfig.cloud.allowLocalState.value
121
- ) {
122
- const causes: string[] = [];
123
- if (options.localState.dirty === true) causes.push("the worktree or index is dirty");
124
- if (options.localState.dirty === "unknown") causes.push("Git could not determine whether the worktree or index is clean");
125
- if (options.localState.comparison === "unpushed") causes.push("local HEAD contains unpushed commits");
126
- if (options.localState.comparison === "unknown") {
127
- causes.push(...options.localState.reasons
128
- .filter((reason) => reason.code !== "status_failed")
129
- .map((reason) => CLOUD_LOCAL_STATE_REASON_LABELS[reason.code]));
130
- }
131
- issues.push({
132
- code: "local_state_not_allowed",
133
- message: `Cursor cloud runtime cannot safely omit local state because ${causes.join("; ")}. Configure an explicit repository branch/ref with current local tracking evidence, or pass --cursor-cloud-allow-local-state only after accepting that risk.`,
134
- });
135
- }
136
- if (resolvedConfig.cloud.envNames.value.length > 0 || resolvedConfig.cloud.envFromFiles.value) {
137
- issues.push({
138
- code: "env_forwarding_not_implemented",
139
- message: "Cursor cloud env forwarding is not implemented; use Cursor-native environment setup such as .cursor/environment.json or dashboard-managed secrets.",
140
- });
141
- }
142
- if (resolvedConfig.cloud.repo.value && !parseCursorCloudRepositoryUrl(resolvedConfig.cloud.repo.value)) {
143
- issues.push({
144
- code: "cloud_repo_invalid",
145
- message: CLOUD_REPO_URL_MESSAGE,
146
- });
147
- }
148
- if (resolvedConfig.cloud.branch.value && !resolvedConfig.cloud.repo.value) {
149
- issues.push({
150
- code: "cloud_branch_repo_required",
151
- message: "Cursor cloud branch/ref requires --cursor-cloud-repo because the installed Cursor SDK supports startingRef only on cloud.repos entries.",
152
- });
153
- }
154
- if (normalizeCursorCloudStartingRef(resolvedConfig.cloud.branch.value).kind === "unsupported") {
155
- issues.push({ code: "cloud_branch_invalid", message: CLOUD_STARTING_REF_MESSAGE });
156
- }
157
- const environment = resolvedConfig.cloud.environment.value;
158
- if (environment?.type && !isCursorCloudEnvironmentType(environment.type)) {
159
- issues.push({
160
- code: "cloud_environment_type_invalid",
161
- message: `Invalid Cursor cloud environment type "${environment.type}"; expected cloud, pool, or machine.`,
162
- });
163
- }
164
- if (environment?.name && !environment.type) {
165
- issues.push({
166
- code: "cloud_environment_type_required",
167
- message: "Cursor cloud environment name requires --cursor-cloud-env-type=cloud|pool|machine or PI_CURSOR_CLOUD_ENV_TYPE.",
168
- });
169
- }
170
- if (environment?.type === "cloud" && environment.name && resolvedConfig.cloud.repo.value) {
171
- issues.push({
172
- code: "cloud_environment_repo_conflict",
173
- message: "Cursor cloud named environments cannot be combined with --cursor-cloud-repo; omit the repo or use a pool/machine environment.",
174
- });
175
- }
176
- return { ok: issues.length === 0, issues };
177
- }
178
-
179
- export function formatCursorCloudPreflightError(result: CursorCloudPreflightResult): string {
180
- const details = result.issues.map((issue) => `- ${issue.message}`).join("\n");
181
- return `Cursor cloud runtime is not ready to start.\n${details}\nUse --cursor-runtime local or /cursor-runtime local to run with the local Cursor SDK agent.`;
182
- }
@@ -1,267 +0,0 @@
1
- import type { Run, RunResult, SDKAgent, SDKArtifact, TokenUsage } from "@cursor/sdk";
2
- import { truncateCursorDisplayLine } from "./cursor-display-text.js";
3
- import { scrubSensitiveText } from "./cursor-sensitive-text.js";
4
- import { asRecord, getArray, getNumber, getRecord, getString } from "./cursor-record-utils.js";
5
-
6
- const DEFAULT_CURSOR_CLOUD_API_BASE_URL = "https://api.cursor.com";
7
- const CLOUD_REPORT_TIMEOUT_MS = 5000;
8
- const MAX_CLOUD_ID_DISPLAY_LENGTH = 160;
9
- const MAX_CLOUD_REPO_DISPLAY_LENGTH = 240;
10
- const MAX_CLOUD_BRANCH_DISPLAY_LENGTH = 160;
11
- const MAX_CLOUD_ARTIFACT_DISPLAY_LENGTH = 240;
12
- export const MAX_CLOUD_REPORT_BRANCHES = 5;
13
- export const MAX_CLOUD_REPORT_ARTIFACTS = 10;
14
-
15
- export interface CursorCloudUsageReport {
16
- totalUsage?: TokenUsage;
17
- runUsage?: TokenUsage;
18
- }
19
-
20
- export interface CursorCloudRunBranch {
21
- repoUrl: string;
22
- branch?: string;
23
- prUrl?: string;
24
- }
25
-
26
- export interface CursorCloudRunReport {
27
- agentId: string;
28
- runId: string;
29
- branches: CursorCloudRunBranch[];
30
- artifacts?: SDKArtifact[];
31
- usage?: CursorCloudUsageReport;
32
- }
33
-
34
- export interface FetchCursorCloudRawUsageOptions {
35
- agentId: string;
36
- runId?: string;
37
- apiKey: string | undefined;
38
- baseUrl?: string;
39
- fetchImpl?: typeof fetch;
40
- timeoutMs?: number;
41
- }
42
-
43
- function withTimeout<T>(promise: Promise<T>, timeoutMs = CLOUD_REPORT_TIMEOUT_MS): Promise<T | undefined> {
44
- let timer: ReturnType<typeof setTimeout> | undefined;
45
- const timeout = new Promise<undefined>((resolve) => {
46
- timer = setTimeout(() => resolve(undefined), timeoutMs);
47
- timer.unref?.();
48
- });
49
- return Promise.race([promise.catch(() => undefined), timeout]).finally(() => {
50
- if (timer) clearTimeout(timer);
51
- });
52
- }
53
-
54
- function fetchJsonWithAbortTimeout(
55
- fetchImpl: typeof fetch,
56
- url: URL,
57
- init: RequestInit,
58
- timeoutMs = CLOUD_REPORT_TIMEOUT_MS,
59
- ): Promise<unknown> {
60
- const controller = new AbortController();
61
- let timer: ReturnType<typeof setTimeout> | undefined;
62
- const request = (async () => {
63
- const response = await fetchImpl(url, { ...init, signal: controller.signal });
64
- if (!response.ok) return undefined;
65
- return response.json();
66
- })().catch(() => undefined);
67
- const timeout = new Promise<undefined>((resolve) => {
68
- timer = setTimeout(() => {
69
- controller.abort();
70
- resolve(undefined);
71
- }, timeoutMs);
72
- timer.unref?.();
73
- });
74
- return Promise.race([request, timeout]).finally(() => {
75
- if (timer) clearTimeout(timer);
76
- });
77
- }
78
-
79
- function readNonnegativeNumber(record: Record<string, unknown> | undefined, key: string): number | undefined {
80
- const value = getNumber(record, key);
81
- return value !== undefined && Number.isFinite(value) && value >= 0 ? value : undefined;
82
- }
83
-
84
- function readTokenUsage(value: unknown): TokenUsage | undefined {
85
- const record = asRecord(value);
86
- const inputTokens = readNonnegativeNumber(record, "inputTokens");
87
- const outputTokens = readNonnegativeNumber(record, "outputTokens");
88
- const cacheReadTokens = readNonnegativeNumber(record, "cacheReadTokens");
89
- const cacheWriteTokens = readNonnegativeNumber(record, "cacheWriteTokens");
90
- const totalTokens = readNonnegativeNumber(record, "totalTokens");
91
- if (
92
- inputTokens === undefined ||
93
- outputTokens === undefined ||
94
- cacheReadTokens === undefined ||
95
- cacheWriteTokens === undefined ||
96
- totalTokens === undefined
97
- ) return undefined;
98
- const reasoningTokens = readNonnegativeNumber(record, "reasoningTokens");
99
- return {
100
- inputTokens,
101
- outputTokens,
102
- cacheReadTokens,
103
- cacheWriteTokens,
104
- totalTokens,
105
- ...(reasoningTokens === undefined ? {} : { reasoningTokens }),
106
- };
107
- }
108
-
109
- function readNonemptyString(record: Record<string, unknown> | undefined, key: string): string | undefined {
110
- const value = getString(record, key);
111
- return value && truncateCursorDisplayLine(value) ? value : undefined;
112
- }
113
-
114
- function normalizeBranches(value: unknown): CursorCloudRunBranch[] {
115
- if (!Array.isArray(value)) return [];
116
- return value.flatMap((item) => {
117
- const record = asRecord(item);
118
- const repoUrl = readNonemptyString(record, "repoUrl");
119
- if (!repoUrl) return [];
120
- const branch = readNonemptyString(record, "branch");
121
- const prUrl = readNonemptyString(record, "prUrl");
122
- return branch || prUrl ? [{ repoUrl, ...(branch ? { branch } : {}), ...(prUrl ? { prUrl } : {}) }] : [];
123
- });
124
- }
125
-
126
- function normalizeArtifacts(value: unknown): SDKArtifact[] | undefined {
127
- if (value === undefined) return undefined;
128
- if (!Array.isArray(value)) return undefined;
129
- return value.flatMap((item) => {
130
- const record = asRecord(item);
131
- const path = readNonemptyString(record, "path");
132
- const sizeBytes = readNonnegativeNumber(record, "sizeBytes");
133
- const updatedAt = readNonemptyString(record, "updatedAt");
134
- return path && sizeBytes !== undefined && updatedAt ? [{ path, sizeBytes, updatedAt }] : [];
135
- });
136
- }
137
-
138
- function normalizeUsage(value: unknown): CursorCloudUsageReport | undefined {
139
- const record = asRecord(value);
140
- const totalUsage = readTokenUsage(getRecord(record, "totalUsage"));
141
- const runUsage = readTokenUsage(getRecord(record, "runUsage"));
142
- return totalUsage || runUsage ? { ...(totalUsage ? { totalUsage } : {}), ...(runUsage ? { runUsage } : {}) } : undefined;
143
- }
144
-
145
- export async function fetchCursorCloudRawUsage(
146
- options: FetchCursorCloudRawUsageOptions,
147
- ): Promise<CursorCloudUsageReport | undefined> {
148
- if (!options.apiKey || typeof options.fetchImpl !== "function" && typeof fetch !== "function") return undefined;
149
- try {
150
- const baseUrl = options.baseUrl ?? DEFAULT_CURSOR_CLOUD_API_BASE_URL;
151
- const url = new URL(`/v1/agents/${encodeURIComponent(options.agentId)}/usage`, baseUrl);
152
- if (options.runId) url.searchParams.set("runId", options.runId);
153
- const fetchImpl = options.fetchImpl ?? fetch;
154
- const body = asRecord(await fetchJsonWithAbortTimeout(fetchImpl, url, {
155
- headers: {
156
- Authorization: `Bearer ${options.apiKey}`,
157
- "x-cursor-client-type": "sdk",
158
- },
159
- }, options.timeoutMs));
160
- const runs = getArray(body, "runs") ?? [];
161
- const matchingRun = options.runId
162
- ? runs.map(asRecord).find((run) => getString(run, "id") === options.runId)
163
- : undefined;
164
- return normalizeUsage({
165
- totalUsage: getRecord(body, "totalUsage"),
166
- runUsage: getRecord(matchingRun, "usage"),
167
- });
168
- } catch {
169
- return undefined;
170
- }
171
- }
172
-
173
- function mapCursorSdkAgentUsageToCloudReport(value: unknown, runId?: string): CursorCloudUsageReport | undefined {
174
- const record = asRecord(value);
175
- const runs = (getArray(record, "runs") ?? []).map(asRecord);
176
- const matchingRun = runId ? runs.find((run) => getString(run, "runId") === runId) : undefined;
177
- return normalizeUsage({
178
- totalUsage: getRecord(record, "usage"),
179
- runUsage: getRecord(matchingRun, "usage"),
180
- });
181
- }
182
-
183
- export async function collectCursorCloudRunReport(options: {
184
- agent: SDKAgent;
185
- run: Run;
186
- waitResult: RunResult;
187
- apiKey: string | undefined;
188
- agentUsage?: unknown;
189
- }): Promise<CursorCloudRunReport> {
190
- const listArtifacts = options.agent.listArtifacts;
191
- const sdkUsage =
192
- options.agentUsage !== undefined
193
- ? mapCursorSdkAgentUsageToCloudReport(options.agentUsage, options.run.id)
194
- : typeof options.agent.getUsage === "function"
195
- ? mapCursorSdkAgentUsageToCloudReport(
196
- await withTimeout(Promise.resolve(options.agent.getUsage({ runId: options.run.id }))),
197
- options.run.id,
198
- )
199
- : undefined;
200
- const [artifactResult, restUsage] = await Promise.all([
201
- typeof listArtifacts === "function" ? withTimeout(listArtifacts.call(options.agent)) : undefined,
202
- sdkUsage ? undefined : fetchCursorCloudRawUsage({ agentId: options.run.agentId, runId: options.run.id, apiKey: options.apiKey }),
203
- ]);
204
- const usage = sdkUsage ?? restUsage;
205
- const waitBranches = getArray(getRecord(asRecord(options.waitResult), "git"), "branches");
206
- const runBranches = getArray(getRecord(asRecord(options.run), "git"), "branches");
207
- return {
208
- agentId: options.run.agentId,
209
- runId: options.run.id,
210
- branches: normalizeBranches(waitBranches ?? runBranches),
211
- artifacts: normalizeArtifacts(artifactResult),
212
- usage: normalizeUsage(usage),
213
- };
214
- }
215
-
216
- function formatTokenUsage(usage: TokenUsage): string {
217
- const parts = [
218
- `input ${usage.inputTokens.toLocaleString("en-US")}`,
219
- `output ${usage.outputTokens.toLocaleString("en-US")}`,
220
- `cache read ${usage.cacheReadTokens.toLocaleString("en-US")}`,
221
- `cache write ${usage.cacheWriteTokens.toLocaleString("en-US")}`,
222
- `total ${usage.totalTokens.toLocaleString("en-US")}`,
223
- ];
224
- if (usage.reasoningTokens !== undefined) parts.splice(2, 0, `reasoning ${usage.reasoningTokens.toLocaleString("en-US")}`);
225
- return parts.join(", ");
226
- }
227
-
228
- function remoteDisplayString(value: unknown, maxLength: number, apiKey: string | undefined): string {
229
- return typeof value === "string" ? truncateCursorDisplayLine(scrubSensitiveText(value, apiKey), maxLength) : "";
230
- }
231
-
232
- function formatBranchLine(branch: CursorCloudRunBranch, apiKey: string | undefined): string[] {
233
- const lines: string[] = [];
234
- const branchName = remoteDisplayString(branch.branch, MAX_CLOUD_BRANCH_DISPLAY_LENGTH, apiKey);
235
- const repoUrl = remoteDisplayString(branch.repoUrl, MAX_CLOUD_REPO_DISPLAY_LENGTH, apiKey);
236
- const prUrl = remoteDisplayString(branch.prUrl, MAX_CLOUD_REPO_DISPLAY_LENGTH, apiKey);
237
- if (branchName) lines.push(`- branch: ${branchName}${repoUrl ? ` (${repoUrl})` : ""}`);
238
- if (prUrl) lines.push(`- PR: ${prUrl}`);
239
- return lines;
240
- }
241
-
242
- export function formatCursorCloudRunReport(report: CursorCloudRunReport, options: { apiKey?: string } = {}): string {
243
- const agentId = remoteDisplayString(report.agentId, MAX_CLOUD_ID_DISPLAY_LENGTH, options.apiKey) || "unknown";
244
- const runId = remoteDisplayString(report.runId, MAX_CLOUD_ID_DISPLAY_LENGTH, options.apiKey) || "unknown";
245
- const branches = normalizeBranches(report.branches);
246
- const artifacts = normalizeArtifacts(report.artifacts);
247
- const lines = ["Cursor cloud run:", `- agent: ${agentId}`, `- run: ${runId}`];
248
- for (const branch of branches.slice(0, MAX_CLOUD_REPORT_BRANCHES)) lines.push(...formatBranchLine(branch, options.apiKey));
249
- if (branches.length > MAX_CLOUD_REPORT_BRANCHES) lines.push(`- branches: +${branches.length - MAX_CLOUD_REPORT_BRANCHES} more`);
250
- if (artifacts) {
251
- if (artifacts.length === 0) {
252
- lines.push("- artifacts: none");
253
- } else {
254
- lines.push("- artifacts:");
255
- for (const artifact of artifacts.slice(0, MAX_CLOUD_REPORT_ARTIFACTS)) {
256
- const path = remoteDisplayString(artifact.path, MAX_CLOUD_ARTIFACT_DISPLAY_LENGTH, options.apiKey);
257
- const updatedAt = remoteDisplayString(artifact.updatedAt, MAX_CLOUD_ARTIFACT_DISPLAY_LENGTH, options.apiKey);
258
- lines.push(` - ${path} (${artifact.sizeBytes.toLocaleString("en-US")} bytes, updated ${updatedAt})`);
259
- }
260
- if (artifacts.length > MAX_CLOUD_REPORT_ARTIFACTS) lines.push(` - +${artifacts.length - MAX_CLOUD_REPORT_ARTIFACTS} more`);
261
- }
262
- }
263
- const normalizedUsage = normalizeUsage(report.usage);
264
- const usage = normalizedUsage?.runUsage ?? normalizedUsage?.totalUsage;
265
- if (usage) lines.push(`- raw usage (display only): ${formatTokenUsage(usage)}`);
266
- return scrubSensitiveText(`${lines.join("\n")}\n`, options.apiKey);
267
- }
@@ -1,32 +0,0 @@
1
- import { accessSync, constants } from "node:fs";
2
- import { createRequire } from "node:module";
3
- import { dirname, isAbsolute, join } from "node:path";
4
-
5
- const RIPGREP_ENV = "CURSOR_RIPGREP_PATH";
6
-
7
- export function resolveBundledCursorRipgrepPath(
8
- fromModuleUrl: string | URL = import.meta.url,
9
- ): string | undefined {
10
- try {
11
- const require = createRequire(fromModuleUrl);
12
- const platformPackage = `@cursor/sdk-${process.platform}-${process.arch}`;
13
- const sdkEntry = require.resolve("@cursor/sdk");
14
- const packageDirectory = dirname(
15
- require.resolve(`${platformPackage}/package.json`, { paths: [dirname(sdkEntry)] }),
16
- );
17
- const ripgrepPath = join(packageDirectory, "bin", process.platform === "win32" ? "rg.exe" : "rg");
18
- accessSync(ripgrepPath, constants.X_OK);
19
- return ripgrepPath;
20
- } catch {
21
- return undefined;
22
- }
23
- }
24
-
25
- export function ensureCursorRipgrepPath(): string | undefined {
26
- const configuredPath = process.env[RIPGREP_ENV];
27
- if (configuredPath && isAbsolute(configuredPath)) return configuredPath;
28
-
29
- const bundledPath = resolveBundledCursorRipgrepPath();
30
- if (bundledPath) process.env[RIPGREP_ENV] = bundledPath;
31
- return bundledPath;
32
- }