262 lines
6.4 KiB
Go
262 lines
6.4 KiB
Go
package cmd
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func sha256hex() string {
|
|
b := make([]byte, 32)
|
|
_, _ = rand.Read(b)
|
|
h := sha256.Sum256(b)
|
|
return hex.EncodeToString(h[:])
|
|
}
|
|
|
|
func sha512hex() string {
|
|
b := make([]byte, 64)
|
|
_, _ = rand.Read(b)
|
|
h := sha512.Sum512(b)
|
|
return hex.EncodeToString(h[:])
|
|
}
|
|
|
|
func newUUID() string {
|
|
return uuid.New().String()
|
|
}
|
|
|
|
func log(icon, msg string) {
|
|
fmt.Printf(" %s %s\n", icon, msg)
|
|
}
|
|
|
|
func getCursorGlobalStorage() string {
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
home, _ := os.UserHomeDir()
|
|
return filepath.Join(home, "Library", "Application Support", "Cursor", "User", "globalStorage")
|
|
case "windows":
|
|
appdata := os.Getenv("APPDATA")
|
|
return filepath.Join(appdata, "Cursor", "User", "globalStorage")
|
|
default:
|
|
xdg := os.Getenv("XDG_CONFIG_HOME")
|
|
if xdg == "" {
|
|
home, _ := os.UserHomeDir()
|
|
xdg = filepath.Join(home, ".config")
|
|
}
|
|
return filepath.Join(xdg, "Cursor", "User", "globalStorage")
|
|
}
|
|
}
|
|
|
|
func getCursorRoot() string {
|
|
gs := getCursorGlobalStorage()
|
|
return filepath.Dir(filepath.Dir(gs))
|
|
}
|
|
|
|
func generateNewIDs() map[string]string {
|
|
return map[string]string{
|
|
"telemetry.machineId": sha256hex(),
|
|
"telemetry.macMachineId": sha512hex(),
|
|
"telemetry.devDeviceId": newUUID(),
|
|
"telemetry.sqmId": "{" + fmt.Sprintf("%s", newUUID()+"") + "}",
|
|
"storage.serviceMachineId": newUUID(),
|
|
}
|
|
}
|
|
|
|
func killCursor() {
|
|
log("", "Stopping Cursor processes...")
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
exec.Command("taskkill", "/F", "/IM", "Cursor.exe").Run()
|
|
default:
|
|
exec.Command("pkill", "-x", "Cursor").Run()
|
|
exec.Command("pkill", "-f", "Cursor.app").Run()
|
|
}
|
|
log("", "Cursor stopped (or was not running)")
|
|
}
|
|
|
|
func updateStorageJSON(storagePath string, ids map[string]string) {
|
|
if _, err := os.Stat(storagePath); os.IsNotExist(err) {
|
|
log("", fmt.Sprintf("storage.json not found: %s", storagePath))
|
|
return
|
|
}
|
|
|
|
if runtime.GOOS == "darwin" {
|
|
exec.Command("chflags", "nouchg", storagePath).Run()
|
|
exec.Command("chmod", "644", storagePath).Run()
|
|
}
|
|
|
|
data, err := os.ReadFile(storagePath)
|
|
if err != nil {
|
|
log("", fmt.Sprintf("storage.json read error: %v", err))
|
|
return
|
|
}
|
|
|
|
var obj map[string]interface{}
|
|
if err := json.Unmarshal(data, &obj); err != nil {
|
|
log("", fmt.Sprintf("storage.json parse error: %v", err))
|
|
return
|
|
}
|
|
|
|
for k, v := range ids {
|
|
obj[k] = v
|
|
}
|
|
|
|
out, err := json.MarshalIndent(obj, "", " ")
|
|
if err != nil {
|
|
log("", fmt.Sprintf("storage.json marshal error: %v", err))
|
|
return
|
|
}
|
|
|
|
if err := os.WriteFile(storagePath, out, 0644); err != nil {
|
|
log("", fmt.Sprintf("storage.json write error: %v", err))
|
|
return
|
|
}
|
|
log("", "storage.json updated")
|
|
}
|
|
|
|
func updateStateVscdb(dbPath string, ids map[string]string) {
|
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
|
log("", fmt.Sprintf("state.vscdb not found: %s", dbPath))
|
|
return
|
|
}
|
|
|
|
if runtime.GOOS == "darwin" {
|
|
exec.Command("chflags", "nouchg", dbPath).Run()
|
|
exec.Command("chmod", "644", dbPath).Run()
|
|
}
|
|
|
|
if err := updateVscdbPureGo(dbPath, ids); err != nil {
|
|
log("", fmt.Sprintf("state.vscdb error: %v", err))
|
|
} else {
|
|
log("", "state.vscdb updated")
|
|
}
|
|
}
|
|
|
|
func updateMachineIDFile(machineID, cursorRoot string) {
|
|
var candidates []string
|
|
if runtime.GOOS == "linux" {
|
|
candidates = []string{
|
|
filepath.Join(cursorRoot, "machineid"),
|
|
filepath.Join(cursorRoot, "machineId"),
|
|
}
|
|
} else {
|
|
candidates = []string{filepath.Join(cursorRoot, "machineId")}
|
|
}
|
|
|
|
filePath := candidates[0]
|
|
for _, c := range candidates {
|
|
if _, err := os.Stat(c); err == nil {
|
|
filePath = c
|
|
break
|
|
}
|
|
}
|
|
|
|
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
|
|
log("", fmt.Sprintf("machineId dir error: %v", err))
|
|
return
|
|
}
|
|
|
|
if runtime.GOOS == "darwin" {
|
|
if _, err := os.Stat(filePath); err == nil {
|
|
exec.Command("chflags", "nouchg", filePath).Run()
|
|
exec.Command("chmod", "644", filePath).Run()
|
|
}
|
|
}
|
|
|
|
if err := os.WriteFile(filePath, []byte(machineID+"\n"), 0644); err != nil {
|
|
log("", fmt.Sprintf("machineId write error: %v", err))
|
|
return
|
|
}
|
|
log("", fmt.Sprintf("machineId file updated (%s)", filepath.Base(filePath)))
|
|
}
|
|
|
|
var dirsToWipe = []string{
|
|
"Session Storage", "Local Storage", "IndexedDB", "Cache", "Code Cache",
|
|
"GPUCache", "Service Worker", "Network", "Cookies", "Cookies-journal",
|
|
}
|
|
|
|
func deepClean(cursorRoot string) {
|
|
log("", "Deep-cleaning session data...")
|
|
wiped := 0
|
|
for _, name := range dirsToWipe {
|
|
target := filepath.Join(cursorRoot, name)
|
|
if _, err := os.Stat(target); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
info, err := os.Stat(target)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if info.IsDir() {
|
|
if err := os.RemoveAll(target); err == nil {
|
|
wiped++
|
|
}
|
|
} else {
|
|
if err := os.Remove(target); err == nil {
|
|
wiped++
|
|
}
|
|
}
|
|
}
|
|
log("", fmt.Sprintf("Wiped %d cache/session items", wiped))
|
|
}
|
|
|
|
func HandleResetHwid(doDeepClean, dryRun bool) error {
|
|
fmt.Print("\nCursor HWID Reset\n\n")
|
|
fmt.Println(" Resets all machine / telemetry IDs so Cursor sees a fresh install.")
|
|
fmt.Print(" Cursor must be closed — it will be killed automatically.\n\n")
|
|
|
|
globalStorage := getCursorGlobalStorage()
|
|
cursorRoot := getCursorRoot()
|
|
|
|
if _, err := os.Stat(globalStorage); os.IsNotExist(err) {
|
|
fmt.Printf("Cursor config not found at:\n %s\n", globalStorage)
|
|
fmt.Println(" Make sure Cursor is installed and has been run at least once.")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if dryRun {
|
|
fmt.Println(" [DRY RUN] Would reset IDs in:")
|
|
fmt.Printf(" %s\n", filepath.Join(globalStorage, "storage.json"))
|
|
fmt.Printf(" %s\n", filepath.Join(globalStorage, "state.vscdb"))
|
|
fmt.Printf(" %s\n", filepath.Join(cursorRoot, "machineId"))
|
|
return nil
|
|
}
|
|
|
|
killCursor()
|
|
|
|
time.Sleep(800 * time.Millisecond)
|
|
|
|
newIDs := generateNewIDs()
|
|
log("", "Generated new IDs:")
|
|
for k, v := range newIDs {
|
|
fmt.Printf(" %s: %s\n", k, v)
|
|
}
|
|
fmt.Println()
|
|
|
|
log("", "Updating storage.json...")
|
|
updateStorageJSON(filepath.Join(globalStorage, "storage.json"), newIDs)
|
|
|
|
log("", "Updating state.vscdb...")
|
|
updateStateVscdb(filepath.Join(globalStorage, "state.vscdb"), newIDs)
|
|
|
|
log("", "Updating machineId file...")
|
|
updateMachineIDFile(newIDs["telemetry.machineId"], cursorRoot)
|
|
|
|
if doDeepClean {
|
|
fmt.Println()
|
|
deepClean(cursorRoot)
|
|
}
|
|
|
|
fmt.Print("\nHWID reset complete. You can now restart Cursor.\n\n")
|
|
return nil
|
|
}
|