From aaa76fc3874c0314586b6fe04e6cffddfab29abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Sun, 21 Dec 2025 01:47:44 +0800 Subject: [PATCH] =?UTF-8?q?refactor(scripts):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=97=A7=E7=89=88=E6=9E=84=E5=BB=BA=E5=92=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 Windows 批处理构建脚本 (build_all.bat) - 删除 Linux Shell 构建脚本 (build_all.sh) - 删除 Python 实现的 Cursor ID 修改器主脚本 (cursor_id_modifier.py) - 为后续重构和优化清理旧代码 --- .goreleaser.yml | 92 --- cmd/cursor-id-modifier/main.go | 328 -------- go.mod | 15 - go.sum | 26 - internal/config/config.go | 153 ---- internal/lang/lang.go | 187 ----- internal/process/manager.go | 216 ------ internal/ui/display.go | 94 --- internal/ui/logo.go | 20 - internal/ui/spinner.go | 122 --- pkg/idgen/generator.go | 116 --- process_cursor_links.py | 375 --------- scripts/build_all.bat | 74 -- scripts/build_all.sh | 143 ---- scripts/cursor_id_modifier.pot | 318 -------- scripts/cursor_id_modifier.py | 1298 -------------------------------- scripts/git-actions.sh | 9 - scripts/install.ps1 | 193 ----- scripts/install.sh | 127 ---- 19 files changed, 3906 deletions(-) delete mode 100644 .goreleaser.yml delete mode 100644 cmd/cursor-id-modifier/main.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 internal/config/config.go delete mode 100644 internal/lang/lang.go delete mode 100644 internal/process/manager.go delete mode 100644 internal/ui/display.go delete mode 100644 internal/ui/logo.go delete mode 100644 internal/ui/spinner.go delete mode 100644 pkg/idgen/generator.go delete mode 100644 process_cursor_links.py delete mode 100644 scripts/build_all.bat delete mode 100755 scripts/build_all.sh delete mode 100644 scripts/cursor_id_modifier.pot delete mode 100644 scripts/cursor_id_modifier.py delete mode 100755 scripts/git-actions.sh delete mode 100644 scripts/install.ps1 delete mode 100755 scripts/install.sh diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index 13c0b21..0000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,92 +0,0 @@ -version: 2 - -before: - hooks: - - go mod tidy - - go mod vendor - - go mod verify - -builds: - - id: cursor-id-modifier - main: ./cmd/cursor-id-modifier/main.go - binary: cursor-id-modifier - env: - - CGO_ENABLED=0 - - GO111MODULE=on - goos: - - linux - - windows - - darwin - goarch: - - amd64 - - arm64 - - "386" - ignore: - - goos: darwin - goarch: "386" - ldflags: - - -s -w - - -X 'main.version={{.Version}}' - - -X 'main.commit={{.ShortCommit}}' - - -X 'main.date={{.CommitDate}}' - - -X 'main.builtBy=goreleaser' - flags: - - -trimpath - mod_timestamp: '{{ .CommitTimestamp }}' - -archives: - - id: binary - format: binary - name_template: >- - {{- .ProjectName }}_ - {{- .Version }}_ - {{- .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - builds: - - cursor-id-modifier - allow_different_binary_count: true - files: - - none* - -checksum: - name_template: 'checksums.txt' - algorithm: sha256 - -release: - draft: true - prerelease: auto - mode: replace - header: | - ## Release {{.Tag}} ({{.Date}}) - - See [CHANGELOG.md](CHANGELOG.md) for details. - footer: | - **Full Changelog**: https://github.com/owner/repo/compare/{{ .PreviousTag }}...{{ .Tag }} - extra_files: - - glob: 'LICENSE*' - - glob: 'README*' - - glob: 'CHANGELOG*' - -changelog: - sort: asc - use: github - filters: - exclude: - - '^docs:' - - '^test:' - - '^ci:' - - Merge pull request - - Merge branch - groups: - - title: Features - regexp: "^.*feat[(\\w)]*:+.*$" - order: 0 - - title: 'Bug fixes' - regexp: "^.*fix[(\\w)]*:+.*$" - order: 1 - - title: Others - order: 999 - -project_name: cursor-id-modifier diff --git a/cmd/cursor-id-modifier/main.go b/cmd/cursor-id-modifier/main.go deleted file mode 100644 index 4ff9e0e..0000000 --- a/cmd/cursor-id-modifier/main.go +++ /dev/null @@ -1,328 +0,0 @@ -package main - -import ( - "bufio" - "flag" - "fmt" - "os" - "os/exec" - "os/user" - "runtime" - "runtime/debug" - "strings" - - "github.com/sirupsen/logrus" - - "github.com/yuaotian/go-cursor-help/internal/config" - "github.com/yuaotian/go-cursor-help/internal/lang" - "github.com/yuaotian/go-cursor-help/internal/process" - "github.com/yuaotian/go-cursor-help/internal/ui" - "github.com/yuaotian/go-cursor-help/pkg/idgen" -) - -// Global variables -var ( - version = "dev" - setReadOnly = flag.Bool("r", false, "set storage.json to read-only mode") - showVersion = flag.Bool("v", false, "show version information") - log = logrus.New() -) - -func main() { - // Place defer at the beginning of main to ensure it can catch panics from all subsequent function calls - defer func() { - if r := recover(); r != nil { - log.Errorf("Panic recovered: %v\n", r) - debug.PrintStack() - waitExit() - } - }() - - handleFlags() - setupLogger() - - username := getCurrentUser() - log.Debug("Running as user:", username) - - // Initialize components - display := ui.NewDisplay(nil) - configManager := initConfigManager(username) - generator := idgen.NewGenerator() - processManager := process.NewManager(nil, log) - - // Check and handle privileges - if err := handlePrivileges(display); err != nil { - return - } - - // Setup display - setupDisplay(display) - - text := lang.GetText() - - // Handle Cursor processes - if err := handleCursorProcesses(display, processManager); err != nil { - return - } - - // Handle configuration - oldConfig := readExistingConfig(display, configManager, text) - newConfig := generateNewConfig(display, generator, oldConfig, text) - - if err := saveConfiguration(display, configManager, newConfig); err != nil { - return - } - - // Show completion messages - showCompletionMessages(display) - - if os.Getenv("AUTOMATED_MODE") != "1" { - waitExit() - } -} - -func handleFlags() { - flag.Parse() - if *showVersion { - fmt.Printf("Cursor ID Modifier v%s\n", version) - os.Exit(0) - } -} - -func setupLogger() { - log.SetFormatter(&logrus.TextFormatter{ - FullTimestamp: true, - DisableLevelTruncation: true, - PadLevelText: true, - }) - log.SetLevel(logrus.InfoLevel) -} - -func getCurrentUser() string { - if username := os.Getenv("SUDO_USER"); username != "" { - return username - } - - user, err := user.Current() - if err != nil { - log.Fatal(err) - } - return user.Username -} - -func initConfigManager(username string) *config.Manager { - configManager, err := config.NewManager(username) - if err != nil { - log.Fatal(err) - } - return configManager -} - -func handlePrivileges(display *ui.Display) error { - isAdmin, err := checkAdminPrivileges() - if err != nil { - log.Error(err) - waitExit() - return err - } - - if !isAdmin { - if runtime.GOOS == "windows" { - return handleWindowsPrivileges(display) - } - display.ShowPrivilegeError( - lang.GetText().PrivilegeError, - lang.GetText().RunWithSudo, - lang.GetText().SudoExample, - ) - waitExit() - return fmt.Errorf("insufficient privileges") - } - return nil -} - -func handleWindowsPrivileges(display *ui.Display) error { - message := "\nRequesting administrator privileges..." - if lang.GetCurrentLanguage() == lang.CN { - message = "\n请求管理员权限..." - } - fmt.Println(message) - - if err := selfElevate(); err != nil { - log.Error(err) - display.ShowPrivilegeError( - lang.GetText().PrivilegeError, - lang.GetText().RunAsAdmin, - lang.GetText().RunWithSudo, - lang.GetText().SudoExample, - ) - waitExit() - return err - } - return nil -} - -func setupDisplay(display *ui.Display) { - if err := display.ClearScreen(); err != nil { - log.Warn("Failed to clear screen:", err) - } - display.ShowLogo() - fmt.Println() -} - -func handleCursorProcesses(display *ui.Display, processManager *process.Manager) error { - if os.Getenv("AUTOMATED_MODE") == "1" { - log.Debug("Running in automated mode, skipping Cursor process closing") - return nil - } - - display.ShowProgress("Closing Cursor...") - log.Debug("Attempting to close Cursor processes") - - if err := processManager.KillCursorProcesses(); err != nil { - log.Error("Failed to close Cursor:", err) - display.StopProgress() - display.ShowError("Failed to close Cursor. Please close it manually and try again.") - waitExit() - return err - } - - if processManager.IsCursorRunning() { - log.Error("Cursor processes still detected after closing") - display.StopProgress() - display.ShowError("Failed to close Cursor completely. Please close it manually and try again.") - waitExit() - return fmt.Errorf("cursor still running") - } - - log.Debug("Successfully closed all Cursor processes") - display.StopProgress() - fmt.Println() - return nil -} - -func readExistingConfig(display *ui.Display, configManager *config.Manager, text lang.TextResource) *config.StorageConfig { - fmt.Println() - display.ShowProgress(text.ReadingConfig) - oldConfig, err := configManager.ReadConfig() - if err != nil { - log.Warn("Failed to read existing config:", err) - oldConfig = nil - } - display.StopProgress() - fmt.Println() - return oldConfig -} - -func generateNewConfig(display *ui.Display, generator *idgen.Generator, oldConfig *config.StorageConfig, text lang.TextResource) *config.StorageConfig { - display.ShowProgress(text.GeneratingIds) - newConfig := &config.StorageConfig{} - - if machineID, err := generator.GenerateMachineID(); err != nil { - log.Fatal("Failed to generate machine ID:", err) - } else { - newConfig.TelemetryMachineId = machineID - } - - if macMachineID, err := generator.GenerateMacMachineID(); err != nil { - log.Fatal("Failed to generate MAC machine ID:", err) - } else { - newConfig.TelemetryMacMachineId = macMachineID - } - - if deviceID, err := generator.GenerateDeviceID(); err != nil { - log.Fatal("Failed to generate device ID:", err) - } else { - newConfig.TelemetryDevDeviceId = deviceID - } - - if oldConfig != nil && oldConfig.TelemetrySqmId != "" { - newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId - } else if sqmID, err := generator.GenerateSQMID(); err != nil { - log.Fatal("Failed to generate SQM ID:", err) - } else { - newConfig.TelemetrySqmId = sqmID - } - - display.StopProgress() - fmt.Println() - return newConfig -} - -func saveConfiguration(display *ui.Display, configManager *config.Manager, newConfig *config.StorageConfig) error { - display.ShowProgress("Saving configuration...") - if err := configManager.SaveConfig(newConfig, *setReadOnly); err != nil { - log.Error(err) - waitExit() - return err - } - display.StopProgress() - fmt.Println() - return nil -} - -func showCompletionMessages(display *ui.Display) { - display.ShowSuccess(lang.GetText().SuccessMessage, lang.GetText().RestartMessage) - fmt.Println() - - message := "Operation completed!" - if lang.GetCurrentLanguage() == lang.CN { - message = "操作完成!" - } - display.ShowInfo(message) -} - -func waitExit() { - fmt.Print(lang.GetText().PressEnterToExit) - os.Stdout.Sync() - bufio.NewReader(os.Stdin).ReadString('\n') -} - -func checkAdminPrivileges() (bool, error) { - switch runtime.GOOS { - case "windows": - cmd := exec.Command("net", "session") - return cmd.Run() == nil, nil - - case "darwin", "linux": - currentUser, err := user.Current() - if err != nil { - return false, fmt.Errorf("failed to get current user: %w", err) - } - return currentUser.Uid == "0", nil - - default: - return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) - } -} - -func selfElevate() error { - os.Setenv("AUTOMATED_MODE", "1") - - switch runtime.GOOS { - case "windows": - verb := "runas" - exe, _ := os.Executable() - cwd, _ := os.Getwd() - args := strings.Join(os.Args[1:], " ") - - cmd := exec.Command("cmd", "/C", "start", verb, exe, args) - cmd.Dir = cwd - return cmd.Run() - - case "darwin", "linux": - exe, err := os.Executable() - if err != nil { - return err - } - - cmd := exec.Command("sudo", append([]string{exe}, os.Args[1:]...)...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() - - default: - return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) - } -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 6bcebe3..0000000 --- a/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module github.com/yuaotian/go-cursor-help - -go 1.21 - -require ( - github.com/fatih/color v1.15.0 - github.com/sirupsen/logrus v1.9.3 -) - -require ( - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/stretchr/testify v1.10.0 // indirect - golang.org/x/sys v0.13.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index d930a66..0000000 --- a/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 75b64df..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,153 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "runtime" - "sync" - "time" -) - -// StorageConfig represents the storage configuration -type StorageConfig struct { - TelemetryMacMachineId string `json:"telemetry.macMachineId"` - TelemetryMachineId string `json:"telemetry.machineId"` - TelemetryDevDeviceId string `json:"telemetry.devDeviceId"` - TelemetrySqmId string `json:"telemetry.sqmId"` - LastModified string `json:"lastModified"` - Version string `json:"version"` -} - -// Manager handles configuration operations -type Manager struct { - configPath string - mu sync.RWMutex -} - -// NewManager creates a new configuration manager -func NewManager(username string) (*Manager, error) { - configPath, err := getConfigPath(username) - if err != nil { - return nil, fmt.Errorf("failed to get config path: %w", err) - } - return &Manager{configPath: configPath}, nil -} - -// ReadConfig reads the existing configuration -func (m *Manager) ReadConfig() (*StorageConfig, error) { - m.mu.RLock() - defer m.mu.RUnlock() - - data, err := os.ReadFile(m.configPath) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, fmt.Errorf("failed to read config file: %w", err) - } - - var config StorageConfig - if err := json.Unmarshal(data, &config); err != nil { - return nil, fmt.Errorf("failed to parse config file: %w", err) - } - - return &config, nil -} - -// SaveConfig saves the configuration -func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error { - m.mu.Lock() - defer m.mu.Unlock() - - // Ensure parent directories exist - if err := os.MkdirAll(filepath.Dir(m.configPath), 0755); err != nil { - return fmt.Errorf("failed to create config directory: %w", err) - } - - // Prepare updated configuration - updatedConfig := m.prepareUpdatedConfig(config) - - // Write configuration - if err := m.writeConfigFile(updatedConfig, readOnly); err != nil { - return err - } - - return nil -} - -// prepareUpdatedConfig merges existing config with updates -func (m *Manager) prepareUpdatedConfig(config *StorageConfig) map[string]interface{} { - // Read existing config - originalFile := make(map[string]interface{}) - if data, err := os.ReadFile(m.configPath); err == nil { - json.Unmarshal(data, &originalFile) - } - - // Update fields - originalFile["telemetry.sqmId"] = config.TelemetrySqmId - originalFile["telemetry.macMachineId"] = config.TelemetryMacMachineId - originalFile["telemetry.machineId"] = config.TelemetryMachineId - originalFile["telemetry.devDeviceId"] = config.TelemetryDevDeviceId - originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339) - // originalFile["version"] = "1.0.1" - - return originalFile -} - -// writeConfigFile handles the atomic write of the config file -func (m *Manager) writeConfigFile(config map[string]interface{}, readOnly bool) error { - // Marshal with indentation - content, err := json.MarshalIndent(config, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal config: %w", err) - } - - // Write to temporary file - tmpPath := m.configPath + ".tmp" - if err := os.WriteFile(tmpPath, content, 0666); err != nil { - return fmt.Errorf("failed to write temporary file: %w", err) - } - - // Set final permissions - fileMode := os.FileMode(0666) - if readOnly { - fileMode = 0444 - } - - if err := os.Chmod(tmpPath, fileMode); err != nil { - os.Remove(tmpPath) - return fmt.Errorf("failed to set temporary file permissions: %w", err) - } - - // Atomic rename - if err := os.Rename(tmpPath, m.configPath); err != nil { - os.Remove(tmpPath) - return fmt.Errorf("failed to rename file: %w", err) - } - - // Sync directory - if dir, err := os.Open(filepath.Dir(m.configPath)); err == nil { - defer dir.Close() - dir.Sync() - } - - return nil -} - -// getConfigPath returns the path to the configuration file -func getConfigPath(username string) (string, error) { - var configDir string - switch runtime.GOOS { - case "windows": - configDir = filepath.Join(os.Getenv("APPDATA"), "Cursor", "User", "globalStorage") - case "darwin": - configDir = filepath.Join("/Users", username, "Library", "Application Support", "Cursor", "User", "globalStorage") - case "linux": - configDir = filepath.Join("/home", username, ".config", "Cursor", "User", "globalStorage") - default: - return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS) - } - return filepath.Join(configDir, "storage.json"), nil -} diff --git a/internal/lang/lang.go b/internal/lang/lang.go deleted file mode 100644 index 973fc24..0000000 --- a/internal/lang/lang.go +++ /dev/null @@ -1,187 +0,0 @@ -package lang - -import ( - "os" - "os/exec" - "strings" - "sync" -) - -// Language represents a supported language code -type Language string - -const ( - // CN represents Chinese language - CN Language = "cn" - // EN represents English language - EN Language = "en" -) - -// TextResource contains all translatable text resources -type TextResource struct { - // Success messages - SuccessMessage string - RestartMessage string - - // Progress messages - ReadingConfig string - GeneratingIds string - CheckingProcesses string - ClosingProcesses string - ProcessesClosed string - PleaseWait string - - // Error messages - ErrorPrefix string - PrivilegeError string - - // Instructions - RunAsAdmin string - RunWithSudo string - SudoExample string - PressEnterToExit string - SetReadOnlyMessage string - - // Info messages - ConfigLocation string -} - -var ( - currentLanguage Language - currentLanguageOnce sync.Once - languageMutex sync.RWMutex -) - -// GetCurrentLanguage returns the current language, detecting it if not already set -func GetCurrentLanguage() Language { - currentLanguageOnce.Do(func() { - currentLanguage = detectLanguage() - }) - - languageMutex.RLock() - defer languageMutex.RUnlock() - return currentLanguage -} - -// SetLanguage sets the current language -func SetLanguage(lang Language) { - languageMutex.Lock() - defer languageMutex.Unlock() - currentLanguage = lang -} - -// GetText returns the TextResource for the current language -func GetText() TextResource { - return texts[GetCurrentLanguage()] -} - -// detectLanguage detects the system language -func detectLanguage() Language { - // Check environment variables first - if isChineseEnvVar() { - return CN - } - - // Then check OS-specific locale - if isWindows() { - if isWindowsChineseLocale() { - return CN - } - } else if isUnixChineseLocale() { - return CN - } - - return EN -} - -func isChineseEnvVar() bool { - for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} { - if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") { - return true - } - } - return false -} - -func isWindows() bool { - return os.Getenv("OS") == "Windows_NT" -} - -func isWindowsChineseLocale() bool { - // Check Windows UI culture - cmd := exec.Command("powershell", "-Command", - "[System.Globalization.CultureInfo]::CurrentUICulture.Name") - output, err := cmd.Output() - if err == nil && strings.HasPrefix(strings.ToLower(strings.TrimSpace(string(output))), "zh") { - return true - } - - // Check Windows locale - cmd = exec.Command("wmic", "os", "get", "locale") - output, err = cmd.Output() - return err == nil && strings.Contains(string(output), "2052") -} - -func isUnixChineseLocale() bool { - cmd := exec.Command("locale") - output, err := cmd.Output() - return err == nil && strings.Contains(strings.ToLower(string(output)), "zh_cn") -} - -// texts contains all translations -var texts = map[Language]TextResource{ - CN: { - // Success messages - SuccessMessage: "[√] 配置文件已成功更新!", - RestartMessage: "[!] 请手动重启 Cursor 以使更新生效", - - // Progress messages - ReadingConfig: "正在读取配置文件...", - GeneratingIds: "正在生成新的标识符...", - CheckingProcesses: "正在检查运行中的 Cursor 实例...", - ClosingProcesses: "正在关闭 Cursor 实例...", - ProcessesClosed: "所有 Cursor 实例已关闭", - PleaseWait: "请稍候...", - - // Error messages - ErrorPrefix: "程序发生严重错误: %v", - PrivilegeError: "\n[!] 错误:需要管理员权限", - - // Instructions - RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」", - RunWithSudo: "请使用 sudo 命令运行此程序", - SudoExample: "示例: sudo %s", - PressEnterToExit: "\n按回车键退出程序...", - SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题", - - // Info messages - ConfigLocation: "配置文件位置:", - }, - EN: { - // Success messages - SuccessMessage: "[√] Configuration file updated successfully!", - RestartMessage: "[!] Please restart Cursor manually for changes to take effect", - - // Progress messages - ReadingConfig: "Reading configuration file...", - GeneratingIds: "Generating new identifiers...", - CheckingProcesses: "Checking for running Cursor instances...", - ClosingProcesses: "Closing Cursor instances...", - ProcessesClosed: "All Cursor instances have been closed", - PleaseWait: "Please wait...", - - // Error messages - ErrorPrefix: "Program encountered a serious error: %v", - PrivilegeError: "\n[!] Error: Administrator privileges required", - - // Instructions - RunAsAdmin: "Please right-click and select 'Run as Administrator'", - RunWithSudo: "Please run this program with sudo", - SudoExample: "Example: sudo %s", - PressEnterToExit: "\nPress Enter to exit...", - SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records", - - // Info messages - ConfigLocation: "Config file location:", - }, -} diff --git a/internal/process/manager.go b/internal/process/manager.go deleted file mode 100644 index f48ac20..0000000 --- a/internal/process/manager.go +++ /dev/null @@ -1,216 +0,0 @@ -package process - -import ( - "fmt" - "os/exec" - "runtime" - "strings" - "time" - - "github.com/sirupsen/logrus" -) - -// Config holds process manager configuration -type Config struct { - MaxAttempts int // Maximum number of attempts to kill processes - RetryDelay time.Duration // Delay between retry attempts - ProcessPatterns []string // Process names to look for -} - -// DefaultConfig returns the default configuration -func DefaultConfig() *Config { - return &Config{ - MaxAttempts: 3, - RetryDelay: 2 * time.Second, - ProcessPatterns: []string{ - "Cursor.exe", // Windows executable - "Cursor ", // Linux/macOS executable with space - "cursor ", // Linux/macOS executable lowercase with space - "cursor", // Linux/macOS executable lowercase - "Cursor", // Linux/macOS executable - "*cursor*", // Any process containing cursor - "*Cursor*", // Any process containing Cursor - }, - } -} - -// Manager handles process-related operations -type Manager struct { - config *Config - log *logrus.Logger -} - -// NewManager creates a new process manager with optional config and logger -func NewManager(config *Config, log *logrus.Logger) *Manager { - if config == nil { - config = DefaultConfig() - } - if log == nil { - log = logrus.New() - } - return &Manager{ - config: config, - log: log, - } -} - -// IsCursorRunning checks if any Cursor process is currently running -func (m *Manager) IsCursorRunning() bool { - processes, err := m.getCursorProcesses() - if err != nil { - m.log.Warn("Failed to get Cursor processes:", err) - return false - } - return len(processes) > 0 -} - -// KillCursorProcesses attempts to kill all running Cursor processes -func (m *Manager) KillCursorProcesses() error { - for attempt := 1; attempt <= m.config.MaxAttempts; attempt++ { - processes, err := m.getCursorProcesses() - if err != nil { - return fmt.Errorf("failed to get processes: %w", err) - } - - if len(processes) == 0 { - return nil - } - - // Try graceful shutdown first on Windows - if runtime.GOOS == "windows" { - for _, pid := range processes { - exec.Command("taskkill", "/PID", pid).Run() - time.Sleep(500 * time.Millisecond) - } - } - - // Force kill remaining processes - remainingProcesses, _ := m.getCursorProcesses() - for _, pid := range remainingProcesses { - m.killProcess(pid) - } - - time.Sleep(m.config.RetryDelay) - - if processes, _ := m.getCursorProcesses(); len(processes) == 0 { - return nil - } - } - - return nil -} - -// getCursorProcesses returns PIDs of running Cursor processes -func (m *Manager) getCursorProcesses() ([]string, error) { - cmd := m.getProcessListCommand() - if cmd == nil { - return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) - } - - output, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to execute command: %w", err) - } - - return m.parseProcessList(string(output)), nil -} - -// getProcessListCommand returns the appropriate command to list processes based on OS -func (m *Manager) getProcessListCommand() *exec.Cmd { - switch runtime.GOOS { - case "windows": - return exec.Command("tasklist", "/FO", "CSV", "/NH") - case "darwin": - return exec.Command("ps", "-ax") - case "linux": - return exec.Command("ps", "-A") - default: - return nil - } -} - -// parseProcessList extracts Cursor process PIDs from process list output -func (m *Manager) parseProcessList(output string) []string { - var processes []string - for _, line := range strings.Split(output, "\n") { - lowerLine := strings.ToLower(line) - - if m.isOwnProcess(lowerLine) { - continue - } - - if pid := m.findCursorProcess(line, lowerLine); pid != "" { - processes = append(processes, pid) - } - } - return processes -} - -// isOwnProcess checks if the process belongs to this application -func (m *Manager) isOwnProcess(line string) bool { - return strings.Contains(line, "cursor-id-modifier") || - strings.Contains(line, "cursor-helper") -} - -// findCursorProcess checks if a process line matches Cursor patterns and returns its PID -func (m *Manager) findCursorProcess(line, lowerLine string) string { - for _, pattern := range m.config.ProcessPatterns { - if m.matchPattern(lowerLine, strings.ToLower(pattern)) { - return m.extractPID(line) - } - } - return "" -} - -// matchPattern checks if a line matches a pattern, supporting wildcards -func (m *Manager) matchPattern(line, pattern string) bool { - switch { - case strings.HasPrefix(pattern, "*") && strings.HasSuffix(pattern, "*"): - search := pattern[1 : len(pattern)-1] - return strings.Contains(line, search) - case strings.HasPrefix(pattern, "*"): - return strings.HasSuffix(line, pattern[1:]) - case strings.HasSuffix(pattern, "*"): - return strings.HasPrefix(line, pattern[:len(pattern)-1]) - default: - return line == pattern - } -} - -// extractPID extracts process ID from a process list line based on OS format -func (m *Manager) extractPID(line string) string { - switch runtime.GOOS { - case "windows": - parts := strings.Split(line, ",") - if len(parts) >= 2 { - return strings.Trim(parts[1], "\"") - } - case "darwin", "linux": - parts := strings.Fields(line) - if len(parts) >= 1 { - return parts[0] - } - } - return "" -} - -// killProcess forcefully terminates a process by PID -func (m *Manager) killProcess(pid string) error { - cmd := m.getKillCommand(pid) - if cmd == nil { - return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) - } - return cmd.Run() -} - -// getKillCommand returns the appropriate command to kill a process based on OS -func (m *Manager) getKillCommand(pid string) *exec.Cmd { - switch runtime.GOOS { - case "windows": - return exec.Command("taskkill", "/F", "/PID", pid) - case "darwin", "linux": - return exec.Command("kill", "-9", pid) - default: - return nil - } -} diff --git a/internal/ui/display.go b/internal/ui/display.go deleted file mode 100644 index 32ed566..0000000 --- a/internal/ui/display.go +++ /dev/null @@ -1,94 +0,0 @@ -package ui - -import ( - "fmt" - "os" - "os/exec" - "runtime" - "strings" - - "github.com/fatih/color" -) - -// Display handles UI operations for terminal output -type Display struct { - spinner *Spinner -} - -// NewDisplay creates a new display instance with an optional spinner -func NewDisplay(spinner *Spinner) *Display { - if spinner == nil { - spinner = NewSpinner(nil) - } - return &Display{spinner: spinner} -} - -// Terminal Operations - -// ClearScreen clears the terminal screen based on OS -func (d *Display) ClearScreen() error { - var cmd *exec.Cmd - switch runtime.GOOS { - case "windows": - cmd = exec.Command("cmd", "/c", "cls") - default: - cmd = exec.Command("clear") - } - cmd.Stdout = os.Stdout - return cmd.Run() -} - -// Progress Indicator - -// ShowProgress displays a progress message with a spinner -func (d *Display) ShowProgress(message string) { - d.spinner.SetMessage(message) - d.spinner.Start() -} - -// StopProgress stops the progress spinner -func (d *Display) StopProgress() { - d.spinner.Stop() -} - -// Message Display - -// ShowSuccess displays success messages in green -func (d *Display) ShowSuccess(messages ...string) { - green := color.New(color.FgGreen) - for _, msg := range messages { - green.Println(msg) - } -} - -// ShowInfo displays an info message in cyan -func (d *Display) ShowInfo(message string) { - cyan := color.New(color.FgCyan) - cyan.Println(message) -} - -// ShowError displays an error message in red -func (d *Display) ShowError(message string) { - red := color.New(color.FgRed) - red.Println(message) -} - -// ShowPrivilegeError displays privilege error messages with instructions -func (d *Display) ShowPrivilegeError(messages ...string) { - red := color.New(color.FgRed, color.Bold) - yellow := color.New(color.FgYellow) - - // Main error message - red.Println(messages[0]) - fmt.Println() - - // Additional instructions - for _, msg := range messages[1:] { - if strings.Contains(msg, "%s") { - exe, _ := os.Executable() - yellow.Printf(msg+"\n", exe) - } else { - yellow.Println(msg) - } - } -} diff --git a/internal/ui/logo.go b/internal/ui/logo.go deleted file mode 100644 index 564525a..0000000 --- a/internal/ui/logo.go +++ /dev/null @@ -1,20 +0,0 @@ -package ui - -import ( - "github.com/fatih/color" -) - -const cyberpunkLogo = ` - ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ - ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ - ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ - ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ - ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ - ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ -` - -// ShowLogo displays the application logo -func (d *Display) ShowLogo() { - cyan := color.New(color.FgCyan, color.Bold) - cyan.Println(cyberpunkLogo) -} diff --git a/internal/ui/spinner.go b/internal/ui/spinner.go deleted file mode 100644 index 145f730..0000000 --- a/internal/ui/spinner.go +++ /dev/null @@ -1,122 +0,0 @@ -package ui - -import ( - "fmt" - "sync" - "time" - - "github.com/fatih/color" -) - -// SpinnerConfig defines spinner configuration -type SpinnerConfig struct { - Frames []string // Animation frames for the spinner - Delay time.Duration // Delay between frame updates -} - -// DefaultSpinnerConfig returns the default spinner configuration -func DefaultSpinnerConfig() *SpinnerConfig { - return &SpinnerConfig{ - Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, - Delay: 100 * time.Millisecond, - } -} - -// Spinner represents a progress spinner -type Spinner struct { - config *SpinnerConfig - message string - current int - active bool - stopCh chan struct{} - mu sync.RWMutex -} - -// NewSpinner creates a new spinner with the given configuration -func NewSpinner(config *SpinnerConfig) *Spinner { - if config == nil { - config = DefaultSpinnerConfig() - } - return &Spinner{ - config: config, - stopCh: make(chan struct{}), - } -} - -// State management - -// SetMessage sets the spinner message -func (s *Spinner) SetMessage(message string) { - s.mu.Lock() - defer s.mu.Unlock() - s.message = message -} - -// IsActive returns whether the spinner is currently active -func (s *Spinner) IsActive() bool { - s.mu.RLock() - defer s.mu.RUnlock() - return s.active -} - -// Control methods - -// Start begins the spinner animation -func (s *Spinner) Start() { - s.mu.Lock() - if s.active { - s.mu.Unlock() - return - } - s.active = true - s.mu.Unlock() - - go s.run() -} - -// Stop halts the spinner animation -func (s *Spinner) Stop() { - s.mu.Lock() - defer s.mu.Unlock() - - if !s.active { - return - } - - s.active = false - close(s.stopCh) - s.stopCh = make(chan struct{}) - fmt.Print("\r") // Clear the spinner line -} - -// Internal methods - -func (s *Spinner) run() { - ticker := time.NewTicker(s.config.Delay) - defer ticker.Stop() - - cyan := color.New(color.FgCyan, color.Bold) - message := s.message - - // Print initial state - fmt.Printf("\r %s %s", cyan.Sprint(s.config.Frames[0]), message) - - for { - select { - case <-s.stopCh: - return - case <-ticker.C: - s.mu.RLock() - if !s.active { - s.mu.RUnlock() - return - } - frame := s.config.Frames[s.current%len(s.config.Frames)] - s.current++ - s.mu.RUnlock() - - fmt.Printf("\r %s", cyan.Sprint(frame)) - fmt.Printf("\033[%dG%s", 4, message) // Move cursor and print message - } - } -} diff --git a/pkg/idgen/generator.go b/pkg/idgen/generator.go deleted file mode 100644 index 3061f74..0000000 --- a/pkg/idgen/generator.go +++ /dev/null @@ -1,116 +0,0 @@ -package idgen - -import ( - "crypto/rand" - "encoding/hex" - "fmt" - "sync" -) - -// Generator handles secure ID generation for machines and devices -type Generator struct { - bufferPool sync.Pool -} - -// NewGenerator creates a new ID generator -func NewGenerator() *Generator { - return &Generator{ - bufferPool: sync.Pool{ - New: func() interface{} { - return make([]byte, 64) - }, - }, - } -} - -// Constants for ID generation -const ( - machineIDPrefix = "auth0|user_" - uuidFormat = "%s-%s-%s-%s-%s" -) - -// generateRandomHex generates a random hex string of specified length -func (g *Generator) generateRandomHex(length int) (string, error) { - buffer := g.bufferPool.Get().([]byte) - defer g.bufferPool.Put(buffer) - - if _, err := rand.Read(buffer[:length]); err != nil { - return "", fmt.Errorf("failed to generate random bytes: %w", err) - } - return hex.EncodeToString(buffer[:length]), nil -} - -// GenerateMachineID generates a new machine ID with auth0|user_ prefix -func (g *Generator) GenerateMachineID() (string, error) { - randomPart, err := g.generateRandomHex(32) // 生成64字符的十六进制 - if err != nil { - return "", err - } - - return fmt.Sprintf("%x%s", []byte(machineIDPrefix), randomPart), nil -} - -// GenerateMacMachineID generates a new 64-byte MAC machine ID -func (g *Generator) GenerateMacMachineID() (string, error) { - return g.generateRandomHex(32) // 生成64字符的十六进制 -} - -// GenerateDeviceID generates a new device ID in UUID format -func (g *Generator) GenerateDeviceID() (string, error) { - id, err := g.generateRandomHex(16) - if err != nil { - return "", err - } - return fmt.Sprintf(uuidFormat, - id[0:8], id[8:12], id[12:16], id[16:20], id[20:32]), nil -} - -// GenerateSQMID generates a new SQM ID in UUID format (with braces) -func (g *Generator) GenerateSQMID() (string, error) { - id, err := g.GenerateDeviceID() - if err != nil { - return "", err - } - return fmt.Sprintf("{%s}", id), nil -} - -// ValidateID validates the format of various ID types -func (g *Generator) ValidateID(id string, idType string) bool { - switch idType { - case "machineID", "macMachineID": - return len(id) == 64 && isHexString(id) - case "deviceID": - return isValidUUID(id) - case "sqmID": - if len(id) < 2 || id[0] != '{' || id[len(id)-1] != '}' { - return false - } - return isValidUUID(id[1 : len(id)-1]) - default: - return false - } -} - -// Helper functions -func isHexString(s string) bool { - _, err := hex.DecodeString(s) - return err == nil -} - -func isValidUUID(uuid string) bool { - if len(uuid) != 36 { - return false - } - for i, r := range uuid { - if i == 8 || i == 13 || i == 18 || i == 23 { - if r != '-' { - return false - } - continue - } - if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) { - return false - } - } - return true -} diff --git a/process_cursor_links.py b/process_cursor_links.py deleted file mode 100644 index 2952064..0000000 --- a/process_cursor_links.py +++ /dev/null @@ -1,375 +0,0 @@ -import csv -from dataclasses import dataclass -from typing import List -import json - -@dataclass -class CursorVersion: - version: str - build_id: str - - def get_download_links(self) -> dict: - base_url = f"https://downloader.cursor.sh/builds/{self.build_id}" - return { - "windows": { - "x64": f"{base_url}/windows/nsis/x64", - "arm64": f"{base_url}/windows/nsis/arm64" - }, - "mac": { - "universal": f"{base_url}/mac/installer/universal", - "arm64": f"{base_url}/mac/installer/arm64", - "x64": f"{base_url}/mac/installer/x64" - }, - "linux": { - "x64": f"{base_url}/linux/appImage/x64" - } - } - -def parse_versions(data: str) -> List[CursorVersion]: - versions = [] - for line in data.strip().split('\n'): - if not line: - continue - version, build_id = line.strip().split(',') - versions.append(CursorVersion(version, build_id)) - return versions - -def generate_markdown(versions: List[CursorVersion]) -> str: - md = """# 🖥️ Windows - -## x64 -
-📦 Windows x64 安装包 - -| 版本 | 下载链接 | -|------|----------| -""" - - # Windows x64 - for version in versions: - links = version.get_download_links() - md += f"| {version.version} | [下载]({links['windows']['x64']}) |\n" - - md += """ -
- -## ARM64 -
-📱 Windows ARM64 安装包 - -| 版本 | 下载链接 | -|------|----------| -""" - - # Windows ARM64 - for version in versions: - links = version.get_download_links() - md += f"| {version.version} | [下载]({links['windows']['arm64']}) |\n" - - md += """ -
- -# 🍎 macOS - -## Universal -
-🎯 macOS Universal 安装包 - -| 版本 | 下载链接 | -|------|----------| -""" - - # macOS Universal - for version in versions: - links = version.get_download_links() - md += f"| {version.version} | [下载]({links['mac']['universal']}) |\n" - - md += """ -
- -## ARM64 -
-💪 macOS ARM64 安装包 - -| 版本 | 下载链接 | -|------|----------| -""" - - # macOS ARM64 - for version in versions: - links = version.get_download_links() - md += f"| {version.version} | [下载]({links['mac']['arm64']}) |\n" - - md += """ -
- -## Intel -
-💻 macOS Intel 安装包 - -| 版本 | 下载链接 | -|------|----------| -""" - - # macOS Intel - for version in versions: - links = version.get_download_links() - md += f"| {version.version} | [下载]({links['mac']['x64']}) |\n" - - md += """ -
- -# 🐧 Linux - -## x64 -
-🎮 Linux x64 AppImage - -| 版本 | 下载链接 | -|------|----------| -""" - - # Linux x64 - for version in versions: - links = version.get_download_links() - md += f"| {version.version} | [下载]({links['linux']['x64']}) |\n" - - md += """ -
- - -""" - return md - -def main(): - # 示例数据 - data = """ -0.45.11,250207y6nbaw5qc -0.45.10,250205buadkzpea -0.45.9,250202tgstl42dt -0.45.8,250201b44xw1x2k -0.45.7,250130nr6eorv84 -0.45.6,25013021lv9say3 -0.45.5,250128loaeyulq8 -0.45.4,250126vgr3vztvj -0.45.3,250124b0rcj0qql -0.45.2,250123mhituoa6o -0.45.1,2501213ljml5byg -0.45.0,250120dh9ezx9pg -0.44.11,250103fqxdt5u9z -0.44.10,250102ys80vtnud -0.44.9,2412268nc6pfzgo -0.44.8,241222ooktny8mh -0.44.7,2412219nhracv01 -0.44.6,2412214pmryneua -0.44.5,241220s3ux0e1tv -0.44.4,241219117fcvexy -0.44.3,241218sybfbogmq -0.44.2,241218ntls52u8v -0.44.0,2412187f9v0nffu -0.43.6,241206z7j6me2e2 -0.43.5,241127pdg4cnbu2 -0.43.4,241126w13goyvrs -0.43.3,2411246yqzx1jmm -0.43.1,241124gsiwb66nc -0.42.5,24111460bf2loz1 -0.42.4,2410291z3bdg1dy -0.42.3,241016kxu9umuir -0.42.2,2410127mj66lvaq -0.42.1,241011i66p9fuvm -0.42.0,241009fij7nohn5 -0.41.3,240925fkhcqg263 -0.41.2,240921llnho65ov -0.41.1,2409189xe3envg5 -0.40.4,2409052yfcjagw2 -0.40.3,240829epqamqp7h -0.40.2,240828c021k3aib -0.40.1,2408245thnycuzj -0.40.0,24082202sreugb2 -0.39.6,240819ih4ta2fye -0.39.5,240814y9rhzmu7h -0.39.4,240810elmeg3seq -0.39.3,2408092hoyaxt9m -0.39.2,240808phaxh4b5r -0.39.1,240807g919tr4ly -0.39.0,240802cdixtv9a6 -0.38.1,240725f0ti25os7 -0.38.0,240723790oxe4a2 -0.37.1,240714yrr3gmv3k -0.36.2,2407077n6pzboby -0.36.1,240706uekt2eaft -0.36.0,240703xqkjv5aqa -0.35.1,240621pc2f7rl8a -0.35.0,240608cv11mfsjl -0.34.6,240606kgzq24cfb -0.34.6,240605r495newcf -0.34.5,240602rq6xovt3a -0.34.4,2406014h0rgjghe -0.34.3,240529baisuyd2e -0.34.2,240528whh1qyo9h -0.34.1,24052838ygfselt -0.34.0,240527xus72jmkj -0.33.4,240511kb8wt1tms -0.33.3,2405103lx8342ta -0.33.2,240510dwmw395qe -0.33.1,2405039a9h2fqc9 -0.33.0,240503hyjsnhazo -0.32.8,240428d499o6zja -0.32.7,240427w5guozr0l -0.32.2,240417ab4wag7sx -0.32.1,2404152czor73fk -0.32.0,240412ugli06ue0 -0.31.3,240402rq154jw46 -0.31.1,240402pkwfm2ps6 -0.31.0,2404018j7z0xv2g -0.30.5,240327tmd2ozdc7 -0.30.4,240325dezy8ziab -0.30.3,2403229gtuhto9g -0.30.2,240322gzqjm3p0d -0.30.1,2403212w1ejubt8 -0.30.0,240320tpx86e7hk -0.29.1,2403027twmz0d1t -0.29.0,240301kpqvacw2h -0.28.1,240226tstim4evd -0.28.0,240224g2d7jazcq -0.27.4,240219qdbagglqz -0.27.3,240218dxhc6y8os -0.27.2,240216kkzl9nhxi -0.27.1,240215l4ooehnyl -0.27.0,240215at6ewkd59 -0.26.2,240212o6r9qxtcg -0.26.1,2402107t904hing -0.26.0,240210k8is5xr6v -0.25.3,240207aacboj1k8 -0.25.2,240206p3708uc9z -0.25.1,2402033t030rprh -0.25.0,240203kh86t91q8 -0.24.4,240129iecm3e33w -0.24.3,2401289dx79qsc0 -0.24.1,240127cad17436d -0.24.0,240126wp9irhmza -0.23.9,240124dsmraeml3 -0.23.8,240123fnn1hj1fg -0.23.7,240123xsfe7ywcv -0.23.6,240121m1740elox -0.23.5,2401215utj6tx6q -0.23.4,240121f4qy6ba2y -0.23.3,2401201und3ytom -0.23.2,240120an2k2hf1i -0.23.1,240119fgzxwudn9 -0.22.2,24011721vsch1l1 -0.22.1,2401083eyk8kmzc -0.22.0,240107qk62kvva3 -0.21.1,231230h0vi6srww -0.21.0,231229ezidnxiu3 -0.20.2,231219aksf83aad -0.20.1,231218ywfaxax09 -0.20.0,231216nsyfew5j1 -0.19.1,2312156z2ric57n -0.19.0,231214per9qal2p -0.18.8,2312098ffjr3ign -0.18.7,23120880aolip2i -0.18.6,231207ueqazwde8 -0.18.5,231206jzy2n2sbi -0.18.4,2312033zjv5fqai -0.18.3,231203k2vnkxq2m -0.18.1,23120176kaer07t -0.17.0,231127p7iyxn8rg -0.16.0,231116rek2xuq6a -0.15.5,231115a5mv63u9f -0.15.4,23111469e1i3xyi -0.15.3,231113b0yv3uqem -0.15.2,231113ah0kuf3pf -0.15.1,231111yanyyovap -0.15.0,231110mdkomczmw -0.14.1,231109xitrgihlk -0.14.0,231102m6tuamwbx -0.13.4,231029rso7pso8l -0.13.3,231025uihnjkh9v -0.13.2,231024w4iv7xlm6 -0.13.1,231022f3j0ubckv -0.13.0,231022ptw6i4j42 -0.12.3,231008c5ursm0oj""" - - versions = parse_versions(data) - - # 生成 Markdown 文件 - markdown_content = generate_markdown(versions) - with open('Cursor历史.md', 'w', encoding='utf-8') as f: - f.write(markdown_content) - - # 创建结果数据结构 - result = { - "versions": [] - } - - # 处理每个版本 - for version in versions: - version_info = { - "version": version.version, - "build_id": version.build_id, - "downloads": version.get_download_links() - } - result["versions"].append(version_info) - - # 保存为JSON文件 - with open('cursor_downloads.json', 'w', encoding='utf-8') as f: - json.dump(result, f, indent=2, ensure_ascii=False) - - # 同时生成CSV格式的下载链接 - with open('cursor_downloads.csv', 'w', newline='', encoding='utf-8') as f: - writer = csv.writer(f) - writer.writerow(['Version', 'Platform', 'Architecture', 'Download URL']) - - for version in versions: - links = version.get_download_links() - for platform, archs in links.items(): - for arch, url in archs.items(): - writer.writerow([version.version, platform, arch, url]) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/scripts/build_all.bat b/scripts/build_all.bat deleted file mode 100644 index e07e387..0000000 --- a/scripts/build_all.bat +++ /dev/null @@ -1,74 +0,0 @@ -@echo off -setlocal EnableDelayedExpansion - -:: Build optimization flags -set "OPTIMIZATION_FLAGS=-trimpath -ldflags=\"-s -w\"" -set "BUILD_JOBS=4" - -:: Messages / 消息 -set "EN_MESSAGES[0]=Starting build process for version" -set "EN_MESSAGES[1]=Using optimization flags:" -set "EN_MESSAGES[2]=Cleaning old builds..." -set "EN_MESSAGES[3]=Cleanup completed" -set "EN_MESSAGES[4]=Starting builds for all platforms..." -set "EN_MESSAGES[5]=Building for" -set "EN_MESSAGES[6]=Build successful:" -set "EN_MESSAGES[7]=All builds completed!" - -:: Colors -set "GREEN=[32m" -set "RED=[31m" -set "RESET=[0m" - -:: Cleanup function -:cleanup -if exist "..\bin" ( - rd /s /q "..\bin" - echo %GREEN%!EN_MESSAGES[3]!%RESET% -) -mkdir "..\bin" 2>nul - -:: Build function with optimizations -:build -set "os=%~1" -set "arch=%~2" -set "ext=" -if "%os%"=="windows" set "ext=.exe" - -echo %GREEN%!EN_MESSAGES[5]! %os%/%arch%%RESET% - -set "CGO_ENABLED=0" -set "GOOS=%os%" -set "GOARCH=%arch%" - -start /b cmd /c "go build -trimpath -ldflags=\"-s -w\" -o ..\bin\%os%\%arch%\cursor-id-modifier%ext% -a -installsuffix cgo -mod=readonly ..\cmd\cursor-id-modifier" -exit /b 0 - -:: Main execution -echo %GREEN%!EN_MESSAGES[0]!%RESET% -echo %GREEN%!EN_MESSAGES[1]! %OPTIMIZATION_FLAGS%%RESET% - -call :cleanup - -echo %GREEN%!EN_MESSAGES[4]!%RESET% - -:: Start builds in parallel -set "pending=0" -for %%o in (windows linux darwin) do ( - for %%a in (amd64 386) do ( - call :build %%o %%a - set /a "pending+=1" - if !pending! geq %BUILD_JOBS% ( - timeout /t 1 /nobreak >nul - set "pending=0" - ) - ) -) - -:: Wait for all builds to complete -:wait_builds -timeout /t 2 /nobreak >nul -tasklist /fi "IMAGENAME eq go.exe" 2>nul | find "go.exe" >nul -if not errorlevel 1 goto wait_builds - -echo %GREEN%!EN_MESSAGES[7]!%RESET% \ No newline at end of file diff --git a/scripts/build_all.sh b/scripts/build_all.sh deleted file mode 100755 index 7db5e7b..0000000 --- a/scripts/build_all.sh +++ /dev/null @@ -1,143 +0,0 @@ -#!/bin/bash - -# 设置颜色代码 / Set color codes -GREEN='\033[0;32m' -RED='\033[0;31m' -NC='\033[0m' # No Color / 无颜色 - -# Build optimization flags -OPTIMIZATION_FLAGS="-trimpath -ldflags=\"-s -w\"" -PARALLEL_JOBS=$(nproc || echo "4") # Get number of CPU cores or default to 4 - -# Messages / 消息 -EN_MESSAGES=( - "Starting build process for version" - "Cleaning old builds..." - "Creating bin directory..." - "Failed to create bin directory" - "Building for" - "Successfully built:" - "Failed to build for" - "Build Summary:" - "Successful builds:" - "Failed builds:" - "Generated files:" -) - -CN_MESSAGES=( - "开始构建版本" - "正在清理旧的构建文件..." - "正在创建bin目录..." - "创建bin目录失败" - "正在构建" - "构建成功:" - "构建失败:" - "构建摘要:" - "成功构建数:" - "失败构建数:" - "生成的文件:" - "构建过程被中断" - "错误:" -) - -# 版本信息 / Version info -VERSION="1.0.0" - -# Detect system language / 检测系统语言 -detect_language() { - if [[ $(locale | grep "LANG=zh_CN") ]]; then - echo "cn" - else - echo "en" - fi -} - -# Get message based on language / 根据语言获取消息 -get_message() { - local index=$1 - local lang=$(detect_language) - - if [[ "$lang" == "cn" ]]; then - echo "${CN_MESSAGES[$index]}" - else - echo "${EN_MESSAGES[$index]}" - fi -} - -# 错误处理函数 / Error handling function -handle_error() { - echo -e "${RED}$(get_message 12) $1${NC}" - exit 1 -} - -# 清理函数 / Cleanup function -cleanup() { - if [ -d "../bin" ]; then - rm -rf ../bin - echo -e "${GREEN}$(get_message 1)${NC}" - fi -} - -# Build function with optimizations -build() { - local os=$1 - local arch=$2 - local ext="" - [ "$os" = "windows" ] && ext=".exe" - - echo -e "${GREEN}$(get_message 4) $os/$arch${NC}" - - GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build \ - -trimpath \ - -ldflags="-s -w" \ - -o "../bin/$os/$arch/cursor-id-modifier$ext" \ - -a -installsuffix cgo \ - -mod=readonly \ - ../cmd/cursor-id-modifier & -} - -# Parallel build execution -build_all() { - local builds=0 - local max_parallel=$PARALLEL_JOBS - - # Define build targets - declare -A targets=( - ["linux/amd64"]=1 - ["linux/386"]=1 - ["linux/arm64"]=1 - ["windows/amd64"]=1 - ["windows/386"]=1 - ["darwin/amd64"]=1 - ["darwin/arm64"]=1 - ) - - for target in "${!targets[@]}"; do - IFS='/' read -r os arch <<< "$target" - build "$os" "$arch" - - ((builds++)) - - if ((builds >= max_parallel)); then - wait - builds=0 - fi - done - - # Wait for remaining builds - wait -} - -# Main execution -main() { - cleanup - mkdir -p ../bin || { echo -e "${RED}$(get_message 3)${NC}"; exit 1; } - build_all - echo -e "${GREEN}Build completed successfully${NC}" -} - -# 捕获错误信号 / Catch error signals -trap 'echo -e "\n${RED}$(get_message 11)${NC}"; exit 1' INT TERM - -# 执行主函数 / Execute main function -main \ No newline at end of file diff --git a/scripts/cursor_id_modifier.pot b/scripts/cursor_id_modifier.pot deleted file mode 100644 index 54f4417..0000000 --- a/scripts/cursor_id_modifier.pot +++ /dev/null @@ -1,318 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: cursor_id_modifier\n" -"POT-Creation-Date: 2025-04-25 12:00+0000\n" -"PO-Revision-Date: 2025-04-25 12:00+0000\n" -"Language-Team: None\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -msgid "Error: No translation file found for domain 'cursor_id_modifier' in {}/zh_CN/LC_MESSAGES/" -msgstr "" - -msgid "========== Cursor ID modification tool log start {} ==========" -msgstr "" - -msgid "[INFO] {} {}" -msgstr "" - -msgid "[WARN] {} {}" -msgstr "" - -msgid "[ERROR] {} {}" -msgstr "" - -msgid "[DEBUG] {} {}" -msgstr "" - -msgid "[CMD] {} Executing command: {}" -msgstr "" - -msgid "[CMD] {}:" -msgstr "" - -msgid "Unable to get username" -msgstr "" - -msgid "Finding Cursor installation path..." -msgstr "" - -msgid "Found Cursor installation path: {}" -msgstr "" - -msgid "Found Cursor via which: {}" -msgstr "" - -msgid "Cursor executable not found, will try using config directory" -msgstr "" - -msgid "Found Cursor via search: {}" -msgstr "" - -msgid "Finding Cursor resource directory..." -msgstr "" - -msgid "Found Cursor resource directory: {}" -msgstr "" - -msgid "Found resource directory via binary path: {}" -msgstr "" - -msgid "Cursor resource directory not found" -msgstr "" - -msgid "Please run this script with sudo" -msgstr "" - -msgid "Example: sudo {}" -msgstr "" - -msgid "Checking Cursor processes..." -msgstr "" - -msgid "Getting process details for {}:" -msgstr "" - -msgid "No running Cursor processes found" -msgstr "" - -msgid "Found running Cursor processes" -msgstr "" - -msgid "Attempting to terminate Cursor processes..." -msgstr "" - -msgid "Attempting to forcefully terminate processes..." -msgstr "" - -msgid "Waiting for processes to terminate, attempt {}/{}..." -msgstr "" - -msgid "Cursor processes successfully terminated" -msgstr "" - -msgid "Unable to terminate Cursor processes after {} attempts" -msgstr "" - -msgid "Please manually terminate the processes and try again" -msgstr "" - -msgid "Configuration file does not exist, skipping backup" -msgstr "" - -msgid "Configuration backed up to: {}" -msgstr "" - -msgid "Backup failed" -msgstr "" - -msgid "File does not exist: {}" -msgstr "" - -msgid "Unable to modify file permissions: {}" -msgstr "" - -msgid "Generated temporary file is empty" -msgstr "" - -msgid "Unable to write to file: {}" -msgstr "" - -msgid "Machine code reset options" -msgstr "" - -msgid "Do you need to reset the machine code? (Usually, modifying JS files is sufficient):" -msgstr "" - -msgid "Don't reset - only modify JS files" -msgstr "" - -msgid "Reset - modify both config file and machine code" -msgstr "" - -msgid "[INPUT_DEBUG] Machine code reset option selected: {}" -msgstr "" - -msgid "You chose to reset the machine code" -msgstr "" - -msgid "Found existing configuration file: {}" -msgstr "" - -msgid "Setting new device and machine IDs..." -msgstr "" - -msgid "New device ID: {}" -msgstr "" - -msgid "New machine ID: {}" -msgstr "" - -msgid "Configuration file modified successfully" -msgstr "" - -msgid "Configuration file modification failed" -msgstr "" - -msgid "Configuration file not found, this is normal, skipping ID modification" -msgstr "" - -msgid "You chose not to reset the machine code, will only modify JS files" -msgstr "" - -msgid "Configuration processing completed" -msgstr "" - -msgid "Finding Cursor's JS files..." -msgstr "" - -msgid "Searching for JS files in resource directory: {}" -msgstr "" - -msgid "Found JS file: {}" -msgstr "" - -msgid "No JS files found in resource directory, trying other directories..." -msgstr "" - -msgid "Searching directory: {}" -msgstr "" - -msgid "No modifiable JS files found" -msgstr "" - -msgid "Found {} JS files to modify" -msgstr "" - -msgid "Starting to modify Cursor's JS files..." -msgstr "" - -msgid "Unable to find modifiable JS files" -msgstr "" - -msgid "Processing file: {}" -msgstr "" - -msgid "Unable to create backup for file: {}" -msgstr "" - -msgid "Found x-cursor-checksum setting code" -msgstr "" - -msgid "Successfully modified x-cursor-checksum setting code" -msgstr "" - -msgid "Failed to modify x-cursor-checksum setting code" -msgstr "" - -msgid "Found IOPlatformUUID keyword" -msgstr "" - -msgid "Successfully injected randomUUID call into a$ function" -msgstr "" - -msgid "Failed to modify a$ function" -msgstr "" - -msgid "Successfully injected randomUUID call into v5 function" -msgstr "" - -msgid "Failed to modify v5 function" -msgstr "" - -msgid "Completed universal modification" -msgstr "" - -msgid "File already contains custom injection code, skipping modification" -msgstr "" - -msgid "Completed most universal injection" -msgstr "" - -msgid "File has already been modified, skipping modification" -msgstr "" - -msgid "Failed to modify any JS files" -msgstr "" - -msgid "Successfully modified {} JS files" -msgstr "" - -msgid "Disabling Cursor auto-update..." -msgstr "" - -msgid "Found update configuration file: {}" -msgstr "" - -msgid "Disabled update configuration file: {}" -msgstr "" - -msgid "Found updater: {}" -msgstr "" - -msgid "Disabled updater: {}" -msgstr "" - -msgid "No update configuration files or updaters found" -msgstr "" - -msgid "Successfully disabled auto-update" -msgstr "" - -msgid "You selected: {}" -msgstr "" - -msgid "This script only supports Linux systems" -msgstr "" - -msgid "Script started..." -msgstr "" - -msgid "System information: {}" -msgstr "" - -msgid "Current user: {}" -msgstr "" - -msgid "System version information" -msgstr "" - -msgid "Cursor Linux startup tool" -msgstr "" - -msgid "Important notice" -msgstr "" - -msgid "This tool prioritizes modifying JS files, which is safer and more reliable" -msgstr "" - -msgid "Modifying Cursor JS files..." -msgstr "" - -msgid "JS files modified successfully!" -msgstr "" - -msgid "JS file modification failed, but configuration file modification may have succeeded" -msgstr "" - -msgid "If Cursor still indicates the device is disabled after restarting, please rerun this script" -msgstr "" - -msgid "Please restart Cursor to apply the new configuration" -msgstr "" - -msgid "Follow the WeChat public account [Pancake AI] to discuss more Cursor tips and AI knowledge (script is free, join the group via the public account for more tips and experts)" -msgstr "" - -msgid "Script execution completed" -msgstr "" - -msgid "========== Cursor ID modification tool log end {} ==========" -msgstr "" - -msgid "Detailed log saved to: {}" -msgstr "" - -msgid "If you encounter issues, please provide this log file to the developer for troubleshooting" -msgstr "" diff --git a/scripts/cursor_id_modifier.py b/scripts/cursor_id_modifier.py deleted file mode 100644 index aad0bc8..0000000 --- a/scripts/cursor_id_modifier.py +++ /dev/null @@ -1,1298 +0,0 @@ -# -*- coding: utf-8 -*- -""" -AppImage instructions: -mkdir -p ~/Downloads/Cursor -cd ~/Downloads/Cursor -cd Cursor && ./Cursor-1.0.5-x86_64.AppImage --appimage-extract -mkdir -p ~/.local -rsync -rt ~/Downloads/Cursor/squashfs-root/usr/ ~/.local -# ^ copy the subfolders not usr itself, so the resulting executable should be ~/.local/bin/cursor -""" -import subprocess -import os - -SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__)) -# repo_dir = os.path.dirname(SCRIPTS_DIR) -repo_dir = SCRIPTS_DIR -locales_dir = os.path.join(repo_dir, 'locales') -t_domain = "cursor_id_modifier" - -def compile_messages(): - global _ - languages = ['en_US', 'zh_CN'] - for lang in languages: - lang_dir = os.path.join(locales_dir, lang, 'LC_MESSAGES') - if os.path.isdir(lang_dir): - # Change the directory to the LC_MESSAGES folder - os.chdir(lang_dir) - # Run msgfmt command - out_name = 'cursor_id_modifier.mo' - subprocess.run(['msgfmt', '-o', out_name, 'cursor_id_modifier.po'], check=True) - print(os.path.abspath(out_name)) - else: - print(f"Directory not found: {lang_dir}") - -import sys -import subprocess -import datetime -import tempfile -import shutil -import uuid -import hashlib -import re -import getpass -import time -import select -import tty -import termios -import signal -import json -from pathlib import Path -import glob -import pwd - -import gettext -import random - -# 尝试导入netifaces,如果不可用则使用系统命令 -# Try to import netifaces, use system commands if not available -try: - import netifaces - NETIFACES_AVAILABLE = True -except ImportError: - NETIFACES_AVAILABLE = False - log_warn = lambda x: print(f"[WARN] {x}") # 临时日志函数 - log_warn("netifaces library not found, will use system commands for network operations") - -# 设置语言环境 -# Set language environment - -# lang = 'zh' if '--en' not in sys.argv else 'en' -lang = 'en' - -assert os.path.isdir(locales_dir) -# gettext.bindtextdomain(t_domain, localedir=locales_dir) -if lang == 'zh': - translation = gettext.translation( - t_domain, - localedir=locales_dir, - languages=['en_US', 'zh_CN'], - ) -else: - translation = gettext.NullTranslations() -translation.install() -_ = translation.gettext - -# 设置错误处理 -# Set error handling -def set_error_handling(): - global _ - # 在 Python 中,我们使用 try/except 来处理错误,而不是 bash 的 set -e - # In Python, we use try/except to handle errors instead of bash's set -e - pass - -set_error_handling() - -# 定义日志文件路径 -# Define log file path -LOG_FILE = "/tmp/cursor_linux_id_modifier.log" - -# 初始化日志文件 -# Initialize log file -def initialize_log(): - with open(LOG_FILE, 'w') as f: - f.write(_("========== Cursor ID modification tool log start {} ==========").format(datetime.datetime.now()) + "\n") - os.chmod(LOG_FILE, 0o644) - -# 颜色定义 -# Color definitions -RED = '\033[0;31m' -GREEN = '\033[0;32m' -YELLOW = '\033[1;33m' -BLUE = '\033[0;34m' -NC = '\033[0m' # No Color - -# 日志函数 - 同时输出到终端和日志文件 -# Log functions - output to terminal and log file simultaneously -def log_info(message): - global _ - print(f"{GREEN}[INFO]{NC} {_(message)}") - with open(LOG_FILE, 'a') as f: - f.write(_("[INFO] {} {}").format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), _(message)) + "\n") - -def log_warn(message): - global _ - print(f"{YELLOW}[WARN]{NC} {_(message)}") - with open(LOG_FILE, 'a') as f: - f.write(_("[WARN] {} {}").format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), _(message)) + "\n") - -def log_error(message): - global _ - print(f"{RED}[ERROR]{NC} {_(message)}") - with open(LOG_FILE, 'a') as f: - f.write(_("[ERROR] {} {}").format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), _(message)) + "\n") - -def log_debug(message): - global _ - print(f"{BLUE}[DEBUG]{NC} {_(message)}") - with open(LOG_FILE, 'a') as f: - f.write(_("[DEBUG] {} {}").format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), _(message)) + "\n") - -# 记录命令输出到日志文件 -# Log command output to log file -def log_cmd_output(cmd, msg): - global _ - with open(LOG_FILE, 'a') as f: - f.write(_("[CMD] {} Executing command: {}").format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), cmd) + "\n") - f.write(_("[CMD] {}:").format(_(msg)) + "\n") - process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) - with open(LOG_FILE, 'a') as f: - for line in process.stdout: - print(line, end='') - f.write(line) - process.wait() - with open(LOG_FILE, 'a') as f: - f.write("\n") - -# 获取当前用户 -# Get current user -def get_current_user(): - global _ - if os.geteuid() == 0: - return os.environ.get('SUDO_USER', '') - return getpass.getuser() - -CURRENT_USER = get_current_user() -if not CURRENT_USER: - log_error(_("Unable to get username")) - sys.exit(1) - -# 定义Linux下的Cursor路径 -# Define Cursor paths on Linux -CURSOR_CONFIG_DIR = os.path.expanduser("~/.config/Cursor") -STORAGE_FILE = os.path.join(CURSOR_CONFIG_DIR, "User/globalStorage/storage.json") -BACKUP_DIR = os.path.join(CURSOR_CONFIG_DIR, "User/globalStorage/backups") - -# 可能的Cursor二进制路径 -# Possible Cursor binary paths -CURSOR_BIN_PATHS = [ - "/usr/bin/cursor", - "/usr/local/bin/cursor", - os.path.expanduser("~/.local/bin/cursor"), - "/opt/cursor/cursor", - "/snap/bin/cursor", -] - -# 找到Cursor安装路径 -# Find Cursor installation path -def find_cursor_path(): - global _ - log_info(_("Finding Cursor installation path...")) - - for path in CURSOR_BIN_PATHS: - if os.path.isfile(path): - log_info(_("Found Cursor installation path: {}").format(path)) - os.environ['CURSOR_PATH'] = path - return True - - # 尝试通过which命令定位 - # Try locating via which command - try: - result = subprocess.run(['which', 'cursor'], capture_output=True, text=True) - if result.returncode == 0: - os.environ['CURSOR_PATH'] = result.stdout.strip() - log_info(_("Found Cursor via which: {}").format(os.environ['CURSOR_PATH'])) - return True - except subprocess.CalledProcessError: - pass - - # 尝试查找可能的安装路径 - # Try finding possible installation paths - search_dirs = ['/usr', '/opt', os.path.expanduser('~/.local')] - for dir in search_dirs: - try: - for root, _1, files in os.walk(dir): - if 'cursor' in files: - path = os.path.join(root, 'cursor') - if os.access(path, os.X_OK): - os.environ['CURSOR_PATH'] = path - log_info(_("Found Cursor via search: {}").format(os.environ['CURSOR_PATH'])) - return True - except PermissionError: - continue - log_warn(_("Cursor executable not found, will try using config directory")) - return False - -# 查找并定位Cursor资源文件目录 -# Find and locate Cursor resource directory -def find_cursor_resources(): - global _ - log_info(_("Finding Cursor resource directory...")) - - # 可能的资源目录路径 - # Possible resource directory paths - resource_paths = [ - "/usr/lib/cursor", - "/usr/share/cursor", - "/opt/cursor", - os.path.expanduser("~/.local/share/cursor"), - ] - - for path in resource_paths: - if os.path.isdir(path): - log_info(_("Found Cursor resource directory: {}").format(path)) - os.environ['CURSOR_RESOURCES'] = path - return True - - # 如果有CURSOR_PATH,尝试从它推断 - # If CURSOR_PATH exists, try to infer from it - if os.environ.get('CURSOR_PATH'): - base_dir = os.path.dirname(os.environ['CURSOR_PATH']) - resource_dir = os.path.join(base_dir, 'resources') - if os.path.isdir(resource_dir): - os.environ['CURSOR_RESOURCES'] = resource_dir - log_info(_("Found resource directory via binary path: {}").format(os.environ['CURSOR_RESOURCES'])) - return True - - log_warn(_("Cursor resource directory not found")) - return False - -# 检查权限 -# Check permissions -def check_permissions(): - global _ - if os.geteuid() != 0: - log_error(_("Please run this script with sudo")) - print(_("Example: sudo {}").format(sys.argv[0])) - sys.exit(1) - -# 检查并关闭 Cursor 进程 -# Check and kill Cursor processes -def check_and_kill_cursor(): - global _ - log_info(_("Checking Cursor processes...")) - - attempt = 1 - max_attempts = 5 - - # 函数:获取进程详细信息 - # Function: Get process details - def get_process_details(process_name): - log_debug(_("Getting process details for {}:").format(process_name)) - try: - result = subprocess.run( - 'ps aux | grep -i "cursor" | grep -v grep | grep -v "cursor_id_modifier.py"', - shell=True, capture_output=True, text=True - ) - print(result.stdout) - except subprocess.CalledProcessError: - pass - - while attempt <= max_attempts: - # 使用更精确的匹配来获取 Cursor 进程,排除当前脚本和grep进程 - # Use more precise matching to get Cursor processes, excluding current script and grep - try: - result = subprocess.run( - 'ps aux | grep -i "cursor" | grep -v "grep" | grep -v "cursor_id_modifier.py" | awk \'{print $2}\'', - shell=True, capture_output=True, text=True - ) - CURSOR_PIDS = result.stdout.strip().split('\n') - CURSOR_PIDS = [pid for pid in CURSOR_PIDS if pid] - except subprocess.CalledProcessError: - CURSOR_PIDS = [] - - if not CURSOR_PIDS: - log_info(_("No running Cursor processes found")) - return True - - log_warn(_("Found running Cursor processes")) - get_process_details("cursor") - - log_warn(_("Attempting to terminate Cursor processes...")) - - for pid in CURSOR_PIDS: - try: - if attempt == max_attempts: - log_warn(_("Attempting to forcefully terminate processes...")) - os.kill(int(pid), signal.SIGKILL) - else: - os.kill(int(pid), signal.SIGTERM) - except (OSError, ValueError): - continue - - time.sleep(1) - - # 再次检查进程是否还在运行 - # Check again if processes are still running - try: - result = subprocess.run( - 'ps aux | grep -i "cursor" | grep -v "grep" | grep -v "cursor_id_modifier.py"', - shell=True, capture_output=True, text=True - ) - if not result.stdout.strip(): - log_info(_("Cursor processes successfully terminated")) - return True - except subprocess.CalledProcessError: - log_info(_("Cursor processes successfully terminated")) - return True - - log_warn(_("Waiting for processes to terminate, attempt {}/{}...").format(attempt, max_attempts)) - attempt += 1 - - log_error(_("Unable to terminate Cursor processes after {} attempts").format(max_attempts)) - get_process_details("cursor") - log_error(_("Please manually terminate the processes and try again")) - sys.exit(1) - -# 备份配置文件 -# Backup configuration file -def backup_config(): - global _ - if not os.path.isfile(STORAGE_FILE): - log_warn(_("Configuration file does not exist, skipping backup")) - return True - - os.makedirs(BACKUP_DIR, exist_ok=True) - backup_file = os.path.join(BACKUP_DIR, "storage.json.backup_{}".format(datetime.datetime.now().strftime('%Y%m%d_%H%M%S'))) - - try: - shutil.copy(STORAGE_FILE, backup_file) - os.chmod(backup_file, 0o644) - os.chown(backup_file, pwd.getpwnam(CURRENT_USER).pw_uid, -1) - log_info(_("Configuration backed up to: {}").format(backup_file)) - except (OSError, shutil.Error): - log_error(_("Backup failed")) - sys.exit(1) - -# 生成随机 ID -# Generate random ID -def generate_random_id(): - global _ - # 生成32字节(64个十六进制字符)的随机数 - # Generate 32 bytes (64 hexadecimal characters) of random data - return hashlib.sha256(os.urandom(32)).hexdigest() - -# 生成随机 UUID -# Generate random UUID -def generate_uuid(): - global _ - # 在Linux上使用uuid模块生成UUID - # Use uuid module to generate UUID on Linux - try: - return str(uuid.uuid1()).lower() - except Exception: - # 备选方案:生成类似UUID的字符串 - # Fallback: Generate UUID-like string - rand_bytes = os.urandom(16) - rand_hex = rand_bytes.hex() - return f"{rand_hex[:8]}-{rand_hex[8:12]}-{rand_hex[12:16]}-{rand_hex[16:20]}-{rand_hex[20:]}" - -# 修改现有文件 -# Modify or add to configuration file -def modify_or_add_config(key, value, file): - global _ - if not os.path.isfile(file): - log_error(_("File does not exist: {}").format(file)) - return False - - # 确保文件可写 - # Ensure file is writable - try: - os.chmod(file, 0o644) - except OSError: - log_error(_("Unable to modify file permissions: {}").format(file)) - return False - - # 创建临时文件 - # Create temporary file - with tempfile.NamedTemporaryFile(mode='w', delete=False) as temp_file: - temp_path = temp_file.name - - # 读取原始文件 - # Read original file - with open(file, 'r') as f: - content = f.read() - - # 检查key是否存在 - # Check if key exists - if f'"{key}":' in content: - # key存在,执行替换 - # Key exists, perform replacement - pattern = f'"{key}":\\s*"[^"]*"' - replacement = f'"{key}": "{value}"' - new_content = re.sub(pattern, replacement, content) - else: - # key不存在,添加新的key-value对 - # Key does not exist, add new key-value pair - new_content = content.rstrip('}\n') + f',\n "{key}": "{value}"\n}}' - - # 写入临时文件 - # Write to temporary file - with open(temp_path, 'w') as f: - f.write(new_content) - - # 检查临时文件是否为空 - # Check if temporary file is empty - if os.path.getsize(temp_path) == 0: - log_error(_("Generated temporary file is empty")) - os.unlink(temp_path) - return False - - # 替换原文件 - # Replace original file - try: - shutil.move(temp_path, file) - except OSError: - log_error(_("Unable to write to file: {}").format(file)) - os.unlink(temp_path) - return False - - # 恢复文件权限 - # Restore file permissions - os.chmod(file, 0o444) - return True - -# 生成新的配置 -# Generate new configuration -def generate_new_config(): - global _ - print() - log_warn(_("Machine code reset options")) - - # 使用菜单选择函数询问用户是否重置机器码 - # Use menu selection function to ask user whether to reset machine code - reset_choice = select_menu_option( - _("Do you need to reset the machine code? (Usually, modifying JS files is sufficient):"), - [_("Don't reset - only modify JS files"), _("Reset - modify both config file and machine code")], - 0 - ) - - # 记录日志以便调试 - # Log for debugging - with open(LOG_FILE, 'a') as f: - f.write(_("[INPUT_DEBUG] Machine code reset option selected: {}").format(reset_choice) + "\n") - - # 处理用户选择 - # Handle user selection - if reset_choice == 1: - log_info(_("You chose to reset the machine code")) - - # 确保配置文件目录存在 - # Ensure configuration file directory exists - if os.path.isfile(STORAGE_FILE): - log_info(_("Found existing configuration file: {}").format(STORAGE_FILE)) - - # 备份现有配置 - # Backup existing configuration - backup_config() - - # 生成并设置新的设备ID - # Generate and set new device ID - new_device_id = generate_uuid() - new_machine_id = "auth0|user_{}".format(hashlib.sha256(os.urandom(16)).hexdigest()[:32]) - - log_info(_("Setting new device and machine IDs...")) - log_debug(_("New device ID: {}").format(new_device_id)) - log_debug(_("New machine ID: {}").format(new_machine_id)) - - # 修改配置文件 - # Modify configuration file - if (modify_or_add_config("deviceId", new_device_id, STORAGE_FILE) and - modify_or_add_config("machineId", new_machine_id, STORAGE_FILE)): - log_info(_("Configuration file modified successfully")) - else: - log_error(_("Configuration file modification failed")) - else: - log_warn(_("Configuration file not found, this is normal, skipping ID modification")) - else: - log_info(_("You chose not to reset the machine code, will only modify JS files")) - - # 确保配置文件目录存在 - # Ensure configuration file directory exists - if os.path.isfile(STORAGE_FILE): - log_info(_("Found existing configuration file: {}").format(STORAGE_FILE)) - - # 备份现有配置 - # Backup existing configuration - backup_config() - else: - log_warn(_("Configuration file not found, this is normal, skipping ID modification")) - - print() - log_info(_("Configuration processing completed")) - -# 查找Cursor的JS文件 -# Find Cursor's JS files -def find_cursor_js_files(): - global _ - log_info(_("Finding Cursor's JS files...")) - - js_files = [] - found = False - - # 如果找到了资源目录,在资源目录中搜索 - # If resource directory is found, search in it - if os.environ.get('CURSOR_RESOURCES'): - log_debug(_("Searching for JS files in resource directory: {}").format(os.environ['CURSOR_RESOURCES'])) - - # 在资源目录中递归搜索特定JS文件 - # Recursively search for specific JS files in resource directory - js_patterns = [ - "*/extensionHostProcess.js", - "*/main.js", - "*/cliProcessMain.js", - "*/app/out/vs/workbench/api/node/extensionHostProcess.js", - "*/app/out/main.js", - "*/app/out/vs/code/node/cliProcessMain.js", - ] - - for pattern in js_patterns: - try: - files = glob.glob(os.path.join(os.environ['CURSOR_RESOURCES'], pattern), recursive=True) - for file in files: - log_info(_("Found JS file: {}").format(file)) - js_files.append(file) - found = True - except Exception: - continue - - # 如果还没找到,尝试在/usr和$HOME目录下搜索 - # If not found, try searching in /usr and $HOME directories - if not found: - log_warn(_("No JS files found in resource directory, trying other directories...")) - - search_dirs = [ - "/usr/lib/cursor", - "/usr/share/cursor", - "/opt/cursor", - os.path.expanduser("~/.config/Cursor"), - os.path.expanduser("~/.local/share/cursor"), - ] - - for dir in search_dirs: - if os.path.isdir(dir): - log_debug(_("Searching directory: {}").format(dir)) - try: - for root, _1, files in os.walk(dir): - for file in files: - if file.endswith('.js'): - file_path = os.path.join(root, file) - with open(file_path, 'r', errors='ignore') as f: - content = f.read() - if "IOPlatformUUID" in content or "x-cursor-checksum" in content: - log_info(_("Found JS file: {}").format(file_path)) - js_files.append(file_path) - found = True - except Exception: - continue - - if not found: - log_error(_("No modifiable JS files found")) - return False, [] - - # 保存找到的文件列表到环境变量 - # Save found files to environment variable - os.environ['CURSOR_JS_FILES'] = json.dumps(js_files) - log_info(_("Found {} JS files to modify").format(len(js_files))) - return True, js_files - -# 修改Cursor的JS文件 -# Modify Cursor's JS files -def modify_cursor_js_files(): - global _ - log_info(_("Starting to modify Cursor's JS files...")) - - # 先查找需要修改的JS文件 - # First find JS files to modify - success, js_files = find_cursor_js_files() - if not success: - log_error(_("Unable to find modifiable JS files")) - return False - - modified_count = 0 - - for file in js_files: - log_info(_("Processing file: {}").format(file)) - - # 创建文件备份 - # Create file backup - backup_file = f"{file}.backup_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}" - try: - shutil.copy(file, backup_file) - except OSError: - log_error(_("Unable to create backup for file: {}").format(file)) - continue - - # 确保文件可写 - # Ensure file is writable - try: - os.chmod(file, 0o644) - except OSError: - log_error(_("Unable to modify file permissions: {}").format(file)) - continue - - # 读取文件内容 - # Read file content - with open(file, 'r', errors='ignore') as f: - content = f.read() - - # 检查文件内容并进行相应修改 - # Check file content and make appropriate modifications - if 'i.header.set("x-cursor-checksum' in content: - log_debug(_("Found x-cursor-checksum setting code")) - new_content = content.replace( - 'i.header.set("x-cursor-checksum",e===void 0?`${p}${t}`:`${p}${t}/${e}`)', - 'i.header.set("x-cursor-checksum",e===void 0?`${p}${t}`:`${p}${t}/${p}`)' - ) - if new_content != content: - with open(file, 'w') as f: - f.write(new_content) - log_info(_("Successfully modified x-cursor-checksum setting code")) - modified_count += 1 - else: - log_error(_("Failed to modify x-cursor-checksum setting code")) - shutil.copy(backup_file, file) - elif "IOPlatformUUID" in content: - log_debug(_("Found IOPlatformUUID keyword")) - if "function a$" in content and "return crypto.randomUUID()" not in content: - new_content = content.replace( - "function a$(t){switch", - "function a$(t){return crypto.randomUUID(); switch" - ) - if new_content != content: - with open(file, 'w') as f: - f.write(new_content) - log_debug(_("Successfully injected randomUUID call into a$ function")) - modified_count += 1 - else: - log_error(_("Failed to modify a$ function")) - shutil.copy(backup_file, file) - elif "async function v5" in content and "return crypto.randomUUID()" not in content: - new_content = content.replace( - "async function v5(t){let e=", - "async function v5(t){return crypto.randomUUID(); let e=" - ) - if new_content != content: - with open(file, 'w') as f: - f.write(new_content) - log_debug(_("Successfully injected randomUUID call into v5 function")) - modified_count += 1 - else: - log_error(_("Failed to modify v5 function")) - shutil.copy(backup_file, file) - else: - # 通用注入方法 - # Universal injection method - if "// Cursor ID 修改工具注入" not in content: - timestamp = datetime.datetime.now().strftime('%s') - datetime_s = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - inject_code = f""" -// Cursor ID 修改工具注入 - {datetime_s} -// 随机设备ID生成器注入 - {timestamp} -const randomDeviceId_{timestamp} = () => {{ - try {{ - return require('crypto').randomUUID(); - }} catch (e) {{ - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {{ - const r = Math.random() * 16 | 0; - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); - }}); - }} -}}; -""" - # NOTE: double {{ or }} is literal, so code matches: - old_bash_inject_code = """ -// Cursor ID 修改工具注入 - $(date +%Y%m%d%H%M%S) -// 随机设备ID生成器注入 - $(date +%s) -const randomDeviceId_$(date +%s) = () => { - try { - return require('crypto').randomUUID(); - } catch (e) { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { - const r = Math.random() * 16 | 0; - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); - }); - } -}; -""" - new_content = inject_code + content - new_content = new_content.replace(f"await v5(!1)", f"randomDeviceId_{timestamp}()") - new_content = new_content.replace(f"a$(t)", f"randomDeviceId_{timestamp}()") - with open(file, 'w') as f: - f.write(new_content) - log_debug(_("Completed universal modification")) - modified_count += 1 - else: - log_info(_("File already contains custom injection code, skipping modification")) - else: - # 未找到关键字,尝试通用方法 - # No keywords found, try universal method - if "return crypto.randomUUID()" not in content and "// Cursor ID 修改工具注入" not in content: - if "function t$()" in content or "async function y5" in content: - new_content = content - if "function t$()" in new_content: - new_content = new_content.replace( - "function t$(){", - 'function t$(){return "00:00:00:00:00:00";' - ) - if "async function y5" in new_content: - new_content = new_content.replace( - "async function y5(t){", - "async function y5(t){return crypto.randomUUID();" - ) - if new_content != content: - with open(file, 'w') as f: - f.write(new_content) - modified_count += 1 - else: - # 最通用的注入方法 - # Most universal injection method - new_uuid = generate_uuid() - machine_id = f"auth0|user_{hashlib.sha256(os.urandom(16)).hexdigest()[:32]}" - device_id = generate_uuid() - mac_machine_id = hashlib.sha256(os.urandom(32)).hexdigest() - timestamp = datetime.datetime.now().strftime('%s') - datetime_s = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - inject_universal_code = f""" -// Cursor ID 修改工具注入 - {datetime_s} -// 全局拦截设备标识符 - {timestamp} -const originalRequire_{timestamp} = require; -require = function(module) {{ - const result = originalRequire_{timestamp}(module); - if (module === 'crypto' && result.randomUUID) {{ - const originalRandomUUID_{timestamp} = result.randomUUID; - result.randomUUID = function() {{ - return '{new_uuid}'; - }}; - }} - return result; -}}; - -// 覆盖所有可能的系统ID获取函数 -global.getMachineId = function() {{ return '{machine_id}'; }}; -global.getDeviceId = function() {{ return '{device_id}'; }}; -global.macMachineId = '{mac_machine_id}'; -""" - # NOTE: Double {{ or }} is literal, so matches: - old_bash_inject_code = """ -// Cursor ID 修改工具注入 - $(date +%Y%m%d%H%M%S) -// 全局拦截设备标识符 - $(date +%s) -const originalRequire_$(date +%s) = require; -require = function(module) { - const result = originalRequire_$(date +%s)(module); - if (module === 'crypto' && result.randomUUID) { - const originalRandomUUID_$(date +%s) = result.randomUUID; - result.randomUUID = function() { - return '$new_uuid'; - }; - } - return result; -}; - -// 覆盖所有可能的系统ID获取函数 -global.getMachineId = function() { return '$machine_id'; }; -global.getDeviceId = function() { return '$device_id'; }; -global.macMachineId = '$mac_machine_id'; -""" - new_content = inject_universal_code + content - with open(file, 'w') as f: - f.write(new_content) - log_debug(_("Completed most universal injection")) - modified_count += 1 - else: - log_info(_("File has already been modified, skipping modification")) - - # 恢复文件权限 - # Restore file permissions - os.chmod(file, 0o444) - - if modified_count == 0: - log_error(_("Failed to modify any JS files")) - return False - - log_info(_("Successfully modified {} JS files").format(modified_count)) - return True - -# 获取网络接口列表 -# Get network interface list -def get_network_interfaces(): - global _ - if NETIFACES_AVAILABLE: - try: - # 使用netifaces库 - # Use netifaces library - interfaces = netifaces.interfaces() - # 过滤掉回环接口 - # Filter out loopback interfaces - return [iface for iface in interfaces if not iface.startswith('lo')] - except Exception: - pass - - # 如果netifaces不可用或失败,使用系统命令 - # If netifaces is not available or failed, use system commands - try: - result = subprocess.run(['ip', 'link', 'show'], capture_output=True, text=True) - interfaces = [] - for line in result.stdout.split('\n'): - if ': ' in line and 'state' in line.lower(): - iface_name = line.split(': ')[1].split('@')[0] - if not iface_name.startswith('lo'): - interfaces.append(iface_name) - return interfaces - except subprocess.CalledProcessError: - # 最后的备选方案 - # Last fallback option - try: - result = subprocess.run(['ls', '/sys/class/net/'], capture_output=True, text=True) - interfaces = result.stdout.strip().split('\n') - return [iface for iface in interfaces if not iface.startswith('lo')] - except subprocess.CalledProcessError: - log_error(_("Unable to get network interface list")) - return [] - -# 生成随机MAC地址 -# Generate random MAC address -def generate_random_mac(): - global _ - # 生成随机MAC地址,确保第一个字节的最低位为0(单播地址) - # Generate random MAC address, ensure the lowest bit of first byte is 0 (unicast address) - mac = [0x00, 0x16, 0x3e, - random.randint(0x00, 0x7f), - random.randint(0x00, 0xff), - random.randint(0x00, 0xff)] - return ':'.join(map(lambda x: "%02x" % x, mac)) - -# 获取当前MAC地址 -# Get current MAC address -def get_current_mac(interface): - global _ - if NETIFACES_AVAILABLE: - try: - # 使用netifaces库 - # Use netifaces library - addrs = netifaces.ifaddresses(interface) - if netifaces.AF_LINK in addrs: - return addrs[netifaces.AF_LINK][0]['addr'] - except Exception: - pass - - # 使用系统命令获取MAC地址 - # Use system command to get MAC address - try: - result = subprocess.run(['cat', f'/sys/class/net/{interface}/address'], - capture_output=True, text=True) - if result.returncode == 0: - return result.stdout.strip() - except subprocess.CalledProcessError: - pass - - # 备选方案:使用ip命令 - # Fallback: use ip command - try: - result = subprocess.run(['ip', 'link', 'show', interface], - capture_output=True, text=True) - for line in result.stdout.split('\n'): - if 'link/ether' in line: - return line.split()[1] - except subprocess.CalledProcessError: - pass - - return None - -# 修改MAC地址 -# Modify MAC address -def modify_mac_address(): - global _ - log_info(_("Starting MAC address modification...")) - - # 获取网络接口列表 - # Get network interface list - interfaces = get_network_interfaces() - if not interfaces: - log_error(_("No network interfaces found")) - return False - - log_info(_("Found network interfaces: {}").format(', '.join(interfaces))) - - # 选择要修改的接口(通常选择第一个非回环接口) - # Select interface to modify (usually the first non-loopback interface) - target_interface = None - for iface in interfaces: - # 优先选择以太网接口 - # Prefer ethernet interfaces - if any(prefix in iface.lower() for prefix in ['eth', 'enp', 'ens', 'enx']): - target_interface = iface - break - - # 如果没有找到以太网接口,选择第一个可用接口 - # If no ethernet interface found, select first available interface - if not target_interface and interfaces: - target_interface = interfaces[0] - - if not target_interface: - log_error(_("No suitable network interface found")) - return False - - log_info(_("Selected network interface: {}").format(target_interface)) - - # 获取当前MAC地址 - # Get current MAC address - current_mac = get_current_mac(target_interface) - if current_mac: - log_info(_("Current MAC address: {}").format(current_mac)) - - # 备份当前MAC地址 - # Backup current MAC address - backup_file = os.path.join(BACKUP_DIR, f"original_mac_{target_interface}.txt") - try: - os.makedirs(BACKUP_DIR, exist_ok=True) - with open(backup_file, 'w') as f: - f.write(f"{target_interface}:{current_mac}\n") - log_info(_("Original MAC address backed up to: {}").format(backup_file)) - except OSError: - log_warn(_("Failed to backup original MAC address")) - else: - log_warn(_("Unable to get current MAC address")) - - # 生成新的MAC地址 - # Generate new MAC address - new_mac = generate_random_mac() - log_info(_("Generated new MAC address: {}").format(new_mac)) - - # 修改MAC地址 - # Modify MAC address - success = False - - # 方法1:使用ip命令 - # Method 1: Use ip command - try: - log_debug(_("Attempting to modify MAC address using ip command...")) - - # 先关闭接口 - # First bring down the interface - subprocess.run(['ip', 'link', 'set', 'dev', target_interface, 'down'], - check=True, capture_output=True) - - # 修改MAC地址 - # Modify MAC address - subprocess.run(['ip', 'link', 'set', 'dev', target_interface, 'address', new_mac], - check=True, capture_output=True) - - # 重新启用接口 - # Bring up the interface - subprocess.run(['ip', 'link', 'set', 'dev', target_interface, 'up'], - check=True, capture_output=True) - - success = True - log_info(_("Successfully modified MAC address using ip command")) - - except subprocess.CalledProcessError as e: - log_warn(_("Failed to modify MAC address using ip command: {}").format(str(e))) - - # 方法2:使用ifconfig命令(备选方案) - # Method 2: Use ifconfig command (fallback) - if not success: - try: - log_debug(_("Attempting to modify MAC address using ifconfig command...")) - - # 先关闭接口 - # First bring down the interface - subprocess.run(['ifconfig', target_interface, 'down'], - check=True, capture_output=True) - - # 修改MAC地址 - # Modify MAC address - subprocess.run(['ifconfig', target_interface, 'hw', 'ether', new_mac], - check=True, capture_output=True) - - # 重新启用接口 - # Bring up the interface - subprocess.run(['ifconfig', target_interface, 'up'], - check=True, capture_output=True) - - success = True - log_info(_("Successfully modified MAC address using ifconfig command")) - - except subprocess.CalledProcessError as e: - log_warn(_("Failed to modify MAC address using ifconfig command: {}").format(str(e))) - - # 验证MAC地址修改 - # Verify MAC address modification - if success: - time.sleep(2) # 等待网络接口稳定 - # Wait for network interface to stabilize - - new_current_mac = get_current_mac(target_interface) - if new_current_mac and new_current_mac.lower() == new_mac.lower(): - log_info(_("MAC address modification verified successfully")) - log_info(_("New MAC address: {}").format(new_current_mac)) - - # 保存新MAC地址信息 - # Save new MAC address information - new_mac_file = os.path.join(BACKUP_DIR, f"new_mac_{target_interface}.txt") - try: - with open(new_mac_file, 'w') as f: - f.write(f"{target_interface}:{new_current_mac}\n") - f.write(f"Modified at: {datetime.datetime.now()}\n") - log_info(_("New MAC address information saved to: {}").format(new_mac_file)) - except OSError: - log_warn(_("Failed to save new MAC address information")) - - return True - else: - log_error(_("MAC address modification verification failed")) - log_error(_("Expected: {}, Actual: {}").format(new_mac, new_current_mac)) - return False - else: - log_error(_("All MAC address modification methods failed")) - log_error(_("Please check if you have sufficient permissions or try running with sudo")) - return False - -# 禁用自动更新 -# Disable auto-update -def disable_auto_update(): - global _ - log_info(_("Disabling Cursor auto-update...")) - - # 查找可能的更新配置文件 - # Find possible update configuration files - update_configs = [ - os.path.join(CURSOR_CONFIG_DIR, "update-config.json"), - os.path.expanduser("~/.local/share/cursor/update-config.json"), - "/opt/cursor/resources/app-update.yml", - ] - - disabled = False - - for config in update_configs: - if os.path.isfile(config): - log_info(_("Found update configuration file: {}").format(config)) - try: - shutil.copy(config, f"{config}.bak") - with open(config, 'w') as f: - json.dump({"autoCheck": False, "autoDownload": False}, f) - os.chmod(config, 0o444) - log_info(_("Disabled update configuration file: {}").format(config)) - disabled = True - except (OSError, shutil.Error): - continue - - # 尝试查找updater可执行文件并禁用 - # Try to find and disable updater executable - updater_paths = [ - os.path.join(CURSOR_CONFIG_DIR, "updater"), - "/opt/cursor/updater", - "/usr/lib/cursor/updater", - ] - - for updater in updater_paths: - if os.path.exists(updater): - log_info(_("Found updater: {}").format(updater)) - try: - if os.path.isfile(updater): - shutil.move(updater, f"{updater}.bak") - else: - Path(f"{updater}.disabled").touch() - log_info(_("Disabled updater: {}").format(updater)) - disabled = True - except (OSError, shutil.Error): - continue - - if not disabled: - log_warn(_("No update configuration files or updaters found")) - else: - log_info(_("Successfully disabled auto-update")) - -# 新增:通用菜单选择函数 -# New: Universal menu selection function -def select_menu_option(prompt, options, default_index=0): - global _ - # 保存终端设置 - # Save terminal settings - old_settings = termios.tcgetattr(sys.stdin) - selected_index = default_index - - try: - # 设置终端为非缓冲模式 - # Set terminal to non-buffered mode - tty.setcbreak(sys.stdin.fileno()) - - # 显示提示信息 - # Display prompt - print(_(prompt)) - - # 第一次显示菜单 - # Display menu initially - for i, option in enumerate(options): - if i == selected_index: - print(f" {GREEN}►{NC} {_(option)}") - else: - print(f" {_(option)}") - - while True: - # 读取键盘输入 - # Read keyboard input - rlist, _1, _2 = select.select([sys.stdin], [], [], 0.1) - if rlist: - key = sys.stdin.read(1) - # 上箭头键 - # Up arrow key - if key == '\033': - next_char = sys.stdin.read(2) - if next_char == '[A' and selected_index > 0: - selected_index -= 1 - # 下箭头键 - # Down arrow key - elif next_char == '[B' and selected_index < len(options) - 1: - selected_index += 1 - # Enter键 - # Enter key - elif key == '\n': - print() - log_info(_("You selected: {}").format(options[selected_index])) - return selected_index - - # 清除当前菜单 - # Clear current menu - sys.stdout.write('\033[{}A\033[J'.format(len(options) + 1)) - sys.stdout.flush() - - # 重新显示提示和菜单 - # Redisplay prompt and menu - print(_(prompt)) - for i, option in enumerate(options): - if i == selected_index: - print(f" {GREEN}►{NC} {_(option)}") - else: - print(f" {_(option)}") - sys.stdout.flush() - - finally: - # 恢复终端设置 - # Restore terminal settings - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) - -# 主函数 -# Main function -def main(): - global _ - # 检查系统环境 - # Check system environment - if sys.platform != "linux": - log_error(_("This script only supports Linux systems")) - sys.exit(1) - - # 初始化日志文件 - # Initialize log file - initialize_log() - log_info(_("Script started...")) - - # 记录系统信息 - # Log system information - log_info(_("System information: {}").format(subprocess.getoutput("uname -a"))) - log_info(_("Current user: {}").format(CURRENT_USER)) - log_cmd_output( - "lsb_release -a 2>/dev/null || cat /etc/*release 2>/dev/null || cat /etc/issue", - _("System version information") - ) - - # 清除终端 - # Clear terminal - os.system('clear') - - # 显示 Logo - # Display Logo - print(""" - ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ - ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ - ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ - ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ - ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ - ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ - """) - print(f"{BLUE}================================{NC}") - print(f"{GREEN} {_('Cursor Linux startup tool')} {NC}") - print(f"{BLUE}================================{NC}") - print() - print(f"{YELLOW}[{_('Important notice')}] {NC} {_('This tool prioritizes modifying JS files, which is safer and more reliable')}") - print() - - # 执行主要功能 - # Execute main functions - check_permissions() - find_cursor_path() - find_cursor_resources() - check_and_kill_cursor() - backup_config() - generate_new_config() - - # 修改JS文件 - # Modify JS files - log_info(_("Modifying Cursor JS files...")) - js_success = modify_cursor_js_files() - if js_success: - log_info(_("JS files modified successfully!")) - else: - log_warn(_("JS file modification failed, but configuration file modification may have succeeded")) - log_warn(_("If Cursor still indicates the device is disabled after restarting, please rerun this script")) - - # 修改MAC地址 - # Modify MAC address - log_info(_("Modifying system MAC address...")) - mac_success = modify_mac_address() - if mac_success: - log_info(_("MAC address modified successfully!")) - else: - log_warn(_("MAC address modification failed")) - log_warn(_("This may affect the effectiveness of device identification bypass")) - - # 禁用自动更新 - # Disable auto-update - disable_auto_update() - - # 显示修改结果总结 - # Display modification result summary - print() - print(f"{GREEN}================================{NC}") - print(f"{BLUE} {_('Modification Results Summary')} {NC}") - print(f"{GREEN}================================{NC}") - - if js_success: - print(f"{GREEN}✓{NC} {_('JS files modification: SUCCESS')}") - else: - print(f"{RED}✗{NC} {_('JS files modification: FAILED')}") - - if mac_success: - print(f"{GREEN}✓{NC} {_('MAC address modification: SUCCESS')}") - else: - print(f"{RED}✗{NC} {_('MAC address modification: FAILED')}") - - print(f"{GREEN}================================{NC}") - print() - - log_info(_("Please restart Cursor to apply the new configuration")) - - # 显示最后的提示信息 - # Display final prompt - print() - print(f"{GREEN}================================{NC}") - print(f"{YELLOW} {_('Follow the WeChat public account [Pancake AI] to discuss more Cursor tips and AI knowledge (script is free, join the group via the public account for more tips and experts)')} {NC}") - print("WeChat account: [煎饼果子卷AI]") - print(f"{GREEN}================================{NC}") - print() - - # 记录脚本完成信息 - # Log script completion information - log_info(_("Script execution completed")) - with open(LOG_FILE, 'a') as f: - f.write(_("========== Cursor ID modification tool log end {} ==========").format(datetime.datetime.now()) + "\n") - - # 显示日志文件位置 - # Display log file location - print() - log_info(_("Detailed log saved to: {}").format(LOG_FILE)) - print(_("If you encounter issues, please provide this log file to the developer for troubleshooting")) - print() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/scripts/git-actions.sh b/scripts/git-actions.sh deleted file mode 100755 index 632e312..0000000 --- a/scripts/git-actions.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -REPO_DIR="$PWD" -LOCALES_DIR="$REPO_DIR/locales" -msginit -i cursor_id_modifier.pot -o $LOCALES_DIR/en_US/LC_MESSAGES/cursor_id_modifier.po -l en_US -for lang in en_US zh_CN; do - cd $LOCALES_DIR/$lang/LC_MESSAGES - msgfmt -o cursor_id_modifier.mo cursor_id_modifier.po -done - diff --git a/scripts/install.ps1 b/scripts/install.ps1 deleted file mode 100644 index b30350b..0000000 --- a/scripts/install.ps1 +++ /dev/null @@ -1,193 +0,0 @@ -# Check for admin rights and handle elevation -$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") -if (-NOT $isAdmin) { - # Detect PowerShell version and path - $pwshPath = if (Get-Command "pwsh" -ErrorAction SilentlyContinue) { - (Get-Command "pwsh").Source # PowerShell 7+ - } elseif (Test-Path "$env:ProgramFiles\PowerShell\7\pwsh.exe") { - "$env:ProgramFiles\PowerShell\7\pwsh.exe" - } else { - "powershell.exe" # Windows PowerShell - } - - try { - Write-Host "`nRequesting administrator privileges..." -ForegroundColor Cyan - $scriptPath = $MyInvocation.MyCommand.Path - $argList = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" - Start-Process -FilePath $pwshPath -Verb RunAs -ArgumentList $argList -Wait - exit - } - catch { - Write-Host "`nError: Administrator privileges required" -ForegroundColor Red - Write-Host "Please run this script from an Administrator PowerShell window" -ForegroundColor Yellow - Write-Host "`nTo do this:" -ForegroundColor Cyan - Write-Host "1. Press Win + X" -ForegroundColor White - Write-Host "2. Click 'Windows Terminal (Admin)' or 'PowerShell (Admin)'" -ForegroundColor White - Write-Host "3. Run the installation command again" -ForegroundColor White - Write-Host "`nPress enter to exit..." - $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - exit 1 - } -} - -# Set TLS to 1.2 -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - -# Create temporary directory -$TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString()) -New-Item -ItemType Directory -Path $TmpDir | Out-Null - -# Cleanup function -function Cleanup { - if (Test-Path $TmpDir) { - Remove-Item -Recurse -Force $TmpDir - } -} - -# Error handler -trap { - Write-Host "Error: $_" -ForegroundColor Red - Cleanup - Write-Host "Press enter to exit..." - $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - exit 1 -} - -# Detect system architecture -function Get-SystemArch { - if ([Environment]::Is64BitOperatingSystem) { - return "x86_64" - } else { - return "i386" - } -} - -# Download with progress -function Get-FileWithProgress { - param ( - [string]$Url, - [string]$OutputFile - ) - - try { - $webClient = New-Object System.Net.WebClient - $webClient.Headers.Add("User-Agent", "PowerShell Script") - - $webClient.DownloadFile($Url, $OutputFile) - return $true - } - catch { - Write-Host "Failed to download: $_" -ForegroundColor Red - return $false - } -} - -# Main installation function -function Install-CursorModifier { - Write-Host "Starting installation..." -ForegroundColor Cyan - - # Detect architecture - $arch = Get-SystemArch - Write-Host "Detected architecture: $arch" -ForegroundColor Green - - # Set installation directory - $InstallDir = "$env:ProgramFiles\CursorModifier" - if (!(Test-Path $InstallDir)) { - New-Item -ItemType Directory -Path $InstallDir | Out-Null - } - - # Get latest release - try { - $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" - Write-Host "Found latest release: $($latestRelease.tag_name)" -ForegroundColor Cyan - - # Look for Windows binary with our architecture - $version = $latestRelease.tag_name.TrimStart('v') - Write-Host "Version: $version" -ForegroundColor Cyan - $possibleNames = @( - "cursor-id-modifier_${version}_windows_x86_64.exe", - "cursor-id-modifier_${version}_windows_$($arch).exe" - ) - - $asset = $null - foreach ($name in $possibleNames) { - Write-Host "Checking for asset: $name" -ForegroundColor Cyan - $asset = $latestRelease.assets | Where-Object { $_.name -eq $name } - if ($asset) { - Write-Host "Found matching asset: $($asset.name)" -ForegroundColor Green - break - } - } - - if (!$asset) { - Write-Host "`nAvailable assets:" -ForegroundColor Yellow - $latestRelease.assets | ForEach-Object { Write-Host "- $($_.name)" } - throw "Could not find appropriate Windows binary for $arch architecture" - } - - $downloadUrl = $asset.browser_download_url - } - catch { - Write-Host "Failed to get latest release: $_" -ForegroundColor Red - exit 1 - } - - # Download binary - Write-Host "`nDownloading latest release..." -ForegroundColor Cyan - $binaryPath = Join-Path $TmpDir "cursor-id-modifier.exe" - - if (!(Get-FileWithProgress -Url $downloadUrl -OutputFile $binaryPath)) { - exit 1 - } - - # Install binary - Write-Host "Installing..." -ForegroundColor Cyan - try { - Copy-Item -Path $binaryPath -Destination "$InstallDir\cursor-id-modifier.exe" -Force - - # Add to PATH if not already present - $currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine") - if ($currentPath -notlike "*$InstallDir*") { - [Environment]::SetEnvironmentVariable("Path", "$currentPath;$InstallDir", "Machine") - } - } - catch { - Write-Host "Failed to install: $_" -ForegroundColor Red - exit 1 - } - - Write-Host "Installation completed successfully!" -ForegroundColor Green - Write-Host "Running cursor-id-modifier..." -ForegroundColor Cyan - - # Run the program - try { - & "$InstallDir\cursor-id-modifier.exe" - if ($LASTEXITCODE -ne 0) { - Write-Host "Failed to run cursor-id-modifier" -ForegroundColor Red - exit 1 - } - } - catch { - Write-Host "Failed to run cursor-id-modifier: $_" -ForegroundColor Red - exit 1 - } -} - -# Run installation -try { - Install-CursorModifier -} -catch { - Write-Host "Installation failed: $_" -ForegroundColor Red - Cleanup - Write-Host "Press enter to exit..." - $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - exit 1 -} -finally { - Cleanup - if ($LASTEXITCODE -ne 0) { - Write-Host "Press enter to exit..." -ForegroundColor Green - $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - } -} \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh deleted file mode 100755 index adc7881..0000000 --- a/scripts/install.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash - -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -BLUE='\033[0;36m' -YELLOW='\033[0;33m' -NC='\033[0m' - -# Temporary directory for downloads -TMP_DIR=$(mktemp -d) -trap 'rm -rf "$TMP_DIR"' EXIT - -# Check for required commands -check_requirements() { - if ! command -v curl >/dev/null 2>&1; then - echo -e "${RED}Error: curl is required${NC}" - exit 1 - fi -} - -# Detect system information -detect_system() { - local os arch suffix - - case "$(uname -s)" in - Linux*) os="linux";; - Darwin*) os="darwin";; - *) echo -e "${RED}Unsupported OS${NC}"; exit 1;; - esac - - case "$(uname -m)" in - x86_64) - arch="x86_64" - ;; - aarch64|arm64) - arch="arm64" - ;; - i386|i686) - arch="i386" - ;; - *) echo -e "${RED}Unsupported architecture${NC}"; exit 1;; - esac - - echo "$os $arch" -} - -# Download with progress -download() { - local url="$1" - local output="$2" - curl -#L "$url" -o "$output" -} - -# Check and create installation directory -setup_install_dir() { - local install_dir="$1" - - if [ ! -d "$install_dir" ]; then - mkdir -p "$install_dir" || { - echo -e "${RED}Failed to create installation directory${NC}" - exit 1 - } - fi -} - -# Main installation function -main() { - check_requirements - - echo -e "${BLUE}Starting installation...${NC}" - - # Detect system - read -r OS ARCH SUFFIX <<< "$(detect_system)" - echo -e "${GREEN}Detected: $OS $ARCH${NC}" - - # Set installation directory - INSTALL_DIR="/usr/local/bin" - - # Setup installation directory - setup_install_dir "$INSTALL_DIR" - - # Get latest release info - echo -e "${BLUE}Fetching latest release information...${NC}" - LATEST_URL="https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" - - # Get latest version and remove 'v' prefix - VERSION=$(curl -s "$LATEST_URL" | grep "tag_name" | cut -d'"' -f4 | sed 's/^v//') - - # Construct binary name - BINARY_NAME="cursor-id-modifier_${VERSION}_${OS}_${ARCH}" - echo -e "${BLUE}Looking for asset: $BINARY_NAME${NC}" - - # Get download URL directly - DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep -o "\"browser_download_url\": \"[^\"]*${BINARY_NAME}[^\"]*\"" | cut -d'"' -f4) - - if [ -z "$DOWNLOAD_URL" ]; then - echo -e "${RED}Error: Could not find appropriate binary for $OS $ARCH${NC}" - echo -e "${YELLOW}Available assets:${NC}" - curl -s "$LATEST_URL" | grep "browser_download_url" | cut -d'"' -f4 - exit 1 - fi - - echo -e "${GREEN}Found matching asset: $BINARY_NAME${NC}" - echo -e "${BLUE}Downloading from: $DOWNLOAD_URL${NC}" - - download "$DOWNLOAD_URL" "$TMP_DIR/cursor-id-modifier" - - # Install binary - echo -e "${BLUE}Installing...${NC}" - chmod +x "$TMP_DIR/cursor-id-modifier" - sudo mv "$TMP_DIR/cursor-id-modifier" "$INSTALL_DIR/" - - echo -e "${GREEN}Installation completed successfully!${NC}" - echo -e "${BLUE}Running cursor-id-modifier...${NC}" - - # Run the program with sudo, preserving environment variables - export AUTOMATED_MODE=1 - if ! sudo -E cursor-id-modifier; then - echo -e "${RED}Failed to run cursor-id-modifier${NC}" - exit 1 - fi -} - -main