mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 15:52:52 +03:00
Add automated SQLite health diagnostics with optional auto-repair, startup and scheduled execution, authenticated API routes, an MCP tool, and dashboard visibility for status and repair actions. Improve provider compatibility by adding Cursor usage fetching and v3.1.0 parity headers, introducing a per-connection Codex Responses store opt-in with session fallback, fixing Codex non-stream combo and SSE translation behavior, sanitizing Gemini googleSearch tool payloads and Qwen thinking tool_choice handling, and hardening cleanup and call log storage paths.
141 lines
4.2 KiB
TypeScript
141 lines
4.2 KiB
TypeScript
/**
|
|
* 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";
|
|
|
|
const CURSOR_CLIENT_VERSION = "3.1.0";
|
|
const CURSOR_USER_AGENT = `Cursor/${CURSOR_CLIENT_VERSION}`;
|
|
|
|
/**
|
|
* 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);
|
|
|
|
return {
|
|
Authorization: `Bearer ${cleanToken}`,
|
|
"connect-accept-encoding": "gzip",
|
|
"connect-protocol-version": "1",
|
|
"Content-Type": "application/connect+proto",
|
|
"User-Agent": CURSOR_USER_AGENT,
|
|
"x-amzn-trace-id": `Root=${crypto.randomUUID()}`,
|
|
"x-client-key": clientKey,
|
|
"x-cursor-checksum": checksum,
|
|
"x-cursor-client-version": CURSOR_CLIENT_VERSION,
|
|
"x-cursor-user-agent": CURSOR_USER_AGENT,
|
|
"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,
|
|
Host: "api2.cursor.sh",
|
|
};
|
|
}
|
|
|
|
const cursorChecksumUtils = {
|
|
generateCursorChecksum,
|
|
buildCursorHeaders,
|
|
generateHashed64Hex,
|
|
generateSessionId,
|
|
};
|
|
|
|
export default cursorChecksumUtils;
|