diff --git a/README.md b/README.md
index f009477..2d4eea2 100644
--- a/README.md
+++ b/README.md
@@ -58,21 +58,15 @@ this is a mistake.
### 🚀 One-Click Solution
-
-Linux/macOS: Copy and paste in terminal
-
+**Linux/macOS**: Copy and paste in terminal
```bash
curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash
```
-
-
-
-Windows: Copy and paste in PowerShell (Admin)
+**Windows**: Copy and paste in PowerShell (Admin)
```powershell
irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex
```
-
That's it! The script will:
1. ✨ Install the tool automatically
@@ -85,23 +79,23 @@ That's it! The script will:
Windows Packages
-- 64-bit: `cursor-id-modifier_vX.X.X_Windows_x64.zip`
-- 32-bit: `cursor-id-modifier_vX.X.X_Windows_x86.zip`
+- 64-bit: `cursor-id-modifier_windows_x64.exe`
+- 32-bit: `cursor-id-modifier_windows_x86.exe`
macOS Packages
-- Intel: `cursor-id-modifier_vX.X.X_macOS_x64_intel.tar.gz`
-- M1/M2: `cursor-id-modifier_vX.X.X_macOS_arm64_apple_silicon.tar.gz`
+- Intel: `cursor-id-modifier_darwin_x64_intel`
+- M1/M2: `cursor-id-modifier_darwin_arm64_apple_silicon`
Linux Packages
-- 64-bit: `cursor-id-modifier_vX.X.X_Linux_x64.tar.gz`
-- 32-bit: `cursor-id-modifier_vX.X.X_Linux_x86.tar.gz`
-- ARM64: `cursor-id-modifier_vX.X.X_Linux_arm64.tar.gz`
+- 64-bit: `cursor-id-modifier_linux_x64`
+- 32-bit: `cursor-id-modifier_linux_x86`
+- ARM64: `cursor-id-modifier_linux_arm64`
### 🔧 Technical Details
@@ -176,21 +170,15 @@ this is a mistake.
### 🚀 一键解决
-
-Linux/macOS: 在终端中复制粘贴
-
+**Linux/macOS**: 在终端中复制粘贴
```bash
curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash
```
-
-
-
-Windows: 在PowerShell(管理员)中复制粘贴
+**Windows**: 在PowerShell(管理员)中复制粘贴
```powershell
irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex
```
-
就这样!脚本会:
1. ✨ 自动安装工具
diff --git a/cmd/cursor-id-modifier/main.go b/cmd/cursor-id-modifier/main.go
index ea846ea..856a816 100644
--- a/cmd/cursor-id-modifier/main.go
+++ b/cmd/cursor-id-modifier/main.go
@@ -10,7 +10,6 @@ import (
"runtime"
"runtime/debug"
"strings"
- "time"
"github.com/dacrab/go-cursor-help/internal/config"
"github.com/dacrab/go-cursor-help/internal/lang"
@@ -21,6 +20,7 @@ import (
"github.com/sirupsen/logrus"
)
+// Global variables
var (
version = "dev"
setReadOnly = flag.Bool("r", false, "set storage.json to read-only mode")
@@ -29,7 +29,51 @@ var (
)
func main() {
- // Initialize error recovery
+ setupErrorRecovery()
+ 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 setupErrorRecovery() {
defer func() {
if r := recover(); r != nil {
log.Errorf("Panic recovered: %v\n", r)
@@ -37,230 +81,206 @@ func main() {
waitExit()
}
}()
+}
- // Parse flags
+func handleFlags() {
flag.Parse()
-
- // Show version if requested
if *showVersion {
fmt.Printf("Cursor ID Modifier v%s\n", version)
- return
+ os.Exit(0)
}
+}
- // Initialize logger
+func setupLogger() {
log.SetFormatter(&logrus.TextFormatter{
- FullTimestamp: true,
+ FullTimestamp: true,
+ DisableLevelTruncation: true,
+ PadLevelText: true,
})
+ log.SetLevel(logrus.InfoLevel)
+}
- // Get current user
- username := os.Getenv("SUDO_USER")
- if username == "" {
- user, err := user.Current()
- if err != nil {
- log.Fatal(err)
- }
- username = user.Username
+func getCurrentUser() string {
+ if username := os.Getenv("SUDO_USER"); username != "" {
+ return username
}
- // Initialize components
- display := ui.NewDisplay(nil)
- procManager := process.NewManager(process.DefaultConfig(), log)
+ 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)
}
- generator := idgen.NewGenerator()
+ return configManager
+}
- // Check privileges
+func handlePrivileges(display *ui.Display) error {
isAdmin, err := checkAdminPrivileges()
if err != nil {
log.Error(err)
waitExit()
- return
+ return err
}
if !isAdmin {
if runtime.GOOS == "windows" {
- 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
- }
- return
+ return handleWindowsPrivileges(display)
}
display.ShowPrivilegeError(
lang.GetText().PrivilegeError,
- lang.GetText().RunAsAdmin,
lang.GetText().RunWithSudo,
lang.GetText().SudoExample,
)
waitExit()
- return
+ return fmt.Errorf("insufficient privileges")
}
+ return nil
+}
- // Ensure Cursor is closed
- if err := ensureCursorClosed(display, procManager); err != nil {
- message := "\nError: Please close Cursor manually before running this program."
- if lang.GetCurrentLanguage() == lang.CN {
- message = "\n错误:请在运行此程序之前手动关闭 Cursor。"
- }
- display.ShowError(message)
+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
+ return err
}
+ return nil
+}
- // Kill any remaining Cursor processes
- if procManager.IsCursorRunning() {
- text := lang.GetText()
- display.ShowProcessStatus(text.ClosingProcesses)
-
- if err := procManager.KillCursorProcesses(); err != nil {
- fmt.Println()
- message := "Warning: Could not close all Cursor instances. Please close them manually."
- if lang.GetCurrentLanguage() == lang.CN {
- message = "警告:无法关闭所有 Cursor 实例,请手动关闭。"
- }
- display.ShowWarning(message)
- waitExit()
- return
- }
+func setupDisplay(display *ui.Display) {
+ if err := display.ClearScreen(); err != nil {
+ log.Warn("Failed to clear screen:", err)
+ }
+ display.ShowLogo()
+ fmt.Println()
+}
- if procManager.IsCursorRunning() {
- fmt.Println()
- message := "\nWarning: Cursor is still running. Please close it manually."
- if lang.GetCurrentLanguage() == lang.CN {
- message = "\n警告:Cursor 仍在运行,请手动关闭。"
- }
- display.ShowWarning(message)
- waitExit()
- return
- }
+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")
- display.ShowProcessStatus(text.ProcessesClosed)
- fmt.Println()
+ 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
}
- // Clear screen
- if err := display.ClearScreen(); err != nil {
- log.Warn("Failed to clear screen:", 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")
}
- // Show logo
- display.ShowLogo()
+ log.Debug("Successfully closed all Cursor processes")
+ display.StopProgress()
+ fmt.Println()
+ return nil
+}
- // Read existing config
- text := lang.GetText()
+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
+}
- // Generate new IDs
+func generateNewConfig(display *ui.Display, generator *idgen.Generator, oldConfig *config.StorageConfig, text lang.TextResource) *config.StorageConfig {
display.ShowProgress(text.GeneratingIds)
+ newConfig := &config.StorageConfig{}
- machineID, err := generator.GenerateMachineID()
- if err != nil {
+ if machineID, err := generator.GenerateMachineID(); err != nil {
log.Fatal("Failed to generate machine ID:", err)
+ } else {
+ newConfig.TelemetryMachineId = machineID
}
- macMachineID, err := generator.GenerateMacMachineID()
- if err != nil {
+ if macMachineID, err := generator.GenerateMacMachineID(); err != nil {
log.Fatal("Failed to generate MAC machine ID:", err)
+ } else {
+ newConfig.TelemetryMacMachineId = macMachineID
}
- deviceID, err := generator.GenerateDeviceID()
- if err != nil {
+ if deviceID, err := generator.GenerateDeviceID(); err != nil {
log.Fatal("Failed to generate device ID:", err)
- }
-
- // Create new config
- newConfig := &config.StorageConfig{
- TelemetryMachineId: machineID,
- TelemetryMacMachineId: macMachineID,
- TelemetryDevDeviceId: deviceID,
+ } else {
+ newConfig.TelemetryDevDeviceId = deviceID
}
if oldConfig != nil && oldConfig.TelemetrySqmId != "" {
newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId
+ } else if sqmID, err := generator.GenerateMacMachineID(); err != nil {
+ log.Fatal("Failed to generate SQM ID:", err)
} else {
- sqmID, err := generator.GenerateMacMachineID()
- if err != nil {
- log.Fatal("Failed to generate SQM ID:", err)
- }
newConfig.TelemetrySqmId = sqmID
}
- // Save config
+ 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
+ return err
}
+ display.StopProgress()
+ fmt.Println()
+ return nil
+}
+
+func showCompletionMessages(display *ui.Display) {
+ display.ShowSuccess(lang.GetText().SuccessMessage, lang.GetText().RestartMessage)
+ fmt.Println()
- // Show success
- display.ShowSuccess(text.SuccessMessage, text.RestartMessage)
- message := "\nOperation completed!"
+ message := "Operation completed!"
if lang.GetCurrentLanguage() == lang.CN {
- message = "\n操作完成!"
+ message = "操作完成!"
}
display.ShowInfo(message)
-
- if os.Getenv("AUTOMATED_MODE") != "1" {
- waitExit()
- }
+ fmt.Println()
}
func waitExit() {
- if os.Getenv("AUTOMATED_MODE") == "1" {
- return
- }
-
- fmt.Println(lang.GetText().PressEnterToExit)
+ fmt.Print(lang.GetText().PressEnterToExit)
os.Stdout.Sync()
bufio.NewReader(os.Stdin).ReadString('\n')
}
-func ensureCursorClosed(display *ui.Display, procManager *process.Manager) error {
- maxAttempts := 3
- text := lang.GetText()
-
- display.ShowProcessStatus(text.CheckingProcesses)
-
- for attempt := 1; attempt <= maxAttempts; attempt++ {
- if !procManager.IsCursorRunning() {
- display.ShowProcessStatus(text.ProcessesClosed)
- fmt.Println()
- return nil
- }
-
- message := fmt.Sprintf("Please close Cursor before continuing. Attempt %d/%d\n%s",
- attempt, maxAttempts, text.PleaseWait)
- if lang.GetCurrentLanguage() == lang.CN {
- message = fmt.Sprintf("请在继续之前关闭 Cursor。尝试 %d/%d\n%s",
- attempt, maxAttempts, text.PleaseWait)
- }
- display.ShowProcessStatus(message)
-
- time.Sleep(5 * time.Second)
- }
-
- return fmt.Errorf("cursor is still running")
-}
-
func checkAdminPrivileges() (bool, error) {
switch runtime.GOOS {
case "windows":
diff --git a/go.mod b/go.mod
index 8bf96e2..d3192c7 100644
--- a/go.mod
+++ b/go.mod
@@ -5,14 +5,11 @@ go 1.21
require (
github.com/fatih/color v1.15.0
github.com/sirupsen/logrus v1.9.3
- github.com/stretchr/testify v1.10.0
)
require (
- github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/stretchr/testify v1.10.0 // indirect
golang.org/x/sys v0.13.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 75c1611..d930a66 100644
--- a/go.sum
+++ b/go.sum
@@ -20,7 +20,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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=
diff --git a/internal/config/config.go b/internal/config/config.go
index e3f3f53..b3d2c81 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -32,10 +32,7 @@ func NewManager(username string) (*Manager, error) {
if err != nil {
return nil, fmt.Errorf("failed to get config path: %w", err)
}
-
- return &Manager{
- configPath: configPath,
- }, nil
+ return &Manager{configPath: configPath}, nil
}
// ReadConfig reads the existing configuration
@@ -69,22 +66,23 @@ func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error {
return fmt.Errorf("failed to create config directory: %w", err)
}
- // Set file permissions
- if err := os.Chmod(m.configPath, 0666); err != nil && !os.IsNotExist(err) {
- return fmt.Errorf("failed to set file permissions: %w", err)
+ // Prepare updated configuration
+ updatedConfig := m.prepareUpdatedConfig(config)
+
+ // Write configuration
+ if err := m.writeConfigFile(updatedConfig, readOnly); err != nil {
+ return err
}
- // Read existing config to preserve other fields
- var originalFile map[string]interface{}
- originalFileContent, err := os.ReadFile(m.configPath)
- if err != nil && !os.IsNotExist(err) {
- return fmt.Errorf("failed to read original file: %w", err)
- } else if err == nil {
- if err := json.Unmarshal(originalFileContent, &originalFile); err != nil {
- return fmt.Errorf("failed to parse original file: %w", err)
- }
- } else {
- originalFile = make(map[string]interface{})
+ 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
@@ -95,15 +93,20 @@ func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error {
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
- newFileContent, err := json.MarshalIndent(originalFile, "", " ")
+ 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, newFileContent, 0666); err != nil {
+ if err := os.WriteFile(tmpPath, content, 0666); err != nil {
return fmt.Errorf("failed to write temporary file: %w", err)
}
@@ -126,8 +129,8 @@ func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error {
// Sync directory
if dir, err := os.Open(filepath.Dir(m.configPath)); err == nil {
+ defer dir.Close()
dir.Sync()
- dir.Close()
}
return nil
diff --git a/internal/lang/lang.go b/internal/lang/lang.go
index f5d8a7a..5b6ddde 100644
--- a/internal/lang/lang.go
+++ b/internal/lang/lang.go
@@ -7,34 +7,43 @@ import (
"sync"
)
-// Language represents a supported language
+// Language represents a supported language code
type Language string
const (
// CN represents Chinese language
CN Language = "cn"
- // EN represents English language
+ // EN represents English language
EN Language = "en"
)
// TextResource contains all translatable text resources
type TextResource struct {
- SuccessMessage string
- RestartMessage string
- ReadingConfig string
- GeneratingIds string
- PressEnterToExit string
- ErrorPrefix string
- PrivilegeError string
+ // 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
- ConfigLocation string
- CheckingProcesses string
- ClosingProcesses string
- ProcessesClosed string
- PleaseWait string
+ PressEnterToExit string
SetReadOnlyMessage string
+
+ // Info messages
+ ConfigLocation string
}
var (
@@ -68,28 +77,32 @@ func GetText() TextResource {
// detectLanguage detects the system language
func detectLanguage() Language {
- // Check environment variables
- for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} {
- if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") {
- return CN
- }
+ // Check environment variables first
+ if isChineseEnvVar() {
+ return CN
}
- // Check Windows language settings
+ // Then check OS-specific locale
if isWindows() {
if isWindowsChineseLocale() {
return CN
}
- } else {
- // Check Unix locale
- if isUnixChineseLocale() {
- 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"
}
@@ -118,39 +131,57 @@ func isUnixChineseLocale() bool {
// texts contains all translations
var texts = map[Language]TextResource{
CN: {
- SuccessMessage: "[√] 配置文件已成功更新!",
- RestartMessage: "[!] 请手动重启 Cursor 以使更新生效",
- ReadingConfig: "正在读取配置文件...",
- GeneratingIds: "正在生成新的标识符...",
- PressEnterToExit: "按回车键退出程序...",
- ErrorPrefix: "程序发生严重错误: %v",
- PrivilegeError: "\n[!] 错误:需要管理员权限",
+ // 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",
- ConfigLocation: "配置文件位置:",
- CheckingProcesses: "正在检查运行中的 Cursor 实例...",
- ClosingProcesses: "正在关闭 Cursor 实例...",
- ProcessesClosed: "所有 Cursor 实例已关闭",
- PleaseWait: "请稍候...",
+ PressEnterToExit: "按回车键退出程序...",
SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题",
+
+ // Info messages
+ ConfigLocation: "配置文件位置:",
},
EN: {
- SuccessMessage: "[√] Configuration file updated successfully!",
- RestartMessage: "[!] Please restart Cursor manually for changes to take effect",
- ReadingConfig: "Reading configuration file...",
- GeneratingIds: "Generating new identifiers...",
- PressEnterToExit: "Press Enter to exit...",
- ErrorPrefix: "Program encountered a serious error: %v",
- PrivilegeError: "\n[!] Error: Administrator privileges required",
+ // 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",
- ConfigLocation: "Config file location:",
- CheckingProcesses: "Checking for running Cursor instances...",
- ClosingProcesses: "Closing Cursor instances...",
- ProcessesClosed: "All Cursor instances have been closed",
- PleaseWait: "Please wait...",
+ PressEnterToExit: "Press Enter to exit...",
SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records",
+
+ // Info messages
+ ConfigLocation: "Config file location:",
},
}
diff --git a/internal/process/manager.go b/internal/process/manager.go
index 00d568a..f48ac20 100644
--- a/internal/process/manager.go
+++ b/internal/process/manager.go
@@ -12,22 +12,24 @@ import (
// Config holds process manager configuration
type Config struct {
- MaxAttempts int
- RetryDelay time.Duration
- ProcessPatterns []string
+ 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: time.Second,
+ RetryDelay: 2 * time.Second,
ProcessPatterns: []string{
- "Cursor.exe", // Windows
- "Cursor", // Linux/macOS binary
- "cursor", // Linux/macOS process
- "cursor-helper", // Helper process
- "cursor-id-modifier", // Our tool
+ "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
},
}
}
@@ -38,7 +40,7 @@ type Manager struct {
log *logrus.Logger
}
-// NewManager creates a new process manager
+// NewManager creates a new process manager with optional config and logger
func NewManager(config *Config, log *logrus.Logger) *Manager {
if config == nil {
config = DefaultConfig()
@@ -52,7 +54,7 @@ func NewManager(config *Config, log *logrus.Logger) *Manager {
}
}
-// IsCursorRunning checks if any Cursor process is running
+// IsCursorRunning checks if any Cursor process is currently running
func (m *Manager) IsCursorRunning() bool {
processes, err := m.getCursorProcesses()
if err != nil {
@@ -62,7 +64,7 @@ func (m *Manager) IsCursorRunning() bool {
return len(processes) > 0
}
-// KillCursorProcesses attempts to kill all Cursor processes
+// 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()
@@ -74,68 +76,116 @@ func (m *Manager) KillCursorProcesses() error {
return nil
}
- for _, proc := range processes {
- if err := m.killProcess(proc); err != nil {
- m.log.Warnf("Failed to kill process %s: %v", proc, err)
+ // 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 m.IsCursorRunning() {
- return fmt.Errorf("failed to kill all Cursor processes after %d attempts", m.config.MaxAttempts)
+ if processes, _ := m.getCursorProcesses(); len(processes) == 0 {
+ return nil
+ }
}
return nil
}
+// getCursorProcesses returns PIDs of running Cursor processes
func (m *Manager) getCursorProcesses() ([]string, error) {
- var cmd *exec.Cmd
- var processes []string
+ 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":
- cmd = exec.Command("tasklist", "/FO", "CSV", "/NH")
+ return exec.Command("tasklist", "/FO", "CSV", "/NH")
case "darwin":
- cmd = exec.Command("ps", "-ax")
+ return exec.Command("ps", "-ax")
case "linux":
- cmd = exec.Command("ps", "-A")
+ return exec.Command("ps", "-A")
default:
- return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
+ return nil
}
+}
- output, err := cmd.Output()
- if err != nil {
- return nil, fmt.Errorf("failed to execute command: %w", err)
+// 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
+}
- lines := strings.Split(string(output), "\n")
- for _, line := range lines {
- for _, pattern := range m.config.ProcessPatterns {
- if strings.Contains(strings.ToLower(line), strings.ToLower(pattern)) {
- // Extract PID based on OS
- pid := m.extractPID(line)
- if pid != "" {
- processes = append(processes, pid)
- }
- }
+// 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 ""
+}
- return processes, nil
+// 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":
- // Windows CSV format: "ImageName","PID",...
parts := strings.Split(line, ",")
if len(parts) >= 2 {
return strings.Trim(parts[1], "\"")
}
case "darwin", "linux":
- // Unix format: PID TTY TIME CMD
parts := strings.Fields(line)
if len(parts) >= 1 {
return parts[0]
@@ -144,17 +194,23 @@ func (m *Manager) extractPID(line string) string {
return ""
}
+// killProcess forcefully terminates a process by PID
func (m *Manager) killProcess(pid string) error {
- var cmd *exec.Cmd
+ 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":
- cmd = exec.Command("taskkill", "/F", "/PID", pid)
+ return exec.Command("taskkill", "/F", "/PID", pid)
case "darwin", "linux":
- cmd = exec.Command("kill", "-9", pid)
+ return exec.Command("kill", "-9", pid)
default:
- return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
+ return nil
}
-
- return cmd.Run()
}
diff --git a/internal/ui/display.go b/internal/ui/display.go
index 0f94f69..32ed566 100644
--- a/internal/ui/display.go
+++ b/internal/ui/display.go
@@ -10,92 +10,85 @@ import (
"github.com/fatih/color"
)
-// Display handles UI display operations
+// Display handles UI operations for terminal output
type Display struct {
spinner *Spinner
}
-// NewDisplay creates a new display handler
+// 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,
- }
+ return &Display{spinner: spinner}
}
-// ShowProgress shows a progress message with spinner
-func (d *Display) ShowProgress(message string) {
- d.spinner.SetMessage(message)
- d.spinner.Start()
-}
+// Terminal Operations
-// StopProgress stops the progress spinner
-func (d *Display) StopProgress() {
- d.spinner.Stop()
-}
-
-// ClearScreen clears the terminal screen
+// ClearScreen clears the terminal screen based on OS
func (d *Display) ClearScreen() error {
var cmd *exec.Cmd
- if runtime.GOOS == "windows" {
+ switch runtime.GOOS {
+ case "windows":
cmd = exec.Command("cmd", "/c", "cls")
- } else {
+ default:
cmd = exec.Command("clear")
}
cmd.Stdout = os.Stdout
return cmd.Run()
}
-// ShowProcessStatus shows the current process status
-func (d *Display) ShowProcessStatus(message string) {
- fmt.Printf("\r%s", strings.Repeat(" ", 80)) // Clear line
- fmt.Printf("\r%s", color.CyanString("⚡ "+message))
-}
-
-// ShowPrivilegeError shows the privilege error message
-func (d *Display) ShowPrivilegeError(errorMsg, adminMsg, sudoMsg, sudoExample string) {
- red := color.New(color.FgRed, color.Bold)
- yellow := color.New(color.FgYellow)
+// Progress Indicator
- red.Println(errorMsg)
- if runtime.GOOS == "windows" {
- yellow.Println(adminMsg)
- } else {
- yellow.Printf("%s\n%s\n", sudoMsg, fmt.Sprintf(sudoExample, os.Args[0]))
- }
+// ShowProgress displays a progress message with a spinner
+func (d *Display) ShowProgress(message string) {
+ d.spinner.SetMessage(message)
+ d.spinner.Start()
}
-// ShowSuccess shows a success message
-func (d *Display) ShowSuccess(successMsg, restartMsg string) {
- green := color.New(color.FgGreen, color.Bold)
- yellow := color.New(color.FgYellow, color.Bold)
-
- green.Printf("\n%s\n", successMsg)
- yellow.Printf("%s\n", restartMsg)
+// StopProgress stops the progress spinner
+func (d *Display) StopProgress() {
+ d.spinner.Stop()
}
-// ShowError shows an error message
-func (d *Display) ShowError(message string) {
- red := color.New(color.FgRed, color.Bold)
- red.Printf("\n%s\n", message)
-}
+// Message Display
-// ShowWarning shows a warning message
-func (d *Display) ShowWarning(message string) {
- yellow := color.New(color.FgYellow, color.Bold)
- yellow.Printf("\n%s\n", message)
+// 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 shows an info message
+// ShowInfo displays an info message in cyan
func (d *Display) ShowInfo(message string) {
cyan := color.New(color.FgCyan)
- cyan.Printf("\n%s\n", message)
+ cyan.Println(message)
+}
+
+// ShowError displays an error message in red
+func (d *Display) ShowError(message string) {
+ red := color.New(color.FgRed)
+ red.Println(message)
}
-// ShowPrompt shows a prompt message and waits for user input
-func (d *Display) ShowPrompt(message string) {
- fmt.Print(message)
- os.Stdout.Sync()
+// ShowPrivilegeError displays privilege error messages with instructions
+func (d *Display) ShowPrivilegeError(messages ...string) {
+ red := color.New(color.FgRed, color.Bold)
+ yellow := color.New(color.FgYellow)
+
+ // Main error message
+ red.Println(messages[0])
+ fmt.Println()
+
+ // Additional instructions
+ for _, msg := range messages[1:] {
+ if strings.Contains(msg, "%s") {
+ exe, _ := os.Executable()
+ yellow.Printf(msg+"\n", exe)
+ } else {
+ yellow.Println(msg)
+ }
+ }
}
diff --git a/internal/ui/logo.go b/internal/ui/logo.go
index 439af52..564525a 100644
--- a/internal/ui/logo.go
+++ b/internal/ui/logo.go
@@ -5,15 +5,15 @@ import (
)
const cyberpunkLogo = `
- ______ ______ ______
- / ____/_ __________ ___ _____/ __/ // / / /
- / / / / / / ___/ _ \/ __ \/ ___/ /_/ // /_/ /
-/ /___/ /_/ / / / __/ /_/ (__ ) __/__ __/ /
-\____/\__,_/_/ \___/\____/____/_/ /_/ /_/
-
+ ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
+ ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
+ ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
+ ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
+ ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
+ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
`
-// ShowLogo displays the cyberpunk-style logo
+// ShowLogo displays the application logo
func (d *Display) ShowLogo() {
cyan := color.New(color.FgCyan, color.Bold)
cyan.Println(cyberpunkLogo)
diff --git a/internal/ui/spinner.go b/internal/ui/spinner.go
index 4f85fcb..145f730 100644
--- a/internal/ui/spinner.go
+++ b/internal/ui/spinner.go
@@ -10,8 +10,8 @@ import (
// SpinnerConfig defines spinner configuration
type SpinnerConfig struct {
- Frames []string
- Delay time.Duration
+ Frames []string // Animation frames for the spinner
+ Delay time.Duration // Delay between frame updates
}
// DefaultSpinnerConfig returns the default spinner configuration
@@ -43,6 +43,8 @@ func NewSpinner(config *SpinnerConfig) *Spinner {
}
}
+// State management
+
// SetMessage sets the spinner message
func (s *Spinner) SetMessage(message string) {
s.mu.Lock()
@@ -50,7 +52,16 @@ func (s *Spinner) SetMessage(message string) {
s.message = message
}
-// Start starts the spinner animation
+// 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 {
@@ -63,7 +74,7 @@ func (s *Spinner) Start() {
go s.run()
}
-// Stop stops the spinner animation
+// Stop halts the spinner animation
func (s *Spinner) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
@@ -75,20 +86,21 @@ func (s *Spinner) Stop() {
s.active = false
close(s.stopCh)
s.stopCh = make(chan struct{})
- fmt.Println()
+ fmt.Print("\r") // Clear the spinner line
}
-// IsActive returns whether the spinner is currently active
-func (s *Spinner) IsActive() bool {
- s.mu.RLock()
- defer s.mu.RUnlock()
- return s.active
-}
+// 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:
@@ -100,11 +112,11 @@ func (s *Spinner) run() {
return
}
frame := s.config.Frames[s.current%len(s.config.Frames)]
- message := s.message
s.current++
s.mu.RUnlock()
- fmt.Printf("\r%s %s", color.CyanString(frame), message)
+ fmt.Printf("\r %s", cyan.Sprint(frame))
+ fmt.Printf("\033[%dG%s", 4, message) // Move cursor and print message
}
}
}
diff --git a/pkg/idgen/generator.go b/pkg/idgen/generator.go
index 5a0058f..4a69341 100644
--- a/pkg/idgen/generator.go
+++ b/pkg/idgen/generator.go
@@ -1,95 +1,59 @@
package idgen
import (
- cryptorand "crypto/rand"
- "crypto/sha256"
+ "crypto/rand"
"encoding/hex"
"fmt"
- "math/big"
- "sync"
+ "time"
)
-// Generator handles the generation of various IDs
-type Generator struct {
- charsetMu sync.RWMutex
- charset string
-}
+// Generator handles secure ID generation for machines and devices
+type Generator struct{}
-// NewGenerator creates a new ID generator with default settings
+// NewGenerator creates a new ID generator
func NewGenerator() *Generator {
- return &Generator{
- charset: "0123456789ABCDEFGHJKLMNPQRSTVWXYZ",
- }
+ return &Generator{}
}
-// SetCharset allows customizing the character set used for ID generation
-func (g *Generator) SetCharset(charset string) {
- g.charsetMu.Lock()
- defer g.charsetMu.Unlock()
- g.charset = charset
-}
+// Helper methods
+// -------------
-// GenerateMachineID generates a new machine ID with the format auth0|user_XX[unique_id]
-func (g *Generator) GenerateMachineID() (string, error) {
- prefix := "auth0|user_"
+// simulateWork adds a small delay to make progress visible
+func (g *Generator) simulateWork() {
+ time.Sleep(800 * time.Millisecond)
+}
- // Generate random sequence number between 0-99
- seqNum, err := cryptorand.Int(cryptorand.Reader, big.NewInt(100))
- if err != nil {
- return "", fmt.Errorf("failed to generate sequence number: %w", err)
+// generateRandomHex generates a random hex string of specified length
+func (g *Generator) generateRandomHex(length int) (string, error) {
+ bytes := make([]byte, length)
+ if _, err := rand.Read(bytes); err != nil {
+ return "", fmt.Errorf("failed to generate random bytes: %w", err)
}
- sequence := fmt.Sprintf("%02d", seqNum.Int64())
+ return hex.EncodeToString(bytes), nil
+}
- uniqueID, err := g.generateUniqueID(23)
- if err != nil {
- return "", fmt.Errorf("failed to generate unique ID: %w", err)
- }
+// Public methods
+// -------------
- fullID := prefix + sequence + uniqueID
- return hex.EncodeToString([]byte(fullID)), nil
+// GenerateMachineID generates a new 32-byte machine ID
+func (g *Generator) GenerateMachineID() (string, error) {
+ g.simulateWork()
+ return g.generateRandomHex(32)
}
-// GenerateMacMachineID generates a new MAC machine ID using SHA-256
+// GenerateMacMachineID generates a new 64-byte MAC machine ID
func (g *Generator) GenerateMacMachineID() (string, error) {
- data := make([]byte, 32)
- if _, err := cryptorand.Read(data); err != nil {
- return "", fmt.Errorf("failed to generate random data: %w", err)
- }
-
- hash := sha256.Sum256(data)
- return hex.EncodeToString(hash[:]), nil
+ g.simulateWork()
+ return g.generateRandomHex(64)
}
-// GenerateDeviceID generates a new device ID in UUID v4 format
+// GenerateDeviceID generates a new device ID in UUID format
func (g *Generator) GenerateDeviceID() (string, error) {
- uuid := make([]byte, 16)
- if _, err := cryptorand.Read(uuid); err != nil {
- return "", fmt.Errorf("failed to generate UUID: %w", err)
- }
-
- // Set version (4) and variant (2) bits according to RFC 4122
- uuid[6] = (uuid[6] & 0x0f) | 0x40
- uuid[8] = (uuid[8] & 0x3f) | 0x80
-
- return fmt.Sprintf("%x-%x-%x-%x-%x",
- uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16]), nil
-}
-
-// generateUniqueID generates a random string of specified length using the configured charset
-func (g *Generator) generateUniqueID(length int) (string, error) {
- g.charsetMu.RLock()
- defer g.charsetMu.RUnlock()
-
- result := make([]byte, length)
- max := big.NewInt(int64(len(g.charset)))
-
- for i := range result {
- randNum, err := cryptorand.Int(cryptorand.Reader, max)
- if err != nil {
- return "", fmt.Errorf("failed to generate random number: %w", err)
- }
- result[i] = g.charset[randNum.Int64()]
+ g.simulateWork()
+ id, err := g.generateRandomHex(16)
+ if err != nil {
+ return "", err
}
-
- return string(result), nil
+ return fmt.Sprintf("%s-%s-%s-%s-%s",
+ id[0:8], id[8:12], id[12:16], id[16:20], id[20:32]), nil
}
diff --git a/pkg/idgen/generator_test.go b/pkg/idgen/generator_test.go
deleted file mode 100644
index 81dcea9..0000000
--- a/pkg/idgen/generator_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package idgen
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestNewGenerator(t *testing.T) {
- gen := NewGenerator()
- assert.NotNil(t, gen, "Generator should not be nil")
-}
-
-func TestGenerateMachineID(t *testing.T) {
- gen := NewGenerator()
- id, err := gen.GenerateMachineID()
- assert.NoError(t, err, "Should not return an error")
- assert.NotEmpty(t, id, "Generated machine ID should not be empty")
-}
-
-func TestGenerateDeviceID(t *testing.T) {
- gen := NewGenerator()
- id, err := gen.GenerateDeviceID()
- assert.NoError(t, err, "Should not return an error")
- assert.NotEmpty(t, id, "Generated device ID should not be empty")
-}
diff --git a/scripts/install.ps1 b/scripts/install.ps1
index fb14a5b..18e6cb9 100644
--- a/scripts/install.ps1
+++ b/scripts/install.ps1
@@ -9,13 +9,6 @@ if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdenti
# Set TLS to 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
-# Colors for output
-$Red = "`e[31m"
-$Green = "`e[32m"
-$Blue = "`e[36m"
-$Yellow = "`e[33m"
-$Reset = "`e[0m"
-
# Create temporary directory
$TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString())
New-Item -ItemType Directory -Path $TmpDir | Out-Null
@@ -29,7 +22,7 @@ function Cleanup {
# Error handler
trap {
- Write-Host "${Red}Error: $_${Reset}"
+ Write-Host "Error: $_" -ForegroundColor Red
Cleanup
exit 1
}
@@ -44,7 +37,7 @@ function Get-SystemArch {
}
# Download with progress
-function Download-WithProgress {
+function Get-FileWithProgress {
param (
[string]$Url,
[string]$OutputFile
@@ -58,18 +51,18 @@ function Download-WithProgress {
return $true
}
catch {
- Write-Host "${Red}Failed to download: $_${Reset}"
+ Write-Host "Failed to download: $_" -ForegroundColor Red
return $false
}
}
# Main installation function
function Install-CursorModifier {
- Write-Host "${Blue}Starting installation...${Reset}"
+ Write-Host "Starting installation..." -ForegroundColor Cyan
# Detect architecture
$arch = Get-SystemArch
- Write-Host "${Green}Detected architecture: $arch${Reset}"
+ Write-Host "Detected architecture: $arch" -ForegroundColor Green
# Set installation directory
$InstallDir = "$env:ProgramFiles\CursorModifier"
@@ -80,28 +73,36 @@ function Install-CursorModifier {
# Get latest release
try {
$latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/dacrab/go-cursor-help/releases/latest"
+ Write-Host "Found latest release: $($latestRelease.tag_name)" -ForegroundColor Cyan
+
+ # Updated binary name format to match actual assets
$binaryName = "cursor-id-modifier_windows_$arch.exe"
- $downloadUrl = $latestRelease.assets | Where-Object { $_.name -eq $binaryName } | Select-Object -ExpandProperty browser_download_url
+ Write-Host "Looking for asset: $binaryName" -ForegroundColor Cyan
+
+ $asset = $latestRelease.assets | Where-Object { $_.name -eq $binaryName }
+ $downloadUrl = $asset.browser_download_url
if (!$downloadUrl) {
+ Write-Host "Available assets:" -ForegroundColor Yellow
+ $latestRelease.assets | ForEach-Object { Write-Host $_.name }
throw "Could not find download URL for $binaryName"
}
}
catch {
- Write-Host "${Red}Failed to get latest release: $_${Reset}"
+ Write-Host "Failed to get latest release: $_" -ForegroundColor Red
exit 1
}
# Download binary
- Write-Host "${Blue}Downloading latest release...${Reset}"
+ Write-Host "Downloading latest release from $downloadUrl..." -ForegroundColor Cyan
$binaryPath = Join-Path $TmpDir "cursor-id-modifier.exe"
- if (!(Download-WithProgress -Url $downloadUrl -OutputFile $binaryPath)) {
+ if (!(Get-FileWithProgress -Url $downloadUrl -OutputFile $binaryPath)) {
exit 1
}
# Install binary
- Write-Host "${Blue}Installing...${Reset}"
+ Write-Host "Installing..." -ForegroundColor Cyan
try {
Copy-Item -Path $binaryPath -Destination "$InstallDir\cursor-id-modifier.exe" -Force
@@ -112,24 +113,23 @@ function Install-CursorModifier {
}
}
catch {
- Write-Host "${Red}Failed to install: $_${Reset}"
+ Write-Host "Failed to install: $_" -ForegroundColor Red
exit 1
}
- Write-Host "${Green}Installation completed successfully!${Reset}"
- Write-Host "${Blue}Running cursor-id-modifier...${Reset}"
+ Write-Host "Installation completed successfully!" -ForegroundColor Green
+ Write-Host "Running cursor-id-modifier..." -ForegroundColor Cyan
# Run the program
try {
- $env:AUTOMATED_MODE = "1"
& "$InstallDir\cursor-id-modifier.exe"
if ($LASTEXITCODE -ne 0) {
- Write-Host "${Red}Failed to run cursor-id-modifier${Reset}"
+ Write-Host "Failed to run cursor-id-modifier" -ForegroundColor Red
exit 1
}
}
catch {
- Write-Host "${Red}Failed to run cursor-id-modifier: $_${Reset}"
+ Write-Host "Failed to run cursor-id-modifier: $_" -ForegroundColor Red
exit 1
}
}
@@ -140,4 +140,6 @@ try {
}
finally {
Cleanup
+ Write-Host "Press any key to continue..."
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
}
\ No newline at end of file