Browse Source

refactor(scripts): 移除旧版构建和修改脚本

- 删除 Windows 批处理构建脚本 (build_all.bat)
- 删除 Linux Shell 构建脚本 (build_all.sh)
- 删除 Python 实现的 Cursor ID 修改器主脚本 (cursor_id_modifier.py)
- 为后续重构和优化清理旧代码
煎饼果子卷鲨鱼辣椒 1 month ago
parent
commit
aaa76fc387
  1. 92
      .goreleaser.yml
  2. 328
      cmd/cursor-id-modifier/main.go
  3. 15
      go.mod
  4. 26
      go.sum
  5. 153
      internal/config/config.go
  6. 187
      internal/lang/lang.go
  7. 216
      internal/process/manager.go
  8. 94
      internal/ui/display.go
  9. 20
      internal/ui/logo.go
  10. 122
      internal/ui/spinner.go
  11. 116
      pkg/idgen/generator.go
  12. 375
      process_cursor_links.py
  13. 74
      scripts/build_all.bat
  14. 143
      scripts/build_all.sh
  15. 318
      scripts/cursor_id_modifier.pot
  16. 1298
      scripts/cursor_id_modifier.py
  17. 9
      scripts/git-actions.sh
  18. 193
      scripts/install.ps1
  19. 127
      scripts/install.sh

92
.goreleaser.yml

@ -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

328
cmd/cursor-id-modifier/main.go

@ -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)
}
}

15
go.mod

@ -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
)

26
go.sum

@ -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=

153
internal/config/config.go

@ -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
}

187
internal/lang/lang.go

@ -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:",
},
}

216
internal/process/manager.go

@ -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
}
}

94
internal/ui/display.go

@ -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)
}
}
}

20
internal/ui/logo.go

@ -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)
}

122
internal/ui/spinner.go

@ -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
}
}
}

116
pkg/idgen/generator.go

@ -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
}

375
process_cursor_links.py

@ -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
<details>
<summary style="font-size:1.2em">📦 Windows x64 </summary>
| | |
|------|----------|
"""
# Windows x64
for version in versions:
links = version.get_download_links()
md += f"| {version.version} | [下载]({links['windows']['x64']}) |\n"
md += """
</details>
## ARM64
<details>
<summary style="font-size:1.2em">📱 Windows ARM64 </summary>
| | |
|------|----------|
"""
# Windows ARM64
for version in versions:
links = version.get_download_links()
md += f"| {version.version} | [下载]({links['windows']['arm64']}) |\n"
md += """
</details>
# 🍎 macOS
## Universal
<details>
<summary style="font-size:1.2em">🎯 macOS Universal </summary>
| | |
|------|----------|
"""
# macOS Universal
for version in versions:
links = version.get_download_links()
md += f"| {version.version} | [下载]({links['mac']['universal']}) |\n"
md += """
</details>
## ARM64
<details>
<summary style="font-size:1.2em">💪 macOS ARM64 </summary>
| | |
|------|----------|
"""
# macOS ARM64
for version in versions:
links = version.get_download_links()
md += f"| {version.version} | [下载]({links['mac']['arm64']}) |\n"
md += """
</details>
## Intel
<details>
<summary style="font-size:1.2em">💻 macOS Intel </summary>
| | |
|------|----------|
"""
# macOS Intel
for version in versions:
links = version.get_download_links()
md += f"| {version.version} | [下载]({links['mac']['x64']}) |\n"
md += """
</details>
# 🐧 Linux
## x64
<details>
<summary style="font-size:1.2em">🎮 Linux x64 AppImage</summary>
| | |
|------|----------|
"""
# Linux x64
for version in versions:
links = version.get_download_links()
md += f"| {version.version} | [下载]({links['linux']['x64']}) |\n"
md += """
</details>
<style>
details {
margin: 1em 0;
padding: 0.5em 1em;
background: #f8f9fa;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
summary {
cursor: pointer;
font-weight: bold;
margin: -0.5em -1em;
padding: 0.5em 1em;
}
summary:hover {
background: #f1f3f5;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 1em;
}
th, td {
padding: 0.5em;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
tr:hover {
background: #f1f3f5;
}
a {
color: #0366d6;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
</style>
"""
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()

74
scripts/build_all.bat

@ -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%

143
scripts/build_all.sh

@ -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

318
scripts/cursor_id_modifier.pot

@ -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 ""

1298
scripts/cursor_id_modifier.py
File diff suppressed because it is too large
View File

9
scripts/git-actions.sh

@ -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

193
scripts/install.ps1

@ -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')
}
}

127
scripts/install.sh

@ -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
Loading…
Cancel
Save