@rahularya01/pi-cursor 1.1.0 → 1.2.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.
- package/CHANGELOG.md +11 -0
- package/dist/index.js +27 -0
- package/package.json +8 -5
- package/src/auth/cli-credentials.ts +0 -193
- package/src/auth/consent.ts +0 -27
- package/src/auth/index.ts +0 -22
- package/src/auth/oauth.ts +0 -214
- package/src/client/bridge.ts +0 -206
- package/src/client/cursor-wire.ts +0 -212
- package/src/client/index.ts +0 -19
- package/src/diagnostics/diagnostics.ts +0 -79
- package/src/diagnostics/index.ts +0 -1
- package/src/index.ts +0 -1436
- package/src/models/catalog.json +0 -1163
- package/src/models/index.ts +0 -2
- package/src/proto/agent_pb.ts +0 -15294
- package/src/stream/config.ts +0 -69
- package/src/stream/context-normalize.ts +0 -104
- package/src/stream/index.ts +0 -42
- package/src/stream/model-routing.ts +0 -100
- package/src/stream/native-core.ts +0 -5401
- package/src/stream/protocol.ts +0 -41
- package/src/stream/recovery.ts +0 -454
- package/src/usage.ts +0 -262
- package/src/utils/index.ts +0 -2
- package/src/utils/security.ts +0 -65
- package/src/utils/util.ts +0 -19
- package/tsconfig.json +0 -21
- /package/{src/client → dist}/h2-bridge.mjs +0 -0
package/src/usage.ts
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { cursorEnv, isRecord } from "./utils/util.js";
|
|
2
|
-
|
|
3
|
-
export interface CursorUsageSummary {
|
|
4
|
-
billingCycleStart?: string;
|
|
5
|
-
billingCycleEnd?: string;
|
|
6
|
-
membershipType?: string;
|
|
7
|
-
limitType?: string;
|
|
8
|
-
isUnlimited?: boolean;
|
|
9
|
-
individualUsage?: {
|
|
10
|
-
plan?: UsageBucket;
|
|
11
|
-
onDemand?: UsageBucket;
|
|
12
|
-
};
|
|
13
|
-
teamUsage?: {
|
|
14
|
-
onDemand?: UsageBucket;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface UsageBucket {
|
|
19
|
-
enabled?: boolean;
|
|
20
|
-
used?: number | null;
|
|
21
|
-
limit?: number | null;
|
|
22
|
-
remaining?: number | null;
|
|
23
|
-
breakdown?: {
|
|
24
|
-
included?: number | null;
|
|
25
|
-
bonus?: number | null;
|
|
26
|
-
total?: number | null;
|
|
27
|
-
};
|
|
28
|
-
totalPercentUsed?: number | null;
|
|
29
|
-
autoPercentUsed?: number | null;
|
|
30
|
-
apiPercentUsed?: number | null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const USAGE_SUMMARY_URL = "https://cursor.com/api/usage-summary";
|
|
34
|
-
|
|
35
|
-
function asNumberOrNull(value: unknown): number | null | undefined {
|
|
36
|
-
return typeof value === "number" && Number.isFinite(value)
|
|
37
|
-
? value
|
|
38
|
-
: value === null
|
|
39
|
-
? null
|
|
40
|
-
: undefined;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function parseBucket(value: unknown): UsageBucket | undefined {
|
|
44
|
-
if (!isRecord(value)) return undefined;
|
|
45
|
-
const breakdown = isRecord(value.breakdown)
|
|
46
|
-
? {
|
|
47
|
-
included: asNumberOrNull(value.breakdown.included),
|
|
48
|
-
bonus: asNumberOrNull(value.breakdown.bonus),
|
|
49
|
-
total: asNumberOrNull(value.breakdown.total),
|
|
50
|
-
}
|
|
51
|
-
: undefined;
|
|
52
|
-
return {
|
|
53
|
-
enabled: typeof value.enabled === "boolean" ? value.enabled : undefined,
|
|
54
|
-
used: asNumberOrNull(value.used),
|
|
55
|
-
limit: asNumberOrNull(value.limit),
|
|
56
|
-
remaining: asNumberOrNull(value.remaining),
|
|
57
|
-
breakdown,
|
|
58
|
-
totalPercentUsed: asNumberOrNull(value.totalPercentUsed),
|
|
59
|
-
autoPercentUsed: asNumberOrNull(value.autoPercentUsed),
|
|
60
|
-
apiPercentUsed: asNumberOrNull(value.apiPercentUsed),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function parseCursorUsageSummary(value: unknown): CursorUsageSummary {
|
|
65
|
-
if (!isRecord(value)) throw new Error("Cursor usage endpoint returned an invalid response");
|
|
66
|
-
return {
|
|
67
|
-
billingCycleStart:
|
|
68
|
-
typeof value.billingCycleStart === "string" ? value.billingCycleStart : undefined,
|
|
69
|
-
billingCycleEnd: typeof value.billingCycleEnd === "string" ? value.billingCycleEnd : undefined,
|
|
70
|
-
membershipType: typeof value.membershipType === "string" ? value.membershipType : undefined,
|
|
71
|
-
limitType: typeof value.limitType === "string" ? value.limitType : undefined,
|
|
72
|
-
isUnlimited: typeof value.isUnlimited === "boolean" ? value.isUnlimited : undefined,
|
|
73
|
-
individualUsage: isRecord(value.individualUsage)
|
|
74
|
-
? {
|
|
75
|
-
plan: parseBucket(value.individualUsage.plan),
|
|
76
|
-
onDemand: parseBucket(value.individualUsage.onDemand),
|
|
77
|
-
}
|
|
78
|
-
: undefined,
|
|
79
|
-
teamUsage: isRecord(value.teamUsage)
|
|
80
|
-
? { onDemand: parseBucket(value.teamUsage.onDemand) }
|
|
81
|
-
: undefined,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function parseConnectPeriodUsage(value: unknown): CursorUsageSummary {
|
|
86
|
-
if (!isRecord(value))
|
|
87
|
-
throw new Error("Cursor period usage endpoint returned an invalid response");
|
|
88
|
-
|
|
89
|
-
const billingCycleStart =
|
|
90
|
-
typeof value.billingCycleStart === "string"
|
|
91
|
-
? new Date(Number(value.billingCycleStart)).toISOString()
|
|
92
|
-
: undefined;
|
|
93
|
-
const billingCycleEnd =
|
|
94
|
-
typeof value.billingCycleEnd === "string"
|
|
95
|
-
? new Date(Number(value.billingCycleEnd)).toISOString()
|
|
96
|
-
: undefined;
|
|
97
|
-
|
|
98
|
-
const planUsage = isRecord(value.planUsage) ? value.planUsage : undefined;
|
|
99
|
-
const spendLimitUsage = isRecord(value.spendLimitUsage) ? value.spendLimitUsage : undefined;
|
|
100
|
-
|
|
101
|
-
const limitType =
|
|
102
|
-
typeof spendLimitUsage?.limitType === "string" ? spendLimitUsage.limitType : undefined;
|
|
103
|
-
const totalPercentUsed = asNumberOrNull(planUsage?.totalPercentUsed);
|
|
104
|
-
const autoPercentUsed = asNumberOrNull(planUsage?.autoPercentUsed);
|
|
105
|
-
const apiPercentUsed = asNumberOrNull(planUsage?.apiPercentUsed);
|
|
106
|
-
const includedSpend = asNumberOrNull(planUsage?.includedSpend);
|
|
107
|
-
const limit = asNumberOrNull(planUsage?.limit);
|
|
108
|
-
|
|
109
|
-
// Infer membership type from limitType or displayMessage or fallback to Pro
|
|
110
|
-
let membershipType = "Pro";
|
|
111
|
-
if (limitType === "user") membershipType = "Pro";
|
|
112
|
-
else if (limitType === "team") membershipType = "Team";
|
|
113
|
-
else if (typeof value.membershipType === "string") membershipType = value.membershipType;
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
billingCycleStart,
|
|
117
|
-
billingCycleEnd,
|
|
118
|
-
membershipType,
|
|
119
|
-
limitType,
|
|
120
|
-
individualUsage: {
|
|
121
|
-
plan: {
|
|
122
|
-
enabled: true,
|
|
123
|
-
used: includedSpend,
|
|
124
|
-
limit,
|
|
125
|
-
remaining:
|
|
126
|
-
limit !== null &&
|
|
127
|
-
limit !== undefined &&
|
|
128
|
-
includedSpend !== null &&
|
|
129
|
-
includedSpend !== undefined
|
|
130
|
-
? Math.max(0, limit - includedSpend)
|
|
131
|
-
: undefined,
|
|
132
|
-
totalPercentUsed,
|
|
133
|
-
autoPercentUsed,
|
|
134
|
-
apiPercentUsed,
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export async function getCursorUsageSummary(
|
|
141
|
-
getAccessToken?: () => Promise<string>,
|
|
142
|
-
sessionToken = cursorEnv("USAGE_SESSION_TOKEN"),
|
|
143
|
-
): Promise<CursorUsageSummary> {
|
|
144
|
-
if (getAccessToken) {
|
|
145
|
-
try {
|
|
146
|
-
const accessToken = await getAccessToken();
|
|
147
|
-
if (accessToken) {
|
|
148
|
-
const response = await fetch(
|
|
149
|
-
"https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage",
|
|
150
|
-
{
|
|
151
|
-
method: "POST",
|
|
152
|
-
headers: {
|
|
153
|
-
Authorization: `Bearer ${accessToken}`,
|
|
154
|
-
"Content-Type": "application/json",
|
|
155
|
-
},
|
|
156
|
-
body: "{}",
|
|
157
|
-
signal: AbortSignal.timeout(10_000),
|
|
158
|
-
},
|
|
159
|
-
);
|
|
160
|
-
if (response.ok) {
|
|
161
|
-
return parseConnectPeriodUsage(await response.json());
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
} catch {
|
|
165
|
-
// Connect usage call failed; fall back to session token
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (sessionToken) {
|
|
170
|
-
const response = await fetch(USAGE_SUMMARY_URL, {
|
|
171
|
-
headers: { Cookie: `WorkosCursorSessionToken=${sessionToken}` },
|
|
172
|
-
signal: AbortSignal.timeout(10_000),
|
|
173
|
-
});
|
|
174
|
-
if (response.ok) {
|
|
175
|
-
return parseCursorUsageSummary(await response.json());
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
throw new Error(
|
|
180
|
-
"Not logged in to Cursor. Please log in with Cursor CLI ('cursor' / 'agent'), run /login cursor, or set CURSOR_USAGE_SESSION_TOKEN to check usage.",
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function formatDollars(cents: number | null | undefined): string {
|
|
185
|
-
return cents === null || cents === undefined ? "unlimited" : `$${(cents / 100).toFixed(2)}`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function renderProgressBar(pct: number, width = 20): string {
|
|
189
|
-
const clamped = Math.max(0, Math.min(100, pct));
|
|
190
|
-
const filled = Math.round((clamped / 100) * width);
|
|
191
|
-
const empty = width - filled;
|
|
192
|
-
return "█".repeat(filled) + "░".repeat(empty);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function formatResetDate(dateStr: string | undefined): string {
|
|
196
|
-
if (!dateStr) return "";
|
|
197
|
-
const d = new Date(dateStr);
|
|
198
|
-
if (Number.isNaN(d.valueOf())) return "";
|
|
199
|
-
const day = d.getDate();
|
|
200
|
-
const month = d.toLocaleString("en-US", { month: "short" });
|
|
201
|
-
return `Resets ${day} ${month}`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function formatPctLabel(pct: number | null | undefined): string {
|
|
205
|
-
if (pct === null || pct === undefined) return "0% used";
|
|
206
|
-
return `${Math.round(pct)}% used`;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function capitalize(str: string): string {
|
|
210
|
-
if (!str) return "Pro";
|
|
211
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export function formatCursorUsage(summary: CursorUsageSummary): string {
|
|
215
|
-
const plan = summary.individualUsage?.plan;
|
|
216
|
-
const onDemand = summary.individualUsage?.onDemand;
|
|
217
|
-
const resetStr = formatResetDate(summary.billingCycleEnd);
|
|
218
|
-
|
|
219
|
-
const planName = capitalize(summary.membershipType || "Pro");
|
|
220
|
-
const headerLeft = `Usage • ${planName}`;
|
|
221
|
-
const totalWidth = 60;
|
|
222
|
-
const headerRight = resetStr
|
|
223
|
-
? resetStr.padStart(Math.max(1, totalWidth - headerLeft.length))
|
|
224
|
-
: "";
|
|
225
|
-
|
|
226
|
-
const lines = [
|
|
227
|
-
`${headerLeft}${headerRight}`,
|
|
228
|
-
"Monthly plan and on-demand usage",
|
|
229
|
-
"",
|
|
230
|
-
"Category Current Usage",
|
|
231
|
-
];
|
|
232
|
-
|
|
233
|
-
const totalPct = plan?.totalPercentUsed ?? 0;
|
|
234
|
-
lines.push(
|
|
235
|
-
`Included ${formatPctLabel(totalPct).padEnd(16)}${renderProgressBar(totalPct)}`,
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
if (plan?.autoPercentUsed !== undefined && plan.autoPercentUsed !== null) {
|
|
239
|
-
lines.push(
|
|
240
|
-
` Auto ${formatPctLabel(plan.autoPercentUsed).padEnd(16)}${renderProgressBar(plan.autoPercentUsed)}`,
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (plan?.apiPercentUsed !== undefined && plan.apiPercentUsed !== null) {
|
|
245
|
-
lines.push(
|
|
246
|
-
` API ${formatPctLabel(plan.apiPercentUsed).padEnd(16)}${renderProgressBar(plan.apiPercentUsed)}`,
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const isOnDemandActive = Boolean(onDemand?.enabled && (onDemand.used ?? 0) > 0);
|
|
251
|
-
lines.push(`On-Demand ${isOnDemandActive ? formatDollars(onDemand?.used) : "Disabled"}`);
|
|
252
|
-
lines.push("-".repeat(totalWidth));
|
|
253
|
-
lines.push(
|
|
254
|
-
isOnDemandActive
|
|
255
|
-
? `On-demand spend: ${formatDollars(onDemand?.used)}`
|
|
256
|
-
: "On-demand usage is off",
|
|
257
|
-
);
|
|
258
|
-
lines.push("");
|
|
259
|
-
lines.push("View in dashboard: cursor.com/dashboard?tab=usage");
|
|
260
|
-
|
|
261
|
-
return lines.join("\n");
|
|
262
|
-
}
|
package/src/utils/index.ts
DELETED
package/src/utils/security.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { cursorEnv } from "./util.js";
|
|
2
|
-
|
|
3
|
-
const ALLOWED_HOST_SUFFIXES = [".cursor.sh", ".cursor.com"];
|
|
4
|
-
const ALLOWED_HOSTS = new Set([
|
|
5
|
-
"cursor.sh",
|
|
6
|
-
"cursor.com",
|
|
7
|
-
"api2.cursor.sh",
|
|
8
|
-
"authenticator.cursor.sh",
|
|
9
|
-
]);
|
|
10
|
-
|
|
11
|
-
/** Prevent token exfiltration via poisoned agent URL. */
|
|
12
|
-
export function assertSafeCursorBaseUrl(raw: string): string {
|
|
13
|
-
let url: URL;
|
|
14
|
-
try {
|
|
15
|
-
url = new URL(raw);
|
|
16
|
-
} catch {
|
|
17
|
-
throw new Error(`Invalid Cursor agent URL: ${raw}`);
|
|
18
|
-
}
|
|
19
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
20
|
-
throw new Error(`Cursor agent URL must use http(s) (got ${url.protocol})`);
|
|
21
|
-
}
|
|
22
|
-
if (url.username || url.password) {
|
|
23
|
-
throw new Error("Cursor agent URL must not include credentials");
|
|
24
|
-
}
|
|
25
|
-
const host = url.hostname.toLowerCase();
|
|
26
|
-
const allowed =
|
|
27
|
-
ALLOWED_HOSTS.has(host) ||
|
|
28
|
-
ALLOWED_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix)) ||
|
|
29
|
-
host === "localhost" ||
|
|
30
|
-
host === "127.0.0.1";
|
|
31
|
-
if (!allowed) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
`Cursor agent URL host "${host}" is not allowed. Use a *.cursor.sh / *.cursor.com endpoint.`,
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
const path = url.pathname.replace(/\/+$/, "");
|
|
37
|
-
return `${url.origin}${path === "/" ? "" : path}`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Redact JWTs, bearer tokens, and common secret keys from diagnostics/errors. */
|
|
41
|
-
export function redactSecrets(text: string): string {
|
|
42
|
-
return text
|
|
43
|
-
.replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[redacted-jwt]")
|
|
44
|
-
.replace(/\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi, "Bearer [redacted]")
|
|
45
|
-
.replace(
|
|
46
|
-
/("?(?:access_token|refresh_token|accessToken|refreshToken|token|authorization|code_verifier)"?\s*[:=]\s*")[^"]*(")/gi,
|
|
47
|
-
"$1[redacted]$2",
|
|
48
|
-
)
|
|
49
|
-
.replace(
|
|
50
|
-
/("?(?:access_token|refresh_token|accessToken|refreshToken|token|authorization|code_verifier)"?\s*[:=]\s*)[^\s&,}]+/gi,
|
|
51
|
-
"$1[redacted]",
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function safeError(error: unknown): string {
|
|
56
|
-
const raw = error instanceof Error ? error.message : String(error);
|
|
57
|
-
return redactSecrets(raw);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function debugEnabled(): boolean {
|
|
61
|
-
const raw = (cursorEnv("DEBUG") || process.env.PI_CURSOR_PROVIDER_DEBUG || "")
|
|
62
|
-
.trim()
|
|
63
|
-
.toLowerCase();
|
|
64
|
-
return !!raw && raw !== "0" && raw !== "false" && raw !== "off";
|
|
65
|
-
}
|
package/src/utils/util.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export function cursorEnv(name: string): string | undefined {
|
|
2
|
-
return (
|
|
3
|
-
process.env[`PI_CURSOR_${name}`] ||
|
|
4
|
-
process.env[`CURSOR_${name}`] ||
|
|
5
|
-
process.env[`PI_CURSOR_PROVIDER_${name}`]
|
|
6
|
-
);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
10
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function asString(value: unknown): string | undefined {
|
|
14
|
-
return typeof value === "string" && value ? value : undefined;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function sanitizeText(text: unknown): string {
|
|
18
|
-
return String(text ?? "").replace(/[\uD800-\uDFFF]/g, "\uFFFD");
|
|
19
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"lib": ["ES2022"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"noEmit": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"resolveJsonModule": true,
|
|
12
|
-
"isolatedModules": true,
|
|
13
|
-
"noUnusedLocals": false,
|
|
14
|
-
"noUnusedParameters": false,
|
|
15
|
-
"noFallthroughCasesInSwitch": true,
|
|
16
|
-
"noImplicitOverride": true,
|
|
17
|
-
"forceConsistentCasingInFileNames": true,
|
|
18
|
-
"types": ["node"]
|
|
19
|
-
},
|
|
20
|
-
"include": ["src/**/*.ts", "tests/**/*.ts", "vitest.config.ts"]
|
|
21
|
-
}
|
|
File without changes
|