diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml
new file mode 100644
index 0000000..ab9e5e5
--- /dev/null
+++ b/.github/workflows/auto-tag-release.yml
@@ -0,0 +1,272 @@
+# This workflow requires Ubuntu 22.04 or 24.04
+
+name: Auto Tag & Release
+
+on:
+ push:
+ branches:
+ - master
+ - main
+ tags:
+ - "v*"
+ paths-ignore:
+ - "**.md"
+ - "LICENSE"
+ - ".gitignore"
+ workflow_call: {}
+
+permissions:
+ contents: write
+ packages: write
+ actions: write
+
+jobs:
+ pre_job:
+ runs-on: ubuntu-22.04
+ outputs:
+ should_skip: ${{ steps.skip_check.outputs.should_skip }}
+ steps:
+ - id: skip_check
+ uses: fkirc/skip-duplicate-actions@v5.3.0
+ with:
+ cancel_others: "true"
+ concurrent_skipping: "same_content"
+
+ auto-tag-release:
+ needs: pre_job
+ if: |
+ needs.pre_job.outputs.should_skip != 'true' ||
+ startsWith(github.ref, 'refs/tags/v')
+ runs-on: ubuntu-22.04
+ timeout-minutes: 15
+ outputs:
+ version: ${{ steps.get_latest_tag.outputs.version }}
+ concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ lfs: true
+ submodules: recursive
+
+ - name: Setup Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: "1.21"
+ check-latest: true
+ cache: true
+
+ - name: Cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cache/go-build
+ ~/go/pkg/mod
+ ~/.cache/git
+ key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }}
+ restore-keys: |
+ ${{ runner.os }}-build-
+ ${{ runner.os }}-
+
+ # 只在非tag推送时执行自动打tag
+ - name: Get latest tag
+ if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
+ id: get_latest_tag
+ run: |
+ set -euo pipefail
+ git fetch --tags --force || {
+ echo "::error::Failed to fetch tags"
+ exit 1
+ }
+ latest_tag=$(git tag -l 'v*' --sort=-v:refname | head -n 1)
+ if [ -z "$latest_tag" ]; then
+ new_version="v0.1.0"
+ else
+ major=$(echo $latest_tag | cut -d. -f1)
+ minor=$(echo $latest_tag | cut -d. -f2)
+ patch=$(echo $latest_tag | cut -d. -f3)
+ new_patch=$((patch + 1))
+ new_version="$major.$minor.$new_patch"
+ fi
+ echo "version=$new_version" >> "$GITHUB_OUTPUT"
+ echo "Generated version: $new_version"
+
+ - name: Validate version
+ if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
+ run: |
+ set -euo pipefail
+ new_tag="${{ steps.get_latest_tag.outputs.version }}"
+ echo "Validating version: $new_tag"
+ if [[ ! $new_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "::error::Invalid version format: $new_tag"
+ exit 1
+ fi
+ major=$(echo $new_tag | cut -d. -f1 | tr -d 'v')
+ minor=$(echo $new_tag | cut -d. -f2)
+ patch=$(echo $new_tag | cut -d. -f3)
+ if [[ $major -gt 99 || $minor -gt 99 || $patch -gt 999 ]]; then
+ echo "::error::Version numbers out of valid range"
+ exit 1
+ fi
+ echo "Version validation passed"
+
+ - name: Create new tag
+ if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ new_tag=${{ steps.get_latest_tag.outputs.version }}
+ git config --global user.name 'github-actions[bot]'
+ git config --global user.email 'github-actions[bot]@users.noreply.github.com'
+ git tag -a $new_tag -m "Release $new_tag"
+ git push origin $new_tag
+
+ # 在 Run GoReleaser 之前添加配置检查步骤
+ - name: Check GoReleaser config
+ run: |
+ if [ ! -f ".goreleaser.yml" ] && [ ! -f ".goreleaser.yaml" ]; then
+ echo "::error::GoReleaser configuration file not found"
+ exit 1
+ fi
+
+ # 添加依赖检查步骤
+ - name: Check Dependencies
+ run: |
+ go mod verify
+ go mod download
+ # 如果使用 vendor 模式,则执行以下命令
+ if [ -d "vendor" ]; then
+ go mod vendor
+ fi
+
+ # 添加构建环境准备步骤
+ - name: Prepare Build Environment
+ run: |
+ echo "Building version: ${VERSION:-development}"
+ echo "GOOS=${GOOS:-$(go env GOOS)}" >> $GITHUB_ENV
+ echo "GOARCH=${GOARCH:-$(go env GOARCH)}" >> $GITHUB_ENV
+ echo "GO111MODULE=on" >> $GITHUB_ENV
+
+ # 添加清理步骤
+ - name: Cleanup workspace
+ run: |
+ rm -rf /tmp/go/
+ rm -rf .cache/
+ rm -rf dist/
+ git clean -fdx
+ git status
+
+ # 修改 GoReleaser 步骤
+ - name: Run GoReleaser
+ if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }}
+ uses: goreleaser/goreleaser-action@v3
+ with:
+ distribution: goreleaser
+ version: latest
+ args: release --clean --timeout 60m
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ VERSION: ${{ steps.get_latest_tag.outputs.version }}
+ CGO_ENABLED: 0
+ GOPATH: /tmp/go
+ GOROOT: ${{ env.GOROOT }}
+ GOCACHE: /tmp/.cache/go-build
+ GOMODCACHE: /tmp/go/pkg/mod
+ GORELEASER_DEBUG: 1
+ GORELEASER_CURRENT_TAG: ${{ steps.get_latest_tag.outputs.version }}
+ # 添加额外的构建信息
+ BUILD_TIME: ${{ steps.get_latest_tag.outputs.version }}
+ BUILD_COMMIT: ${{ github.sha }}
+
+ # 优化 vendor 同步步骤
+ - name: Sync vendor directory
+ run: |
+ echo "Syncing vendor directory..."
+ go mod tidy
+ go mod vendor
+ go mod verify
+ # 验证 vendor 目录
+ if [ -d "vendor" ]; then
+ echo "Verifying vendor directory..."
+ go mod verify
+ # 检查是否有未跟踪的文件
+ if [ -n "$(git status --porcelain vendor/)" ]; then
+ echo "Warning: Vendor directory has uncommitted changes"
+ git status vendor/
+ fi
+ fi
+
+ # 添加错误检查步骤
+ - name: Check GoReleaser Output
+ if: failure()
+ run: |
+ echo "::group::GoReleaser Debug Info"
+ cat dist/artifacts.json || true
+ echo "::endgroup::"
+
+ echo "::group::GoReleaser Config"
+ cat .goreleaser.yml
+ echo "::endgroup::"
+
+ echo "::group::Environment Info"
+ go version
+ go env
+ echo "::endgroup::"
+
+ - name: Set Release Version
+ if: startsWith(github.ref, 'refs/tags/v')
+ run: |
+ echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
+
+ # 改进验证步骤
+ - name: Verify Release
+ if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }}
+ run: |
+ echo "Verifying release artifacts..."
+ if [ ! -d "dist" ]; then
+ echo "::error::Release artifacts not found"
+ exit 1
+ fi
+ # 验证生成的二进制文件
+ for file in dist/cursor-id-modifier_*; do
+ if [ -f "$file" ]; then
+ echo "Verifying: $file"
+ if [[ "$file" == *.exe ]]; then
+ # Windows 二进制文件检查
+ if ! [ -x "$file" ]; then
+ echo "::error::$file is not executable"
+ exit 1
+ fi
+ else
+ # Unix 二进制文件检查
+ if ! [ -x "$file" ]; then
+ echo "::error::$file is not executable"
+ exit 1
+ fi
+ fi
+ fi
+ done
+
+ - name: Notify on failure
+ if: failure()
+ run: |
+ echo "::error::Release process failed"
+
+ # 修改构建摘要步骤
+ - name: Build Summary
+ if: always()
+ run: |
+ echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
+ echo "- Go Version: $(go version)" >> $GITHUB_STEP_SUMMARY
+ echo "- Release Version: ${VERSION:-N/A}" >> $GITHUB_STEP_SUMMARY
+ echo "- GPG Signing: Disabled" >> $GITHUB_STEP_SUMMARY
+ echo "- Build Status: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
+
+ if [ -d "dist" ]; then
+ echo "### Generated Artifacts" >> $GITHUB_STEP_SUMMARY
+ ls -lh dist/ | awk '{print "- "$9" ("$5")"}' >> $GITHUB_STEP_SUMMARY
+ fi
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index 9feeb88..0000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-name: Release
-
-on:
- push:
- tags:
- - 'v*'
-
-permissions:
- contents: write
-
-jobs:
- goreleaser:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Set up Go
- uses: actions/setup-go@v5
- with:
- check-latest: true
-
- - name: Set Repository Variables
- run: |
- echo "GITHUB_REPOSITORY_OWNER=$(echo ${{ github.repository }} | cut -d '/' -f 1)" >> $GITHUB_ENV
- echo "GITHUB_REPOSITORY_NAME=$(echo ${{ github.repository }} | cut -d '/' -f 2)" >> $GITHUB_ENV
-
- - name: Run GoReleaser
- uses: goreleaser/goreleaser-action@v5
- with:
- distribution: goreleaser
- version: latest
- args: release --clean
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 54f0356..27161b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,42 @@
-# Binary files
-*.dll
-*.so
-*.dylib
-
-# Build directories
-releases/
-cursor-id-modifier
+# Compiled binary
+/cursor-id-modifier
+/cursor-id-modifier.exe
+# Build output directories
+bin/
+dist/
# Go specific
go.sum
+go/
+.cache/
-# Temporary files
-*.tmp
-*~
+# IDE and editor files
+.vscode/
+.idea/
+*.swp
+*.swo
-# System files
+# OS specific
.DS_Store
Thumbs.db
-.vscode
-/.idea
\ No newline at end of file
+# Build and release artifacts
+releases/
+*.syso
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test files
+*.test
+*.out
+coverage.txt
+
+# Temporary files
+*.tmp
+*~
+*.bak
+*.log
\ No newline at end of file
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 862b38d..13c0b21 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -1,14 +1,18 @@
-project_name: cursor-id-modifier
+version: 2
before:
hooks:
- go mod tidy
+ - go mod vendor
+ - go mod verify
builds:
- - env:
+ - id: cursor-id-modifier
+ main: ./cmd/cursor-id-modifier/main.go
+ binary: cursor-id-modifier
+ env:
- CGO_ENABLED=0
- ldflags:
- - -s -w -X main.version={{.Version}}
+ - GO111MODULE=on
goos:
- linux
- windows
@@ -16,41 +20,73 @@ builds:
goarch:
- amd64
- arm64
- mod_timestamp: '{{ .CommitTimestamp }}'
+ - "386"
+ ignore:
+ - goos: darwin
+ goarch: "386"
+ ldflags:
+ - -s -w
+ - -X 'main.version={{.Version}}'
+ - -X 'main.commit={{.ShortCommit}}'
+ - -X 'main.date={{.CommitDate}}'
+ - -X 'main.builtBy=goreleaser'
flags:
- -trimpath
- binary: cursor_id_modifier_{{ .Version }}_{{ .Os }}_{{ .Arch }}
+ mod_timestamp: '{{ .CommitTimestamp }}'
archives:
- - format: binary
- name_template: "{{ .Binary }}"
+ - id: binary
+ format: binary
+ name_template: >-
+ {{- .ProjectName }}_
+ {{- .Version }}_
+ {{- .Os }}_
+ {{- if eq .Arch "amd64" }}x86_64
+ {{- else if eq .Arch "386" }}i386
+ {{- else }}{{ .Arch }}{{ end }}
+ builds:
+ - cursor-id-modifier
allow_different_binary_count: true
+ files:
+ - none*
+
+checksum:
+ name_template: 'checksums.txt'
+ algorithm: sha256
+
+release:
+ draft: true
+ prerelease: auto
+ mode: replace
+ header: |
+ ## Release {{.Tag}} ({{.Date}})
+
+ See [CHANGELOG.md](CHANGELOG.md) for details.
+ footer: |
+ **Full Changelog**: https://github.com/owner/repo/compare/{{ .PreviousTag }}...{{ .Tag }}
+ extra_files:
+ - glob: 'LICENSE*'
+ - glob: 'README*'
+ - glob: 'CHANGELOG*'
changelog:
- use: github
sort: asc
+ use: github
+ filters:
+ exclude:
+ - '^docs:'
+ - '^test:'
+ - '^ci:'
+ - Merge pull request
+ - Merge branch
groups:
- title: Features
regexp: "^.*feat[(\\w)]*:+.*$"
order: 0
- - title: 'Bug Fixes'
+ - 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
+project_name: cursor-id-modifier
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..2af6908
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,53 @@
+# 📝 Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [0.1.23] - 2024-12-29 🚀
+
+### ✨ Features
+- **Initial Release**: First public release of Cursor ID Modifier
+- **Multi-Platform Support**:
+ - 🪟 Windows (x64, x86)
+ - 🍎 macOS (Intel & Apple Silicon)
+ - 🐧 Linux (x64, x86, ARM64)
+- **Installation**:
+ - Automated installation scripts for all platforms
+ - One-line installation commands
+ - Secure download and verification
+- **Core Functionality**:
+ - Telemetry ID modification for Cursor IDE
+ - Automatic process management
+ - Secure configuration handling
+
+### 🐛 Bug Fixes
+- **Installation**:
+ - Fixed environment variable preservation in sudo operations
+ - Enhanced error handling during installation
+ - Improved binary download reliability
+- **Process Management**:
+ - Improved Cursor process detection accuracy
+ - Enhanced process termination reliability
+ - Better handling of edge cases
+- **User Experience**:
+ - Enhanced error messages and user feedback
+ - Improved progress indicators
+ - Better handling of system permissions
+
+### 🔧 Technical Improvements
+- Optimized binary size with proper build flags
+- Enhanced cross-platform compatibility
+- Improved error handling and logging
+- Better system resource management
+
+### 📚 Documentation
+- Added comprehensive installation instructions
+- Included platform-specific guidelines
+- Enhanced error troubleshooting guide
+
+---
+*For more details about the changes, please refer to the [commit history](https://github.com/yuaotian/go-cursor-help/commits/main).*
+
+[0.1.22]: https://github.com/yuaotian/go-cursor-help/releases/tag/v0.1.23
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..11eeb01
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 dacrab
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index 0f3b3f6..edc9eb6 100644
--- a/README.md
+++ b/README.md
@@ -3,22 +3,24 @@
[](https://github.com/yuaotian/go-cursor-help/releases/latest)
-[](https://github.com/yuaotian/go-cursor-help/blob/main/LICENSE)
+[](https://github.com/yuaotian/go-cursor-help/blob/master/LICENSE)
[](https://github.com/yuaotian/go-cursor-help/stargazers)
-[English](#-english) | [中文](#-chinese)
+[🌟 English](#english) | [🌏 中文](#chinese)
-# 🌟 English
+---
+
+## 🌟 English
### 📝 Description
-Resets Cursor's free trial limitation when you see:
+> Resets Cursor's free trial limitation when you see:
-```
+```text
Too many free trial accounts used on this machine.
Please upgrade to pro. We have this limit in place
to prevent abuse. Please let us know if you believe
@@ -27,96 +29,120 @@ this is a mistake.
### 💻 System Support
-**Windows** ✅ x64
-**macOS** ✅ Intel & M-series
-**Linux** ✅ x64 & ARM64
+
+
+
+
+**Windows** ✅
+- x64 (64-bit)
+- x86 (32-bit)
+
+ |
+
+
+**macOS** ✅
+- Intel (x64)
+- Apple Silicon (M1/M2)
+
+ |
+
-### 📥 Installation
+**Linux** ✅
+- x64 (64-bit)
+- x86 (32-bit)
+- ARM64
-#### Automatic Installation (Recommended)
+ |
+
+
-**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
```
-**Windows** (Run PowerShell as Admin)
+**Windows**: Copy and paste in PowerShell
```powershell
irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.ps1 | iex
```
-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
+#### Windows Installation Features:
+- 🔍 Automatically detects and uses PowerShell 7 if available
+- 🛡️ Requests administrator privileges via UAC prompt
+- 📝 Falls back to Windows PowerShell if PS7 isn't found
+- 💡 Provides manual instructions if elevation fails
+
+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/yuaotian/go-cursor-help/releases/latest)
+
+
+Windows Packages
+
+- 64-bit: `cursor-id-modifier_windows_x64.exe`
+- 32-bit: `cursor-id-modifier_windows_x86.exe`
+
+
+
+macOS Packages
+
+- Intel: `cursor-id-modifier_darwin_x64_intel`
+- M1/M2: `cursor-id-modifier_darwin_arm64_apple_silicon`
+
+
+
+Linux Packages
+
+- 64-bit: `cursor-id-modifier_linux_x64`
+- 32-bit: `cursor-id-modifier_linux_x86`
+- ARM64: `cursor-id-modifier_linux_arm64`
+
### 🔧 Technical Details
-#### Configuration Files
+
+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/`
-#### Modified Fields
+- 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:
- `telemetry.machineId`
- `telemetry.macMachineId`
- `telemetry.devDeviceId`
- `telemetry.sqmId`
+
+
+
+Safety Features
-#### 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
+
---
-# 🌏 Chinese
+## 🌏 Chinese
### 📝 问题描述
-当看到以下提示时重置Cursor试用期:
+> 当看到以下提示时重置Cursor试用期:
-```
+```text
Too many free trial accounts used on this machine.
Please upgrade to pro. We have this limit in place
to prevent abuse. Please let us know if you believe
@@ -125,98 +151,127 @@ this is a mistake.
### 💻 系统支持
-**Windows** ✅ x64
-**macOS** ✅ Intel和M系列
-**Linux** ✅ x64和ARM64
+
+
+
-### 📥 安装方法
+**Windows** ✅
+- x64 & x86
-#### 自动安装(推荐)
+ |
+
-**Linux/macOS**
+**macOS** ✅
+- Intel & M-series
+
+ |
+
+
+**Linux** ✅
+- x64 & ARM64
+
+ |
+
+
+
+### 🚀 一键解决
+
+**Linux/macOS**: 在终端中复制粘贴
```bash
curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.sh | sudo bash
```
-**Windows** (以管理员身份运行PowerShell)
+**Windows**: 在PowerShell中复制粘贴
```powershell
irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.ps1 | iex
```
-安装脚本会自动:
-- 请求必要的权限(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
+#### Windows 安装特性:
+- 🔍 自动检测并使用 PowerShell 7(如果可用)
+- 🛡️ 通过 UAC 提示请求管理员权限
+- 📝 如果没有 PS7 则使用 Windows PowerShell
+- 💡 如果提权失败会提供手动说明
+
+That's it! The script will:
+1. ✨ 自动安装工具
+2. 🔄 立即重置Cursor试用期
+
+### 📦 Manual Installation
+
+> Download the appropriate file for your system from [releases](https://github.com/yuaotian/go-cursor-help/releases/latest)
+
+
+Windows Packages
+
+- 64-bit: `cursor-id-modifier_windows_x64.exe`
+- 32-bit: `cursor-id-modifier_windows_x86.exe`
+
+
+
+macOS Packages
+
+- Intel: `cursor-id-modifier_darwin_x64_intel`
+- M1/M2: `cursor-id-modifier_darwin_arm64_apple_silicon`
+
+
+
+Linux Packages
+
+- 64-bit: `cursor-id-modifier_linux_x64`
+- 32-bit: `cursor-id-modifier_linux_x86`
+- ARM64: `cursor-id-modifier_linux_arm64`
+
### 🔧 技术细节
-#### 配置文件
+
+配置文件
+
程序修改Cursor的`storage.json`配置文件,位于:
+
- Windows: `%APPDATA%\Cursor\User\globalStorage\`
- macOS: `~/Library/Application Support/Cursor/User/globalStorage/`
- Linux: `~/.config/Cursor/User/globalStorage/`
+
+
+
+修改字段
-#### 修改字段
工具会生成新的唯一标识符:
- `telemetry.machineId`
- `telemetry.macMachineId`
- `telemetry.devDeviceId`
- `telemetry.sqmId`
+
-#### 安全特性
-- 自动备份现有配置
-- 安全的进程终止
-- 原子文件操作
-- 错误处理和回滚
+
+安全特性
-## ⭐ Star History or Repobeats
+## 🔔 关注公众号
+#### 获取更多精彩内容
+- 第一时间获取最新版本更新
+- CursorAI使用技巧和最佳实践
+- 利用AI提升编程效率
+- 更多AI工具和开发资源
-[](https://star-history.com/#yuaotian/go-cursor-help&Date)
+
+
+---
+## ⭐ Project Stats
+
+
+
+[](https://star-history.com/#yuaotian/go-cursor-help&Date)

+
## 📄 License
-MIT License
+
+MIT License
Copyright (c) 2024
@@ -229,4 +284,4 @@ furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
-
+
diff --git a/cmd/cursor-id-modifier/main.go b/cmd/cursor-id-modifier/main.go
new file mode 100644
index 0000000..89de5f3
--- /dev/null
+++ b/cmd/cursor-id-modifier/main.go
@@ -0,0 +1,330 @@
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "os"
+ "os/exec"
+ "os/user"
+ "runtime"
+ "runtime/debug"
+ "strings"
+
+ "github.com/yuaotian/go-cursor-help/internal/config"
+ "github.com/yuaotian/go-cursor-help/internal/lang"
+ "github.com/yuaotian/go-cursor-help/internal/process"
+ "github.com/yuaotian/go-cursor-help/internal/ui"
+ "github.com/yuaotian/go-cursor-help/pkg/idgen"
+
+ "github.com/sirupsen/logrus"
+)
+
+// Global variables
+var (
+ version = "dev"
+ setReadOnly = flag.Bool("r", false, "set storage.json to read-only mode")
+ showVersion = flag.Bool("v", false, "show version information")
+ log = logrus.New()
+)
+
+func main() {
+ 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)
+ debug.PrintStack()
+ waitExit()
+ }
+ }()
+}
+
+func handleFlags() {
+ flag.Parse()
+ if *showVersion {
+ fmt.Printf("Cursor ID Modifier v%s\n", version)
+ os.Exit(0)
+ }
+}
+
+func setupLogger() {
+ log.SetFormatter(&logrus.TextFormatter{
+ FullTimestamp: true,
+ DisableLevelTruncation: true,
+ PadLevelText: true,
+ })
+ log.SetLevel(logrus.InfoLevel)
+}
+
+func getCurrentUser() string {
+ if username := os.Getenv("SUDO_USER"); username != "" {
+ return username
+ }
+
+ user, err := user.Current()
+ if err != nil {
+ log.Fatal(err)
+ }
+ return user.Username
+}
+
+func initConfigManager(username string) *config.Manager {
+ configManager, err := config.NewManager(username)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return configManager
+}
+
+func handlePrivileges(display *ui.Display) error {
+ isAdmin, err := checkAdminPrivileges()
+ if err != nil {
+ log.Error(err)
+ waitExit()
+ return err
+ }
+
+ if !isAdmin {
+ if runtime.GOOS == "windows" {
+ return handleWindowsPrivileges(display)
+ }
+ display.ShowPrivilegeError(
+ lang.GetText().PrivilegeError,
+ lang.GetText().RunWithSudo,
+ lang.GetText().SudoExample,
+ )
+ waitExit()
+ return fmt.Errorf("insufficient privileges")
+ }
+ return nil
+}
+
+func handleWindowsPrivileges(display *ui.Display) error {
+ message := "\nRequesting administrator privileges..."
+ if lang.GetCurrentLanguage() == lang.CN {
+ message = "\n请求管理员权限..."
+ }
+ fmt.Println(message)
+
+ if err := selfElevate(); err != nil {
+ log.Error(err)
+ display.ShowPrivilegeError(
+ lang.GetText().PrivilegeError,
+ lang.GetText().RunAsAdmin,
+ lang.GetText().RunWithSudo,
+ lang.GetText().SudoExample,
+ )
+ waitExit()
+ return err
+ }
+ return nil
+}
+
+func setupDisplay(display *ui.Display) {
+ if err := display.ClearScreen(); err != nil {
+ log.Warn("Failed to clear screen:", err)
+ }
+ display.ShowLogo()
+ fmt.Println()
+}
+
+func handleCursorProcesses(display *ui.Display, processManager *process.Manager) error {
+ if os.Getenv("AUTOMATED_MODE") == "1" {
+ log.Debug("Running in automated mode, skipping Cursor process closing")
+ return nil
+ }
+
+ display.ShowProgress("Closing Cursor...")
+ log.Debug("Attempting to close Cursor processes")
+
+ if err := processManager.KillCursorProcesses(); err != nil {
+ log.Error("Failed to close Cursor:", err)
+ display.StopProgress()
+ display.ShowError("Failed to close Cursor. Please close it manually and try again.")
+ waitExit()
+ return err
+ }
+
+ if processManager.IsCursorRunning() {
+ log.Error("Cursor processes still detected after closing")
+ display.StopProgress()
+ display.ShowError("Failed to close Cursor completely. Please close it manually and try again.")
+ waitExit()
+ return fmt.Errorf("cursor still running")
+ }
+
+ log.Debug("Successfully closed all Cursor processes")
+ display.StopProgress()
+ fmt.Println()
+ return nil
+}
+
+func readExistingConfig(display *ui.Display, configManager *config.Manager, text lang.TextResource) *config.StorageConfig {
+ fmt.Println()
+ display.ShowProgress(text.ReadingConfig)
+ oldConfig, err := configManager.ReadConfig()
+ if err != nil {
+ log.Warn("Failed to read existing config:", err)
+ oldConfig = nil
+ }
+ display.StopProgress()
+ fmt.Println()
+ return oldConfig
+}
+
+func generateNewConfig(display *ui.Display, generator *idgen.Generator, oldConfig *config.StorageConfig, text lang.TextResource) *config.StorageConfig {
+ display.ShowProgress(text.GeneratingIds)
+ newConfig := &config.StorageConfig{}
+
+ if machineID, err := generator.GenerateMachineID(); err != nil {
+ log.Fatal("Failed to generate machine ID:", err)
+ } else {
+ newConfig.TelemetryMachineId = machineID
+ }
+
+ if macMachineID, err := generator.GenerateMacMachineID(); err != nil {
+ log.Fatal("Failed to generate MAC machine ID:", err)
+ } else {
+ newConfig.TelemetryMacMachineId = macMachineID
+ }
+
+ if deviceID, err := generator.GenerateDeviceID(); err != nil {
+ log.Fatal("Failed to generate device ID:", err)
+ } else {
+ newConfig.TelemetryDevDeviceId = deviceID
+ }
+
+ if oldConfig != nil && oldConfig.TelemetrySqmId != "" {
+ newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId
+ } else if sqmID, err := generator.GenerateSQMID(); err != nil {
+ log.Fatal("Failed to generate SQM ID:", err)
+ } else {
+ newConfig.TelemetrySqmId = sqmID
+ }
+
+ display.StopProgress()
+ fmt.Println()
+ return newConfig
+}
+
+func saveConfiguration(display *ui.Display, configManager *config.Manager, newConfig *config.StorageConfig) error {
+ display.ShowProgress("Saving configuration...")
+ if err := configManager.SaveConfig(newConfig, *setReadOnly); err != nil {
+ log.Error(err)
+ waitExit()
+ return err
+ }
+ display.StopProgress()
+ fmt.Println()
+ return nil
+}
+
+func showCompletionMessages(display *ui.Display) {
+ display.ShowSuccess(lang.GetText().SuccessMessage, lang.GetText().RestartMessage)
+ fmt.Println()
+
+ message := "Operation completed!"
+ if lang.GetCurrentLanguage() == lang.CN {
+ message = "操作完成!"
+ }
+ display.ShowInfo(message)
+}
+
+func waitExit() {
+ fmt.Print(lang.GetText().PressEnterToExit)
+ os.Stdout.Sync()
+ bufio.NewReader(os.Stdin).ReadString('\n')
+}
+
+func checkAdminPrivileges() (bool, error) {
+ switch runtime.GOOS {
+ case "windows":
+ cmd := exec.Command("net", "session")
+ return cmd.Run() == nil, nil
+
+ case "darwin", "linux":
+ currentUser, err := user.Current()
+ if err != nil {
+ return false, fmt.Errorf("failed to get current user: %w", err)
+ }
+ return currentUser.Uid == "0", nil
+
+ default:
+ return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
+ }
+}
+
+func selfElevate() error {
+ os.Setenv("AUTOMATED_MODE", "1")
+
+ switch runtime.GOOS {
+ case "windows":
+ verb := "runas"
+ exe, _ := os.Executable()
+ cwd, _ := os.Getwd()
+ args := strings.Join(os.Args[1:], " ")
+
+ cmd := exec.Command("cmd", "/C", "start", verb, exe, args)
+ cmd.Dir = cwd
+ return cmd.Run()
+
+ case "darwin", "linux":
+ exe, err := os.Executable()
+ if err != nil {
+ return err
+ }
+
+ cmd := exec.Command("sudo", append([]string{exe}, os.Args[1:]...)...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+
+ default:
+ return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
+ }
+}
diff --git a/go.mod b/go.mod
index 6b674f1..6bcebe3 100644
--- a/go.mod
+++ b/go.mod
@@ -1,11 +1,15 @@
-module cursor-id-modifier
+module github.com/yuaotian/go-cursor-help
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
+)
require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
+ github.com/stretchr/testify v1.10.0 // indirect
golang.org/x/sys v0.13.0 // indirect
)
diff --git a/go.sum b/go.sum
index b4bb98d..d930a66 100644
--- a/go.sum
+++ b/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,19 @@ 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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/img/wx_public.jpg b/img/wx_public.jpg
new file mode 100644
index 0000000..2455fc5
Binary files /dev/null and b/img/wx_public.jpg differ
diff --git a/img/wx_public_2.png b/img/wx_public_2.png
new file mode 100644
index 0000000..5974ac8
Binary files /dev/null and b/img/wx_public_2.png differ
diff --git a/internal/config/config.go b/internal/config/config.go
new file mode 100644
index 0000000..75b64df
--- /dev/null
+++ b/internal/config/config.go
@@ -0,0 +1,153 @@
+package config
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sync"
+ "time"
+)
+
+// StorageConfig represents the storage configuration
+type StorageConfig struct {
+ TelemetryMacMachineId string `json:"telemetry.macMachineId"`
+ TelemetryMachineId string `json:"telemetry.machineId"`
+ TelemetryDevDeviceId string `json:"telemetry.devDeviceId"`
+ TelemetrySqmId string `json:"telemetry.sqmId"`
+ LastModified string `json:"lastModified"`
+ Version string `json:"version"`
+}
+
+// Manager handles configuration operations
+type Manager struct {
+ configPath string
+ mu sync.RWMutex
+}
+
+// NewManager creates a new configuration manager
+func NewManager(username string) (*Manager, error) {
+ configPath, err := getConfigPath(username)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get config path: %w", err)
+ }
+ return &Manager{configPath: configPath}, nil
+}
+
+// ReadConfig reads the existing configuration
+func (m *Manager) ReadConfig() (*StorageConfig, error) {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+
+ data, err := os.ReadFile(m.configPath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ }
+ return nil, fmt.Errorf("failed to read config file: %w", err)
+ }
+
+ var config StorageConfig
+ if err := json.Unmarshal(data, &config); err != nil {
+ return nil, fmt.Errorf("failed to parse config file: %w", err)
+ }
+
+ return &config, nil
+}
+
+// SaveConfig saves the configuration
+func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ // Ensure parent directories exist
+ if err := os.MkdirAll(filepath.Dir(m.configPath), 0755); err != nil {
+ return fmt.Errorf("failed to create config directory: %w", err)
+ }
+
+ // Prepare updated configuration
+ updatedConfig := m.prepareUpdatedConfig(config)
+
+ // Write configuration
+ if err := m.writeConfigFile(updatedConfig, readOnly); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// prepareUpdatedConfig merges existing config with updates
+func (m *Manager) prepareUpdatedConfig(config *StorageConfig) map[string]interface{} {
+ // Read existing config
+ originalFile := make(map[string]interface{})
+ if data, err := os.ReadFile(m.configPath); err == nil {
+ json.Unmarshal(data, &originalFile)
+ }
+
+ // Update fields
+ originalFile["telemetry.sqmId"] = config.TelemetrySqmId
+ originalFile["telemetry.macMachineId"] = config.TelemetryMacMachineId
+ originalFile["telemetry.machineId"] = config.TelemetryMachineId
+ originalFile["telemetry.devDeviceId"] = config.TelemetryDevDeviceId
+ originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339)
+ // originalFile["version"] = "1.0.1"
+
+ return originalFile
+}
+
+// writeConfigFile handles the atomic write of the config file
+func (m *Manager) writeConfigFile(config map[string]interface{}, readOnly bool) error {
+ // Marshal with indentation
+ content, err := json.MarshalIndent(config, "", " ")
+ if err != nil {
+ return fmt.Errorf("failed to marshal config: %w", err)
+ }
+
+ // Write to temporary file
+ tmpPath := m.configPath + ".tmp"
+ if err := os.WriteFile(tmpPath, content, 0666); err != nil {
+ return fmt.Errorf("failed to write temporary file: %w", err)
+ }
+
+ // Set final permissions
+ fileMode := os.FileMode(0666)
+ if readOnly {
+ fileMode = 0444
+ }
+
+ if err := os.Chmod(tmpPath, fileMode); err != nil {
+ os.Remove(tmpPath)
+ return fmt.Errorf("failed to set temporary file permissions: %w", err)
+ }
+
+ // Atomic rename
+ if err := os.Rename(tmpPath, m.configPath); err != nil {
+ os.Remove(tmpPath)
+ return fmt.Errorf("failed to rename file: %w", err)
+ }
+
+ // Sync directory
+ if dir, err := os.Open(filepath.Dir(m.configPath)); err == nil {
+ defer dir.Close()
+ dir.Sync()
+ }
+
+ return nil
+}
+
+// getConfigPath returns the path to the configuration file
+func getConfigPath(username string) (string, error) {
+ var configDir string
+ switch runtime.GOOS {
+ case "windows":
+ configDir = filepath.Join(os.Getenv("APPDATA"), "Cursor", "User", "globalStorage")
+ case "darwin":
+ configDir = filepath.Join("/Users", username, "Library", "Application Support", "Cursor", "User", "globalStorage")
+ case "linux":
+ configDir = filepath.Join("/home", username, ".config", "Cursor", "User", "globalStorage")
+ default:
+ return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
+ }
+ return filepath.Join(configDir, "storage.json"), nil
+}
diff --git a/internal/lang/lang.go b/internal/lang/lang.go
new file mode 100644
index 0000000..973fc24
--- /dev/null
+++ b/internal/lang/lang.go
@@ -0,0 +1,187 @@
+package lang
+
+import (
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+)
+
+// Language represents a supported language code
+type Language string
+
+const (
+ // CN represents Chinese language
+ CN Language = "cn"
+ // EN represents English language
+ EN Language = "en"
+)
+
+// TextResource contains all translatable text resources
+type TextResource struct {
+ // Success messages
+ SuccessMessage string
+ RestartMessage string
+
+ // Progress messages
+ ReadingConfig string
+ GeneratingIds string
+ CheckingProcesses string
+ ClosingProcesses string
+ ProcessesClosed string
+ PleaseWait string
+
+ // Error messages
+ ErrorPrefix string
+ PrivilegeError string
+
+ // Instructions
+ RunAsAdmin string
+ RunWithSudo string
+ SudoExample string
+ PressEnterToExit string
+ SetReadOnlyMessage string
+
+ // Info messages
+ ConfigLocation string
+}
+
+var (
+ currentLanguage Language
+ currentLanguageOnce sync.Once
+ languageMutex sync.RWMutex
+)
+
+// GetCurrentLanguage returns the current language, detecting it if not already set
+func GetCurrentLanguage() Language {
+ currentLanguageOnce.Do(func() {
+ currentLanguage = detectLanguage()
+ })
+
+ languageMutex.RLock()
+ defer languageMutex.RUnlock()
+ return currentLanguage
+}
+
+// SetLanguage sets the current language
+func SetLanguage(lang Language) {
+ languageMutex.Lock()
+ defer languageMutex.Unlock()
+ currentLanguage = lang
+}
+
+// GetText returns the TextResource for the current language
+func GetText() TextResource {
+ return texts[GetCurrentLanguage()]
+}
+
+// detectLanguage detects the system language
+func detectLanguage() Language {
+ // Check environment variables first
+ if isChineseEnvVar() {
+ return CN
+ }
+
+ // Then check OS-specific locale
+ if isWindows() {
+ if isWindowsChineseLocale() {
+ return CN
+ }
+ } else if isUnixChineseLocale() {
+ return CN
+ }
+
+ return EN
+}
+
+func isChineseEnvVar() bool {
+ for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} {
+ if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") {
+ return true
+ }
+ }
+ return false
+}
+
+func isWindows() bool {
+ return os.Getenv("OS") == "Windows_NT"
+}
+
+func isWindowsChineseLocale() bool {
+ // Check Windows UI culture
+ cmd := exec.Command("powershell", "-Command",
+ "[System.Globalization.CultureInfo]::CurrentUICulture.Name")
+ output, err := cmd.Output()
+ if err == nil && strings.HasPrefix(strings.ToLower(strings.TrimSpace(string(output))), "zh") {
+ return true
+ }
+
+ // Check Windows locale
+ cmd = exec.Command("wmic", "os", "get", "locale")
+ output, err = cmd.Output()
+ return err == nil && strings.Contains(string(output), "2052")
+}
+
+func isUnixChineseLocale() bool {
+ cmd := exec.Command("locale")
+ output, err := cmd.Output()
+ return err == nil && strings.Contains(strings.ToLower(string(output)), "zh_cn")
+}
+
+// texts contains all translations
+var texts = map[Language]TextResource{
+ CN: {
+ // Success messages
+ SuccessMessage: "[√] 配置文件已成功更新!",
+ RestartMessage: "[!] 请手动重启 Cursor 以使更新生效",
+
+ // Progress messages
+ ReadingConfig: "正在读取配置文件...",
+ GeneratingIds: "正在生成新的标识符...",
+ CheckingProcesses: "正在检查运行中的 Cursor 实例...",
+ ClosingProcesses: "正在关闭 Cursor 实例...",
+ ProcessesClosed: "所有 Cursor 实例已关闭",
+ PleaseWait: "请稍候...",
+
+ // Error messages
+ ErrorPrefix: "程序发生严重错误: %v",
+ PrivilegeError: "\n[!] 错误:需要管理员权限",
+
+ // Instructions
+ RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」",
+ RunWithSudo: "请使用 sudo 命令运行此程序",
+ SudoExample: "示例: sudo %s",
+ PressEnterToExit: "\n按回车键退出程序...",
+ SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题",
+
+ // Info messages
+ ConfigLocation: "配置文件位置:",
+ },
+ EN: {
+ // Success messages
+ SuccessMessage: "[√] Configuration file updated successfully!",
+ RestartMessage: "[!] Please restart Cursor manually for changes to take effect",
+
+ // Progress messages
+ ReadingConfig: "Reading configuration file...",
+ GeneratingIds: "Generating new identifiers...",
+ CheckingProcesses: "Checking for running Cursor instances...",
+ ClosingProcesses: "Closing Cursor instances...",
+ ProcessesClosed: "All Cursor instances have been closed",
+ PleaseWait: "Please wait...",
+
+ // Error messages
+ ErrorPrefix: "Program encountered a serious error: %v",
+ PrivilegeError: "\n[!] Error: Administrator privileges required",
+
+ // Instructions
+ RunAsAdmin: "Please right-click and select 'Run as Administrator'",
+ RunWithSudo: "Please run this program with sudo",
+ SudoExample: "Example: sudo %s",
+ PressEnterToExit: "\nPress Enter to exit...",
+ SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records",
+
+ // Info messages
+ ConfigLocation: "Config file location:",
+ },
+}
diff --git a/internal/process/manager.go b/internal/process/manager.go
new file mode 100644
index 0000000..f48ac20
--- /dev/null
+++ b/internal/process/manager.go
@@ -0,0 +1,216 @@
+package process
+
+import (
+ "fmt"
+ "os/exec"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/sirupsen/logrus"
+)
+
+// Config holds process manager configuration
+type Config struct {
+ MaxAttempts int // Maximum number of attempts to kill processes
+ RetryDelay time.Duration // Delay between retry attempts
+ ProcessPatterns []string // Process names to look for
+}
+
+// DefaultConfig returns the default configuration
+func DefaultConfig() *Config {
+ return &Config{
+ MaxAttempts: 3,
+ RetryDelay: 2 * time.Second,
+ ProcessPatterns: []string{
+ "Cursor.exe", // Windows executable
+ "Cursor ", // Linux/macOS executable with space
+ "cursor ", // Linux/macOS executable lowercase with space
+ "cursor", // Linux/macOS executable lowercase
+ "Cursor", // Linux/macOS executable
+ "*cursor*", // Any process containing cursor
+ "*Cursor*", // Any process containing Cursor
+ },
+ }
+}
+
+// Manager handles process-related operations
+type Manager struct {
+ config *Config
+ log *logrus.Logger
+}
+
+// NewManager creates a new process manager with optional config and logger
+func NewManager(config *Config, log *logrus.Logger) *Manager {
+ if config == nil {
+ config = DefaultConfig()
+ }
+ if log == nil {
+ log = logrus.New()
+ }
+ return &Manager{
+ config: config,
+ log: log,
+ }
+}
+
+// IsCursorRunning checks if any Cursor process is currently running
+func (m *Manager) IsCursorRunning() bool {
+ processes, err := m.getCursorProcesses()
+ if err != nil {
+ m.log.Warn("Failed to get Cursor processes:", err)
+ return false
+ }
+ return len(processes) > 0
+}
+
+// KillCursorProcesses attempts to kill all running Cursor processes
+func (m *Manager) KillCursorProcesses() error {
+ for attempt := 1; attempt <= m.config.MaxAttempts; attempt++ {
+ processes, err := m.getCursorProcesses()
+ if err != nil {
+ return fmt.Errorf("failed to get processes: %w", err)
+ }
+
+ if len(processes) == 0 {
+ return nil
+ }
+
+ // Try graceful shutdown first on Windows
+ if runtime.GOOS == "windows" {
+ for _, pid := range processes {
+ exec.Command("taskkill", "/PID", pid).Run()
+ time.Sleep(500 * time.Millisecond)
+ }
+ }
+
+ // Force kill remaining processes
+ remainingProcesses, _ := m.getCursorProcesses()
+ for _, pid := range remainingProcesses {
+ m.killProcess(pid)
+ }
+
+ time.Sleep(m.config.RetryDelay)
+
+ if processes, _ := m.getCursorProcesses(); len(processes) == 0 {
+ return nil
+ }
+ }
+
+ return nil
+}
+
+// getCursorProcesses returns PIDs of running Cursor processes
+func (m *Manager) getCursorProcesses() ([]string, error) {
+ cmd := m.getProcessListCommand()
+ if cmd == nil {
+ return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
+ }
+
+ output, err := cmd.Output()
+ if err != nil {
+ return nil, fmt.Errorf("failed to execute command: %w", err)
+ }
+
+ return m.parseProcessList(string(output)), nil
+}
+
+// getProcessListCommand returns the appropriate command to list processes based on OS
+func (m *Manager) getProcessListCommand() *exec.Cmd {
+ switch runtime.GOOS {
+ case "windows":
+ return exec.Command("tasklist", "/FO", "CSV", "/NH")
+ case "darwin":
+ return exec.Command("ps", "-ax")
+ case "linux":
+ return exec.Command("ps", "-A")
+ default:
+ return nil
+ }
+}
+
+// parseProcessList extracts Cursor process PIDs from process list output
+func (m *Manager) parseProcessList(output string) []string {
+ var processes []string
+ for _, line := range strings.Split(output, "\n") {
+ lowerLine := strings.ToLower(line)
+
+ if m.isOwnProcess(lowerLine) {
+ continue
+ }
+
+ if pid := m.findCursorProcess(line, lowerLine); pid != "" {
+ processes = append(processes, pid)
+ }
+ }
+ return processes
+}
+
+// isOwnProcess checks if the process belongs to this application
+func (m *Manager) isOwnProcess(line string) bool {
+ return strings.Contains(line, "cursor-id-modifier") ||
+ strings.Contains(line, "cursor-helper")
+}
+
+// findCursorProcess checks if a process line matches Cursor patterns and returns its PID
+func (m *Manager) findCursorProcess(line, lowerLine string) string {
+ for _, pattern := range m.config.ProcessPatterns {
+ if m.matchPattern(lowerLine, strings.ToLower(pattern)) {
+ return m.extractPID(line)
+ }
+ }
+ return ""
+}
+
+// matchPattern checks if a line matches a pattern, supporting wildcards
+func (m *Manager) matchPattern(line, pattern string) bool {
+ switch {
+ case strings.HasPrefix(pattern, "*") && strings.HasSuffix(pattern, "*"):
+ search := pattern[1 : len(pattern)-1]
+ return strings.Contains(line, search)
+ case strings.HasPrefix(pattern, "*"):
+ return strings.HasSuffix(line, pattern[1:])
+ case strings.HasSuffix(pattern, "*"):
+ return strings.HasPrefix(line, pattern[:len(pattern)-1])
+ default:
+ return line == pattern
+ }
+}
+
+// extractPID extracts process ID from a process list line based on OS format
+func (m *Manager) extractPID(line string) string {
+ switch runtime.GOOS {
+ case "windows":
+ parts := strings.Split(line, ",")
+ if len(parts) >= 2 {
+ return strings.Trim(parts[1], "\"")
+ }
+ case "darwin", "linux":
+ parts := strings.Fields(line)
+ if len(parts) >= 1 {
+ return parts[0]
+ }
+ }
+ return ""
+}
+
+// killProcess forcefully terminates a process by PID
+func (m *Manager) killProcess(pid string) error {
+ cmd := m.getKillCommand(pid)
+ if cmd == nil {
+ return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
+ }
+ return cmd.Run()
+}
+
+// getKillCommand returns the appropriate command to kill a process based on OS
+func (m *Manager) getKillCommand(pid string) *exec.Cmd {
+ switch runtime.GOOS {
+ case "windows":
+ return exec.Command("taskkill", "/F", "/PID", pid)
+ case "darwin", "linux":
+ return exec.Command("kill", "-9", pid)
+ default:
+ return nil
+ }
+}
diff --git a/internal/ui/display.go b/internal/ui/display.go
new file mode 100644
index 0000000..32ed566
--- /dev/null
+++ b/internal/ui/display.go
@@ -0,0 +1,94 @@
+package ui
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "runtime"
+ "strings"
+
+ "github.com/fatih/color"
+)
+
+// Display handles UI operations for terminal output
+type Display struct {
+ spinner *Spinner
+}
+
+// NewDisplay creates a new display instance with an optional spinner
+func NewDisplay(spinner *Spinner) *Display {
+ if spinner == nil {
+ spinner = NewSpinner(nil)
+ }
+ return &Display{spinner: spinner}
+}
+
+// Terminal Operations
+
+// ClearScreen clears the terminal screen based on OS
+func (d *Display) ClearScreen() error {
+ var cmd *exec.Cmd
+ switch runtime.GOOS {
+ case "windows":
+ cmd = exec.Command("cmd", "/c", "cls")
+ default:
+ cmd = exec.Command("clear")
+ }
+ cmd.Stdout = os.Stdout
+ return cmd.Run()
+}
+
+// Progress Indicator
+
+// ShowProgress displays a progress message with a spinner
+func (d *Display) ShowProgress(message string) {
+ d.spinner.SetMessage(message)
+ d.spinner.Start()
+}
+
+// StopProgress stops the progress spinner
+func (d *Display) StopProgress() {
+ d.spinner.Stop()
+}
+
+// Message Display
+
+// ShowSuccess displays success messages in green
+func (d *Display) ShowSuccess(messages ...string) {
+ green := color.New(color.FgGreen)
+ for _, msg := range messages {
+ green.Println(msg)
+ }
+}
+
+// ShowInfo displays an info message in cyan
+func (d *Display) ShowInfo(message string) {
+ cyan := color.New(color.FgCyan)
+ cyan.Println(message)
+}
+
+// ShowError displays an error message in red
+func (d *Display) ShowError(message string) {
+ red := color.New(color.FgRed)
+ red.Println(message)
+}
+
+// ShowPrivilegeError displays privilege error messages with instructions
+func (d *Display) ShowPrivilegeError(messages ...string) {
+ red := color.New(color.FgRed, color.Bold)
+ yellow := color.New(color.FgYellow)
+
+ // Main error message
+ red.Println(messages[0])
+ fmt.Println()
+
+ // Additional instructions
+ for _, msg := range messages[1:] {
+ if strings.Contains(msg, "%s") {
+ exe, _ := os.Executable()
+ yellow.Printf(msg+"\n", exe)
+ } else {
+ yellow.Println(msg)
+ }
+ }
+}
diff --git a/internal/ui/logo.go b/internal/ui/logo.go
new file mode 100644
index 0000000..564525a
--- /dev/null
+++ b/internal/ui/logo.go
@@ -0,0 +1,20 @@
+package ui
+
+import (
+ "github.com/fatih/color"
+)
+
+const cyberpunkLogo = `
+ ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
+ ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
+ ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
+ ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
+ ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
+ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
+`
+
+// ShowLogo displays the application logo
+func (d *Display) ShowLogo() {
+ cyan := color.New(color.FgCyan, color.Bold)
+ cyan.Println(cyberpunkLogo)
+}
diff --git a/internal/ui/spinner.go b/internal/ui/spinner.go
new file mode 100644
index 0000000..145f730
--- /dev/null
+++ b/internal/ui/spinner.go
@@ -0,0 +1,122 @@
+package ui
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/fatih/color"
+)
+
+// SpinnerConfig defines spinner configuration
+type SpinnerConfig struct {
+ Frames []string // Animation frames for the spinner
+ Delay time.Duration // Delay between frame updates
+}
+
+// DefaultSpinnerConfig returns the default spinner configuration
+func DefaultSpinnerConfig() *SpinnerConfig {
+ return &SpinnerConfig{
+ Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
+ Delay: 100 * time.Millisecond,
+ }
+}
+
+// Spinner represents a progress spinner
+type Spinner struct {
+ config *SpinnerConfig
+ message string
+ current int
+ active bool
+ stopCh chan struct{}
+ mu sync.RWMutex
+}
+
+// NewSpinner creates a new spinner with the given configuration
+func NewSpinner(config *SpinnerConfig) *Spinner {
+ if config == nil {
+ config = DefaultSpinnerConfig()
+ }
+ return &Spinner{
+ config: config,
+ stopCh: make(chan struct{}),
+ }
+}
+
+// State management
+
+// SetMessage sets the spinner message
+func (s *Spinner) SetMessage(message string) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.message = message
+}
+
+// IsActive returns whether the spinner is currently active
+func (s *Spinner) IsActive() bool {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.active
+}
+
+// Control methods
+
+// Start begins the spinner animation
+func (s *Spinner) Start() {
+ s.mu.Lock()
+ if s.active {
+ s.mu.Unlock()
+ return
+ }
+ s.active = true
+ s.mu.Unlock()
+
+ go s.run()
+}
+
+// Stop halts the spinner animation
+func (s *Spinner) Stop() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ if !s.active {
+ return
+ }
+
+ s.active = false
+ close(s.stopCh)
+ s.stopCh = make(chan struct{})
+ fmt.Print("\r") // Clear the spinner line
+}
+
+// Internal methods
+
+func (s *Spinner) run() {
+ ticker := time.NewTicker(s.config.Delay)
+ defer ticker.Stop()
+
+ cyan := color.New(color.FgCyan, color.Bold)
+ message := s.message
+
+ // Print initial state
+ fmt.Printf("\r %s %s", cyan.Sprint(s.config.Frames[0]), message)
+
+ for {
+ select {
+ case <-s.stopCh:
+ return
+ case <-ticker.C:
+ s.mu.RLock()
+ if !s.active {
+ s.mu.RUnlock()
+ return
+ }
+ frame := s.config.Frames[s.current%len(s.config.Frames)]
+ s.current++
+ s.mu.RUnlock()
+
+ fmt.Printf("\r %s", cyan.Sprint(frame))
+ fmt.Printf("\033[%dG%s", 4, message) // Move cursor and print message
+ }
+ }
+}
diff --git a/main.go b/main.go
deleted file mode 100644
index 88520cc..0000000
--- a/main.go
+++ /dev/null
@@ -1,1050 +0,0 @@
-package main
-
-// Core imports / 核心导入
-import (
- "bufio"
- "context"
- cryptorand "crypto/rand"
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "errors"
- "flag"
- "fmt"
- "log"
- "math/rand"
- "os"
- "os/exec"
- "os/user"
- "path/filepath"
- "runtime"
- "runtime/debug"
- "strings"
- "time"
-
- "github.com/fatih/color"
-)
-
-// Version information
-var version = "dev" // This will be overwritten by goreleaser
-
-// Types and Constants / 类型和常量
-type Language string
-
-const (
- // Language options / 语言选项
- CN Language = "cn"
- EN Language = "en"
-
- // Error types / 错误类型
- ErrPermission = "permission_error"
- ErrConfig = "config_error"
- ErrProcess = "process_error"
- ErrSystem = "system_error"
-)
-
-// Configuration Structures / 配置结构
-type (
- // TextResource stores multilingual text / 存储多语言文本
- 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
- }
-
- // StorageConfig optimized storage configuration / 优化的存储配置
- StorageConfig struct {
- TelemetryMacMachineId string `json:"telemetry.macMachineId"`
- TelemetryMachineId string `json:"telemetry.machineId"`
- TelemetryDevDeviceId string `json:"telemetry.devDeviceId"`
- TelemetrySqmId string `json:"telemetry.sqmId"`
- }
- // AppError defines error types / 定义错误类型
- AppError struct {
- Type string
- Op string
- Path string
- Err error
- Context map[string]interface{}
- }
-
- // Config structures / 配置结构
- Config struct {
- Storage StorageConfig
- UI UIConfig
- System SystemConfig
- }
-
- UIConfig struct {
- Language Language
- Theme string
- Spinner SpinnerConfig
- }
-
- SystemConfig struct {
- RetryAttempts int
- RetryDelay time.Duration
- Timeout time.Duration
- }
-
- // SpinnerConfig defines spinner configuration / 定义进度条配置
- SpinnerConfig struct {
- Frames []string
- Delay time.Duration
- }
-
- // ProgressSpinner for showing progress animation / 用于显示进度动画
- ProgressSpinner struct {
- frames []string
- current int
- message string
- }
-)
-
-// Global Variables / 全局变量
-var (
- currentLanguage = CN // Default to Chinese / 默认为中文
-
- 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",
- },
- }
-)
-
-var setReadOnly *bool = flag.Bool("r", false, "set storage.json to read-only mode")
-
-// Error Implementation / 错误实现
-func (e *AppError) Error() string {
- if e.Context != nil {
- return fmt.Sprintf("[%s] %s: %v (context: %v)", e.Type, e.Op, e.Err, e.Context)
- }
- return fmt.Sprintf("[%s] %s: %v", e.Type, e.Op, e.Err)
-}
-
-// Configuration Functions / 配置函数
-func NewStorageConfig(oldConfig *StorageConfig) *StorageConfig {
- // Use different ID generation functions for different fields
- // 为不同字段用不同的ID生成函数
- // Reason: machineId needs new format while others keep old format
- // 原因:machineId需要使用新格式,而其他ID保持旧格式
- newConfig := &StorageConfig{
- TelemetryMacMachineId: generateMacMachineId(), // Use old format / 使用旧格式
- TelemetryMachineId: generateMachineId(), // Use new format / 使用新格式
- TelemetryDevDeviceId: generateDevDeviceId(),
- }
-
- // Keep sqmId from old config or generate new one using old format
- // 保留旧配置的sqmId或使用旧格式生成新的
- if oldConfig != nil {
- newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId
- } else {
- newConfig.TelemetrySqmId = generateMacMachineId()
- }
-
- if newConfig.TelemetrySqmId == "" {
- newConfig.TelemetrySqmId = generateMacMachineId()
- }
-
- return newConfig
-}
-
-func generateMachineId() string {
- // 基础结构:auth0|user_XX[unique_id]
- prefix := "auth0|user_"
-
- // 生成两位数字序列 (00-99)
- sequence := fmt.Sprintf("%02d", rand.Intn(100))
-
- // 生成唯一标识部分 (23字符)
- uniqueId := generateUniqueId(23)
-
- // 组合完整ID
- fullId := prefix + sequence + uniqueId
-
- // 转换为十六进制
- return hex.EncodeToString([]byte(fullId))
-}
-
-func generateUniqueId(length int) string {
- // 字符集:使用类似 Crockford's Base32 的字符集
- const charset = "0123456789ABCDEFGHJKLMNPQRSTVWXYZ"
-
- // 生成随机字符串
- b := make([]byte, length)
- for i := range b {
- b[i] = charset[rand.Intn(len(charset))]
- }
- return string(b)
-}
-
-func generateMacMachineId() string {
- data := make([]byte, 32)
- if _, err := cryptorand.Read(data); err != nil {
- panic(fmt.Errorf("failed to generate random data: %v", err))
- }
- hash := sha256.Sum256(data)
- return hex.EncodeToString(hash[:])
-}
-
-func generateDevDeviceId() string {
- uuid := make([]byte, 16)
- if _, err := cryptorand.Read(uuid); err != nil {
- panic(fmt.Errorf("failed to generate UUID: %v", err))
- }
- uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
- uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
- return fmt.Sprintf("%x-%x-%x-%x-%x",
- uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16])
-}
-
-// File Operations / 文件操作
-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
-}
-
-func saveConfig(config *StorageConfig, username string) error {
- configPath, err := getConfigPath(username)
- if err != nil {
- return err
- }
-
- // Create parent directories with proper permissions
- dir := filepath.Dir(configPath)
- if err := os.MkdirAll(dir, 0755); err != nil {
- return &AppError{
- Type: ErrSystem,
- Op: "create directory",
- Path: dir,
- Err: err,
- }
- }
-
- // First ensure we can write to the file
- if err := os.Chmod(configPath, 0666); err != nil && !os.IsNotExist(err) {
- return &AppError{
- Type: ErrSystem,
- Op: "modify file permissions",
- Path: configPath,
- Err: err,
- }
- }
-
- // Read the original file to preserve all fields
- var originalFile map[string]interface{}
- originalFileContent, err := os.ReadFile(configPath)
- if err != nil {
- if !os.IsNotExist(err) {
- return &AppError{
- Type: ErrSystem,
- Op: "read original file",
- Path: configPath,
- Err: err,
- }
- }
- // If file doesn't exist, create a new map
- originalFile = make(map[string]interface{})
- } else {
- if err := json.Unmarshal(originalFileContent, &originalFile); err != nil {
- return &AppError{
- Type: ErrSystem,
- Op: "unmarshal original file",
- Path: configPath,
- Err: err,
- }
- }
- }
-
- // Get original file mode
- var originalFileMode os.FileMode = 0666
- if stat, err := os.Stat(configPath); err == nil {
- originalFileMode = stat.Mode()
- }
-
- // Update only the telemetry fields while preserving all other fields
- originalFile["telemetry.sqmId"] = config.TelemetrySqmId
- originalFile["telemetry.macMachineId"] = config.TelemetryMacMachineId
- originalFile["telemetry.machineId"] = config.TelemetryMachineId
- originalFile["telemetry.devDeviceId"] = config.TelemetryDevDeviceId
-
- // Add lastModified and version fields if they don't exist
- if _, exists := originalFile["lastModified"]; !exists {
- originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339)
- }
- if _, exists := originalFile["version"]; !exists {
- originalFile["version"] = "1.0.1"
- }
-
- // Marshal with indentation
- newFileContent, err := json.MarshalIndent(originalFile, "", " ")
- if err != nil {
- return &AppError{
- Type: ErrSystem,
- Op: "marshal new file",
- Path: configPath,
- Err: err,
- }
- }
-
- // Write to temporary file first
- tmpPath := configPath + ".tmp"
- if err := os.WriteFile(tmpPath, newFileContent, 0666); err != nil {
- return &AppError{
- Type: ErrSystem,
- Op: "write temporary file",
- Path: tmpPath,
- Err: err,
- }
- }
-
- if *setReadOnly {
- originalFileMode = 0444
- }
-
- // Ensure proper permissions on temporary file
- if err := os.Chmod(tmpPath, originalFileMode); err != nil {
- os.Remove(tmpPath)
- return &AppError{
- Type: ErrSystem,
- Op: "set temporary file permissions",
- Path: tmpPath,
- Err: err,
- }
- }
-
- // Atomic rename
- if err := os.Rename(tmpPath, configPath); err != nil {
- os.Remove(tmpPath)
- return &AppError{
- Type: ErrSystem,
- Op: "rename file",
- Path: configPath,
- Err: err,
- }
- }
-
- // Sync the directory to ensure changes are written to disk
- if dir, err := os.Open(filepath.Dir(configPath)); err == nil {
- dir.Sync()
- dir.Close()
- }
-
- return nil
-}
-
-func readExistingConfig(username string) (*StorageConfig, error) { // Modified to take username
- configPath, err := getConfigPath(username)
- if err != nil {
- return nil, err
- }
-
- data, err := os.ReadFile(configPath)
- if err != nil {
- if os.IsNotExist(err) {
- return nil, nil
- }
- return nil, err
- }
-
- var config StorageConfig
- if err := json.Unmarshal(data, &config); err != nil {
- return nil, err
- }
-
- return &config, nil
-}
-
-// Process Management / 进程管理
-type ProcessManager struct {
- config *SystemConfig
-}
-
-func (pm *ProcessManager) killCursorProcesses() error {
- ctx, cancel := context.WithTimeout(context.Background(), pm.config.Timeout)
- defer cancel()
-
- for attempt := 0; attempt < pm.config.RetryAttempts; attempt++ {
- if err := pm.killProcess(ctx); err != nil {
- time.Sleep(pm.config.RetryDelay)
- continue
- }
- return nil
- }
-
- return &AppError{
- Type: ErrProcess,
- Op: "kill_processes",
- Err: errors.New("failed to kill all Cursor processes after retries"),
- }
-}
-
-func (pm *ProcessManager) killProcess(ctx context.Context) error {
- if runtime.GOOS == "windows" {
- return pm.killWindowsProcess(ctx)
- }
- return pm.killUnixProcess(ctx)
-}
-
-func (pm *ProcessManager) killWindowsProcess(ctx context.Context) error {
- exec.CommandContext(ctx, "taskkill", "/IM", "Cursor.exe").Run()
- time.Sleep(pm.config.RetryDelay)
- exec.CommandContext(ctx, "taskkill", "/F", "/IM", "Cursor.exe").Run()
- return nil
-}
-
-func (pm *ProcessManager) killUnixProcess(ctx context.Context) error {
- // Search for the process by it's executable name (AppRun) in ps output
- cmd := exec.CommandContext(ctx, "ps", "aux")
- output, err := cmd.Output()
- if err != nil {
- return fmt.Errorf("failed to execute ps command: %w", err)
- }
-
- lines := strings.Split(string(output), "\n")
- for _, line := range lines {
- if strings.Contains(line, "AppRun") {
- parts := strings.Fields(line)
- if len(parts) > 1 {
- pid := parts[1]
- if err := pm.forceKillProcess(ctx, pid); err != nil {
- return err
- }
- }
- }
-
- // handle lowercase
- if strings.Contains(line, "apprun") {
- parts := strings.Fields(line)
- if len(parts) > 1 {
- pid := parts[1]
- if err := pm.forceKillProcess(ctx, pid); err != nil {
- return err
- }
- }
- }
- }
-
- return nil
-}
-
-// helper function to kill process by pid
-func (pm *ProcessManager) forceKillProcess(ctx context.Context, pid string) error {
- // First try graceful termination
- if err := exec.CommandContext(ctx, "kill", pid).Run(); err == nil {
- // Wait for processes to terminate gracefully
- time.Sleep(2 * time.Second)
- }
-
- // 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)
- }
-
- return nil
-}
-
-func checkCursorRunning() bool {
- cmd := exec.Command("ps", "aux")
- output, err := cmd.Output()
- if err != nil {
- return false
- }
-
- lines := strings.Split(string(output), "\n")
- for _, line := range lines {
- if strings.Contains(line, "AppRun") || strings.Contains(line, "apprun") {
- return true
- }
- }
-
- return false
-}
-
-// UI Components / UI组件
-type UI struct {
- config *UIConfig
- spinner *ProgressSpinner
-}
-
-func NewUI(config *UIConfig) *UI {
- return &UI{
- config: config,
- spinner: NewProgressSpinner(""),
- }
-}
-
-func (ui *UI) showProgress(message string) {
- ui.spinner.message = message
- ui.spinner.Start()
- defer ui.spinner.Stop()
-
- ticker := time.NewTicker(ui.config.Spinner.Delay)
- defer ticker.Stop()
-
- for i := 0; i < 15; i++ {
- <-ticker.C
- ui.spinner.Spin()
- }
-}
-
-// Display Functions / 显示函数
-func showSuccess() {
- text := texts[currentLanguage]
- successColor := color.New(color.FgGreen, color.Bold)
- warningColor := color.New(color.FgYellow, color.Bold)
- pathColor := color.New(color.FgCyan)
-
- // Clear any previous output
- fmt.Println()
-
- if currentLanguage == EN {
- // English messages with extra spacing
- successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
- successColor.Printf("%s\n", text.SuccessMessage)
- fmt.Println()
- warningColor.Printf("%s\n", text.RestartMessage)
- successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
- } else {
- // Chinese messages with extra spacing
- successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
- successColor.Printf("%s\n", text.SuccessMessage)
- fmt.Println()
- warningColor.Printf("%s\n", text.RestartMessage)
- successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
- }
-
- // Add spacing before config location
- fmt.Println()
-
- username := os.Getenv("SUDO_USER")
- if username == "" {
- user, err := user.Current()
- if err != nil {
- panic(err)
- }
- username = user.Username
- }
- if configPath, err := getConfigPath(username); err == nil {
- pathColor.Printf("%s\n%s\n", text.ConfigLocation, configPath)
- }
-}
-
-func showReadOnlyMessage() {
- if *setReadOnly {
- warningColor := color.New(color.FgYellow, color.Bold)
- warningColor.Printf("%s\n", texts[currentLanguage].SetReadOnlyMessage)
- fmt.Println("Press Enter to continue...")
- bufio.NewReader(os.Stdin).ReadString('\n')
- }
-}
-
-func showPrivilegeError() {
- text := texts[currentLanguage]
- red := color.New(color.FgRed, color.Bold)
- yellow := color.New(color.FgYellow)
-
- if currentLanguage == EN {
- red.Println(text.PrivilegeError)
- if runtime.GOOS == "windows" {
- yellow.Println(text.RunAsAdmin)
- } else {
- yellow.Printf("%s\n%s\n", text.RunWithSudo, fmt.Sprintf(text.SudoExample, os.Args[0]))
- }
- } else {
- red.Printf("\n%s\n", text.PrivilegeError)
- if runtime.GOOS == "windows" {
- yellow.Printf("%s\n", text.RunAsAdmin)
- } else {
- yellow.Printf("%s\n%s\n", text.RunWithSudo, fmt.Sprintf(text.SudoExample, os.Args[0]))
- }
- }
-}
-
-// System Functions / 系统函数
-func checkAdminPrivileges() (bool, error) {
- switch runtime.GOOS {
- case "windows":
- // 使用更可靠的方法检查Windows管理员权限
- cmd := exec.Command("net", "session")
- err := cmd.Run()
- if err == nil {
- return true, nil
- }
- // 如果命令执行失败,说明没有管理员权限
- return false, nil
-
- case "darwin", "linux":
- currentUser, err := user.Current()
- if err != nil {
- return false, fmt.Errorf("failed to get current user: %v", err)
- }
- return currentUser.Uid == "0", nil
-
- default:
- return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
- }
-}
-
-func detectLanguage() Language {
- // Check common environment variables
- for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} {
- if lang := os.Getenv(envVar); lang != "" {
- if strings.Contains(strings.ToLower(lang), "zh") {
- return CN
- }
- }
- }
-
- // Windows-specific language check
- if runtime.GOOS == "windows" {
- cmd := exec.Command("powershell", "-Command",
- "[System.Globalization.CultureInfo]::CurrentUICulture.Name")
- output, err := cmd.Output()
- if err == nil {
- lang := strings.ToLower(strings.TrimSpace(string(output)))
- if strings.HasPrefix(lang, "zh") {
- return CN
- }
- }
-
- // Check Windows locale
- cmd = exec.Command("wmic", "os", "get", "locale")
- output, err = cmd.Output()
- if err == nil && strings.Contains(string(output), "2052") {
- return CN
- }
- }
-
- // Check Unix locale
- if runtime.GOOS != "windows" {
- cmd := exec.Command("locale")
- output, err := cmd.Output()
- if err == nil && strings.Contains(strings.ToLower(string(output)), "zh_cn") {
- return CN
- }
- }
-
- return EN
-}
-
-func selfElevate() error {
- switch runtime.GOOS {
- case "windows":
- // Set automated mode for the elevated process
- os.Setenv("AUTOMATED_MODE", "1")
-
- verb := "runas"
- exe, _ := os.Executable()
- cwd, _ := os.Getwd()
- args := strings.Join(os.Args[1:], " ")
-
- cmd := exec.Command("cmd", "/C", "start", verb, exe, args)
- cmd.Dir = cwd
- return cmd.Run()
-
- case "darwin", "linux":
- // Set automated mode for the elevated process
- os.Setenv("AUTOMATED_MODE", "1")
-
- exe, err := os.Executable()
- if err != nil {
- return err
- }
-
- cmd := exec.Command("sudo", append([]string{exe}, os.Args[1:]...)...)
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- return cmd.Run()
-
- default:
- return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
- }
-}
-
-// Utility Functions / 实用函数
-func handleError(err error) {
- if err == nil {
- return
- }
-
- logger := log.New(os.Stderr, "", log.LstdFlags)
-
- switch e := err.(type) {
- case *AppError:
- logger.Printf("[ERROR] %v\n", e)
- if e.Type == ErrPermission {
- showPrivilegeError()
- }
- default:
- logger.Printf("[ERROR] Unexpected error: %v\n", err)
- }
-}
-
-func waitExit() {
- // Skip waiting in automated mode
- if os.Getenv("AUTOMATED_MODE") == "1" {
- return
- }
-
- if currentLanguage == EN {
- fmt.Println("\nPress Enter to exit...")
- } else {
- fmt.Println("\n按��车键退出程序...")
- }
- os.Stdout.Sync()
- bufio.NewReader(os.Stdin).ReadString('\n')
-}
-
-// Add this new function near the other process management functions
-func ensureCursorClosed() error {
- maxAttempts := 3
- text := texts[currentLanguage]
-
- showProcessStatus(text.CheckingProcesses)
-
- for attempt := 1; attempt <= maxAttempts; attempt++ {
- if !checkCursorRunning() {
- showProcessStatus(text.ProcessesClosed)
- fmt.Println() // New line after status
- return nil
- }
-
- if currentLanguage == EN {
- showProcessStatus(fmt.Sprintf("Please close Cursor before continuing. Attempt %d/%d\n%s",
- attempt, maxAttempts, text.PleaseWait))
- } else {
- showProcessStatus(fmt.Sprintf("请在继续之前关闭 Cursor。尝试 %d/%d\n%s",
- attempt, maxAttempts, text.PleaseWait))
- }
-
- time.Sleep(5 * time.Second)
- }
-
- return errors.New("cursor is still running")
-}
-
-func main() {
- // Initialize error recovery
- defer func() {
- if r := recover(); r != nil {
- log.Printf("Panic recovered: %v\n", r)
- debug.PrintStack()
- waitExit()
- }
- }()
-
- flag.Parse()
- showReadOnlyMessage()
-
- var username string
- if username = os.Getenv("SUDO_USER"); username == "" {
- user, err := user.Current()
- if err != nil {
- panic(err)
- }
- username = user.Username
- }
- log.Println("Current user: ", username)
-
- // Initialize configuration
- ui := NewUI(&UIConfig{
- Language: detectLanguage(),
- Theme: "default",
- Spinner: SpinnerConfig{
- Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
- Delay: 100 * time.Millisecond,
- },
- })
-
- // Check privileges
- os.Stdout.Sync()
- currentLanguage = detectLanguage()
- log.Println("Current language: ", currentLanguage)
- isAdmin, err := checkAdminPrivileges()
- if err != nil {
- handleError(err)
- waitExit()
- return
- }
-
- if !isAdmin && runtime.GOOS == "windows" {
- if currentLanguage == EN {
- fmt.Println("\nRequesting administrator privileges...")
- } else {
- fmt.Println("\n请求管理员权限...")
- }
- if err := selfElevate(); err != nil {
- handleError(err)
- showPrivilegeError()
- waitExit()
- return
- }
- return
- } else if !isAdmin {
- showPrivilegeError()
- waitExit()
- return
- }
-
- // Ensure all Cursor instances are closed
- if err := ensureCursorClosed(); err != nil {
- if currentLanguage == EN {
- fmt.Println("\nError: Please close Cursor manually before running this program.")
- } else {
- fmt.Println("\n错误:请在运���此程序之前手动关闭 Cursor。")
- }
- waitExit()
- return
- }
-
- // Process management
- pm := &ProcessManager{
- config: &SystemConfig{
- RetryAttempts: 3,
- RetryDelay: time.Second,
- Timeout: 30 * time.Second,
- },
- }
- if checkCursorRunning() {
- text := texts[currentLanguage]
- showProcessStatus(text.ClosingProcesses)
-
- if err := pm.killCursorProcesses(); err != nil {
- fmt.Println() // New line after status
- if currentLanguage == EN {
- fmt.Println("Warning: Could not close all Cursor instances. Please close them manually.")
- } else {
- fmt.Println("警告:无法关闭所有 Cursor 实例,请手动关闭。")
- }
- waitExit()
- return
- }
-
- time.Sleep(2 * time.Second)
- if checkCursorRunning() {
- fmt.Println() // New line after status
- if currentLanguage == EN {
- fmt.Println("\nWarning: Cursor is still running. Please close it manually.")
- } else {
- fmt.Println("\n警告:Cursor 仍在运行,请手动关闭。")
- }
- waitExit()
- return
- }
-
- showProcessStatus(text.ProcessesClosed)
- fmt.Println() // New line after status
- }
-
- // Clear screen and show banner
- clearScreen()
- printCyberpunkBanner()
-
- // Read and update configuration
- oldConfig, err := readExistingConfig(username) // add username parameter
- if err != nil {
- oldConfig = nil
- }
-
- storageConfig, err := loadAndUpdateConfig(ui, username) // add username parameter
- if err != nil {
- handleError(err)
- waitExit()
- return
- }
-
- // Show changes and save
- showIdComparison(oldConfig, storageConfig)
-
- if err := saveConfig(storageConfig, username); err != nil { // add username parameter
- handleError(err)
- waitExit()
- return
- }
-
- // Show success and exit
- showSuccess()
- if currentLanguage == EN {
- fmt.Println("\nOperation completed!")
- } else {
- fmt.Println("\n操作完成!")
- }
-
- // Check if running in automated mode
- if os.Getenv("AUTOMATED_MODE") == "1" {
- return
- }
-
- waitExit()
-}
-
-// Progress spinner functions / 进度条函数
-func NewProgressSpinner(message string) *ProgressSpinner {
- return &ProgressSpinner{
- frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
- message: message,
- }
-}
-
-func (s *ProgressSpinner) Spin() {
- frame := s.frames[s.current%len(s.frames)]
- s.current++
- fmt.Printf("\r%s %s", color.CyanString(frame), s.message)
-}
-
-func (s *ProgressSpinner) Stop() {
- fmt.Println()
-}
-
-func (s *ProgressSpinner) Start() {
- s.current = 0
-}
-
-// Display utility functions / 显示工具函数
-func clearScreen() {
- var cmd *exec.Cmd
- if runtime.GOOS == "windows" {
- cmd = exec.Command("cmd", "/c", "cls")
- } else {
- cmd = exec.Command("clear")
- }
- cmd.Stdout = os.Stdout
- cmd.Run()
-}
-
-func printCyberpunkBanner() {
- cyan := color.New(color.FgCyan, color.Bold)
- yellow := color.New(color.FgYellow, color.Bold)
- magenta := color.New(color.FgMagenta, color.Bold)
- green := color.New(color.FgGreen, color.Bold)
-
- banner := `
- ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
- ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
- ██║ ██║ ██║██████╔╝███████╗██║ ██║█████╔╝
- ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
- ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
- ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
- `
- cyan.Println(banner)
- yellow.Printf("\t\t>> Cursor ID Modifier %s <<\n", version)
- magenta.Println("\t\t [ By Pancake Fruit Rolled Shark Chili ]")
-
- langText := "当前语言/Language: "
- if currentLanguage == CN {
- langText += "简体中文"
- } else {
- langText += "English"
- }
- green.Printf("\n\t\t %s\n\n", langText)
-}
-
-func showIdComparison(oldConfig *StorageConfig, newConfig *StorageConfig) {
- cyan := color.New(color.FgCyan)
- yellow := color.New(color.FgYellow)
-
- fmt.Println("\n=== ID Modification Comparison / ID 修改对比 ===")
-
- if oldConfig != nil {
- cyan.Println("\n[Original IDs / 原始 ID]")
- yellow.Printf("Machine ID: %s\n", oldConfig.TelemetryMachineId)
- yellow.Printf("Mac Machine ID: %s\n", oldConfig.TelemetryMacMachineId)
- yellow.Printf("Dev Device ID: %s\n", oldConfig.TelemetryDevDeviceId)
- }
-
- cyan.Println("\n[Newly Generated IDs / 新生成 ID]")
- yellow.Printf("Machine ID: %s\n", newConfig.TelemetryMachineId)
- yellow.Printf("Mac Machine ID: %s\n", newConfig.TelemetryMacMachineId)
- yellow.Printf("Dev Device ID: %s\n", newConfig.TelemetryDevDeviceId)
- yellow.Printf("SQM ID: %s\n", newConfig.TelemetrySqmId)
- fmt.Println()
-}
-
-// Configuration functions / 配置函数
-func loadAndUpdateConfig(ui *UI, username string) (*StorageConfig, error) { // add username parameter
- configPath, err := getConfigPath(username) // add username parameter
- if err != nil {
- return nil, err
- }
-
- text := texts[currentLanguage]
- ui.showProgress(text.ReadingConfig)
-
- oldConfig, err := readExistingConfig(username) // add username parameter
- if err != nil && !os.IsNotExist(err) {
- return nil, &AppError{
- Type: ErrSystem,
- Op: "read config file",
- Path: configPath,
- Err: err,
- }
- }
-
- ui.showProgress(text.GeneratingIds)
- return NewStorageConfig(oldConfig), nil
-}
-
-// Add a new function to show process status
-func showProcessStatus(message string) {
- cyan := color.New(color.FgCyan)
- fmt.Printf("\r%s", strings.Repeat(" ", 80)) // Clear line
- fmt.Printf("\r%s", cyan.Sprint("⚡ "+message))
-}
diff --git a/pkg/idgen/generator.go b/pkg/idgen/generator.go
new file mode 100644
index 0000000..3061f74
--- /dev/null
+++ b/pkg/idgen/generator.go
@@ -0,0 +1,116 @@
+package idgen
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "sync"
+)
+
+// Generator handles secure ID generation for machines and devices
+type Generator struct {
+ bufferPool sync.Pool
+}
+
+// NewGenerator creates a new ID generator
+func NewGenerator() *Generator {
+ return &Generator{
+ bufferPool: sync.Pool{
+ New: func() interface{} {
+ return make([]byte, 64)
+ },
+ },
+ }
+}
+
+// Constants for ID generation
+const (
+ machineIDPrefix = "auth0|user_"
+ uuidFormat = "%s-%s-%s-%s-%s"
+)
+
+// generateRandomHex generates a random hex string of specified length
+func (g *Generator) generateRandomHex(length int) (string, error) {
+ buffer := g.bufferPool.Get().([]byte)
+ defer g.bufferPool.Put(buffer)
+
+ if _, err := rand.Read(buffer[:length]); err != nil {
+ return "", fmt.Errorf("failed to generate random bytes: %w", err)
+ }
+ return hex.EncodeToString(buffer[:length]), nil
+}
+
+// GenerateMachineID generates a new machine ID with auth0|user_ prefix
+func (g *Generator) GenerateMachineID() (string, error) {
+ randomPart, err := g.generateRandomHex(32) // 生成64字符的十六进制
+ if err != nil {
+ return "", err
+ }
+
+ return fmt.Sprintf("%x%s", []byte(machineIDPrefix), randomPart), nil
+}
+
+// GenerateMacMachineID generates a new 64-byte MAC machine ID
+func (g *Generator) GenerateMacMachineID() (string, error) {
+ return g.generateRandomHex(32) // 生成64字符的十六进制
+}
+
+// GenerateDeviceID generates a new device ID in UUID format
+func (g *Generator) GenerateDeviceID() (string, error) {
+ id, err := g.generateRandomHex(16)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf(uuidFormat,
+ id[0:8], id[8:12], id[12:16], id[16:20], id[20:32]), nil
+}
+
+// GenerateSQMID generates a new SQM ID in UUID format (with braces)
+func (g *Generator) GenerateSQMID() (string, error) {
+ id, err := g.GenerateDeviceID()
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("{%s}", id), nil
+}
+
+// ValidateID validates the format of various ID types
+func (g *Generator) ValidateID(id string, idType string) bool {
+ switch idType {
+ case "machineID", "macMachineID":
+ return len(id) == 64 && isHexString(id)
+ case "deviceID":
+ return isValidUUID(id)
+ case "sqmID":
+ if len(id) < 2 || id[0] != '{' || id[len(id)-1] != '}' {
+ return false
+ }
+ return isValidUUID(id[1 : len(id)-1])
+ default:
+ return false
+ }
+}
+
+// Helper functions
+func isHexString(s string) bool {
+ _, err := hex.DecodeString(s)
+ return err == nil
+}
+
+func isValidUUID(uuid string) bool {
+ if len(uuid) != 36 {
+ return false
+ }
+ for i, r := range uuid {
+ if i == 8 || i == 13 || i == 18 || i == 23 {
+ if r != '-' {
+ return false
+ }
+ continue
+ }
+ if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/scripts/build_all.bat b/scripts/build_all.bat
index 0ae443f..e07e387 100644
--- a/scripts/build_all.bat
+++ b/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
\ No newline at end of file
+echo %GREEN%!EN_MESSAGES[7]!%RESET%
\ No newline at end of file
diff --git a/scripts/build_all.sh b/scripts/build_all.sh
old mode 100644
new mode 100755
index d7e38a9..7db5e7b
--- a/scripts/build_all.sh
+++ b/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
diff --git a/scripts/install.ps1 b/scripts/install.ps1
index cd4d5dd..b30350b 100644
--- a/scripts/install.ps1
+++ b/scripts/install.ps1
@@ -1,308 +1,193 @@
-# Auto-elevate to admin rights if not already running as admin
-if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
- Write-Host "Requesting administrator privileges..."
- $arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -ExecutionFromElevated"
- Start-Process powershell.exe -ArgumentList $arguments -Verb RunAs
- Exit
-}
-
-# Set TLS to 1.2 / 设置 TLS 为 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"
-
-# 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:"
-)
-
-$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"
+# Check for admin rights and handle elevation
+$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
+if (-NOT $isAdmin) {
+ # Detect PowerShell version and path
+ $pwshPath = if (Get-Command "pwsh" -ErrorAction SilentlyContinue) {
+ (Get-Command "pwsh").Source # PowerShell 7+
+ } elseif (Test-Path "$env:ProgramFiles\PowerShell\7\pwsh.exe") {
+ "$env:ProgramFiles\PowerShell\7\pwsh.exe"
+ } else {
+ "powershell.exe" # Windows PowerShell
}
- return "en"
-}
-
-# Get message based on language / 根据语言获取消息
-function Get-Message($Index) {
- $lang = Get-SystemLanguage
- if ($lang -eq "cn") {
- return $CN_MESSAGES[$Index]
+
+ try {
+ Write-Host "`nRequesting administrator privileges..." -ForegroundColor Cyan
+ $scriptPath = $MyInvocation.MyCommand.Path
+ $argList = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
+ Start-Process -FilePath $pwshPath -Verb RunAs -ArgumentList $argList -Wait
+ exit
+ }
+ catch {
+ Write-Host "`nError: Administrator privileges required" -ForegroundColor Red
+ Write-Host "Please run this script from an Administrator PowerShell window" -ForegroundColor Yellow
+ Write-Host "`nTo do this:" -ForegroundColor Cyan
+ Write-Host "1. Press Win + X" -ForegroundColor White
+ Write-Host "2. Click 'Windows Terminal (Admin)' or 'PowerShell (Admin)'" -ForegroundColor White
+ Write-Host "3. Run the installation command again" -ForegroundColor White
+ Write-Host "`nPress enter to exit..."
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
+ exit 1
}
- 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"
-}
+# Set TLS to 1.2
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
-function Write-Error($Message) {
- Write-Host "${Red}[✗]${Reset} $Message"
- Exit 1
-}
+# Create temporary directory
+$TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString())
+New-Item -ItemType Directory -Path $TmpDir | Out-Null
-# 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)
- }
+# Cleanup function
+function Cleanup {
+ if (Test-Path $TmpDir) {
+ Remove-Item -Recurse -Force $TmpDir
}
}
-# 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"
- }
+# Error handler
+trap {
+ Write-Host "Error: $_" -ForegroundColor Red
+ Cleanup
+ Write-Host "Press enter to exit..."
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
+ exit 1
}
-# 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
+# Detect system architecture
+function Get-SystemArch {
+ if ([Environment]::Is64BitOperatingSystem) {
+ return "x86_64"
+ } else {
+ return "i386"
+ }
}
-# 在文件开头添加日志函数
-function Write-Log {
- param(
- [string]$Message,
- [string]$Level = "INFO"
+# Download with progress
+function Get-FileWithProgress {
+ 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 "Failed to download: $_" -ForegroundColor Red
+ return $false
}
}
-# 添加安装前检查函数
-function Test-Prerequisites {
- Write-Log "Checking prerequisites..." "INFO"
+# Main installation function
+function Install-CursorModifier {
+ Write-Host "Starting installation..." -ForegroundColor Cyan
- # 检查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 "Detected architecture: $arch" -ForegroundColor Green
+
+ # Set installation directory
+ $InstallDir = "$env:ProgramFiles\CursorModifier"
+ if (!(Test-Path $InstallDir)) {
+ New-Item -ItemType Directory -Path $InstallDir | Out-Null
}
- # 检查网络连接
+ # Get latest release
try {
- $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/yuaotian/go-cursor-help/releases/latest"
+ Write-Host "Found latest release: $($latestRelease.tag_name)" -ForegroundColor Cyan
+
+ # Look for Windows binary with our architecture
+ $version = $latestRelease.tag_name.TrimStart('v')
+ Write-Host "Version: $version" -ForegroundColor Cyan
+ $possibleNames = @(
+ "cursor-id-modifier_${version}_windows_x86_64.exe",
+ "cursor-id-modifier_${version}_windows_$($arch).exe"
+ )
+
+ $asset = $null
+ foreach ($name in $possibleNames) {
+ Write-Host "Checking for asset: $name" -ForegroundColor Cyan
+ $asset = $latestRelease.assets | Where-Object { $_.name -eq $name }
+ if ($asset) {
+ Write-Host "Found matching asset: $($asset.name)" -ForegroundColor Green
+ break
+ }
}
- } catch {
- Write-Log "Failed to check internet connection: $_" "ERROR"
- return $false
+
+ if (!$asset) {
+ Write-Host "`nAvailable assets:" -ForegroundColor Yellow
+ $latestRelease.assets | ForEach-Object { Write-Host "- $($_.name)" }
+ throw "Could not find appropriate Windows binary for $arch architecture"
+ }
+
+ $downloadUrl = $asset.browser_download_url
+ }
+ catch {
+ Write-Host "Failed to get latest release: $_" -ForegroundColor Red
+ exit 1
}
- return $true
-}
-
-# 添加文件验证函数
-function Test-FileHash {
- param(
- [string]$FilePath,
- [string]$ExpectedHash
- )
+ # Download binary
+ Write-Host "`nDownloading latest release..." -ForegroundColor Cyan
+ $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 (!(Get-FileWithProgress -Url $downloadUrl -OutputFile $binaryPath)) {
+ exit 1
+ }
+ # Install binary
+ Write-Host "Installing..." -ForegroundColor Cyan
try {
- $webClient = New-Object System.Net.WebClient
- $webClient.Headers.Add("User-Agent", "PowerShell Script")
-
- $webClient.DownloadFileAsync($Url, $OutFile)
+ Copy-Item -Path $binaryPath -Destination "$InstallDir\cursor-id-modifier.exe" -Force
- 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
+ Write-Host "Failed to install: $_" -ForegroundColor Red
+ exit 1
}
- finally {
- if ($webClient) {
- $webClient.Dispose()
+
+ Write-Host "Installation completed successfully!" -ForegroundColor Green
+ Write-Host "Running cursor-id-modifier..." -ForegroundColor Cyan
+
+ # Run the program
+ try {
+ & "$InstallDir\cursor-id-modifier.exe"
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "Failed to run cursor-id-modifier" -ForegroundColor Red
+ exit 1
}
}
-}
-
-# 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)"
+ catch {
+ Write-Host "Failed to run cursor-id-modifier: $_" -ForegroundColor Red
+ exit 1
}
-} catch {
- Write-Error "$(Get-Message 6) $_"
-}
-
-# Verify download / 验证下载
-if (-not (Test-Path $tempFile)) {
- Write-Error (Get-Message 7)
}
-# 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) $_"
-}
-
-# 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: $_"
+ Install-CursorModifier
+}
+catch {
+ Write-Host "Installation failed: $_" -ForegroundColor Red
+ Cleanup
+ Write-Host "Press enter to exit..."
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
+ exit 1
+}
+finally {
+ Cleanup
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "Press enter to exit..." -ForegroundColor Green
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
+ }
}
\ No newline at end of file
diff --git a/scripts/install.sh b/scripts/install.sh
index 0aed487..adc7881 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -2,294 +2,126 @@
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 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
-}
-
-# 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
-}
-
-# 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
-}
-
-# 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
-
- if [ -f "$storage_path" ]; then
- cp "$storage_path" "${storage_path}.backup"
- print_success "$(get_message 19) ${storage_path}.backup"
+# Check for required commands
+check_requirements() {
+ if ! command -v curl >/dev/null 2>&1; then
+ echo -e "${RED}Error: curl is required${NC}"
+ exit 1
fi
}
-# Detect OS / 检测操作系统
-detect_os() {
- if [[ "$OSTYPE" == "darwin"* ]]; then
- echo "darwin"
- elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
- echo "linux"
- else
- print_error "$(get_message 12)"
- fi
-}
+# Detect system information
+detect_system() {
+ local os arch suffix
-# 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/'
-}
+ case "$(uname -s)" in
+ Linux*) os="linux";;
+ Darwin*) os="darwin";;
+ *) echo -e "${RED}Unsupported OS${NC}"; exit 1;;
+ esac
-# Get the binary name based on OS and architecture / 根据操作系统和架构获取二进制文件名
-get_binary_name() {
- OS=$(detect_os)
- ARCH=$(uname -m)
- VERSION=$(get_latest_version)
-
- case "$ARCH" in
- x86_64)
- echo "cursor_id_modifier_${VERSION}_${OS}_amd64"
+ case "$(uname -m)" in
+ x86_64)
+ arch="x86_64"
;;
- aarch64|arm64)
- echo "cursor_id_modifier_${VERSION}_${OS}_arm64"
+ aarch64|arm64)
+ arch="arm64"
;;
- *)
- print_error "$(get_message 13) $ARCH"
+ i386|i686)
+ arch="i386"
;;
+ *) echo -e "${RED}Unsupported architecture${NC}"; exit 1;;
esac
+
+ echo "$os $arch"
}
-# Add download progress display function
-download_with_progress() {
+# Download with progress
+download() {
local url="$1"
- local output_file="$2"
-
- curl -L -f --progress-bar "$url" -o "$output_file"
- return $?
+ local output="$2"
+ curl -#L "$url" -o "$output"
}
-# 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}"
+# Check and create installation directory
+setup_install_dir() {
+ local install_dir="$1"
- if ! download_with_progress "$DOWNLOAD_URL" "$TMP_DIR/$BINARY_NAME"; then
- rm -rf "$TMP_DIR"
- print_error "$(get_message 8) $DOWNLOAD_URL"
+ if [ ! -d "$install_dir" ]; then
+ mkdir -p "$install_dir" || {
+ echo -e "${RED}Failed to create installation directory${NC}"
+ exit 1
+ }
fi
+}
+
+# Main installation function
+main() {
+ check_requirements
- if [ ! -f "$TMP_DIR/$BINARY_NAME" ]; then
- rm -rf "$TMP_DIR"
- print_error "$(get_message 9)"
- fi
+ echo -e "${BLUE}Starting installation...${NC}"
- print_status "$(get_message 4)"
- INSTALL_DIR="/usr/local/bin"
+ # Detect system
+ read -r OS ARCH SUFFIX <<< "$(detect_system)"
+ echo -e "${GREEN}Detected: $OS $ARCH${NC}"
- # Create directory if it doesn't exist
- mkdir -p "$INSTALL_DIR"
+ # Set installation directory
+ INSTALL_DIR="/usr/local/bin"
- # 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
+ # Setup installation directory
+ setup_install_dir "$INSTALL_DIR"
- if ! chmod +x "$INSTALL_DIR/$FINAL_BINARY_NAME"; then
- rm -rf "$TMP_DIR"
- print_error "Failed to set executable permissions"
- fi
+ # Get latest release info
+ echo -e "${BLUE}Fetching latest release information...${NC}"
+ LATEST_URL="https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest"
- # Cleanup
- print_status "$(get_message 5)"
- rm -rf "$TMP_DIR"
+ # Get latest version and remove 'v' prefix
+ VERSION=$(curl -s "$LATEST_URL" | grep "tag_name" | cut -d'"' -f4 | sed 's/^v//')
- print_success "$(get_message 6)"
- printf "${GREEN}[✓]${NC} $(get_message 7)\n" "$FINAL_BINARY_NAME"
+ # Construct binary name
+ BINARY_NAME="cursor-id-modifier_${VERSION}_${OS}_${ARCH}"
+ echo -e "${BLUE}Looking for asset: $BINARY_NAME${NC}"
- # 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)"
- fi
+ # Get download URL directly
+ DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep -o "\"browser_download_url\": \"[^\"]*${BINARY_NAME}[^\"]*\"" | cut -d'"' -f4)
- if ! command -v sudo >/dev/null 2>&1; then
- print_error "$(get_message 11)"
+ if [ -z "$DOWNLOAD_URL" ]; then
+ echo -e "${RED}Error: Could not find appropriate binary for $OS $ARCH${NC}"
+ echo -e "${YELLOW}Available assets:${NC}"
+ curl -s "$LATEST_URL" | grep "browser_download_url" | cut -d'"' -f4
+ exit 1
fi
-}
-
-# Main installation process / 主安装过程
-main() {
- print_status "$(get_message 0)"
-
- # Check root privileges / 检查root权限
- check_root "$@"
- # Check required tools / 检查必需工具
- check_requirements
+ echo -e "${GREEN}Found matching asset: $BINARY_NAME${NC}"
+ echo -e "${BLUE}Downloading from: $DOWNLOAD_URL${NC}"
- # Close Cursor instances / 关闭Cursor实例
- close_cursor_instances
+ download "$DOWNLOAD_URL" "$TMP_DIR/cursor-id-modifier"
- # Backup storage.json / 备份storage.json
- backup_storage_json
+ # Install binary
+ echo -e "${BLUE}Installing...${NC}"
+ chmod +x "$TMP_DIR/cursor-id-modifier"
+ sudo mv "$TMP_DIR/cursor-id-modifier" "$INSTALL_DIR/"
- OS=$(detect_os)
- print_status "$(get_message 1) $OS"
+ echo -e "${GREEN}Installation completed successfully!${NC}"
+ echo -e "${BLUE}Running cursor-id-modifier...${NC}"
- # Install the binary / 安装二进制文件
- install_binary
+ # Run the program with sudo, preserving environment variables
+ export AUTOMATED_MODE=1
+ if ! sudo -E cursor-id-modifier; then
+ echo -e "${RED}Failed to run cursor-id-modifier${NC}"
+ exit 1
+ fi
}
-# Run main function / 运行主函数
-main "$@"
+main