File size: 4,525 Bytes
88c4c60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/**
 * Cursor Checksum Utility (Jyh Cipher)
 *
 * Generates the x-cursor-checksum header required for Cursor API authentication.
 * Based on the JavaScript implementation from Cursor IDE.
 */

import crypto from "crypto";
import { v5 as uuidv5 } from "uuid";

/**
 * Generate SHA-256 hash like generateHashed64Hex
 * @param {string} input - Input string
 * @param {string} salt - Optional salt
 * @returns {string} - 64-character hex string
 */
export function generateHashed64Hex(input, salt = "") {
  return crypto.createHash("sha256").update(input + salt).digest("hex");
}

/**
 * Generate session ID using UUID v5 with DNS namespace
 * @param {string} authToken - Auth token
 * @returns {string} - UUID string
 */
export function generateSessionId(authToken) {
  return uuidv5(authToken, uuidv5.DNS);
}

/**
 * Generate cursor checksum (Jyh cipher)
 *
 * Algorithm:
 * 1. Get Unix timestamp in specific format
 * 2. XOR each byte with key (starting 165)
 * 3. Update key: key = (key + byte) & 0xFF
 * 4. URL-safe base64 encode
 * 5. Format: {base64_encoded}{machineId}
 *
 * @param {string} machineId - Machine ID from Cursor storage or generated
 * @returns {string} - Checksum string
 */
export function generateCursorChecksum(machineId) {
  // Math.floor(Date.now() / 1e6) - same as Python implementation
  const timestamp = Math.floor(Date.now() / 1000000);

  // Create byte array from timestamp (6 bytes, big-endian)
  const byteArray = new Uint8Array([
    (timestamp >> 40) & 0xFF,
    (timestamp >> 32) & 0xFF,
    (timestamp >> 24) & 0xFF,
    (timestamp >> 16) & 0xFF,
    (timestamp >> 8) & 0xFF,
    timestamp & 0xFF
  ]);

  // Jyh cipher obfuscation
  let t = 165;
  for (let i = 0; i < byteArray.length; i++) {
    byteArray[i] = ((byteArray[i] ^ t) + (i % 256)) & 0xFF;
    t = byteArray[i];
  }

  // URL-safe base64 encode (without padding)
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
  let encoded = "";

  for (let i = 0; i < byteArray.length; i += 3) {
    const a = byteArray[i];
    const b = i + 1 < byteArray.length ? byteArray[i + 1] : 0;
    const c = i + 2 < byteArray.length ? byteArray[i + 2] : 0;

    encoded += alphabet[a >> 2];
    encoded += alphabet[((a & 3) << 4) | (b >> 4)];

    if (i + 1 < byteArray.length) {
      encoded += alphabet[((b & 15) << 2) | (c >> 6)];
    }
    if (i + 2 < byteArray.length) {
      encoded += alphabet[c & 63];
    }
  }

  return `${encoded}${machineId}`;
}

/**
 * Build all Cursor API headers
 *
 * @param {string} accessToken - Bearer token
 * @param {string} machineId - Machine ID (or will be generated from token)
 * @param {boolean} ghostMode - Enable ghost mode (privacy)
 * @returns {Object} - Headers object
 */
export function buildCursorHeaders(accessToken, machineId = null, ghostMode = true) {
  // Clean token if it has prefix
  const cleanToken = accessToken.includes("::")
    ? accessToken.split("::")[1]
    : accessToken;

  // Generate machine ID if not provided
  const effectiveMachineId = machineId || generateHashed64Hex(cleanToken, "machineId");

  // Generate derived values
  const sessionId = generateSessionId(cleanToken);
  const clientKey = generateHashed64Hex(cleanToken);
  const checksum = generateCursorChecksum(effectiveMachineId);

  // Detect OS
  let os = "linux";
  if (typeof process !== "undefined") {
    if (process.platform === "win32") os = "windows";
    else if (process.platform === "darwin") os = "macos";
  }

  // Detect architecture
  let arch = "x64";
  if (typeof process !== "undefined") {
    if (process.arch === "arm64") arch = "aarch64";
  }

  return {
    "authorization": `Bearer ${cleanToken}`,
    "connect-accept-encoding": "gzip",
    "connect-protocol-version": "1",
    "content-type": "application/connect+proto",
    "user-agent": "connect-es/1.6.1",
    "x-amzn-trace-id": `Root=${crypto.randomUUID()}`,
    "x-client-key": clientKey,
    "x-cursor-checksum": checksum,
    "x-cursor-client-version": "3.1.0",
    "x-cursor-client-type": "ide",
    "x-cursor-client-os": os,
    "x-cursor-client-arch": arch,
    "x-cursor-client-device-type": "desktop",
    "x-cursor-config-version": crypto.randomUUID(),
    "x-cursor-timezone": Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
    "x-ghost-mode": ghostMode ? "true" : "false",
    "x-request-id": crypto.randomUUID(),
    "x-session-id": sessionId
  };
}

export default {
  generateCursorChecksum,
  buildCursorHeaders,
  generateHashed64Hex,
  generateSessionId
};