Browse Source

chore: Refactor build and installation scripts; update configuration and release process

- Enhanced build scripts for improved parallel execution and optimization flags.
- Updated installation scripts for better user experience and error handling.
- Modified .gitignore to include new build artifacts and IDE configurations.
- Updated .goreleaser.yml for better release management and platform support.
- Removed deprecated main.go file and adjusted README for clarity on installation and usage.
- Added support for multiple architectures in build process, including 32-bit and 64-bit for Windows, macOS, and Linux.

These changes streamline the development workflow and enhance the overall usability of the Cursor ID Modifier tool.
pull/85/head
dacrab 5 months ago
parent
commit
99e31a7bba
  1. 86
      .github/workflows/release.yml
  2. 43
      .gitignore
  3. 106
      .goreleaser.yml
  4. 21
      Makefile
  5. 174
      README.md
  6. 9
      go.mod
  7. 17
      go.sum
  8. 150
      internal/config/config.go
  9. 156
      internal/lang/lang.go
  10. 162
      internal/process/manager.go
  11. 101
      internal/ui/display.go
  12. 110
      internal/ui/spinner.go
  13. 1050
      main.go
  14. 95
      pkg/idgen/generator.go
  15. 26
      pkg/idgen/generator_test.go
  16. 146
      scripts/build_all.bat
  17. 108
      scripts/build_all.sh
  18. 336
      scripts/install.ps1
  19. 324
      scripts/install.sh

86
.github/workflows/release.yml

@ -9,7 +9,33 @@ permissions:
contents: write
jobs:
goreleaser:
test:
name: Test on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
arch: [amd64, arm64]
exclude:
- os: windows-latest
arch: arm64
- os: ubuntu-latest
arch: arm64
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
cache: true
- name: Run tests
run: go test -v ./...
release:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout
@ -20,8 +46,13 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
cache: true
check-latest: true
- name: Install dependencies
run: go mod tidy
- name: Set Repository Variables
run: |
echo "GITHUB_REPOSITORY_OWNER=$(echo ${{ github.repository }} | cut -d '/' -f 1)" >> $GITHUB_ENV
@ -35,3 +66,56 @@ jobs:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CGO_ENABLED: 0
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries
path: dist/*
retention-days: 5
- name: Generate changelog
if: success()
run: |
echo "# 🚀 Cursor ID Modifier ${{ github.ref_name }}" > ${{ github.workspace }}-CHANGELOG.txt
echo "" >> ${{ github.workspace }}-CHANGELOG.txt
echo "## ✨ Supported Platforms" >> ${{ github.workspace }}-CHANGELOG.txt
echo "" >> ${{ github.workspace }}-CHANGELOG.txt
echo "### 🪟 Windows" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- Windows x64 (64-bit)" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- Windows x86 (32-bit)" >> ${{ github.workspace }}-CHANGELOG.txt
echo "" >> ${{ github.workspace }}-CHANGELOG.txt
echo "### 🍎 macOS" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- macOS Intel (x64)" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- macOS Apple Silicon (M1/M2)" >> ${{ github.workspace }}-CHANGELOG.txt
echo "" >> ${{ github.workspace }}-CHANGELOG.txt
echo "### 🐧 Linux" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- Linux x64 (64-bit)" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- Linux x86 (32-bit)" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- Linux ARM64" >> ${{ github.workspace }}-CHANGELOG.txt
echo "" >> ${{ github.workspace }}-CHANGELOG.txt
echo "## 📦 Quick Start" >> ${{ github.workspace }}-CHANGELOG.txt
echo "" >> ${{ github.workspace }}-CHANGELOG.txt
echo "### Linux/macOS" >> ${{ github.workspace }}-CHANGELOG.txt
echo "\`\`\`bash" >> ${{ github.workspace }}-CHANGELOG.txt
echo "curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier" >> ${{ github.workspace }}-CHANGELOG.txt
echo "\`\`\`" >> ${{ github.workspace }}-CHANGELOG.txt
echo "" >> ${{ github.workspace }}-CHANGELOG.txt
echo "### Windows (PowerShell Admin)" >> ${{ github.workspace }}-CHANGELOG.txt
echo "\`\`\`powershell" >> ${{ github.workspace }}-CHANGELOG.txt
echo "irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier" >> ${{ github.workspace }}-CHANGELOG.txt
echo "\`\`\`" >> ${{ github.workspace }}-CHANGELOG.txt
echo "" >> ${{ github.workspace }}-CHANGELOG.txt
echo "## 🔄 Changes" >> ${{ github.workspace }}-CHANGELOG.txt
echo "* 📦 Release version: ${{ github.ref_name }}" >> ${{ github.workspace }}-CHANGELOG.txt
echo "* 📝 Full changelog: https://github.com/${{ github.repository }}/commits/${{ github.ref_name }}" >> ${{ github.workspace }}-CHANGELOG.txt
echo "" >> ${{ github.workspace }}-CHANGELOG.txt
echo "## 🔍 Download Files" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- Windows: \`cursor-id-modifier_${{ github.ref_name }}_Windows_[x64/x86].zip\`" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- macOS: \`cursor-id-modifier_${{ github.ref_name }}_macOS_[x64/arm64]_[intel/apple_silicon].tar.gz\`" >> ${{ github.workspace }}-CHANGELOG.txt
echo "- Linux: \`cursor-id-modifier_${{ github.ref_name }}_Linux_[x64/x86/arm64].tar.gz\`" >> ${{ github.workspace }}-CHANGELOG.txt

43
.gitignore

@ -1,23 +1,40 @@
# Binary files
*.dll
*.so
*.dylib
# Build directories
releases/
# Compiled binary
cursor-id-modifier
cursor-id-modifier.exe
# Build output directories
bin/
dist/
# Go specific
go.sum
# Temporary files
*.tmp
*~
# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
# System files
# OS specific
.DS_Store
Thumbs.db
.vscode
/.idea
# Build and release artifacts
releases/
*.syso
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test files
*.test
*.out
coverage.txt
# Temporary files
*.tmp
*~
*.bak
*.log

106
.goreleaser.yml

@ -1,56 +1,110 @@
project_name: cursor-id-modifier
before:
hooks:
- go mod tidy
builds:
- env:
- main: ./cmd/cursor-id-modifier
binary: cursor-id-modifier
env:
- CGO_ENABLED=0
ldflags:
- -s -w -X main.version={{.Version}}
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- amd64 # Intel 64-bit
- arm64 # Apple Silicon/ARM64
- "386" # Intel 32-bit
ignore:
- goos: darwin
goarch: "386" # No 32-bit support for macOS
ldflags:
- -s -w
- -X main.version={{.Version}}
flags:
- -trimpath
binary: cursor_id_modifier_{{ .Version }}_{{ .Os }}_{{ .Arch }}
mod_timestamp: '{{ .CommitTimestamp }}'
# Build matrix
matrix:
# Special builds for macOS
- goos: [darwin]
goarch: [amd64]
tags: ["intel"]
- goos: [darwin]
goarch: [arm64]
tags: ["apple_silicon"]
# Windows builds
- goos: [windows]
goarch: [amd64, "386"]
# Linux builds
- goos: [linux]
goarch: [amd64, arm64, "386"]
archives:
- format: binary
name_template: "{{ .Binary }}"
allow_different_binary_count: true
- format: tar.gz
format_overrides:
- goos: windows
format: zip
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- .Os }}_
{{- .Arch }}
{{- with .Tags }}_{{ . }}{{ end }}
files:
- README.md
- LICENSE
- scripts/* # Include installation scripts
replacements:
darwin: macOS
linux: Linux
windows: Windows
386: x86
amd64: x64
arm64: arm64
checksum:
name_template: 'checksums.txt'
algorithm: sha256
changelog:
use: github
sort: asc
groups:
- title: Features
regexp: "^.*feat[(\\w)]*:+.*$"
order: 0
- title: 'Bug Fixes'
regexp: "^.*fix[(\\w)]*:+.*$"
order: 1
- title: Others
order: 999
filters:
exclude:
- '^docs:'
- '^test:'
- '^ci:'
- '^chore:'
- Merge pull request
- Merge branch
release:
github:
owner: '{{ .Env.GITHUB_REPOSITORY_OWNER }}'
name: '{{ .Env.GITHUB_REPOSITORY_NAME }}'
draft: false
prerelease: auto
mode: replace
header: |
## Cursor ID Modifier {{ .Version }}
### Supported Platforms
- Windows: x64, x86
- macOS: Intel (x64), Apple Silicon (M1/M2)
- Linux: x64, x86, ARM64
See [CHANGELOG](CHANGELOG.md) for more details.
footer: |
**Full Changelog**: https://github.com/dacrab/cursor-id-modifier/compare/{{ .PreviousTag }}...{{ .Tag }}
## Quick Installation
**Linux/macOS**:
```bash
curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier
```
**Windows** (PowerShell Admin):
```powershell
irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier
```
snapshot:
name_template: "{{ incpatch .Version }}-next"

21
Makefile

@ -0,0 +1,21 @@
.PHONY: build clean test vet
# Build the application
build:
go build -v ./cmd/cursor-id-modifier
# Clean build artifacts
clean:
rm -f cursor-id-modifier
go clean
# Run tests
test:
go test -v ./...
# Run go vet
vet:
go vet ./...
# Run all checks
all: vet test build

174
README.md

@ -2,9 +2,9 @@
<div align="center">
[![Release](https://img.shields.io/github/v/release/yuaotian/go-cursor-help?style=flat-square&logo=github&color=blue)](https://github.com/yuaotian/go-cursor-help/releases/latest)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/yuaotian/go-cursor-help/blob/main/LICENSE)
[![Stars](https://img.shields.io/github/stars/yuaotian/go-cursor-help?style=flat-square&logo=github)](https://github.com/yuaotian/go-cursor-help/stargazers)
[![Release](https://img.shields.io/github/v/release/dacrab/cursor-id-modifier?style=flat-square&logo=github&color=blue)](https://github.com/dacrab/cursor-id-modifier/releases/latest)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/dacrab/cursor-id-modifier/blob/main/LICENSE)
[![Stars](https://img.shields.io/github/stars/dacrab/cursor-id-modifier?style=flat-square&logo=github)](https://github.com/dacrab/cursor-id-modifier/stargazers)
[English](#-english) | [中文](#-chinese)
@ -27,73 +27,59 @@ this is a mistake.
### 💻 System Support
**Windows** ✅ x64
**macOS** ✅ Intel & M-series
**Linux** ✅ x64 & ARM64
**Windows** ✅
- x64 (64-bit)
- x86 (32-bit)
### 📥 Installation
**macOS** ✅
- Intel (x64)
- Apple Silicon (M1/M2)
#### Automatic Installation (Recommended)
**Linux** ✅
- x64 (64-bit)
- x86 (32-bit)
- ARM64
**Linux/macOS**
### 📥 One-Click Solution
**Linux/macOS**: Copy and paste in terminal:
```bash
curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.sh | sudo bash
curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier
```
**Windows** (Run PowerShell as Admin)
**Windows**: Copy and paste in PowerShell (Admin):
```powershell
irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.ps1 | iex
irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier
```
The installation script will automatically:
- Request necessary privileges (sudo/admin)
- Close any running Cursor instances
- Backup existing configuration
- Install the tool
- Add it to system PATH
- Clean up temporary files
#### Manual Installation
1. Download the latest release for your system from the [releases page](https://github.com/yuaotian/go-cursor-help/releases)
2. Extract and run with administrator/root privileges:
```bash
# Linux/macOS
chmod +x ./cursor_id_modifier_* # Add execute permission
sudo ./cursor_id_modifier_*
# Windows (PowerShell Admin)
.\cursor_id_modifier_*.exe
```
#### Manual Configuration Method
1. Close Cursor completely
2. Navigate to the configuration file location:
- Windows: `%APPDATA%\Cursor\User\globalStorage\storage.json`
- macOS: `~/Library/Application Support/Cursor/User/globalStorage/storage.json`
- Linux: `~/.config/Cursor/User/globalStorage/storage.json`
3. Create a backup of `storage.json`
4. Edit `storage.json` and update these fields with new random UUIDs:
```json
{
"telemetry.machineId": "generate-new-uuid",
"telemetry.macMachineId": "generate-new-uuid",
"telemetry.devDeviceId": "generate-new-uuid",
"telemetry.sqmId": "generate-new-uuid",
"lastModified": "2024-01-01T00:00:00.000Z",
"version": "1.0.1"
}
```
5. Save the file and restart Cursor
That's it! The script will:
1. Install the tool automatically
2. Reset your Cursor trial immediately
### 🔧 Manual Installation
Download the appropriate file for your system from [releases](https://github.com/dacrab/cursor-id-modifier/releases/latest):
**Windows**:
- 64-bit: `cursor-id-modifier_vX.X.X_Windows_x64.zip`
- 32-bit: `cursor-id-modifier_vX.X.X_Windows_x86.zip`
**macOS**:
- 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`
**Linux**:
- 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`
### 🔧 Technical Details
#### Configuration Files
The program modifies Cursor's `storage.json` config file located at:
- Windows: `%APPDATA%\Cursor\User\globalStorage\`
- macOS: `~/Library/Application Support/Cursor/User/globalStorage/`
- Linux: `~/.config/Cursor/User/globalStorage/`
- Windows: `%APPDATA%\Cursor\User\globalStorage\storage.json`
- macOS: `~/Library/Application Support/Cursor/User/globalStorage/storage.json`
- Linux: `~/.config/Cursor/User/globalStorage/storage.json`
#### Modified Fields
The tool generates new unique identifiers for:
@ -103,10 +89,9 @@ The tool generates new unique identifiers for:
- `telemetry.sqmId`
#### Safety Features
- Automatic backup of existing configuration
- Safe process termination
- Atomic file operations
- Error handling and rollback
- ✅ Safe process termination
- ✅ Atomic file operations
- ✅ Error handling and recovery
---
@ -125,65 +110,25 @@ this is a mistake.
### 💻 系统支持
**Windows** ✅ x64
**macOS** ✅ Intel和M系列
**Linux** ✅ x64和ARM64
### 📥 安装方法
**Windows** ✅ x64 & x86
**macOS** ✅ Intel & M-series
**Linux** ✅ x64 & ARM64
#### 自动安装(推荐)
### 📥 一键解决
**Linux/macOS**
**Linux/macOS**: 在终端中复制粘贴:
```bash
curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.sh | sudo bash
curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier
```
**Windows** (以管理员身份运行PowerShell)
**Windows**: 在PowerShell(管理员)中复制粘贴:
```powershell
irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.ps1 | iex
irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier
```
安装脚本会自动:
- 请求必要的权限(sudo/管理员)
- 关闭所有运行中的Cursor实例
- 备份现有配置
- 安装工具
- 添加到系统PATH
- 清理临时文件
#### 手动安装
1. 从[发布页面](https://github.com/yuaotian/go-cursor-help/releases)下载适合您系统的最新版本
2. 解压并以管理员/root权限运行:
```bash
# Linux/macOS
chmod +x ./cursor_id_modifier_* # 添加执行权限
sudo ./cursor_id_modifier_*
# Windows (PowerShell 管理员)
.\cursor_id_modifier_*.exe
```
#### 手动配置方法
1. 完全关闭 Cursor
2. 找到配置文件位置:
- Windows: `%APPDATA%\Cursor\User\globalStorage\storage.json`
- macOS: `~/Library/Application Support/Cursor/User/globalStorage/storage.json`
- Linux: `~/.config/Cursor/User/globalStorage/storage.json`
3. 备份 `storage.json`
4. 编辑 `storage.json` 并更新以下字段(使用新的随机UUID):
```json
{
"telemetry.machineId": "生成新的uuid",
"telemetry.macMachineId": "生成新的uuid",
"telemetry.devDeviceId": "生成新的uuid",
"telemetry.sqmId": "生成新的uuid",
"lastModified": "2024-01-01T00:00:00.000Z",
"version": "1.0.1"
}
```
5. 保存文件并重启 Cursor
就这样!脚本会:
1. 自动安装工具
2. 立即重置Cursor试用期
### 🔧 技术细节
@ -201,10 +146,9 @@ irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/ins
- `telemetry.sqmId`
#### 安全特性
- 自动备份现有配置
- 安全的进程终止
- 原子文件操作
- 错误处理和回滚
- ✅ 安全的进程终止
- ✅ 原子文件操作
- ✅ 错误处理和恢复
## ⭐ Star History or Repobeats

9
go.mod

@ -2,10 +2,17 @@ module cursor-id-modifier
go 1.21
require github.com/fatih/color v1.15.0
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
golang.org/x/sys v0.13.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

17
go.sum

@ -1,3 +1,6 @@
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=
@ -5,6 +8,20 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
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 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=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

150
internal/config/config.go

@ -0,0 +1,150 @@
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)
}
// 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)
}
// 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{})
}
// 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"
// Marshal with indentation
newFileContent, err := json.MarshalIndent(originalFile, "", " ")
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 {
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 {
dir.Sync()
dir.Close()
}
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
}

156
internal/lang/lang.go

@ -0,0 +1,156 @@
package lang
import (
"os"
"os/exec"
"strings"
"sync"
)
// Language represents a supported language
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 {
SuccessMessage string
RestartMessage string
ReadingConfig string
GeneratingIds string
PressEnterToExit string
ErrorPrefix string
PrivilegeError string
RunAsAdmin string
RunWithSudo string
SudoExample string
ConfigLocation string
CheckingProcesses string
ClosingProcesses string
ProcessesClosed string
PleaseWait string
SetReadOnlyMessage 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
for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} {
if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") {
return CN
}
}
// Check Windows language settings
if isWindows() {
if isWindowsChineseLocale() {
return CN
}
} else {
// Check Unix locale
if isUnixChineseLocale() {
return CN
}
}
return EN
}
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: {
SuccessMessage: "[√] 配置文件已成功更新!",
RestartMessage: "[!] 请手动重启 Cursor 以使更新生效",
ReadingConfig: "正在读取配置文件...",
GeneratingIds: "正在生成新的标识符...",
PressEnterToExit: "按回车键退出程序...",
ErrorPrefix: "程序发生严重错误: %v",
PrivilegeError: "\n[!] 错误:需要管理员权限",
RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」",
RunWithSudo: "请使用 sudo 命令运行此程序",
SudoExample: "示例: sudo %s",
ConfigLocation: "配置文件位置:",
CheckingProcesses: "正在检查运行中的 Cursor 实例...",
ClosingProcesses: "正在关闭 Cursor 实例...",
ProcessesClosed: "所有 Cursor 实例已关闭",
PleaseWait: "请稍候...",
SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题",
},
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",
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...",
SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records",
},
}

162
internal/process/manager.go

@ -0,0 +1,162 @@
package process
import (
"context"
"fmt"
"os/exec"
"runtime"
"strings"
"sync"
"time"
"github.com/sirupsen/logrus"
)
// Config holds process manager configuration
type Config struct {
RetryAttempts int
RetryDelay time.Duration
Timeout time.Duration
}
// DefaultConfig returns the default configuration
func DefaultConfig() *Config {
return &Config{
RetryAttempts: 3,
RetryDelay: time.Second,
Timeout: 30 * time.Second,
}
}
// Manager handles process-related operations
type Manager struct {
config *Config
log *logrus.Logger
mu sync.Mutex
}
// NewManager creates a new process manager
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,
}
}
// KillCursorProcesses attempts to kill all Cursor processes
func (m *Manager) KillCursorProcesses() error {
m.mu.Lock()
defer m.mu.Unlock()
ctx, cancel := context.WithTimeout(context.Background(), m.config.Timeout)
defer cancel()
for attempt := 0; attempt < m.config.RetryAttempts; attempt++ {
m.log.Debugf("Attempt %d/%d to kill Cursor processes", attempt+1, m.config.RetryAttempts)
if err := m.killProcess(ctx); err != nil {
m.log.Warnf("Failed to kill processes on attempt %d: %v", attempt+1, err)
time.Sleep(m.config.RetryDelay)
continue
}
return nil
}
return fmt.Errorf("failed to kill all Cursor processes after %d attempts", m.config.RetryAttempts)
}
// IsCursorRunning checks if any Cursor process is running
func (m *Manager) IsCursorRunning() bool {
m.mu.Lock()
defer m.mu.Unlock()
processes, err := m.listCursorProcesses()
if err != nil {
m.log.Warnf("Failed to list Cursor processes: %v", err)
return false
}
return len(processes) > 0
}
func (m *Manager) killProcess(ctx context.Context) error {
if runtime.GOOS == "windows" {
return m.killWindowsProcess(ctx)
}
return m.killUnixProcess(ctx)
}
func (m *Manager) killWindowsProcess(ctx context.Context) error {
// First try graceful termination
if err := exec.CommandContext(ctx, "taskkill", "/IM", "Cursor.exe").Run(); err != nil {
m.log.Debugf("Graceful termination failed: %v", err)
}
time.Sleep(m.config.RetryDelay)
// Force kill if still running
if err := exec.CommandContext(ctx, "taskkill", "/F", "/IM", "Cursor.exe").Run(); err != nil {
return fmt.Errorf("failed to force kill Cursor process: %w", err)
}
return nil
}
func (m *Manager) killUnixProcess(ctx context.Context) error {
processes, err := m.listCursorProcesses()
if err != nil {
return fmt.Errorf("failed to list processes: %w", err)
}
for _, pid := range processes {
if err := m.forceKillProcess(ctx, pid); err != nil {
m.log.Warnf("Failed to kill process %s: %v", pid, err)
continue
}
}
return nil
}
func (m *Manager) forceKillProcess(ctx context.Context, pid string) error {
// Try graceful termination first
if err := exec.CommandContext(ctx, "kill", pid).Run(); err == nil {
m.log.Debugf("Process %s terminated gracefully", pid)
time.Sleep(2 * time.Second)
return nil
}
// Force kill if still running
if err := exec.CommandContext(ctx, "kill", "-9", pid).Run(); err != nil {
return fmt.Errorf("failed to force kill process %s: %w", pid, err)
}
m.log.Debugf("Process %s force killed", pid)
return nil
}
func (m *Manager) listCursorProcesses() ([]string, error) {
cmd := exec.Command("ps", "aux")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to execute ps command: %w", err)
}
var pids []string
for _, line := range strings.Split(string(output), "\n") {
if strings.Contains(strings.ToLower(line), "apprun") {
fields := strings.Fields(line)
if len(fields) > 1 {
pids = append(pids, fields[1])
}
}
}
return pids, nil
}

101
internal/ui/display.go

@ -0,0 +1,101 @@
package ui
import (
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"github.com/fatih/color"
)
// Display handles UI display operations
type Display struct {
spinner *Spinner
}
// NewDisplay creates a new display handler
func NewDisplay(spinner *Spinner) *Display {
if spinner == nil {
spinner = NewSpinner(nil)
}
return &Display{
spinner: spinner,
}
}
// ShowProgress shows a progress message with 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()
}
// ClearScreen clears the terminal screen
func (d *Display) ClearScreen() error {
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("cmd", "/c", "cls")
} else {
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)
red.Println(errorMsg)
if runtime.GOOS == "windows" {
yellow.Println(adminMsg)
} else {
yellow.Printf("%s\n%s\n", sudoMsg, fmt.Sprintf(sudoExample, os.Args[0]))
}
}
// 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)
}
// ShowError shows an error message
func (d *Display) ShowError(message string) {
red := color.New(color.FgRed, color.Bold)
red.Printf("\n%s\n", message)
}
// ShowWarning shows a warning message
func (d *Display) ShowWarning(message string) {
yellow := color.New(color.FgYellow, color.Bold)
yellow.Printf("\n%s\n", message)
}
// ShowInfo shows an info message
func (d *Display) ShowInfo(message string) {
cyan := color.New(color.FgCyan)
cyan.Printf("\n%s\n", message)
}
// ShowPrompt shows a prompt message and waits for user input
func (d *Display) ShowPrompt(message string) {
fmt.Print(message)
os.Stdout.Sync()
}

110
internal/ui/spinner.go

@ -0,0 +1,110 @@
package ui
import (
"fmt"
"sync"
"time"
"github.com/fatih/color"
)
// SpinnerConfig defines spinner configuration
type SpinnerConfig struct {
Frames []string
Delay time.Duration
}
// 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{}),
}
}
// SetMessage sets the spinner message
func (s *Spinner) SetMessage(message string) {
s.mu.Lock()
defer s.mu.Unlock()
s.message = message
}
// Start starts 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 stops 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.Println()
}
// IsActive returns whether the spinner is currently active
func (s *Spinner) IsActive() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.active
}
func (s *Spinner) run() {
ticker := time.NewTicker(s.config.Delay)
defer ticker.Stop()
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)]
message := s.message
s.current++
s.mu.RUnlock()
fmt.Printf("\r%s %s", color.CyanString(frame), message)
}
}
}

1050
main.go
File diff suppressed because it is too large
View File

95
pkg/idgen/generator.go

@ -0,0 +1,95 @@
package idgen
import (
cryptorand "crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"math/big"
"sync"
)
// Generator handles the generation of various IDs
type Generator struct {
charsetMu sync.RWMutex
charset string
}
// NewGenerator creates a new ID generator with default settings
func NewGenerator() *Generator {
return &Generator{
charset: "0123456789ABCDEFGHJKLMNPQRSTVWXYZ",
}
}
// 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
}
// GenerateMachineID generates a new machine ID with the format auth0|user_XX[unique_id]
func (g *Generator) GenerateMachineID() (string, error) {
prefix := "auth0|user_"
// 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)
}
sequence := fmt.Sprintf("%02d", seqNum.Int64())
uniqueID, err := g.generateUniqueID(23)
if err != nil {
return "", fmt.Errorf("failed to generate unique ID: %w", err)
}
fullID := prefix + sequence + uniqueID
return hex.EncodeToString([]byte(fullID)), nil
}
// GenerateMacMachineID generates a new MAC machine ID using SHA-256
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
}
// GenerateDeviceID generates a new device ID in UUID v4 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()]
}
return string(result), nil
}

26
pkg/idgen/generator_test.go

@ -0,0 +1,26 @@
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")
}

146
scripts/build_all.bat

@ -1,128 +1,74 @@
@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]=bin directory does not exist, no cleanup needed"
set "EN_MESSAGES[5]=Starting builds for all platforms..."
set "EN_MESSAGES[6]=Building for"
set "EN_MESSAGES[7]=Build successful:"
set "EN_MESSAGES[8]=Build failed for"
set "EN_MESSAGES[9]=All builds completed! Total time:"
set "EN_MESSAGES[10]=seconds"
set "CN_MESSAGES[0]=开始构建版本"
set "CN_MESSAGES[1]=使用优化标志:"
set "CN_MESSAGES[2]=正在清理旧的构建文件..."
set "CN_MESSAGES[3]=清理完成"
set "CN_MESSAGES[4]=bin 目录不存在,无需清理"
set "CN_MESSAGES[5]=开始编译所有平台..."
set "CN_MESSAGES[6]=正在构建"
set "CN_MESSAGES[7]=构建成功:"
set "CN_MESSAGES[8]=构建失败:"
set "CN_MESSAGES[9]=所有构建完成!总耗时:"
set "CN_MESSAGES[10]=秒"
:: 设置版本信息 / Set version
set VERSION=2.0.0
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!"
:: 设置颜色代码 / Set color codes
:: Colors
set "GREEN=[32m"
set "RED=[31m"
set "YELLOW=[33m"
set "RESET=[0m"
:: 设置编译优化标志 / Set build optimization flags
set "LDFLAGS=-s -w"
set "BUILDMODE=pie"
set "GCFLAGS=-N -l"
:: 设置 CGO / Set CGO
set CGO_ENABLED=0
:: 检测系统语言 / Detect system language
for /f "tokens=2 delims==" %%a in ('wmic os get OSLanguage /value') do set OSLanguage=%%a
if "%OSLanguage%"=="2052" (set LANG=cn) else (set LANG=en)
:: 显示编译信息 / Display build info
echo %YELLOW%!%LANG%_MESSAGES[0]! %VERSION%%RESET%
echo %YELLOW%!%LANG%_MESSAGES[1]! LDFLAGS=%LDFLAGS%, BUILDMODE=%BUILDMODE%%RESET%
echo %YELLOW%CGO_ENABLED=%CGO_ENABLED%%RESET%
:: 清理旧的构建文件 / Clean old builds
echo %YELLOW%!%LANG%_MESSAGES[2]!%RESET%
:: Cleanup function
:cleanup
if exist "..\bin" (
rd /s /q "..\bin"
echo %GREEN%!%LANG%_MESSAGES[3]!%RESET%
) else (
echo %YELLOW%!%LANG%_MESSAGES[4]!%RESET%
echo %GREEN%!EN_MESSAGES[3]!%RESET%
)
:: 创建输出目录 / Create output directory
mkdir "..\bin" 2>nul
:: 定义目标平台数组 / Define target platforms array
set platforms[0].os=windows
set platforms[0].arch=amd64
set platforms[0].ext=.exe
set platforms[0].suffix=
:: Build function with optimizations
:build
set "os=%~1"
set "arch=%~2"
set "ext="
if "%os%"=="windows" set "ext=.exe"
set platforms[1].os=darwin
set platforms[1].arch=amd64
set platforms[1].ext=
set platforms[1].suffix=_intel
echo %GREEN%!EN_MESSAGES[5]! %os%/%arch%%RESET%
set platforms[2].os=darwin
set platforms[2].arch=arm64
set platforms[2].ext=
set platforms[2].suffix=_m1
set "CGO_ENABLED=0"
set "GOOS=%os%"
set "GOARCH=%arch%"
set platforms[3].os=linux
set platforms[3].arch=amd64
set platforms[3].ext=
set platforms[3].suffix=
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
:: 设置开始时间 / Set start time
set start_time=%time%
:: Main execution
echo %GREEN%!EN_MESSAGES[0]!%RESET%
echo %GREEN%!EN_MESSAGES[1]! %OPTIMIZATION_FLAGS%%RESET%
:: 编译所有目标 / Build all targets
echo !%LANG%_MESSAGES[5]!
call :cleanup
for /L %%i in (0,1,3) do (
set "os=!platforms[%%i].os!"
set "arch=!platforms[%%i].arch!"
set "ext=!platforms[%%i].ext!"
set "suffix=!platforms[%%i].suffix!"
echo.
echo !%LANG%_MESSAGES[6]! !os! !arch!...
set GOOS=!os!
set GOARCH=!arch!
:: 构建输出文件名 / Build output filename
set "outfile=..\bin\cursor_id_modifier_v%VERSION%_!os!_!arch!!suffix!!ext!"
:: 执行构建 / Execute build
go build -trimpath -buildmode=%BUILDMODE% -ldflags="%LDFLAGS%" -gcflags="%GCFLAGS%" -o "!outfile!" ..\main.go
if !errorlevel! equ 0 (
echo %GREEN%!%LANG%_MESSAGES[7]! !outfile!%RESET%
) else (
echo %RED%!%LANG%_MESSAGES[8]! !os! !arch!%RESET%
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"
)
)
)
:: 计算总耗时 / Calculate total time
set end_time=%time%
set /a duration = %end_time:~0,2% * 3600 + %end_time:~3,2% * 60 + %end_time:~6,2% - (%start_time:~0,2% * 3600 + %start_time:~3,2% * 60 + %start_time:~6,2%)
echo.
echo %GREEN%!%LANG%_MESSAGES[9]! %duration% !%LANG%_MESSAGES[10]!%RESET%
if exist "..\bin" dir /b "..\bin"
:: 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
pause
endlocal
echo %GREEN%!EN_MESSAGES[7]!%RESET%

108
scripts/build_all.sh

@ -5,6 +5,10 @@ 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"
@ -18,8 +22,6 @@ EN_MESSAGES=(
"Successful builds:"
"Failed builds:"
"Generated files:"
"Build process interrupted"
"Error:"
)
CN_MESSAGES=(
@ -70,82 +72,68 @@ handle_error() {
# 清理函数 / Cleanup function
cleanup() {
echo "$(get_message 1)"
rm -rf ../bin
}
# 创建输出目录 / Create output directory
create_output_dir() {
echo "$(get_message 2)"
mkdir -p ../bin || handle_error "$(get_message 3)"
if [ -d "../bin" ]; then
rm -rf ../bin
echo -e "${GREEN}$(get_message 1)${NC}"
fi
}
# 构建函数 / Build function
# Build function with optimizations
build() {
local os=$1
local arch=$2
local suffix=$3
echo -e "\n$(get_message 4) $os ($arch)..."
local ext=""
[ "$os" = "windows" ] && ext=".exe"
output_name="../bin/cursor_id_modifier_v${VERSION}_${os}_${arch}${suffix}"
echo -e "${GREEN}$(get_message 4) $os/$arch${NC}"
GOOS=$os GOARCH=$arch go build -o "$output_name" ../main.go
if [ $? -eq 0 ]; then
echo -e "${GREEN}$(get_message 5) ${output_name}${NC}"
else
echo -e "${RED}$(get_message 6) $os $arch${NC}"
return 1
fi
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 &
}
# 主函数 / Main function
main() {
# 显示构建信息 / Display build info
echo "$(get_message 0) ${VERSION}"
# 清理旧文件 / Clean old files
cleanup
# 创建输出目录 / Create output directory
create_output_dir
# Parallel build execution
build_all() {
local builds=0
local max_parallel=$PARALLEL_JOBS
# 定义构建目标 / Define build targets
# Define build targets
declare -A targets=(
["windows_amd64"]=".exe"
["darwin_amd64"]=""
["darwin_arm64"]=""
["linux_amd64"]=""
["linux/amd64"]=1
["linux/386"]=1
["linux/arm64"]=1
["windows/amd64"]=1
["windows/386"]=1
["darwin/amd64"]=1
["darwin/arm64"]=1
)
# 构建计数器 / Build counters
local success_count=0
local fail_count=0
# 遍历所有目标进行构建 / Build all targets
for target in "${!targets[@]}"; do
os=${target%_*}
arch=${target#*_}
suffix=${targets[$target]}
IFS='/' read -r os arch <<< "$target"
build "$os" "$arch"
if build "$os" "$arch" "$suffix"; then
((success_count++))
else
((fail_count++))
((builds++))
if ((builds >= max_parallel)); then
wait
builds=0
fi
done
# 显示构建结果 / Display build results
echo -e "\n$(get_message 7)"
echo -e "${GREEN}$(get_message 8) $success_count${NC}"
if [ $fail_count -gt 0 ]; then
echo -e "${RED}$(get_message 9) $fail_count${NC}"
fi
# 显示生成的文件列表 / Display generated files
echo -e "\n$(get_message 10)"
ls -1 ../bin
# 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

336
scripts/install.ps1

@ -6,303 +6,123 @@ if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdenti
Exit
}
# Set TLS to 1.2 / 设置 TLS 为 1.2
# Set TLS to 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Colors for output / 输出颜色
# Colors for output
$Red = "`e[31m"
$Green = "`e[32m"
$Blue = "`e[36m"
$Yellow = "`e[33m"
$Reset = "`e[0m"
# Messages / 消息
$EN_MESSAGES = @(
"Starting installation...",
"Detected architecture:",
"Only 64-bit Windows is supported",
"Latest version:",
"Creating installation directory...",
"Downloading latest release from:",
"Failed to download binary:",
"Downloaded file not found",
"Installing binary...",
"Failed to install binary:",
"Adding to PATH...",
"Cleaning up...",
"Installation completed successfully!",
"You can now use 'cursor-id-modifier' directly",
"Checking for running Cursor instances...",
"Found running Cursor processes. Attempting to close them...",
"Successfully closed all Cursor instances",
"Failed to close Cursor instances. Please close them manually",
"Backing up storage.json...",
"Backup created at:"
)
# Create temporary directory
$TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString())
New-Item -ItemType Directory -Path $TmpDir | Out-Null
$CN_MESSAGES = @(
"开始安装...",
"检测到架构:",
"仅支持64位Windows系统",
"最新版本:",
"正在创建安装目录...",
"正在从以下地址下载最新版本:",
"下载二进制文件失败:",
"未找到下载的文件",
"正在安装程序...",
"安装二进制文件失败:",
"正在添加到PATH...",
"正在清理...",
"安装成功完成!",
"现在可以直接使用 'cursor-id-modifier' 了",
"正在检查运行中的Cursor进程...",
"发现正在运行的Cursor进程,尝试关闭...",
"成功关闭所有Cursor实例",
"无法关闭Cursor实例,请手动关闭",
"正在备份storage.json...",
"备份已创建于:"
)
# Detect system language / 检测系统语言
function Get-SystemLanguage {
if ((Get-Culture).Name -like "zh-CN") {
return "cn"
# Cleanup function
function Cleanup {
if (Test-Path $TmpDir) {
Remove-Item -Recurse -Force $TmpDir
}
return "en"
}
# Get message based on language / 根据语言获取消息
function Get-Message($Index) {
$lang = Get-SystemLanguage
if ($lang -eq "cn") {
return $CN_MESSAGES[$Index]
}
return $EN_MESSAGES[$Index]
}
# Functions for colored output / 彩色输出函数
function Write-Status($Message) {
Write-Host "${Blue}[*]${Reset} $Message"
}
function Write-Success($Message) {
Write-Host "${Green}[✓]${Reset} $Message"
}
function Write-Warning($Message) {
Write-Host "${Yellow}[!]${Reset} $Message"
}
function Write-Error($Message) {
Write-Host "${Red}[✗]${Reset} $Message"
Exit 1
# Error handler
trap {
Write-Host "${Red}Error: $_${Reset}"
Cleanup
exit 1
}
# Close Cursor instances / 关闭Cursor实例
function Close-CursorInstances {
Write-Status (Get-Message 14)
$cursorProcesses = Get-Process "Cursor" -ErrorAction SilentlyContinue
if ($cursorProcesses) {
Write-Status (Get-Message 15)
try {
$cursorProcesses | ForEach-Object { $_.CloseMainWindow() | Out-Null }
Start-Sleep -Seconds 2
$cursorProcesses | Where-Object { !$_.HasExited } | Stop-Process -Force
Write-Success (Get-Message 16)
} catch {
Write-Error (Get-Message 17)
}
}
}
# Backup storage.json / 备份storage.json
function Backup-StorageJson {
Write-Status (Get-Message 18)
$storageJsonPath = "$env:APPDATA\Cursor\User\globalStorage\storage.json"
if (Test-Path $storageJsonPath) {
$backupPath = "$storageJsonPath.backup"
Copy-Item -Path $storageJsonPath -Destination $backupPath -Force
Write-Success "$(Get-Message 19) $backupPath"
# Detect system architecture
function Get-SystemArch {
if ([Environment]::Is64BitOperatingSystem) {
return "amd64"
} else {
return "386"
}
}
# Get latest release version from GitHub / 从GitHub获取最新版本
function Get-LatestVersion {
$repo = "yuaotian/go-cursor-help"
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/releases/latest"
return $release.tag_name
}
# 在文件开头添加日志函数
function Write-Log {
param(
[string]$Message,
[string]$Level = "INFO"
# Download with progress
function Download-WithProgress {
param (
[string]$Url,
[string]$OutputFile
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logMessage = "[$timestamp] [$Level] $Message"
$logFile = "$env:TEMP\cursor-id-modifier-install.log"
Add-Content -Path $logFile -Value $logMessage
# 同时输出到控制台
switch ($Level) {
"ERROR" { Write-Error $Message }
"WARNING" { Write-Warning $Message }
"SUCCESS" { Write-Success $Message }
default { Write-Status $Message }
try {
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "PowerShell Script")
$webClient.DownloadFile($Url, $OutputFile)
return $true
}
catch {
Write-Host "${Red}Failed to download: $_${Reset}"
return $false
}
}
# 添加安装前检查函数
function Test-Prerequisites {
Write-Log "Checking prerequisites..." "INFO"
# Main installation function
function Install-CursorModifier {
Write-Host "${Blue}Starting installation...${Reset}"
# 检查PowerShell版本
if ($PSVersionTable.PSVersion.Major -lt 5) {
Write-Log "PowerShell 5.0 or higher is required" "ERROR"
return $false
# Detect architecture
$arch = Get-SystemArch
Write-Host "${Green}Detected architecture: $arch${Reset}"
# Set installation directory
$InstallDir = "$env:ProgramFiles\CursorModifier"
if (!(Test-Path $InstallDir)) {
New-Item -ItemType Directory -Path $InstallDir | Out-Null
}
# 检查网络连接
# Get latest release
try {
$testConnection = Test-Connection -ComputerName "github.com" -Count 1 -Quiet
if (-not $testConnection) {
Write-Log "No internet connection available" "ERROR"
return $false
$latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/dacrab/cursor-id-modifier/releases/latest"
$downloadUrl = $latestRelease.assets | Where-Object { $_.name -match "windows_$arch" } | Select-Object -ExpandProperty browser_download_url
if (!$downloadUrl) {
throw "Could not find download URL for windows_$arch"
}
} catch {
Write-Log "Failed to check internet connection: $_" "ERROR"
return $false
}
catch {
Write-Host "${Red}Failed to get latest release: $_${Reset}"
exit 1
}
return $true
}
# 添加文件验证函数
function Test-FileHash {
param(
[string]$FilePath,
[string]$ExpectedHash
)
# Download binary
Write-Host "${Blue}Downloading latest release...${Reset}"
$binaryPath = Join-Path $TmpDir "cursor-id-modifier.exe"
$actualHash = Get-FileHash -Path $FilePath -Algorithm SHA256
return $actualHash.Hash -eq $ExpectedHash
}
# 修改下载函数,添加进度条
function Download-File {
param(
[string]$Url,
[string]$OutFile
)
if (!(Download-WithProgress -Url $downloadUrl -OutputFile $binaryPath)) {
exit 1
}
# Install binary
Write-Host "${Blue}Installing...${Reset}"
try {
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "PowerShell Script")
Copy-Item -Path $binaryPath -Destination "$InstallDir\cursor-id-modifier.exe" -Force
$webClient.DownloadFileAsync($Url, $OutFile)
while ($webClient.IsBusy) {
Write-Progress -Activity "Downloading..." -Status "Progress:" -PercentComplete -1
Start-Sleep -Milliseconds 100
# Add to PATH if not already present
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
if ($currentPath -notlike "*$InstallDir*") {
[Environment]::SetEnvironmentVariable("Path", "$currentPath;$InstallDir", "Machine")
}
Write-Progress -Activity "Downloading..." -Completed
return $true
}
catch {
Write-Log "Download failed: $_" "ERROR"
return $false
}
finally {
if ($webClient) {
$webClient.Dispose()
}
}
}
# Main installation process / 主安装过程
Write-Status (Get-Message 0)
# Close any running Cursor instances
Close-CursorInstances
# Backup storage.json
Backup-StorageJson
# Get system architecture / 获取系统架构
$arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" }
Write-Status "$(Get-Message 1) $arch"
if ($arch -ne "amd64") {
Write-Error (Get-Message 2)
}
# Get latest version / 获取最新版本
$version = Get-LatestVersion
Write-Status "$(Get-Message 3) $version"
# Set up paths / 设置路径
$installDir = "$env:ProgramFiles\cursor-id-modifier"
$versionWithoutV = $version.TrimStart('v') # 移除版本号前面的 'v' 字符
$binaryName = "cursor_id_modifier_${versionWithoutV}_windows_amd64.exe"
$downloadUrl = "https://github.com/yuaotian/go-cursor-help/releases/download/$version/$binaryName"
$tempFile = "$env:TEMP\$binaryName"
# Create installation directory / 创建安装目录
Write-Status (Get-Message 4)
if (-not (Test-Path $installDir)) {
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
}
# Download binary / 下载二进制文件
Write-Status "$(Get-Message 5) $downloadUrl"
try {
if (-not (Download-File -Url $downloadUrl -OutFile $tempFile)) {
Write-Error "$(Get-Message 6)"
Write-Host "${Red}Failed to install: $_${Reset}"
exit 1
}
} catch {
Write-Error "$(Get-Message 6) $_"
}
# Verify download / 验证下载
if (-not (Test-Path $tempFile)) {
Write-Error (Get-Message 7)
Write-Host "${Green}Installation completed successfully!${Reset}"
Write-Host "${Blue}You can now run: cursor-id-modifier${Reset}"
}
# Install binary / 安装二进制文件
Write-Status (Get-Message 8)
# Run installation
try {
Move-Item -Force $tempFile "$installDir\cursor-id-modifier.exe"
} catch {
Write-Error "$(Get-Message 9) $_"
Install-CursorModifier
}
# Add to PATH if not already present / 如果尚未添加则添加到PATH
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($userPath -notlike "*$installDir*") {
Write-Status (Get-Message 10)
[Environment]::SetEnvironmentVariable(
"Path",
"$userPath;$installDir",
"User"
)
}
# Cleanup / 清理
Write-Status (Get-Message 11)
if (Test-Path $tempFile) {
Remove-Item -Force $tempFile
}
Write-Success (Get-Message 12)
Write-Success (Get-Message 13)
Write-Host ""
# 直接运行程序
try {
Start-Process "$installDir\cursor-id-modifier.exe" -NoNewWindow
} catch {
Write-Warning "Failed to start cursor-id-modifier: $_"
finally {
Cleanup
}

324
scripts/install.sh

@ -2,294 +2,98 @@
set -e
# Colors for output / 输出颜色
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;36m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color / 无颜色
NC='\033[0m'
# Messages / 消息
EN_MESSAGES=(
"Starting installation..."
"Detected OS:"
"Downloading latest release..."
"URL:"
"Installing binary..."
"Cleaning up..."
"Installation completed successfully!"
"You can now use 'sudo %s' from your terminal"
"Failed to download binary from:"
"Failed to download the binary"
"curl is required but not installed. Please install curl first."
"sudo is required but not installed. Please install sudo first."
"Unsupported operating system"
"Unsupported architecture:"
"Checking for running Cursor instances..."
"Found running Cursor processes. Attempting to close them..."
"Successfully closed all Cursor instances"
"Failed to close Cursor instances. Please close them manually"
"Backing up storage.json..."
"Backup created at:"
"This script requires root privileges. Requesting sudo access..."
)
# Temporary directory for downloads
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
CN_MESSAGES=(
"开始安装..."
"检测到操作系统:"
"正在下载最新版本..."
"下载地址:"
"正在安装程序..."
"正在清理..."
"安装成功完成!"
"现在可以在终端中使用 'sudo %s' 了"
"从以下地址下载二进制文件失败:"
"下载二进制文件失败"
"需要 curl 但未安装。请先安装 curl。"
"需要 sudo 但未安装。请先安装 sudo。"
"不支持的操作系统"
"不支持的架构:"
"正在检查运行中的Cursor进程..."
"发现正在运行的Cursor进程,尝试关闭..."
"成功关闭所有Cursor实例"
"无法关闭Cursor实例,请手动关闭"
"正在备份storage.json..."
"备份已创建于:"
"此脚本需要root权限。正在请求sudo访问..."
)
# Detect system information
detect_system() {
local os arch
# 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
}
# Print with color / 带颜色打印
print_status() {
echo -e "${BLUE}[*]${NC} $1"
}
print_success() {
echo -e "${GREEN}[✓]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[!]${NC} $1"
}
print_error() {
echo -e "${RED}[✗]${NC} $1"
exit 1
}
case "$(uname -s)" in
Linux*) os="linux";;
Darwin*) os="darwin";;
*) echo "Unsupported OS"; exit 1;;
esac
# Check and request root privileges / 检查并请求root权限
check_root() {
if [ "$EUID" -ne 0 ]; then
print_status "$(get_message 20)"
if command -v sudo >/dev/null 2>&1; then
exec sudo bash "$0" "$@"
else
print_error "$(get_message 11)"
fi
fi
}
case "$(uname -m)" in
x86_64) arch="amd64";;
aarch64) arch="arm64";;
arm64) arch="arm64";;
*) echo "Unsupported architecture"; exit 1;;
esac
# Close Cursor instances / 关闭Cursor实例
close_cursor_instances() {
print_status "$(get_message 14)"
if pgrep -x "Cursor" >/dev/null; then
print_status "$(get_message 15)"
if pkill -x "Cursor" 2>/dev/null; then
sleep 2
print_success "$(get_message 16)"
else
print_error "$(get_message 17)"
fi
fi
echo "$os $arch"
}
# Backup storage.json / 备份storage.json
backup_storage_json() {
print_status "$(get_message 18)"
local storage_path
if [ "$(uname)" == "Darwin" ]; then
storage_path="$HOME/Library/Application Support/Cursor/User/globalStorage/storage.json"
else
storage_path="$HOME/.config/Cursor/User/globalStorage/storage.json"
fi
# Download with progress using curl or wget
download() {
local url="$1"
local output="$2"
if [ -f "$storage_path" ]; then
cp "$storage_path" "${storage_path}.backup"
print_success "$(get_message 19) ${storage_path}.backup"
fi
}
# Detect OS / 检测操作系统
detect_os() {
if [[ "$OSTYPE" == "darwin"* ]]; then
echo "darwin"
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
echo "linux"
if command -v curl >/dev/null 2>&1; then
curl -#L "$url" -o "$output"
elif command -v wget >/dev/null 2>&1; then
wget --show-progress -q "$url" -O "$output"
else
print_error "$(get_message 12)"
echo "Error: curl or wget is required"
exit 1
fi
}
# Get latest release version from GitHub / 从GitHub获取最新版本
get_latest_version() {
local repo="yuaotian/go-cursor-help"
curl -s "https://api.github.com/repos/${repo}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/'
}
# Get the binary name based on OS and architecture / 根据操作系统和架构获取二进制文件名
get_binary_name() {
OS=$(detect_os)
ARCH=$(uname -m)
VERSION=$(get_latest_version)
# Check and create installation directory
setup_install_dir() {
local install_dir="$1"
case "$ARCH" in
x86_64)
echo "cursor_id_modifier_${VERSION}_${OS}_amd64"
;;
aarch64|arm64)
echo "cursor_id_modifier_${VERSION}_${OS}_arm64"
;;
*)
print_error "$(get_message 13) $ARCH"
;;
esac
}
# Add download progress display function
download_with_progress() {
local url="$1"
local output_file="$2"
curl -L -f --progress-bar "$url" -o "$output_file"
return $?
if [ ! -d "$install_dir" ]; then
mkdir -p "$install_dir" || {
echo "Failed to create installation directory"
exit 1
}
fi
}
# Optimize installation function
install_binary() {
OS=$(detect_os)
VERSION=$(get_latest_version)
VERSION_WITHOUT_V=${VERSION#v} # Remove 'v' from version number
BINARY_NAME="cursor_id_modifier_${VERSION_WITHOUT_V}_${OS}_$(get_arch)"
REPO="yuaotian/go-cursor-help"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${BINARY_NAME}"
TMP_DIR=$(mktemp -d)
FINAL_BINARY_NAME="cursor-id-modifier"
print_status "$(get_message 2)"
print_status "$(get_message 3) ${DOWNLOAD_URL}"
if ! download_with_progress "$DOWNLOAD_URL" "$TMP_DIR/$BINARY_NAME"; then
rm -rf "$TMP_DIR"
print_error "$(get_message 8) $DOWNLOAD_URL"
fi
# Main installation function
main() {
echo -e "${BLUE}Starting installation...${NC}"
if [ ! -f "$TMP_DIR/$BINARY_NAME" ]; then
rm -rf "$TMP_DIR"
print_error "$(get_message 9)"
fi
# Detect system
read -r OS ARCH <<< "$(detect_system)"
echo -e "${GREEN}Detected: $OS $ARCH${NC}"
print_status "$(get_message 4)"
# Set installation directory
INSTALL_DIR="/usr/local/bin"
[ "$OS" = "darwin" ] && INSTALL_DIR="/usr/local/bin"
# Create directory if it doesn't exist
mkdir -p "$INSTALL_DIR"
# Move binary to installation directory
if ! mv "$TMP_DIR/$BINARY_NAME" "$INSTALL_DIR/$FINAL_BINARY_NAME"; then
rm -rf "$TMP_DIR"
print_error "Failed to move binary to installation directory"
fi
if ! chmod +x "$INSTALL_DIR/$FINAL_BINARY_NAME"; then
rm -rf "$TMP_DIR"
print_error "Failed to set executable permissions"
fi
# Cleanup
print_status "$(get_message 5)"
rm -rf "$TMP_DIR"
# Setup installation directory
setup_install_dir "$INSTALL_DIR"
print_success "$(get_message 6)"
printf "${GREEN}[✓]${NC} $(get_message 7)\n" "$FINAL_BINARY_NAME"
# Download latest release
LATEST_URL="https://api.github.com/repos/dacrab/cursor-id-modifier/releases/latest"
DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep "browser_download_url.*${OS}_${ARCH}" | cut -d '"' -f 4)
# Try to run the program directly
if [ -x "$INSTALL_DIR/$FINAL_BINARY_NAME" ]; then
"$INSTALL_DIR/$FINAL_BINARY_NAME" &
else
print_warning "Failed to start cursor-id-modifier"
fi
}
# Optimize architecture detection function
get_arch() {
case "$(uname -m)" in
x86_64)
echo "amd64"
;;
aarch64|arm64)
echo "arm64"
;;
*)
print_error "$(get_message 13) $(uname -m)"
;;
esac
}
# Check for required tools / 检查必需工具
check_requirements() {
if ! command -v curl >/dev/null 2>&1; then
print_error "$(get_message 10)"
if [ -z "$DOWNLOAD_URL" ]; then
echo -e "${RED}Error: Could not find download URL for $OS $ARCH${NC}"
exit 1
fi
if ! command -v sudo >/dev/null 2>&1; then
print_error "$(get_message 11)"
fi
}
# Main installation process / 主安装过程
main() {
print_status "$(get_message 0)"
# Check root privileges / 检查root权限
check_root "$@"
# Check required tools / 检查必需工具
check_requirements
# Close Cursor instances / 关闭Cursor实例
close_cursor_instances
# Backup storage.json / 备份storage.json
backup_storage_json
echo -e "${BLUE}Downloading latest release...${NC}"
download "$DOWNLOAD_URL" "$TMP_DIR/cursor-id-modifier"
OS=$(detect_os)
print_status "$(get_message 1) $OS"
# Install binary
echo -e "${BLUE}Installing...${NC}"
chmod +x "$TMP_DIR/cursor-id-modifier"
sudo mv "$TMP_DIR/cursor-id-modifier" "$INSTALL_DIR/"
# Install the binary / 安装二进制文件
install_binary
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "${BLUE}You can now run: cursor-id-modifier${NC}"
}
# Run main function / 运行主函数
main "$@"
main
Loading…
Cancel
Save