Browse Source
refactor(scripts): 移除旧版构建和修改脚本
refactor(scripts): 移除旧版构建和修改脚本
- 删除 Windows 批处理构建脚本 (build_all.bat) - 删除 Linux Shell 构建脚本 (build_all.sh) - 删除 Python 实现的 Cursor ID 修改器主脚本 (cursor_id_modifier.py) - 为后续重构和优化清理旧代码
19 changed files with 0 additions and 3906 deletions
-
92.goreleaser.yml
-
328cmd/cursor-id-modifier/main.go
-
15go.mod
-
26go.sum
-
153internal/config/config.go
-
187internal/lang/lang.go
-
216internal/process/manager.go
-
94internal/ui/display.go
-
20internal/ui/logo.go
-
122internal/ui/spinner.go
-
116pkg/idgen/generator.go
-
375process_cursor_links.py
-
74scripts/build_all.bat
-
143scripts/build_all.sh
-
318scripts/cursor_id_modifier.pot
-
1298scripts/cursor_id_modifier.py
-
9scripts/git-actions.sh
-
193scripts/install.ps1
-
127scripts/install.sh
@ -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 |
|||
@ -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) |
|||
} |
|||
} |
|||
@ -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 |
|||
) |
|||
@ -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= |
|||
@ -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 |
|||
} |
|||
@ -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:", |
|||
}, |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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) |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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() |
|||
@ -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% |
|||
@ -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 |
|||
@ -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
File diff suppressed because it is too large
View File
@ -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 |
|||
|
|||
@ -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') |
|||
} |
|||
} |
|||
@ -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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue