├── .github └── workflows │ └── auto-tag-release.yml ├── .gitignore ├── .goreleaser.yml ├── CursorHistoryDown.md ├── LICENSE ├── README.md ├── README_CN.md ├── README_JP.md ├── cmd └── cursor-id-modifier │ └── main.go ├── cursor_downloads.csv ├── cursor_downloads.json ├── go.mod ├── go.sum ├── img ├── alipay.png ├── alipay_scan_pay.jpg ├── etc.png ├── pwsh_1.png ├── pwsh_2.png ├── qun-10.jpg ├── qun-8.png ├── qun11.jpg ├── qun9.png ├── run_success.png ├── wx_group.jpg ├── wx_group6.jpg ├── wx_group7.jpg ├── wx_me.png ├── wx_public.jpg ├── wx_public_2.png ├── wx_zsm.jpg └── wx_zsm2.png ├── internal ├── config │ └── config.go ├── lang │ └── lang.go ├── process │ └── manager.go └── ui │ ├── display.go │ ├── logo.go │ └── spinner.go ├── pkg └── idgen │ └── generator.go ├── process_cursor_links.py └── scripts ├── build_all.bat ├── build_all.sh ├── install.ps1 ├── install.sh └── run ├── cursor_linux_id_modifier.sh ├── cursor_mac_free_trial_reset.sh ├── cursor_mac_id_modifier.sh └── cursor_win_id_modifier.ps1 /.github/workflows/auto-tag-release.yml: -------------------------------------------------------------------------------- 1 | # This workflow requires Ubuntu 22.04 or 24.04 2 | 3 | name: Auto Tag & Release 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | - main 10 | tags: 11 | - "v*" 12 | paths-ignore: 13 | - "**.md" 14 | - "LICENSE" 15 | - ".gitignore" 16 | workflow_call: {} 17 | 18 | permissions: 19 | contents: write 20 | packages: write 21 | actions: write 22 | 23 | jobs: 24 | pre_job: 25 | runs-on: ubuntu-22.04 26 | outputs: 27 | should_skip: ${{ steps.skip_check.outputs.should_skip }} 28 | steps: 29 | - id: skip_check 30 | uses: fkirc/skip-duplicate-actions@v5.3.0 31 | with: 32 | cancel_others: "true" 33 | concurrent_skipping: "same_content" 34 | 35 | auto-tag-release: 36 | needs: pre_job 37 | if: | 38 | needs.pre_job.outputs.should_skip != 'true' || 39 | startsWith(github.ref, 'refs/tags/v') 40 | runs-on: ubuntu-22.04 41 | timeout-minutes: 15 42 | outputs: 43 | version: ${{ steps.get_latest_tag.outputs.version }} 44 | concurrency: 45 | group: ${{ github.workflow }}-${{ github.ref }} 46 | cancel-in-progress: true 47 | 48 | steps: 49 | - name: Checkout 50 | uses: actions/checkout@v3 51 | with: 52 | fetch-depth: 0 53 | lfs: true 54 | submodules: recursive 55 | 56 | - name: Setup Go 57 | uses: actions/setup-go@v3 58 | with: 59 | go-version: "1.21" 60 | check-latest: true 61 | cache: true 62 | 63 | - name: Cache 64 | uses: actions/cache@v3 65 | with: 66 | path: | 67 | ~/.cache/go-build 68 | ~/go/pkg/mod 69 | ~/.cache/git 70 | key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }} 71 | restore-keys: | 72 | ${{ runner.os }}-build- 73 | ${{ runner.os }}- 74 | 75 | # 只在非tag推送时执行自动打tag 76 | - name: Get latest tag 77 | if: ${{ !startsWith(github.ref, 'refs/tags/v') }} 78 | id: get_latest_tag 79 | run: | 80 | set -euo pipefail 81 | git fetch --tags --force || { 82 | echo "::error::Failed to fetch tags" 83 | exit 1 84 | } 85 | latest_tag=$(git tag -l 'v*' --sort=-v:refname | head -n 1) 86 | if [ -z "$latest_tag" ]; then 87 | new_version="v0.1.0" 88 | else 89 | major=$(echo $latest_tag | cut -d. -f1) 90 | minor=$(echo $latest_tag | cut -d. -f2) 91 | patch=$(echo $latest_tag | cut -d. -f3) 92 | new_patch=$((patch + 1)) 93 | new_version="$major.$minor.$new_patch" 94 | fi 95 | echo "version=$new_version" >> "$GITHUB_OUTPUT" 96 | echo "Generated version: $new_version" 97 | 98 | - name: Validate version 99 | if: ${{ !startsWith(github.ref, 'refs/tags/v') }} 100 | run: | 101 | set -euo pipefail 102 | new_tag="${{ steps.get_latest_tag.outputs.version }}" 103 | echo "Validating version: $new_tag" 104 | if [[ ! $new_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 105 | echo "::error::Invalid version format: $new_tag" 106 | exit 1 107 | fi 108 | major=$(echo $new_tag | cut -d. -f1 | tr -d 'v') 109 | minor=$(echo $new_tag | cut -d. -f2) 110 | patch=$(echo $new_tag | cut -d. -f3) 111 | if [[ $major -gt 99 || $minor -gt 99 || $patch -gt 999 ]]; then 112 | echo "::error::Version numbers out of valid range" 113 | exit 1 114 | fi 115 | echo "Version validation passed" 116 | 117 | - name: Create new tag 118 | if: ${{ !startsWith(github.ref, 'refs/tags/v') }} 119 | env: 120 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 121 | run: | 122 | new_tag=${{ steps.get_latest_tag.outputs.version }} 123 | git config --global user.name 'github-actions[bot]' 124 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 125 | git tag -a $new_tag -m "Release $new_tag" 126 | git push origin $new_tag 127 | 128 | # 在 Run GoReleaser 之前添加配置检查步骤 129 | - name: Check GoReleaser config 130 | run: | 131 | if [ ! -f ".goreleaser.yml" ] && [ ! -f ".goreleaser.yaml" ]; then 132 | echo "::error::GoReleaser configuration file not found" 133 | exit 1 134 | fi 135 | 136 | # 添加依赖检查步骤 137 | - name: Check Dependencies 138 | run: | 139 | go mod verify 140 | go mod download 141 | # 如果使用 vendor 模式,则执行以下命令 142 | if [ -d "vendor" ]; then 143 | go mod vendor 144 | fi 145 | 146 | # 添加构建环境准备步骤 147 | - name: Prepare Build Environment 148 | run: | 149 | echo "Building version: ${VERSION:-development}" 150 | echo "GOOS=${GOOS:-$(go env GOOS)}" >> $GITHUB_ENV 151 | echo "GOARCH=${GOARCH:-$(go env GOARCH)}" >> $GITHUB_ENV 152 | echo "GO111MODULE=on" >> $GITHUB_ENV 153 | 154 | # 添加清理步骤 155 | - name: Cleanup workspace 156 | run: | 157 | rm -rf /tmp/go/ 158 | rm -rf .cache/ 159 | rm -rf dist/ 160 | git clean -fdx 161 | git status 162 | 163 | # 修改 GoReleaser 步骤 164 | - name: Run GoReleaser 165 | if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }} 166 | uses: goreleaser/goreleaser-action@v3 167 | with: 168 | distribution: goreleaser 169 | version: latest 170 | args: release --clean --timeout 60m 171 | env: 172 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 173 | VERSION: ${{ steps.get_latest_tag.outputs.version }} 174 | CGO_ENABLED: 0 175 | GOPATH: /tmp/go 176 | GOROOT: ${{ env.GOROOT }} 177 | GOCACHE: /tmp/.cache/go-build 178 | GOMODCACHE: /tmp/go/pkg/mod 179 | GORELEASER_DEBUG: 1 180 | GORELEASER_CURRENT_TAG: ${{ steps.get_latest_tag.outputs.version }} 181 | # 添加额外的构建信息 182 | BUILD_TIME: ${{ steps.get_latest_tag.outputs.version }} 183 | BUILD_COMMIT: ${{ github.sha }} 184 | 185 | # 优化 vendor 同步步骤 186 | - name: Sync vendor directory 187 | run: | 188 | echo "Syncing vendor directory..." 189 | go mod tidy 190 | go mod vendor 191 | go mod verify 192 | # 验证 vendor 目录 193 | if [ -d "vendor" ]; then 194 | echo "Verifying vendor directory..." 195 | go mod verify 196 | # 检查是否有未跟踪的文件 197 | if [ -n "$(git status --porcelain vendor/)" ]; then 198 | echo "Warning: Vendor directory has uncommitted changes" 199 | git status vendor/ 200 | fi 201 | fi 202 | 203 | # 添加错误检查步骤 204 | - name: Check GoReleaser Output 205 | if: failure() 206 | run: | 207 | echo "::group::GoReleaser Debug Info" 208 | cat dist/artifacts.json || true 209 | echo "::endgroup::" 210 | 211 | echo "::group::GoReleaser Config" 212 | cat .goreleaser.yml 213 | echo "::endgroup::" 214 | 215 | echo "::group::Environment Info" 216 | go version 217 | go env 218 | echo "::endgroup::" 219 | 220 | - name: Set Release Version 221 | if: startsWith(github.ref, 'refs/tags/v') 222 | run: | 223 | echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 224 | 225 | # 改进验证步骤 226 | - name: Verify Release 227 | if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }} 228 | run: | 229 | echo "Verifying release artifacts..." 230 | if [ ! -d "dist" ]; then 231 | echo "::error::Release artifacts not found" 232 | exit 1 233 | fi 234 | # 验证生成的二进制文件 235 | for file in dist/cursor-id-modifier_*; do 236 | if [ -f "$file" ]; then 237 | echo "Verifying: $file" 238 | if [[ "$file" == *.exe ]]; then 239 | # Windows 二进制文件检查 240 | if ! [ -x "$file" ]; then 241 | echo "::error::$file is not executable" 242 | exit 1 243 | fi 244 | else 245 | # Unix 二进制文件检查 246 | if ! [ -x "$file" ]; then 247 | echo "::error::$file is not executable" 248 | exit 1 249 | fi 250 | fi 251 | fi 252 | done 253 | 254 | - name: Notify on failure 255 | if: failure() 256 | run: | 257 | echo "::error::Release process failed" 258 | 259 | # 修改构建摘要步骤 260 | - name: Build Summary 261 | if: always() 262 | run: | 263 | echo "## Build Summary" >> $GITHUB_STEP_SUMMARY 264 | echo "- Go Version: $(go version)" >> $GITHUB_STEP_SUMMARY 265 | echo "- Release Version: ${VERSION:-N/A}" >> $GITHUB_STEP_SUMMARY 266 | echo "- GPG Signing: Disabled" >> $GITHUB_STEP_SUMMARY 267 | echo "- Build Status: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY 268 | 269 | if [ -d "dist" ]; then 270 | echo "### Generated Artifacts" >> $GITHUB_STEP_SUMMARY 271 | ls -lh dist/ | awk '{print "- "$9" ("$5")"}' >> $GITHUB_STEP_SUMMARY 272 | fi 273 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled binary 2 | /cursor-id-modifier 3 | /cursor-id-modifier.exe 4 | 5 | # Build output directories 6 | bin/ 7 | dist/ 8 | 9 | # Go specific 10 | go.sum 11 | go/ 12 | .cache/ 13 | 14 | # IDE and editor files 15 | .vscode/ 16 | .idea/ 17 | *.swp 18 | *.swo 19 | 20 | # OS specific 21 | .DS_Store 22 | Thumbs.db 23 | 24 | # Build and release artifacts 25 | releases/ 26 | *.syso 27 | *.exe 28 | *.exe~ 29 | *.dll 30 | *.so 31 | *.dylib 32 | 33 | # Test files 34 | *.test 35 | *.out 36 | coverage.txt 37 | 38 | # Temporary files 39 | *.tmp 40 | *~ 41 | *.bak 42 | *.log -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | before: 4 | hooks: 5 | - go mod tidy 6 | - go mod vendor 7 | - go mod verify 8 | 9 | builds: 10 | - id: cursor-id-modifier 11 | main: ./cmd/cursor-id-modifier/main.go 12 | binary: cursor-id-modifier 13 | env: 14 | - CGO_ENABLED=0 15 | - GO111MODULE=on 16 | goos: 17 | - linux 18 | - windows 19 | - darwin 20 | goarch: 21 | - amd64 22 | - arm64 23 | - "386" 24 | ignore: 25 | - goos: darwin 26 | goarch: "386" 27 | ldflags: 28 | - -s -w 29 | - -X 'main.version={{.Version}}' 30 | - -X 'main.commit={{.ShortCommit}}' 31 | - -X 'main.date={{.CommitDate}}' 32 | - -X 'main.builtBy=goreleaser' 33 | flags: 34 | - -trimpath 35 | mod_timestamp: '{{ .CommitTimestamp }}' 36 | 37 | archives: 38 | - id: binary 39 | format: binary 40 | name_template: >- 41 | {{- .ProjectName }}_ 42 | {{- .Version }}_ 43 | {{- .Os }}_ 44 | {{- if eq .Arch "amd64" }}x86_64 45 | {{- else if eq .Arch "386" }}i386 46 | {{- else }}{{ .Arch }}{{ end }} 47 | builds: 48 | - cursor-id-modifier 49 | allow_different_binary_count: true 50 | files: 51 | - none* 52 | 53 | checksum: 54 | name_template: 'checksums.txt' 55 | algorithm: sha256 56 | 57 | release: 58 | draft: true 59 | prerelease: auto 60 | mode: replace 61 | header: | 62 | ## Release {{.Tag}} ({{.Date}}) 63 | 64 | See [CHANGELOG.md](CHANGELOG.md) for details. 65 | footer: | 66 | **Full Changelog**: https://github.com/owner/repo/compare/{{ .PreviousTag }}...{{ .Tag }} 67 | extra_files: 68 | - glob: 'LICENSE*' 69 | - glob: 'README*' 70 | - glob: 'CHANGELOG*' 71 | 72 | changelog: 73 | sort: asc 74 | use: github 75 | filters: 76 | exclude: 77 | - '^docs:' 78 | - '^test:' 79 | - '^ci:' 80 | - Merge pull request 81 | - Merge branch 82 | groups: 83 | - title: Features 84 | regexp: "^.*feat[(\\w)]*:+.*$" 85 | order: 0 86 | - title: 'Bug fixes' 87 | regexp: "^.*fix[(\\w)]*:+.*$" 88 | order: 1 89 | - title: Others 90 | order: 999 91 | 92 | project_name: cursor-id-modifier 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 dacrab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Cursor Free Trial Reset Tool 2 | 3 |
4 | 5 | [![Release](https://img.shields.io/github/v/release/yuaotian/go-cursor-help?style=flat-square&logo=github&color=blue)](https://github.com/yuaotian/go-cursor-help/releases/latest) 6 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/yuaotian/go-cursor-help/blob/master/LICENSE) 7 | [![Stars](https://img.shields.io/github/stars/yuaotian/go-cursor-help?style=flat-square&logo=github)](https://github.com/yuaotian/go-cursor-help/stargazers) 8 | 9 | [🌟 English](README.md) | [🌏 中文](README_CN.md) | [🌏 日本語](README_JP.md) 10 | 11 | Cursor Logo 12 | 13 |
14 | 15 | > ⚠️ **IMPORTANT NOTICE** 16 | > 17 | > This tool currently supports: 18 | > - ✅ Windows: Latest 0.49.x versions (Supported) 19 | > - ✅ Mac/Linux: Latest 0.49.x versions (Supported, feedback welcome) 20 | > 21 | > Please check your Cursor version before using this tool. 22 | 23 |
24 | 📦 Version History & Downloads 25 | 26 |
27 | 28 | ### 🌟 Latest Versions 29 | 30 | [View Full Version History]([CursorHistoryDown.md](https://github.com/oslook/cursor-ai-downloads?tab=readme-ov-file)) 31 | 32 |
33 | 34 | 35 | 36 |
37 | 38 | ⚠️ **General Solutions for Cursor** 39 | > 1. Close Cursor, log out of your account, and delete your account in the official website Settings (refresh IP node: Japan, Singapore, USA, Hong Kong, prioritizing low latency - not necessarily required but change if conditions allow; Windows users are recommended to refresh DNS cache: `ipconfig /flushdns`) 40 | > Go to the Cursor official website to delete your current account 41 | > Steps: User avatar -> Setting -> Advanced▼ in the bottom left -> Delete Account 42 | > 43 | > 2. Run the machine code refresh script, see the script address below, available in China 44 | > 45 | > 3. Re-register an account, log in, and open Cursor to resume normal use. 46 | > 47 | > 4. Alternative solution: If still unusable after step [**3**], or if you encounter problems such as account registration failure or inability to delete an account, this usually means your browser has been identified or restricted by the target website (risk control). In this case, try switching browsers, such as: Edge, Google Chrome, Firefox. (Or, consider using a browser that can modify or randomize browser fingerprint information). 48 | 49 | 50 | --- 51 | 52 | ⚠️ **MAC Address Modification Warning** 53 | > 54 | > For Mac users: This script includes a MAC address modification feature that will: 55 | > - Modify your network interface's MAC address 56 | > - Backup original MAC addresses before modification 57 | > - This modification may temporarily affect network connectivity 58 | > - You can skip this step when prompted during execution 59 | > 60 | 61 |
62 | 🔒 Disable Auto-Update Feature 63 | 64 | > To prevent Cursor from automatically updating to unsupported new versions, you can choose to disable the auto-update feature. 65 | 66 | #### Method 1: Using Built-in Script (Recommended) 67 | 68 | When running the reset tool, the script will ask if you want to disable auto-updates: 69 | ```text 70 | [Question] Do you want to disable Cursor auto-update feature? 71 | 0) No - Keep default settings (Press Enter) 72 | 1) Yes - Disable auto-update 73 | ``` 74 | 75 | Select `1` to automatically complete the disable operation. 76 | 77 | #### Method 2: Manual Disable 78 | 79 | **Windows:** 80 | 1. Close all Cursor processes 81 | 2. Delete directory: `%LOCALAPPDATA%\cursor-updater` 82 | 3. Create a file with the same name (without extension) in the same location 83 | 84 | **macOS:** 85 | ```bash 86 | # NOTE: As tested, this method only works for version 0.45.11 and below. 87 | # Close Cursor 88 | pkill -f "Cursor" 89 | # Replacing app-update.yml with a blank/read-only file 90 | cd /Applications/Cursor.app/Contents/Resources 91 | mv app-update.yml app-update.yml.bak 92 | touch app-update.yml 93 | chmod 444 app-update.yml 94 | 95 | # Go to Settings -> Application -> Update, set Mode to none. 96 | # This must be done to prevent Cursor from checking for updates. 97 | 98 | # NOTE: The cursor-updater modification method may no longer be effective 99 | # In any case, remove update directory and create blocking file 100 | rm -rf ~/Library/Application\ Support/Caches/cursor-updater 101 | touch ~/Library/Application\ Support/Caches/cursor-updater 102 | ``` 103 | 104 | **Linux:** 105 | ```bash 106 | # Close Cursor 107 | pkill -f "Cursor" 108 | # Remove update directory and create blocking file 109 | rm -rf ~/.config/cursor-updater 110 | touch ~/.config/cursor-updater 111 | ``` 112 | 113 | > ⚠️ **Note:** After disabling auto-updates, you'll need to manually download and install new versions. It's recommended to update only after confirming the new version is compatible. 114 | 115 | 116 |
117 | 118 | --- 119 | 120 | ### 📝 Description 121 | 122 | > When you encounter any of these messages: 123 | 124 | #### Issue 1: Trial Account Limit

Back To Top

125 | 126 | ```text 127 | Too many free trial accounts used on this machine. 128 | Please upgrade to pro. We have this limit in place 129 | to prevent abuse. Please let us know if you believe 130 | this is a mistake. 131 | ``` 132 | 133 | #### Issue 2: API Key Limitation

Back To Top

134 | 135 | ```text 136 | [New Issue] 137 | 138 | Composer relies on custom models that cannot be billed to an API key. 139 | Please disable API keys and use a Pro or Business subscription. 140 | Request ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 141 | ``` 142 | 143 | #### Issue 3: Trial Request Limit 144 | 145 | > This indicates you've reached the usage limit during the VIP free trial period: 146 | 147 | ```text 148 | You've reached your trial request limit. 149 | ``` 150 | 151 | #### Issue 4: Claude 3.7 High Load

Back To Top

152 | 153 | ```text 154 | High Load 155 | We're experiencing high demand for Claude 3.7 Sonnet right now. Please upgrade to Pro, or switch to the 156 | 'default' model, Claude 3.5 sonnet, another model, or try again in a few moments. 157 | ``` 158 | 159 |
160 | 161 |

162 | 163 | #### Solution : Uninstall Cursor Completely And Reinstall (API key Issue) 164 | 165 | 1. Download [Geek.exe Uninstaller[Free]](https://geekuninstaller.com/download) 166 | 2. Uninstall Cursor app completely 167 | 3. Re-Install Cursor app 168 | 4. Continue to Solution 1 169 | 170 |
171 | 172 |

173 | 174 | > Temporary Solution: 175 | 176 | #### Solution 1: Quick Reset (Recommended) 177 | 178 | 1. Close Cursor application 179 | 2. Run the machine code reset script (see installation instructions below) 180 | 3. Reopen Cursor to continue using 181 | 182 | #### Solution 2: Account Switch 183 | 184 | 1. File -> Cursor Settings -> Sign Out 185 | 2. Close Cursor 186 | 3. Run the machine code reset script 187 | 4. Login with a new account 188 | 189 | #### Solution 3: Network Optimization 190 | 191 | If the above solutions don't work, try: 192 | 193 | - Switch to low-latency nodes (Recommended regions: Japan, Singapore, US, Hong Kong) 194 | - Ensure network stability 195 | - Clear browser cache and retry 196 | 197 | #### Solution 4: Claude 3.7 Access Issue (High Load) 198 | 199 | If you see the "High Load" message for Claude 3.7 Sonnet, this indicates Cursor is limiting free trial accounts from using the 3.7 model during certain times of the day. Try: 200 | 201 | 1. Switch to a new account created with Gmail, possibly connecting through a different IP address 202 | 2. Try accessing during off-peak hours (typically 5-10 AM or 3-7 PM when restrictions are often lighter) 203 | 3. Consider upgrading to Pro for guaranteed access 204 | 4. Use Claude 3.5 Sonnet as a fallback option 205 | 206 | > Note: These access patterns may change as Cursor adjusts their resource allocation policies. 207 | 208 | ### 💻 System Support 209 | 210 | 211 | 212 | 220 | 228 | 237 | 238 |
213 | 214 | **Windows** ✅ 215 | 216 | - x64 (64-bit) 217 | - x86 (32-bit) 218 | 219 | 221 | 222 | **macOS** ✅ 223 | 224 | - Intel (x64) 225 | - Apple Silicon (M1/M2) 226 | 227 | 229 | 230 | **Linux** ✅ 231 | 232 | - x64 (64-bit) 233 | - x86 (32-bit) 234 | - ARM64 235 | 236 |
239 | 240 | ### 🚀 One-Click Solution 241 | 242 |
243 | Global Users 244 | 245 | **macOS** 246 | 247 | ```bash 248 | # Method two 249 | curl -fsSL https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_mac_id_modifier.sh -o ./cursor_mac_id_modifier.sh && sudo bash ./cursor_mac_id_modifier.sh && rm ./cursor_mac_id_modifier.sh 250 | ``` 251 | 252 | **Linux** 253 | 254 | ```bash 255 | curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_linux_id_modifier.sh | sudo bash 256 | ``` 257 | 258 | **Windows** 259 | 260 | ```powershell 261 | irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 262 | ``` 263 | 264 |
265 | Run Success 266 |
267 | 268 |
269 | 270 |
271 | China Users (Recommended) 272 | 273 | **macOS** 274 | 275 | ```bash 276 | curl -fsSL https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_mac_id_modifier.sh -o ./cursor_mac_id_modifier.sh && sudo bash ./cursor_mac_id_modifier.sh && rm ./cursor_mac_id_modifier.sh 277 | ``` 278 | 279 | **Linux** 280 | 281 | ```bash 282 | curl -fsSL https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_linux_id_modifier.sh | sudo bash 283 | ``` 284 | 285 | **Windows** 286 | 287 | ```powershell 288 | irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 289 | ``` 290 | 291 |
292 | 293 |
294 | Windows Terminal Run and Configuration 295 | 296 | #### How to Open Administrator Terminal in Windows: 297 | 298 | ##### Method 1: Using Win + X Shortcut 299 | ```md 300 | 1. Press Win + X key combination 301 | 2. Select one of these options from the menu: 302 | - "Windows PowerShell (Administrator)" 303 | - "Windows Terminal (Administrator)" 304 | - "Terminal (Administrator)" 305 | (Options may vary depending on Windows version) 306 | ``` 307 | 308 | ##### Method 2: Using Win + R Run Command 309 | ```md 310 | 1. Press Win + R key combination 311 | 2. Type powershell or pwsh in the Run dialog 312 | 3. Press Ctrl + Shift + Enter to run as administrator 313 | or type in the opened window: Start-Process pwsh -Verb RunAs 314 | 4. Enter the reset script in the administrator terminal: 315 | 316 | irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 317 | ``` 318 | 319 | ##### Method 3: Using Search 320 | >![Search PowerShell](img/pwsh_1.png) 321 | > 322 | >Type pwsh in the search box, right-click and select "Run as administrator" 323 | >![Run as Administrator](img/pwsh_2.png) 324 | 325 | Enter the reset script in the administrator terminal: 326 | ```powershell 327 | irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 328 | ``` 329 | 330 | ### 🔧 PowerShell Installation Guide 331 | 332 | If PowerShell is not installed on your system, you can install it using one of these methods: 333 | 334 | #### Method 1: Install via Winget (Recommended) 335 | 336 | 1. Open Command Prompt or PowerShell 337 | 2. Run the following command: 338 | ```powershell 339 | winget install --id Microsoft.PowerShell --source winget 340 | ``` 341 | 342 | #### Method 2: Manual Installation 343 | 344 | 1. Download the installer for your system: 345 | - [PowerShell-7.4.6-win-x64.msi](https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-x64.msi) (64-bit systems) 346 | - [PowerShell-7.4.6-win-x86.msi](https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-x86.msi) (32-bit systems) 347 | - [PowerShell-7.4.6-win-arm64.msi](https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-arm64.msi) (ARM64 systems) 348 | 349 | 2. Double-click the downloaded installer and follow the installation prompts 350 | 351 | > 💡 If you encounter any issues, please refer to the [Microsoft Official Installation Guide](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows) 352 | 353 |
354 | 355 | #### Windows 安装特性: 356 | 357 | - 🔍 Automatically detects and uses PowerShell 7 if available 358 | - 🛡️ Requests administrator privileges via UAC prompt 359 | - 📝 Falls back to Windows PowerShell if PS7 isn't found 360 | - 💡 Provides manual instructions if elevation fails 361 | 362 | That's it! The script will: 363 | 364 | 1. ✨ Install the tool automatically 365 | 2. 🔄 Reset your Cursor trial immediately 366 | 367 | ### 📦 Manual Installation 368 | 369 | > Download the appropriate file for your system from [releases](https://github.com/yuaotian/go-cursor-help/releases/latest) 370 | 371 |
372 | Windows Packages 373 | 374 | - 64-bit: `cursor-id-modifier_windows_x64.exe` 375 | - 32-bit: `cursor-id-modifier_windows_x86.exe` 376 |
377 | 378 |
379 | macOS Packages 380 | 381 | - Intel: `cursor-id-modifier_darwin_x64_intel` 382 | - M1/M2: `cursor-id-modifier_darwin_arm64_apple_silicon` 383 |
384 | 385 |
386 | Linux Packages 387 | 388 | - 64-bit: `cursor-id-modifier_linux_x64` 389 | - 32-bit: `cursor-id-modifier_linux_x86` 390 | - ARM64: `cursor-id-modifier_linux_arm64` 391 |
392 | 393 | ### 🔧 Technical Details 394 | 395 |
396 | Configuration Files 397 | 398 | The program modifies Cursor's `storage.json` config file located at: 399 | 400 | - Windows: `%APPDATA%\Cursor\User\globalStorage\storage.json` 401 | - macOS: `~/Library/Application Support/Cursor/User/globalStorage/storage.json` 402 | - Linux: `~/.config/Cursor/User/globalStorage/storage.json` 403 |
404 | 405 |
406 | Modified Fields 407 | 408 | The tool generates new unique identifiers for: 409 | 410 | - `telemetry.machineId` 411 | - `telemetry.macMachineId` 412 | - `telemetry.devDeviceId` 413 | - `telemetry.sqmId` 414 |
415 | 416 |
417 | Manual Auto-Update Disable 418 | 419 | Windows users can manually disable the auto-update feature: 420 | 421 | 1. Close all Cursor processes 422 | 2. Delete directory: `C:\Users\username\AppData\Local\cursor-updater` 423 | 3. Create a file with the same name: `cursor-updater` (without extension) 424 | 425 | macOS/Linux users can try to locate similar `cursor-updater` directory in their system and perform the same operation. 426 | 427 |
428 | 429 |
430 | Safety Features 431 | 432 | - ✅ Safe process termination 433 | - ✅ Atomic file operations 434 | - ✅ Error handling and recovery 435 |
436 | 437 |
438 | Registry Modification Notice 439 | 440 | > ⚠️ **Important: This tool modifies the Windows Registry** 441 | 442 | #### Modified Registry 443 | - Path: `Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography` 444 | - Key: `MachineGuid` 445 | 446 | #### Potential Impact 447 | Modifying this registry key may affect: 448 | - Windows system's unique device identification 449 | - Device recognition and authorization status of certain software 450 | - System features based on hardware identification 451 | 452 | #### Safety Measures 453 | 1. Automatic Backup 454 | - Original value is automatically backed up before modification 455 | - Backup location: `%APPDATA%\Cursor\User\globalStorage\backups` 456 | - Backup file format: `MachineGuid.backup_YYYYMMDD_HHMMSS` 457 | 458 | 2. Manual Recovery Steps 459 | - Open Registry Editor (regedit) 460 | - Navigate to: `Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography` 461 | - Right-click on `MachineGuid` 462 | - Select "Modify" 463 | - Paste the value from backup file 464 | 465 | #### Important Notes 466 | - Verify backup file existence before modification 467 | - Use backup file to restore original value if needed 468 | - Administrator privileges required for registry modification 469 |
470 | 471 | --- 472 | 473 | ### 📚 Recommended Reading 474 | 475 | - [Cursor Issues Collection and Solutions](https://mp.weixin.qq.com/s/pnJrH7Ifx4WZvseeP1fcEA) 476 | - [AI Universal Development Assistant Prompt Guide](https://mp.weixin.qq.com/s/PRPz-qVkFJSgkuEKkTdzwg) 477 | 478 | --- 479 | 480 | ## Support 481 | 482 |
483 | If you find this helpful, consider buying me a spicy gluten snack (Latiao) as appreciation~ 💁☕️ 484 | 485 | 486 | 487 | 492 | 497 | 502 | 507 | 513 | 514 | 515 |
488 | 微信赞赏
489 | 微信赞赏码
490 | 要到饭咧?啊咧?啊咧?不给也没事~ 请随意打赏 491 |
493 | 支付宝赞赏
494 | 支付宝赞赏码
495 | 如果觉得有帮助,来包辣条犒劳一下吧~ 496 |
498 | Alipay
499 | Alipay
500 | 1 Latiao = 1 AI thought cycle 501 |
503 | WeChat
504 | WeChat
505 | 二维码7天内(4月25日前)有效,过期请加微信 506 |
516 |
517 | 518 | --- 519 | 520 | ## ⭐ Project Stats 521 | 522 |
523 | 524 | [![Star History Chart](https://api.star-history.com/svg?repos=yuaotian/go-cursor-help&type=Date)](https://star-history.com/#yuaotian/go-cursor-help&Date) 525 | 526 | ![Repobeats analytics image](https://repobeats.axiom.co/api/embed/ddaa9df9a94b0029ec3fad399e1c1c4e75755477.svg "Repobeats analytics image") 527 | 528 |
529 | 530 | ## 📄 License 531 | 532 |
533 | MIT License 534 | 535 | Copyright (c) 2024 536 | 537 | Permission is hereby granted, free of charge, to any person obtaining a copy 538 | of this software and associated documentation files (the "Software"), to deal 539 | in the Software without restriction, including without limitation the rights 540 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 541 | copies of the Software, and to permit persons to whom the Software is 542 | furnished to do so, subject to the following conditions: 543 | 544 | The above copyright notice and this permission notice shall be included in all 545 | copies or substantial portions of the Software. 546 | 547 |
548 | 549 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # 🚀 Cursor 免费试用重置工具 2 | 3 |
4 | 5 | [![Release](https://img.shields.io/github/v/release/yuaotian/go-cursor-help?style=flat-square&logo=github&color=blue)](https://github.com/yuaotian/go-cursor-help/releases/latest) 6 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/yuaotian/go-cursor-help/blob/master/LICENSE) 7 | [![Stars](https://img.shields.io/github/stars/yuaotian/go-cursor-help?style=flat-square&logo=github)](https://github.com/yuaotian/go-cursor-help/stargazers) 8 | 9 | [🌟 English](README.md) | [🌏 中文](README_CN.md) | [🌏 日本語](README_JP.md) 10 | 11 | Cursor Logo 12 | 13 |
14 | 15 | > ⚠️ **重要提示** 16 | > 17 | > 本工具当前支持版本: 18 | > - ✅ Windows: 最新的 0.49.x 版本(已支持) 19 | > - ✅ Mac/Linux: 最新的 0.49.x 版本(已支持,欢迎测试并反馈问题) 20 | 21 | > 使用前请确认您的 Cursor 版本。 22 | 23 |
24 | 📦 版本历史与下载 25 | 26 |
27 | 28 | 29 | [查看完整版本历史]([CursorHistoryDown.md](https://github.com/oslook/cursor-ai-downloads?tab=readme-ov-file)) 30 | 31 |
32 | 33 | 34 |
35 | 36 | ⚠️ **Cursor通用解决方案** 37 | > 1. 关闭Cursor、退出账号、官网Setting删除账号(刷新节点IP:日本、新加坡、 美国、香港,低延迟为主不一定需要但是有条件就换,Windows用户建议刷新DNS缓存:`ipconfig /flushdns`) 38 | > 前往Cursor官网删除当前账号 39 | > 步骤:用户头像->Setting-左下角Advanced▼->Delete Account 40 | > 41 | > 2. 刷新机器码脚本,看下面脚本地址,国内可用 42 | > 43 | > 3. 重新注册账号、登录、打开Cursor,即可恢复正常使用。 44 | > 45 | > 4. 备用方案:如果步骤 [**3**] 后仍不可用,或者遇到注册账号失败、无法删除账号等问题,这通常意味着您的浏览器被目标网站识别或限制(风控)。此时,请尝试更换浏览器,例如:Edge、Google Chrome、Firefox。(或者,可以尝试使用能够修改或随机化浏览器指纹信息的浏览器)。 46 | 47 | 48 | 关注大佬公众号:煎饼果子卷AI 49 | 50 | 51 | --- 52 | 53 | > ⚠️ **MAC地址修改警告** 54 | > 55 | > Mac用户请注意: 本脚本包含MAC地址修改功能,将会: 56 | > - 修改您的网络接口MAC地址 57 | > - 在修改前备份原始MAC地址 58 | > - 此修改可能会暂时影响网络连接 59 | > - 执行过程中可以选择跳过此步骤 60 | 61 | --- 62 | 63 | ### 📝 问题描述 64 | 65 | > 当您遇到以下任何消息时: 66 | 67 | #### 问题 1: 试用账号限制

跳转到顶部

68 | 69 | ```text 70 | Too many free trial accounts used on this machine. 71 | Please upgrade to pro. We have this limit in place 72 | to prevent abuse. Please let us know if you believe 73 | this is a mistake. 74 | ``` 75 | 76 | #### 问题 2: API密钥限制

跳转到顶部

77 | 78 | ```text 79 | [New Issue] 80 | 81 | Composer relies on custom models that cannot be billed to an API key. 82 | Please disable API keys and use a Pro or Business subscription. 83 | Request ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 84 | ``` 85 | 86 | #### 问题 3: 试用请求限制 87 | 88 | > 这表明您在VIP免费试用期间已达到使用限制: 89 | 90 | ```text 91 | You've reached your trial request limit. 92 | ``` 93 | 94 | #### 问题 4: Claude 3.7 高负载 (High Load)

跳转到顶部

95 | 96 | ```text 97 | High Load 98 | We're experiencing high demand for Claude 3.7 Sonnet right now. Please upgrade to Pro, or switch to the 99 | 'default' model, Claude 3.5 sonnet, another model, or try again in a few moments. 100 | ``` 101 | 102 |
103 | 104 |

105 | 106 | #### 解决方案:完全卸载Cursor并重新安装(API密钥问题) 107 | 108 | 1. 下载 [Geek.exe 卸载工具[免费]](https://geekuninstaller.com/download) 109 | 2. 完全卸载Cursor应用 110 | 3. 重新安装Cursor应用 111 | 4. 继续执行解决方案1 112 | 113 |
114 | 115 |

116 | 117 | > 临时解决方案: 118 | 119 | #### 解决方案 1: 快速重置(推荐) 120 | 121 | 1. 关闭Cursor应用 122 | 2. 运行机器码重置脚本(见下方安装说明) 123 | 3. 重新打开Cursor继续使用 124 | 125 | #### 解决方案 2: 切换账号 126 | 127 | 1. 文件 -> Cursor设置 -> 退出登录 128 | 2. 关闭Cursor 129 | 3. 运行机器码重置脚本 130 | 4. 使用新账号登录 131 | 132 | #### 解决方案 3: 网络优化 133 | 134 | 如果上述解决方案不起作用,请尝试: 135 | 136 | - 切换到低延迟节点(推荐区域:日本、新加坡、美国、香港) 137 | - 确保网络稳定性 138 | - 清除浏览器缓存并重试 139 | 140 |

141 | 142 | #### 解决方案 4: Claude 3.7 访问问题(High Load ) 143 | 144 | 如果您看到Claude 3.7 Sonnet的"High Load"(高负载)消息,这表明Cursor在一天中某些时段限制免费试用账号使用3.7模型。请尝试: 145 | 146 | 1. 使用Gmail邮箱创建新账号,可能需要通过不同IP地址连接 147 | 2. 尝试在非高峰时段访问(通常在早上5-10点或下午3-7点之间限制较少) 148 | 3. 考虑升级到Pro版本获取保证访问权限 149 | 4. 使用Claude 3.5 Sonnet作为备选方案 150 | 151 | > 注意:随着Cursor调整资源分配策略,这些访问模式可能会发生变化。 152 | 153 | ### 🚀 系统支持 154 | 155 | 156 | 157 | 164 | 171 | 178 | 179 |
158 | 159 | **Windows** ✅ 160 | 161 | - x64 & x86 162 | 163 | 165 | 166 | **macOS** ✅ 167 | 168 | - Intel & M-series 169 | 170 | 172 | 173 | **Linux** ✅ 174 | 175 | - x64 & ARM64 176 | 177 |
180 | 181 | ### 🚀 一键解决方案 182 | 183 |
184 | 国内用户(推荐) 185 | 186 | **macOS** 187 | 188 | ```bash 189 | curl -fsSL https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_mac_id_modifier.sh -o ./cursor_mac_id_modifier.sh && sudo bash ./cursor_mac_id_modifier.sh && rm ./cursor_mac_id_modifier.sh 190 | ``` 191 | 192 | **Linux** 193 | 194 | ```bash 195 | curl -fsSL https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_linux_id_modifier.sh | sudo bash 196 | ``` 197 | 198 | **Windows** 199 | 200 | ```powershell 201 | irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 202 | ``` 203 |
204 | 运行成功 205 |
206 | 207 |
208 |
209 | Windows 管理员终端运行和手动安装 210 | 211 | #### Windows 系统打开管理员终端的方法: 212 | 213 | ##### 方法一:使用 Win + X 快捷键 214 | ```md 215 | 1. 按下 Win + X 组合键 216 | 2. 在弹出的菜单中选择以下任一选项: 217 | - "Windows PowerShell (管理员)" 218 | - "Windows Terminal (管理员)" 219 | - "终端(管理员)" 220 | (具体选项因Windows版本而异) 221 | ``` 222 | 223 | ##### 方法二:使用 Win + R 运行命令 224 | ```md 225 | 1. 按下 Win + R 组合键 226 | 2. 在运行框中输入 powershell 或 pwsh 227 | 3. 按 Ctrl + Shift + Enter 以管理员身份运行 228 | 或在打开的窗口中输入: Start-Process pwsh -Verb RunAs 229 | 4. 在管理员终端中输入以下重置脚本: 230 | 231 | irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 232 | ``` 233 | 234 | ##### 方法三:通过搜索启动 235 | >![搜索 PowerShell](img/pwsh_1.png) 236 | > 237 | >在搜索框中输入 pwsh,右键选择"以管理员身份运行" 238 | >![管理员运行](img/pwsh_2.png) 239 | 240 | 在管理员终端中输入重置脚本: 241 | ```powershell 242 | irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 243 | ``` 244 | 245 | ### 🔧 PowerShell 安装指南 246 | 247 | 如果您的系统没有安装 PowerShell,可以通过以下方法安装: 248 | 249 | #### 方法一:使用 Winget 安装(推荐) 250 | 251 | 1. 打开命令提示符或 PowerShell 252 | 2. 运行以下命令: 253 | ```powershell 254 | winget install --id Microsoft.PowerShell --source winget 255 | ``` 256 | 257 | #### 方法二:手动下载安装 258 | 259 | 1. 下载对应系统的安装包: 260 | - [PowerShell-7.4.6-win-x64.msi](https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-x64.msi) (64位系统) 261 | - [PowerShell-7.4.6-win-x86.msi](https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-x86.msi) (32位系统) 262 | - [PowerShell-7.4.6-win-arm64.msi](https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-arm64.msi) (ARM64系统) 263 | 264 | 2. 双击下载的安装包,按提示完成安装 265 | 266 | > 💡 如果仍然遇到问题,可以参考 [Microsoft 官方安装指南](https://learn.microsoft.com/zh-cn/powershell/scripting/install/installing-powershell-on-windows) 267 | 268 |
269 | 270 | #### Windows 安装特性: 271 | 272 | - 🔍 自动检测并使用 PowerShell 7(如果可用) 273 | - 🛡️ 通过 UAC 提示请求管理员权限 274 | - 📝 如果没有 PS7 则使用 Windows PowerShell 275 | - 💡 如果提权失败会提供手动说明 276 | 277 | 完成后,脚本将: 278 | 279 | 1. ✨ 自动安装工具 280 | 2. 🔄 立即重置 Cursor 试用期 281 | 282 | ### 📦 手动安装 283 | 284 | > 从 [releases](https://github.com/yuaotian/go-cursor-help/releases/latest) 下载适合您系统的文件 285 | 286 |
287 | Windows 安装包 288 | 289 | - 64 位: `cursor-id-modifier_windows_x64.exe` 290 | - 32 位: `cursor-id-modifier_windows_x86.exe` 291 |
292 | 293 |
294 | macOS 安装包 295 | 296 | - Intel: `cursor-id-modifier_darwin_x64_intel` 297 | - M1/M2: `cursor-id-modifier_darwin_arm64_apple_silicon` 298 |
299 | 300 |
301 | Linux 安装包 302 | 303 | - 64 位: `cursor-id-modifier_linux_x64` 304 | - 32 位: `cursor-id-modifier_linux_x86` 305 | - ARM64: `cursor-id-modifier_linux_arm64` 306 |
307 | 308 | ### 🔧 技术细节 309 | 310 |
311 | 注册表修改说明 312 | 313 | > ⚠️ **重要提示:本工具会修改系统注册表** 314 | 315 | #### 修改内容 316 | - 路径:`计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography` 317 | - 项目:`MachineGuid` 318 | 319 | #### 潜在影响 320 | 修改此注册表项可能会影响: 321 | - Windows 系统对设备的唯一标识 322 | - 某些软件的设备识别和授权状态 323 | - 基于硬件标识的系统功能 324 | 325 | #### 安全措施 326 | 1. 自动备份 327 | - 每次修改前会自动备份原始值 328 | - 备份保存在:`%APPDATA%\Cursor\User\globalStorage\backups` 329 | - 备份文件格式:`MachineGuid.backup_YYYYMMDD_HHMMSS` 330 | 331 | 2. 手动恢复方法 332 | - 打开注册表编辑器(regedit) 333 | - 定位到:`计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography` 334 | - 右键点击 `MachineGuid` 335 | - 选择"修改" 336 | - 粘贴备份文件中的值 337 | 338 | #### 注意事项 339 | - 建议在修改前先确认备份文件的存在 340 | - 如遇问题可通过备份文件恢复原始值 341 | - 必须以管理员权限运行才能修改注册表 342 |
343 | 344 |
345 | 配置文件 346 | 347 | 程序修改 Cursor 的`storage.json`配置文件,位于: 348 | 349 | - Windows: `%APPDATA%\Cursor\User\globalStorage\` 350 | - macOS: `~/Library/Application Support/Cursor/User/globalStorage/` 351 | - Linux: `~/.config/Cursor/User/globalStorage/` 352 |
353 | 354 |
355 | 修改字段 356 | 357 | 工具会生成新的唯一标识符: 358 | 359 | - `telemetry.machineId` 360 | - `telemetry.macMachineId` 361 | - `telemetry.devDeviceId` 362 | - `telemetry.sqmId` 363 |
364 | 365 |
366 | 手动禁用自动更新 367 | 368 | Windows 用户可以手动禁用自动更新功能: 369 | 370 | 1. 关闭所有 Cursor 进程 371 | 2. 删除目录:`C:\Users\用户名\AppData\Local\cursor-updater` 372 | 3. 创建同名文件:`cursor-updater`(不带扩展名) 373 | 374 | Linux用户可以尝试在系统中找到类似的`cursor-updater`目录进行相同操作。 375 | 376 | MacOS用户按照以下步骤操作: 377 | 378 | ```bash 379 | # 注意:经测试,此方法仅适用于0.45.11及以下版本,不支持0.46.*版本 380 | # 关闭所有 Cursor 进程 381 | pkill -f "Cursor" 382 | 383 | # 备份app-update.yml并创建空的只读文件代替原文件 384 | cd /Applications/Cursor.app/Contents/Resources 385 | mv app-update.yml app-update.yml.bak 386 | touch app-update.yml 387 | chmod 444 app-update.yml 388 | 389 | # 打开Cursor设置,将更新模式设置为“无”,该步骤必须执行,否则Cursor依然会自动检查更新 390 | # 步骤:Settings -> Application -> Update, 将Mode设置为none 391 | 392 | # 注意: cursor-updater修改方法可能已失效。但为了以防万一,还是删除更新目录并创建阻止文件 393 | rm -rf ~/Library/Application\ Support/Caches/cursor-updater 394 | touch ~/Library/Application\ Support/Caches/cursor-updater 395 | ``` 396 |
397 | 398 |
399 | 安全特性 400 | 401 | - ✅ 安全的进程终止 402 | - ✅ 原子文件操作 403 | - ✅ 错误处理和恢复 404 |
405 | 406 |
407 | 重置 Cursor 免费试用 408 | 409 | ### 使用 `cursor_free_trial_reset.sh` 脚本 410 | 411 | #### macOS 412 | 413 | ```bash 414 | curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_free_trial_reset.sh -o ./cursor_free_trial_reset.sh && sudo bash ./cursor_free_trial_reset.sh && rm ./cursor_free_trial_reset.sh 415 | ``` 416 | 417 | #### Linux 418 | 419 | ```bash 420 | curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_free_trial_reset.sh | sudo bash 421 | ``` 422 | 423 | #### Windows 424 | 425 | ```powershell 426 | irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_free_trial_reset.sh | iex 427 | ``` 428 | 429 |
430 | 431 | ## 联系方式 432 | 433 |
434 | 435 | 436 | 441 | 446 | 451 | 456 | 461 | 462 |
437 | 个人微信
438 | 作者微信
439 | 微信:JavaRookie666 440 |
442 | 微信交流群
443 | WeChat
444 | 二维码7天内(4月25日前)有效,过期请加微信 445 |
447 | 公众号
448 | 微信公众号
449 | 获取更多AI开发资源 450 |
452 | 微信赞赏
453 | 微信赞赏码
454 | 要到饭咧?啊咧?啊咧?不给也没事~ 请随意打赏 455 |
457 | 支付宝赞赏
458 | 支付宝赞赏码
459 | 如果觉得有帮助,来包辣条犒劳一下吧~ 460 |
463 |
464 | 465 | --- 466 | 467 | ### 📚 推荐阅读 468 | 469 | - [Cursor 异常问题收集和解决方案](https://mp.weixin.qq.com/s/pnJrH7Ifx4WZvseeP1fcEA) 470 | - [AI 通用开发助手提示词指南](https://mp.weixin.qq.com/s/PRPz-qVkFJSgkuEKkTdzwg) 471 | 472 | --- 473 | 474 | ## ⭐ 项目统计 475 | 476 |
477 | 478 | [![Star History Chart](https://api.star-history.com/svg?repos=yuaotian/go-cursor-help&type=Date)](https://star-history.com/#yuaotian/go-cursor-help&Date) 479 | 480 | ![Repobeats analytics image](https://repobeats.axiom.co/api/embed/ddaa9df9a94b0029ec3fad399e1c1c4e75755477.svg "Repobeats analytics image") 481 | 482 |
483 | 484 | ## 📄 许可证 485 | 486 |
487 | MIT 许可证 488 | 489 | Copyright (c) 2024 490 | 491 | Permission is hereby granted, free of charge, to any person obtaining a copy 492 | of this software and associated documentation files (the "Software"), to deal 493 | in the Software without restriction, including without limitation the rights 494 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 495 | copies of the Software, and to permit persons to whom the Software is 496 | furnished to do so, subject to the following conditions: 497 | 498 | The above copyright notice and this permission notice shall be included in all 499 | copies or substantial portions of the Software. 500 | 501 |
502 | -------------------------------------------------------------------------------- /README_JP.md: -------------------------------------------------------------------------------- 1 | # 🚀 Cursor 無料試用リセットツール 2 | 3 |
4 | 5 | [![Release](https://img.shields.io/github/v/release/yuaotian/go-cursor-help?style=flat-square&logo=github&color=blue)](https://github.com/yuaotian/go-cursor-help/releases/latest) 6 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/yuaotian/go-cursor-help/blob/master/LICENSE) 7 | [![Stars](https://img.shields.io/github/stars/yuaotian/go-cursor-help?style=flat-square&logo=github)](https://github.com/yuaotian/go-cursor-help/stargazers) 8 | 9 | [🌟 English](README.md) | [🌏 中文](README_CN.md) | [🌏 日本語](README_JP.md) 10 | 11 | Cursor Logo 12 | 13 |
14 | 15 | > ⚠️ **重要なお知らせ** 16 | > 17 | > このツールは現在以下のバージョンをサポートしています: 18 | > - ✅ Windows: 最新の0.49.xバージョン(サポート済み) 19 | > - ✅ Mac/Linux: 最新の0.49.xバージョン(サポート済み、フィードバック歓迎) 20 | > 21 | > このツールを使用する前に、Cursorのバージョンを確認してください。 22 | 23 |
24 | 📦 バージョン履歴とダウンロード 25 | 26 |
27 | 28 | ### 🌟 最新バージョン 29 | 30 | [完全なバージョン履歴を見る]([CursorHistoryDown.md](https://github.com/oslook/cursor-ai-downloads?tab=readme-ov-file)) 31 | 32 |
33 | 34 |
35 | 36 | ⚠️ **Cursorの一般的な解決策** 37 | > 1. Cursorを閉じ、アカウントからログアウトし、公式サイトの設定からアカウントを削除します(IPノードを更新:日本、シンガポール、アメリカ、香港など、低遅延を優先。必須ではありませんが条件が整えば変更してください。Windowsユーザーの場合はDNSキャッシュの更新をお勧めします:`ipconfig /flushdns`) 38 | > Cursor公式サイトで現在のアカウントを削除します 39 | > 手順:ユーザーアイコン->設定->左下のAdvanced▼->Delete Account 40 | > 41 | > 2. マシンコードリセットスクリプトを実行します。下記のスクリプトアドレスを参照してください。 42 | > 43 | > 3. アカウントを再登録し、ログインして、Cursorを開くと、正常に使用できるようになります。 44 | > 45 | > 4. 代替案:ステップ[**3**]の後でもまだ使用できない場合、またはアカウント登録に失敗したり、アカウントを削除できないなどの問題が発生した場合、これは通常、ブラウザがターゲットサイトに識別または制限されている(リスク管理)ことを意味します。この場合、Edge、Google Chrome、Firefoxなど別のブラウザを試してみてください(または、ブラウザのフィンガープリント情報を変更またはランダム化できるブラウザの使用を検討してください)。 46 | 47 | 48 | --- 49 | 50 | ⚠️ **MACアドレス変更警告** 51 | > 52 | > Macユーザーの皆様へ: このスクリプトにはMACアドレス変更機能が含まれています。以下の操作が行われます: 53 | > - ネットワークインターフェースのMACアドレスを変更します 54 | > - 変更前に元のMACアドレスをバックアップします 55 | > - この変更により一時的にネットワーク接続が影響を受ける可能性があります 56 | > - 実行中にこのステップをスキップすることができます 57 | > 58 | 59 |
60 | 🔒 自動更新機能の無効化 61 | 62 | > Cursorがサポートされていない新しいバージョンに自動的に更新されるのを防ぐために、自動更新機能を無効にすることができます。 63 | 64 | #### 方法1: 組み込みスクリプトを使用する(推奨) 65 | 66 | リセットツールを実行するとき、スクリプトは自動更新を無効にするかどうかを尋ねます: 67 | ```text 68 | [質問] Cursorの自動更新機能を無効にしますか? 69 | 0) いいえ - デフォルト設定を維持(Enterキーを押す) 70 | 1) はい - 自動更新を無効にする 71 | ``` 72 | 73 | `1`を選択して無効化操作を自動的に完了します。 74 | 75 | #### 方法2: 手動で無効化 76 | 77 | **Windows:** 78 | 1. すべてのCursorプロセスを閉じます 79 | 2. ディレクトリを削除します: `%LOCALAPPDATA%\cursor-updater` 80 | 3. 同じ名前のファイルを作成します(拡張子なし) 81 | 82 | **macOS:** 83 | ```bash 84 | # 注意: テスト済みでは、この方法はバージョン0.45.11およびそれ以前のバージョンでのみ機能します。 85 | # Cursorを閉じます 86 | pkill -f "Cursor" 87 | # app-update.ymlを空の読み取り専用ファイルに置き換えます 88 | cd /Applications/Cursor.app/Contents/Resources 89 | mv app-update.yml app-update.yml.bak 90 | touch app-update.yml 91 | chmod 444 app-update.yml 92 | 93 | # 設定 -> アプリケーション -> 更新、モードをnoneに設定します。 94 | # これを行わないと、Cursorは更新をチェックし続けます。 95 | 96 | # 注意: cursor-updaterの変更方法はもはや有効ではないかもしれません 97 | # いずれにせよ、更新ディレクトリを削除し、ブロックファイルを作成します 98 | rm -rf ~/Library/Application\ Support/Caches/cursor-updater 99 | touch ~/Library/Application\ Support/Caches/cursor-updater 100 | ``` 101 | 102 | **Linux:** 103 | ```bash 104 | # Cursorを閉じます 105 | pkill -f "Cursor" 106 | # 更新ディレクトリを削除し、ブロックファイルを作成します 107 | rm -rf ~/.config/cursor-updater 108 | touch ~/.config/cursor-updater 109 | ``` 110 | 111 | > ⚠️ **注意:** 自動更新を無効にした後、新しいバージョンを手動でダウンロードしてインストールする必要があります。新しいバージョンが互換性があることを確認した後に更新することをお勧めします。 112 | 113 |
114 | 115 | --- 116 | 117 | ### 📝 説明 118 | 119 | > これらのメッセージのいずれかに遭遇した場合: 120 | 121 | #### 問題1: 試用アカウント制限

Back To Top

122 | 123 | ```text 124 | Too many free trial accounts used on this machine. 125 | Please upgrade to pro. We have this limit in place 126 | to prevent abuse. Please let us know if you believe 127 | this is a mistake. 128 | ``` 129 | 130 | #### 問題2: APIキー制限

Back To Top

131 | 132 | ```text 133 | [New Issue] 134 | 135 | Composer relies on custom models that cannot be billed to an API key. 136 | Please disable API keys and use a Pro or Business subscription. 137 | Request ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 138 | ``` 139 | 140 | #### 問題3: 試用リクエスト制限 141 | 142 | > これは、VIP無料試用期間中に使用制限に達したことを示しています: 143 | 144 | ```text 145 | You've reached your trial request limit. 146 | ``` 147 | 148 | #### 問題4: Claude 3.7 高負荷

Back To Top

149 | 150 | ```text 151 | High Load 152 | We're experiencing high demand for Claude 3.7 Sonnet right now. Please upgrade to Pro, or switch to the 153 | 'default' model, Claude 3.5 sonnet, another model, or try again in a few moments. 154 | ``` 155 | 156 |
157 | 158 |

159 | 160 | #### 解決策 : Cursorを完全にアンインストールして再インストールする(APIキーの問題) 161 | 162 | 1. [Geek.exeアンインストーラー[無料]](https://geekuninstaller.com/download)をダウンロードします 163 | 2. Cursorアプリを完全にアンインストールします 164 | 3. Cursorアプリを再インストールします 165 | 4. 解決策1を続行します 166 | 167 |
168 | 169 |

170 | 171 | > 一時的な解決策: 172 | 173 | #### 解決策1: クイックリセット(推奨) 174 | 175 | 1. Cursorアプリケーションを閉じます 176 | 2. マシンコードリセットスクリプトを実行します(以下のインストール手順を参照) 177 | 3. Cursorを再度開いて使用を続けます 178 | 179 | #### 解決策2: アカウントの切り替え 180 | 181 | 1. ファイル -> Cursor設定 -> サインアウト 182 | 2. Cursorを閉じます 183 | 3. マシンコードリセットスクリプトを実行します 184 | 4. 新しいアカウントでログインします 185 | 186 | #### 解決策3: ネットワークの最適化 187 | 188 | 上記の解決策が機能しない場合は、次のことを試してください: 189 | 190 | - 低遅延ノードに切り替えます(推奨地域:日本、シンガポール、米国、香港) 191 | - ネットワークの安定性を確保します 192 | - ブラウザのキャッシュをクリアして再試行します 193 | 194 | #### 解決策4: Claude 3.7 アクセス問題(高負荷) 195 | 196 | Claude 3.7 Sonnetの"High Load"メッセージが表示された場合、これはCursorが特定の時間帯に無料試用アカウントの3.7モデルの使用を制限していることを示しています。次のことを試してください: 197 | 198 | 1. Gmailで作成した新しいアカウントに切り替えます。異なるIPアドレスを使用して接続することをお勧めします 199 | 2. 非ピーク時間帯にアクセスを試みます(通常、5-10 AMまたは3-7 PMの間に制限が少ないです) 200 | 3. Proにアップグレードしてアクセスを保証します 201 | 4. Claude 3.5 Sonnetを代替オプションとして使用します 202 | 203 | > 注意: Cursorがリソース配分ポリシーを調整するにつれて、これらのアクセスパターンは変更される可能性があります。 204 | 205 | ### 💻 システムサポート 206 | 207 | 208 | 209 | 217 | 225 | 234 | 235 |
210 | 211 | **Windows** ✅ 212 | 213 | - x64 (64ビット) 214 | - x86 (32ビット) 215 | 216 | 218 | 219 | **macOS** ✅ 220 | 221 | - Intel (x64) 222 | - Apple Silicon (M1/M2) 223 | 224 | 226 | 227 | **Linux** ✅ 228 | 229 | - x64 (64ビット) 230 | - x86 (32ビット) 231 | - ARM64 232 | 233 |
236 | 237 | ### 🚀 ワンクリックソリューション 238 | 239 |
240 | グローバルユーザー 241 | 242 | **macOS** 243 | 244 | ```bash 245 | # 方法2 246 | curl -fsSL https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_mac_id_modifier.sh -o ./cursor_mac_id_modifier.sh && sudo bash ./cursor_mac_id_modifier.sh && rm ./cursor_mac_id_modifier.sh 247 | ``` 248 | 249 | **Linux** 250 | 251 | ```bash 252 | curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_linux_id_modifier.sh | sudo bash 253 | ``` 254 | 255 | **Windows** 256 | 257 | ```powershell 258 | irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 259 | ``` 260 | 261 |
262 | Run Success 263 |
264 | 265 |
266 | 267 |
268 | 中国ユーザー(推奨) 269 | 270 | **macOS** 271 | 272 | ```bash 273 | curl -fsSL https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_mac_id_modifier.sh -o ./cursor_mac_id_modifier.sh && sudo bash ./cursor_mac_id_modifier.sh && rm ./cursor_mac_id_modifier.sh 274 | ``` 275 | 276 | **Linux** 277 | 278 | ```bash 279 | curl -fsSL https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_linux_id_modifier.sh | sudo bash 280 | ``` 281 | 282 | **Windows** 283 | 284 | ```powershell 285 | irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 286 | ``` 287 | 288 |
289 | 290 |
291 | Windowsターミナルの実行と構成 292 | 293 | #### Windowsで管理者ターミナルを開く方法: 294 | 295 | ##### 方法1: Win + Xショートカットを使用する 296 | ```md 297 | 1. Win + Xキーの組み合わせを押します 298 | 2. メニューから次のオプションのいずれかを選択します: 299 | - "Windows PowerShell (管理者)" 300 | - "Windows Terminal (管理者)" 301 | - "ターミナル (管理者)" 302 | (Windowsのバージョンによってオプションが異なる場合があります) 303 | ``` 304 | 305 | ##### 方法2: Win + R実行コマンドを使用する 306 | ```md 307 | 1. Win + Rキーの組み合わせを押します 308 | 2. 実行ダイアログにpowershellまたはpwshと入力します 309 | 3. Ctrl + Shift + Enterを押して管理者として実行します 310 | または開いたウィンドウに次のように入力します: Start-Process pwsh -Verb RunAs 311 | 4. 管理者ターミナルにリセットスクリプトを入力します: 312 | 313 | irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 314 | ``` 315 | 316 | ##### 方法3: 検索を使用する 317 | >![PowerShellを検索](img/pwsh_1.png) 318 | > 319 | >検索ボックスにpwshと入力し、右クリックして「管理者として実行」を選択します 320 | >![管理者として実行](img/pwsh_2.png) 321 | 322 | 管理者ターミナルにリセットスクリプトを入力します: 323 | ```powershell 324 | irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run/cursor_win_id_modifier.ps1 | iex 325 | ``` 326 | 327 | ### 🔧 PowerShellインストールガイド 328 | 329 | システムにPowerShellがインストールされていない場合は、次の方法でインストールできます: 330 | 331 | #### 方法1: Wingetを使用してインストール(推奨) 332 | 333 | 1. コマンドプロンプトまたはPowerShellを開きます 334 | 2. 次のコマンドを実行します: 335 | ```powershell 336 | winget install --id Microsoft.PowerShell --source winget 337 | ``` 338 | 339 | #### 方法2: 手動でインストール 340 | 341 | 1. システムに適したインストーラーをダウンロードします: 342 | - [PowerShell-7.4.6-win-x64.msi](https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-x64.msi)(64ビットシステム用) 343 | - [PowerShell-7.4.6-win-x86.msi](https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-x86.msi)(32ビットシステム用) 344 | - [PowerShell-7.4.6-win-arm64.msi](https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win-arm64.msi)(ARM64システム用) 345 | 346 | 2. ダウンロードしたインストーラーをダブルクリックし、インストールの指示に従います 347 | 348 | > 💡 問題が発生した場合は、[Microsoft公式インストールガイド](https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows)を参照してください 349 | 350 |
351 | 352 | #### Windowsインストール機能: 353 | 354 | - 🔍 PowerShell 7が利用可能な場合は自動的に検出して使用します 355 | - 🛡️ UACプロンプトを介して管理者権限を要求します 356 | - 📝 PS7が見つからない場合はWindows PowerShellにフォールバックします 357 | - 💡 権限昇格に失敗した場合は手動の指示を提供します 358 | 359 | これで完了です!スクリプトは次のことを行います: 360 | 361 | 1. ✨ ツールを自動的にインストールします 362 | 2. 🔄 Cursorの試用期間を即座にリセットします 363 | 364 | ### 📦 手動インストール 365 | 366 | > [リリース](https://github.com/yuaotian/go-cursor-help/releases/latest)からシステムに適したファイルをダウンロードします 367 | 368 |
369 | Windowsパッケージ 370 | 371 | - 64ビット: `cursor-id-modifier_windows_x64.exe` 372 | - 32ビット: `cursor-id-modifier_windows_x86.exe` 373 |
374 | 375 |
376 | macOSパッケージ 377 | 378 | - Intel: `cursor-id-modifier_darwin_x64_intel` 379 | - M1/M2: `cursor-id-modifier_darwin_arm64_apple_silicon` 380 |
381 | 382 |
383 | Linuxパッケージ 384 | 385 | - 64ビット: `cursor-id-modifier_linux_x64` 386 | - 32ビット: `cursor-id-modifier_linux_x86` 387 | - ARM64: `cursor-id-modifier_linux_arm64` 388 |
389 | 390 | ### 🔧 技術的詳細 391 | 392 |
393 | 構成ファイル 394 | 395 | プログラムはCursorの`storage.json`構成ファイルを変更します。場所は次のとおりです: 396 | 397 | - Windows: `%APPDATA%\Cursor\User\globalStorage\storage.json` 398 | - macOS: `~/Library/Application Support/Cursor/User/globalStorage/storage.json` 399 | - Linux: `~/.config/Cursor/User/globalStorage/storage.json` 400 |
401 | 402 |
403 | 変更されたフィールド 404 | 405 | ツールは次の新しい一意の識別子を生成します: 406 | 407 | - `telemetry.machineId` 408 | - `telemetry.macMachineId` 409 | - `telemetry.devDeviceId` 410 | - `telemetry.sqmId` 411 |
412 | 413 |
414 | 手動自動更新無効化 415 | 416 | Windowsユーザーは自動更新機能を手動で無効にすることができます: 417 | 418 | 1. すべてのCursorプロセスを閉じます 419 | 2. ディレクトリを削除します: `C:\Users\username\AppData\Local\cursor-updater` 420 | 3. 同じ名前のファイルを作成します: `cursor-updater`(拡張子なし) 421 | 422 | macOS/Linuxユーザーはシステム内で同様の`cursor-updater`ディレクトリを見つけて同じ操作を行うことができます。 423 | 424 |
425 | 426 |
427 | 安全機能 428 | 429 | - ✅ 安全なプロセス終了 430 | - ✅ アトミックファイル操作 431 | - ✅ エラーハンドリングとリカバリ 432 |
433 | 434 |
435 | レジストリ変更通知 436 | 437 | > ⚠️ **重要: このツールはWindowsレジストリを変更します** 438 | 439 | #### 変更されたレジストリ 440 | - パス: `コンピュータ\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography` 441 | - キー: `MachineGuid` 442 | 443 | #### 潜在的な影響 444 | このレジストリキーを変更すると、次のことに影響を与える可能性があります: 445 | - Windowsシステムの一意のデバイス識別 446 | - 特定のソフトウェアのデバイス認識と認証状態 447 | - ハードウェア識別に基づくシステム機能 448 | 449 | #### 安全対策 450 | 1. 自動バックアップ 451 | - 変更前に元の値が自動的にバックアップされます 452 | - バックアップ場所: `%APPDATA%\Cursor\User\globalStorage\backups` 453 | - バックアップファイル形式: `MachineGuid.backup_YYYYMMDD_HHMMSS` 454 | 455 | 2. 手動復元手順 456 | - レジストリエディタ(regedit)を開きます 457 | - 次の場所に移動します: `コンピュータ\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography` 458 | - `MachineGuid`を右クリックします 459 | - 「修正」を選択します 460 | - バックアップファイルの値を貼り付けます 461 | 462 | #### 重要な注意事項 463 | - 変更前にバックアップファイルの存在を確認します 464 | - 必要に応じてバックアップファイルを使用して元の値を復元します 465 | - レジストリの変更には管理者権限が必要です 466 |
467 | 468 | --- 469 | 470 | ### 📚 推奨読書 471 | 472 | - [Cursorの問題収集と解決策](https://mp.weixin.qq.com/s/pnJrH7Ifx4WZvseeP1fcEA) 473 | - [AIユニバーサル開発アシスタントプロンプトガイド](https://mp.weixin.qq.com/s/PRPz-qVkFJSgkuEKkTdzwg) 474 | 475 | --- 476 | 477 | ## サポート 478 | 479 |
480 | このツールが役立つと感じた場合、スパイシーグルテンのおやつ(Latiao)を買っていただけると嬉しいです~ 💁☕️ 481 | 482 | 483 | 484 | 489 | 494 | 499 | 504 | 510 | 511 | 512 |
485 | WeChat Pay
486 | WeChat Pay
487 | 要到饭咧?啊咧?啊咧?不给也没事~ 请随意打赏 488 |
490 | Alipay
491 | Alipay
492 | 如果觉得有帮助,来包辣条犒劳一下吧~ 493 |
495 | Alipay
496 | Alipay
497 | 1 Latiao = 1 AI thought cycle 498 |
500 | WeChat
501 | WeChat
502 | 二维码7天内(4月25日前)有效,过期请加微信 503 |
513 |
514 | 515 | --- 516 | 517 | ## ⭐ プロジェクト統計 518 | 519 |
520 | 521 | [![Star History Chart](https://api.star-history.com/svg?repos=yuaotian/go-cursor-help&type=Date)](https://star-history.com/#yuaotian/go-cursor-help&Date) 522 | 523 | ![Repobeats analytics image](https://repobeats.axiom.co/api/embed/ddaa9df9a94b0029ec3fad399e1c1c4e75755477.svg "Repobeats analytics image") 524 | 525 |
526 | 527 | ## 📄 ライセンス 528 | 529 |
530 | MITライセンス 531 | 532 | Copyright (c) 2024 533 | 534 | Permission is hereby granted, free of charge, to any person obtaining a copy 535 | of this software and associated documentation files (the "Software"), to deal 536 | in the Software without restriction, including without limitation the rights 537 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 538 | copies of the Software, and to permit persons to whom the Software is 539 | furnished to do so, subject to the following conditions: 540 | 541 | The above copyright notice and this permission notice shall be included in all 542 | copies or substantial portions of the Software. 543 | 544 |
545 | -------------------------------------------------------------------------------- /cmd/cursor-id-modifier/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "os/user" 10 | "runtime" 11 | "runtime/debug" 12 | "strings" 13 | 14 | "github.com/sirupsen/logrus" 15 | 16 | "github.com/yuaotian/go-cursor-help/internal/config" 17 | "github.com/yuaotian/go-cursor-help/internal/lang" 18 | "github.com/yuaotian/go-cursor-help/internal/process" 19 | "github.com/yuaotian/go-cursor-help/internal/ui" 20 | "github.com/yuaotian/go-cursor-help/pkg/idgen" 21 | ) 22 | 23 | // Global variables 24 | var ( 25 | version = "dev" 26 | setReadOnly = flag.Bool("r", false, "set storage.json to read-only mode") 27 | showVersion = flag.Bool("v", false, "show version information") 28 | log = logrus.New() 29 | ) 30 | 31 | func main() { 32 | // Place defer at the beginning of main to ensure it can catch panics from all subsequent function calls 33 | defer func() { 34 | if r := recover(); r != nil { 35 | log.Errorf("Panic recovered: %v\n", r) 36 | debug.PrintStack() 37 | waitExit() 38 | } 39 | }() 40 | 41 | handleFlags() 42 | setupLogger() 43 | 44 | username := getCurrentUser() 45 | log.Debug("Running as user:", username) 46 | 47 | // Initialize components 48 | display := ui.NewDisplay(nil) 49 | configManager := initConfigManager(username) 50 | generator := idgen.NewGenerator() 51 | processManager := process.NewManager(nil, log) 52 | 53 | // Check and handle privileges 54 | if err := handlePrivileges(display); err != nil { 55 | return 56 | } 57 | 58 | // Setup display 59 | setupDisplay(display) 60 | 61 | text := lang.GetText() 62 | 63 | // Handle Cursor processes 64 | if err := handleCursorProcesses(display, processManager); err != nil { 65 | return 66 | } 67 | 68 | // Handle configuration 69 | oldConfig := readExistingConfig(display, configManager, text) 70 | newConfig := generateNewConfig(display, generator, oldConfig, text) 71 | 72 | if err := saveConfiguration(display, configManager, newConfig); err != nil { 73 | return 74 | } 75 | 76 | // Show completion messages 77 | showCompletionMessages(display) 78 | 79 | if os.Getenv("AUTOMATED_MODE") != "1" { 80 | waitExit() 81 | } 82 | } 83 | 84 | func handleFlags() { 85 | flag.Parse() 86 | if *showVersion { 87 | fmt.Printf("Cursor ID Modifier v%s\n", version) 88 | os.Exit(0) 89 | } 90 | } 91 | 92 | func setupLogger() { 93 | log.SetFormatter(&logrus.TextFormatter{ 94 | FullTimestamp: true, 95 | DisableLevelTruncation: true, 96 | PadLevelText: true, 97 | }) 98 | log.SetLevel(logrus.InfoLevel) 99 | } 100 | 101 | func getCurrentUser() string { 102 | if username := os.Getenv("SUDO_USER"); username != "" { 103 | return username 104 | } 105 | 106 | user, err := user.Current() 107 | if err != nil { 108 | log.Fatal(err) 109 | } 110 | return user.Username 111 | } 112 | 113 | func initConfigManager(username string) *config.Manager { 114 | configManager, err := config.NewManager(username) 115 | if err != nil { 116 | log.Fatal(err) 117 | } 118 | return configManager 119 | } 120 | 121 | func handlePrivileges(display *ui.Display) error { 122 | isAdmin, err := checkAdminPrivileges() 123 | if err != nil { 124 | log.Error(err) 125 | waitExit() 126 | return err 127 | } 128 | 129 | if !isAdmin { 130 | if runtime.GOOS == "windows" { 131 | return handleWindowsPrivileges(display) 132 | } 133 | display.ShowPrivilegeError( 134 | lang.GetText().PrivilegeError, 135 | lang.GetText().RunWithSudo, 136 | lang.GetText().SudoExample, 137 | ) 138 | waitExit() 139 | return fmt.Errorf("insufficient privileges") 140 | } 141 | return nil 142 | } 143 | 144 | func handleWindowsPrivileges(display *ui.Display) error { 145 | message := "\nRequesting administrator privileges..." 146 | if lang.GetCurrentLanguage() == lang.CN { 147 | message = "\n请求管理员权限..." 148 | } 149 | fmt.Println(message) 150 | 151 | if err := selfElevate(); err != nil { 152 | log.Error(err) 153 | display.ShowPrivilegeError( 154 | lang.GetText().PrivilegeError, 155 | lang.GetText().RunAsAdmin, 156 | lang.GetText().RunWithSudo, 157 | lang.GetText().SudoExample, 158 | ) 159 | waitExit() 160 | return err 161 | } 162 | return nil 163 | } 164 | 165 | func setupDisplay(display *ui.Display) { 166 | if err := display.ClearScreen(); err != nil { 167 | log.Warn("Failed to clear screen:", err) 168 | } 169 | display.ShowLogo() 170 | fmt.Println() 171 | } 172 | 173 | func handleCursorProcesses(display *ui.Display, processManager *process.Manager) error { 174 | if os.Getenv("AUTOMATED_MODE") == "1" { 175 | log.Debug("Running in automated mode, skipping Cursor process closing") 176 | return nil 177 | } 178 | 179 | display.ShowProgress("Closing Cursor...") 180 | log.Debug("Attempting to close Cursor processes") 181 | 182 | if err := processManager.KillCursorProcesses(); err != nil { 183 | log.Error("Failed to close Cursor:", err) 184 | display.StopProgress() 185 | display.ShowError("Failed to close Cursor. Please close it manually and try again.") 186 | waitExit() 187 | return err 188 | } 189 | 190 | if processManager.IsCursorRunning() { 191 | log.Error("Cursor processes still detected after closing") 192 | display.StopProgress() 193 | display.ShowError("Failed to close Cursor completely. Please close it manually and try again.") 194 | waitExit() 195 | return fmt.Errorf("cursor still running") 196 | } 197 | 198 | log.Debug("Successfully closed all Cursor processes") 199 | display.StopProgress() 200 | fmt.Println() 201 | return nil 202 | } 203 | 204 | func readExistingConfig(display *ui.Display, configManager *config.Manager, text lang.TextResource) *config.StorageConfig { 205 | fmt.Println() 206 | display.ShowProgress(text.ReadingConfig) 207 | oldConfig, err := configManager.ReadConfig() 208 | if err != nil { 209 | log.Warn("Failed to read existing config:", err) 210 | oldConfig = nil 211 | } 212 | display.StopProgress() 213 | fmt.Println() 214 | return oldConfig 215 | } 216 | 217 | func generateNewConfig(display *ui.Display, generator *idgen.Generator, oldConfig *config.StorageConfig, text lang.TextResource) *config.StorageConfig { 218 | display.ShowProgress(text.GeneratingIds) 219 | newConfig := &config.StorageConfig{} 220 | 221 | if machineID, err := generator.GenerateMachineID(); err != nil { 222 | log.Fatal("Failed to generate machine ID:", err) 223 | } else { 224 | newConfig.TelemetryMachineId = machineID 225 | } 226 | 227 | if macMachineID, err := generator.GenerateMacMachineID(); err != nil { 228 | log.Fatal("Failed to generate MAC machine ID:", err) 229 | } else { 230 | newConfig.TelemetryMacMachineId = macMachineID 231 | } 232 | 233 | if deviceID, err := generator.GenerateDeviceID(); err != nil { 234 | log.Fatal("Failed to generate device ID:", err) 235 | } else { 236 | newConfig.TelemetryDevDeviceId = deviceID 237 | } 238 | 239 | if oldConfig != nil && oldConfig.TelemetrySqmId != "" { 240 | newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId 241 | } else if sqmID, err := generator.GenerateSQMID(); err != nil { 242 | log.Fatal("Failed to generate SQM ID:", err) 243 | } else { 244 | newConfig.TelemetrySqmId = sqmID 245 | } 246 | 247 | display.StopProgress() 248 | fmt.Println() 249 | return newConfig 250 | } 251 | 252 | func saveConfiguration(display *ui.Display, configManager *config.Manager, newConfig *config.StorageConfig) error { 253 | display.ShowProgress("Saving configuration...") 254 | if err := configManager.SaveConfig(newConfig, *setReadOnly); err != nil { 255 | log.Error(err) 256 | waitExit() 257 | return err 258 | } 259 | display.StopProgress() 260 | fmt.Println() 261 | return nil 262 | } 263 | 264 | func showCompletionMessages(display *ui.Display) { 265 | display.ShowSuccess(lang.GetText().SuccessMessage, lang.GetText().RestartMessage) 266 | fmt.Println() 267 | 268 | message := "Operation completed!" 269 | if lang.GetCurrentLanguage() == lang.CN { 270 | message = "操作完成!" 271 | } 272 | display.ShowInfo(message) 273 | } 274 | 275 | func waitExit() { 276 | fmt.Print(lang.GetText().PressEnterToExit) 277 | os.Stdout.Sync() 278 | bufio.NewReader(os.Stdin).ReadString('\n') 279 | } 280 | 281 | func checkAdminPrivileges() (bool, error) { 282 | switch runtime.GOOS { 283 | case "windows": 284 | cmd := exec.Command("net", "session") 285 | return cmd.Run() == nil, nil 286 | 287 | case "darwin", "linux": 288 | currentUser, err := user.Current() 289 | if err != nil { 290 | return false, fmt.Errorf("failed to get current user: %w", err) 291 | } 292 | return currentUser.Uid == "0", nil 293 | 294 | default: 295 | return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) 296 | } 297 | } 298 | 299 | func selfElevate() error { 300 | os.Setenv("AUTOMATED_MODE", "1") 301 | 302 | switch runtime.GOOS { 303 | case "windows": 304 | verb := "runas" 305 | exe, _ := os.Executable() 306 | cwd, _ := os.Getwd() 307 | args := strings.Join(os.Args[1:], " ") 308 | 309 | cmd := exec.Command("cmd", "/C", "start", verb, exe, args) 310 | cmd.Dir = cwd 311 | return cmd.Run() 312 | 313 | case "darwin", "linux": 314 | exe, err := os.Executable() 315 | if err != nil { 316 | return err 317 | } 318 | 319 | cmd := exec.Command("sudo", append([]string{exe}, os.Args[1:]...)...) 320 | cmd.Stdin = os.Stdin 321 | cmd.Stdout = os.Stdout 322 | cmd.Stderr = os.Stderr 323 | return cmd.Run() 324 | 325 | default: 326 | return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yuaotian/go-cursor-help 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/fatih/color v1.15.0 7 | github.com/sirupsen/logrus v1.9.3 8 | ) 9 | 10 | require ( 11 | github.com/mattn/go-colorable v0.1.13 // indirect 12 | github.com/mattn/go-isatty v0.0.17 // indirect 13 | github.com/stretchr/testify v1.10.0 // indirect 14 | golang.org/x/sys v0.13.0 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 5 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 6 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 7 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 8 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 9 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 10 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 14 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 16 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 17 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 18 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 19 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 20 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 21 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 22 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 24 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 25 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 26 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | -------------------------------------------------------------------------------- /img/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/alipay.png -------------------------------------------------------------------------------- /img/alipay_scan_pay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/alipay_scan_pay.jpg -------------------------------------------------------------------------------- /img/etc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/etc.png -------------------------------------------------------------------------------- /img/pwsh_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/pwsh_1.png -------------------------------------------------------------------------------- /img/pwsh_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/pwsh_2.png -------------------------------------------------------------------------------- /img/qun-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/qun-10.jpg -------------------------------------------------------------------------------- /img/qun-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/qun-8.png -------------------------------------------------------------------------------- /img/qun11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/qun11.jpg -------------------------------------------------------------------------------- /img/qun9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/qun9.png -------------------------------------------------------------------------------- /img/run_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/run_success.png -------------------------------------------------------------------------------- /img/wx_group.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/wx_group.jpg -------------------------------------------------------------------------------- /img/wx_group6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/wx_group6.jpg -------------------------------------------------------------------------------- /img/wx_group7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/wx_group7.jpg -------------------------------------------------------------------------------- /img/wx_me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/wx_me.png -------------------------------------------------------------------------------- /img/wx_public.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/wx_public.jpg -------------------------------------------------------------------------------- /img/wx_public_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/wx_public_2.png -------------------------------------------------------------------------------- /img/wx_zsm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/wx_zsm.jpg -------------------------------------------------------------------------------- /img/wx_zsm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuaotian/go-cursor-help/207689ff56456749e0635896d91fdba3dd20cf40/img/wx_zsm2.png -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // StorageConfig represents the storage configuration 14 | type StorageConfig struct { 15 | TelemetryMacMachineId string `json:"telemetry.macMachineId"` 16 | TelemetryMachineId string `json:"telemetry.machineId"` 17 | TelemetryDevDeviceId string `json:"telemetry.devDeviceId"` 18 | TelemetrySqmId string `json:"telemetry.sqmId"` 19 | LastModified string `json:"lastModified"` 20 | Version string `json:"version"` 21 | } 22 | 23 | // Manager handles configuration operations 24 | type Manager struct { 25 | configPath string 26 | mu sync.RWMutex 27 | } 28 | 29 | // NewManager creates a new configuration manager 30 | func NewManager(username string) (*Manager, error) { 31 | configPath, err := getConfigPath(username) 32 | if err != nil { 33 | return nil, fmt.Errorf("failed to get config path: %w", err) 34 | } 35 | return &Manager{configPath: configPath}, nil 36 | } 37 | 38 | // ReadConfig reads the existing configuration 39 | func (m *Manager) ReadConfig() (*StorageConfig, error) { 40 | m.mu.RLock() 41 | defer m.mu.RUnlock() 42 | 43 | data, err := os.ReadFile(m.configPath) 44 | if err != nil { 45 | if os.IsNotExist(err) { 46 | return nil, nil 47 | } 48 | return nil, fmt.Errorf("failed to read config file: %w", err) 49 | } 50 | 51 | var config StorageConfig 52 | if err := json.Unmarshal(data, &config); err != nil { 53 | return nil, fmt.Errorf("failed to parse config file: %w", err) 54 | } 55 | 56 | return &config, nil 57 | } 58 | 59 | // SaveConfig saves the configuration 60 | func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error { 61 | m.mu.Lock() 62 | defer m.mu.Unlock() 63 | 64 | // Ensure parent directories exist 65 | if err := os.MkdirAll(filepath.Dir(m.configPath), 0755); err != nil { 66 | return fmt.Errorf("failed to create config directory: %w", err) 67 | } 68 | 69 | // Prepare updated configuration 70 | updatedConfig := m.prepareUpdatedConfig(config) 71 | 72 | // Write configuration 73 | if err := m.writeConfigFile(updatedConfig, readOnly); err != nil { 74 | return err 75 | } 76 | 77 | return nil 78 | } 79 | 80 | // prepareUpdatedConfig merges existing config with updates 81 | func (m *Manager) prepareUpdatedConfig(config *StorageConfig) map[string]interface{} { 82 | // Read existing config 83 | originalFile := make(map[string]interface{}) 84 | if data, err := os.ReadFile(m.configPath); err == nil { 85 | json.Unmarshal(data, &originalFile) 86 | } 87 | 88 | // Update fields 89 | originalFile["telemetry.sqmId"] = config.TelemetrySqmId 90 | originalFile["telemetry.macMachineId"] = config.TelemetryMacMachineId 91 | originalFile["telemetry.machineId"] = config.TelemetryMachineId 92 | originalFile["telemetry.devDeviceId"] = config.TelemetryDevDeviceId 93 | originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339) 94 | // originalFile["version"] = "1.0.1" 95 | 96 | return originalFile 97 | } 98 | 99 | // writeConfigFile handles the atomic write of the config file 100 | func (m *Manager) writeConfigFile(config map[string]interface{}, readOnly bool) error { 101 | // Marshal with indentation 102 | content, err := json.MarshalIndent(config, "", " ") 103 | if err != nil { 104 | return fmt.Errorf("failed to marshal config: %w", err) 105 | } 106 | 107 | // Write to temporary file 108 | tmpPath := m.configPath + ".tmp" 109 | if err := os.WriteFile(tmpPath, content, 0666); err != nil { 110 | return fmt.Errorf("failed to write temporary file: %w", err) 111 | } 112 | 113 | // Set final permissions 114 | fileMode := os.FileMode(0666) 115 | if readOnly { 116 | fileMode = 0444 117 | } 118 | 119 | if err := os.Chmod(tmpPath, fileMode); err != nil { 120 | os.Remove(tmpPath) 121 | return fmt.Errorf("failed to set temporary file permissions: %w", err) 122 | } 123 | 124 | // Atomic rename 125 | if err := os.Rename(tmpPath, m.configPath); err != nil { 126 | os.Remove(tmpPath) 127 | return fmt.Errorf("failed to rename file: %w", err) 128 | } 129 | 130 | // Sync directory 131 | if dir, err := os.Open(filepath.Dir(m.configPath)); err == nil { 132 | defer dir.Close() 133 | dir.Sync() 134 | } 135 | 136 | return nil 137 | } 138 | 139 | // getConfigPath returns the path to the configuration file 140 | func getConfigPath(username string) (string, error) { 141 | var configDir string 142 | switch runtime.GOOS { 143 | case "windows": 144 | configDir = filepath.Join(os.Getenv("APPDATA"), "Cursor", "User", "globalStorage") 145 | case "darwin": 146 | configDir = filepath.Join("/Users", username, "Library", "Application Support", "Cursor", "User", "globalStorage") 147 | case "linux": 148 | configDir = filepath.Join("/home", username, ".config", "Cursor", "User", "globalStorage") 149 | default: 150 | return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS) 151 | } 152 | return filepath.Join(configDir, "storage.json"), nil 153 | } 154 | -------------------------------------------------------------------------------- /internal/lang/lang.go: -------------------------------------------------------------------------------- 1 | package lang 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | // Language represents a supported language code 11 | type Language string 12 | 13 | const ( 14 | // CN represents Chinese language 15 | CN Language = "cn" 16 | // EN represents English language 17 | EN Language = "en" 18 | ) 19 | 20 | // TextResource contains all translatable text resources 21 | type TextResource struct { 22 | // Success messages 23 | SuccessMessage string 24 | RestartMessage string 25 | 26 | // Progress messages 27 | ReadingConfig string 28 | GeneratingIds string 29 | CheckingProcesses string 30 | ClosingProcesses string 31 | ProcessesClosed string 32 | PleaseWait string 33 | 34 | // Error messages 35 | ErrorPrefix string 36 | PrivilegeError string 37 | 38 | // Instructions 39 | RunAsAdmin string 40 | RunWithSudo string 41 | SudoExample string 42 | PressEnterToExit string 43 | SetReadOnlyMessage string 44 | 45 | // Info messages 46 | ConfigLocation string 47 | } 48 | 49 | var ( 50 | currentLanguage Language 51 | currentLanguageOnce sync.Once 52 | languageMutex sync.RWMutex 53 | ) 54 | 55 | // GetCurrentLanguage returns the current language, detecting it if not already set 56 | func GetCurrentLanguage() Language { 57 | currentLanguageOnce.Do(func() { 58 | currentLanguage = detectLanguage() 59 | }) 60 | 61 | languageMutex.RLock() 62 | defer languageMutex.RUnlock() 63 | return currentLanguage 64 | } 65 | 66 | // SetLanguage sets the current language 67 | func SetLanguage(lang Language) { 68 | languageMutex.Lock() 69 | defer languageMutex.Unlock() 70 | currentLanguage = lang 71 | } 72 | 73 | // GetText returns the TextResource for the current language 74 | func GetText() TextResource { 75 | return texts[GetCurrentLanguage()] 76 | } 77 | 78 | // detectLanguage detects the system language 79 | func detectLanguage() Language { 80 | // Check environment variables first 81 | if isChineseEnvVar() { 82 | return CN 83 | } 84 | 85 | // Then check OS-specific locale 86 | if isWindows() { 87 | if isWindowsChineseLocale() { 88 | return CN 89 | } 90 | } else if isUnixChineseLocale() { 91 | return CN 92 | } 93 | 94 | return EN 95 | } 96 | 97 | func isChineseEnvVar() bool { 98 | for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} { 99 | if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") { 100 | return true 101 | } 102 | } 103 | return false 104 | } 105 | 106 | func isWindows() bool { 107 | return os.Getenv("OS") == "Windows_NT" 108 | } 109 | 110 | func isWindowsChineseLocale() bool { 111 | // Check Windows UI culture 112 | cmd := exec.Command("powershell", "-Command", 113 | "[System.Globalization.CultureInfo]::CurrentUICulture.Name") 114 | output, err := cmd.Output() 115 | if err == nil && strings.HasPrefix(strings.ToLower(strings.TrimSpace(string(output))), "zh") { 116 | return true 117 | } 118 | 119 | // Check Windows locale 120 | cmd = exec.Command("wmic", "os", "get", "locale") 121 | output, err = cmd.Output() 122 | return err == nil && strings.Contains(string(output), "2052") 123 | } 124 | 125 | func isUnixChineseLocale() bool { 126 | cmd := exec.Command("locale") 127 | output, err := cmd.Output() 128 | return err == nil && strings.Contains(strings.ToLower(string(output)), "zh_cn") 129 | } 130 | 131 | // texts contains all translations 132 | var texts = map[Language]TextResource{ 133 | CN: { 134 | // Success messages 135 | SuccessMessage: "[√] 配置文件已成功更新!", 136 | RestartMessage: "[!] 请手动重启 Cursor 以使更新生效", 137 | 138 | // Progress messages 139 | ReadingConfig: "正在读取配置文件...", 140 | GeneratingIds: "正在生成新的标识符...", 141 | CheckingProcesses: "正在检查运行中的 Cursor 实例...", 142 | ClosingProcesses: "正在关闭 Cursor 实例...", 143 | ProcessesClosed: "所有 Cursor 实例已关闭", 144 | PleaseWait: "请稍候...", 145 | 146 | // Error messages 147 | ErrorPrefix: "程序发生严重错误: %v", 148 | PrivilegeError: "\n[!] 错误:需要管理员权限", 149 | 150 | // Instructions 151 | RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」", 152 | RunWithSudo: "请使用 sudo 命令运行此程序", 153 | SudoExample: "示例: sudo %s", 154 | PressEnterToExit: "\n按回车键退出程序...", 155 | SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题", 156 | 157 | // Info messages 158 | ConfigLocation: "配置文件位置:", 159 | }, 160 | EN: { 161 | // Success messages 162 | SuccessMessage: "[√] Configuration file updated successfully!", 163 | RestartMessage: "[!] Please restart Cursor manually for changes to take effect", 164 | 165 | // Progress messages 166 | ReadingConfig: "Reading configuration file...", 167 | GeneratingIds: "Generating new identifiers...", 168 | CheckingProcesses: "Checking for running Cursor instances...", 169 | ClosingProcesses: "Closing Cursor instances...", 170 | ProcessesClosed: "All Cursor instances have been closed", 171 | PleaseWait: "Please wait...", 172 | 173 | // Error messages 174 | ErrorPrefix: "Program encountered a serious error: %v", 175 | PrivilegeError: "\n[!] Error: Administrator privileges required", 176 | 177 | // Instructions 178 | RunAsAdmin: "Please right-click and select 'Run as Administrator'", 179 | RunWithSudo: "Please run this program with sudo", 180 | SudoExample: "Example: sudo %s", 181 | PressEnterToExit: "\nPress Enter to exit...", 182 | SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records", 183 | 184 | // Info messages 185 | ConfigLocation: "Config file location:", 186 | }, 187 | } 188 | -------------------------------------------------------------------------------- /internal/process/manager.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "runtime" 7 | "strings" 8 | "time" 9 | 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | // Config holds process manager configuration 14 | type Config struct { 15 | MaxAttempts int // Maximum number of attempts to kill processes 16 | RetryDelay time.Duration // Delay between retry attempts 17 | ProcessPatterns []string // Process names to look for 18 | } 19 | 20 | // DefaultConfig returns the default configuration 21 | func DefaultConfig() *Config { 22 | return &Config{ 23 | MaxAttempts: 3, 24 | RetryDelay: 2 * time.Second, 25 | ProcessPatterns: []string{ 26 | "Cursor.exe", // Windows executable 27 | "Cursor ", // Linux/macOS executable with space 28 | "cursor ", // Linux/macOS executable lowercase with space 29 | "cursor", // Linux/macOS executable lowercase 30 | "Cursor", // Linux/macOS executable 31 | "*cursor*", // Any process containing cursor 32 | "*Cursor*", // Any process containing Cursor 33 | }, 34 | } 35 | } 36 | 37 | // Manager handles process-related operations 38 | type Manager struct { 39 | config *Config 40 | log *logrus.Logger 41 | } 42 | 43 | // NewManager creates a new process manager with optional config and logger 44 | func NewManager(config *Config, log *logrus.Logger) *Manager { 45 | if config == nil { 46 | config = DefaultConfig() 47 | } 48 | if log == nil { 49 | log = logrus.New() 50 | } 51 | return &Manager{ 52 | config: config, 53 | log: log, 54 | } 55 | } 56 | 57 | // IsCursorRunning checks if any Cursor process is currently running 58 | func (m *Manager) IsCursorRunning() bool { 59 | processes, err := m.getCursorProcesses() 60 | if err != nil { 61 | m.log.Warn("Failed to get Cursor processes:", err) 62 | return false 63 | } 64 | return len(processes) > 0 65 | } 66 | 67 | // KillCursorProcesses attempts to kill all running Cursor processes 68 | func (m *Manager) KillCursorProcesses() error { 69 | for attempt := 1; attempt <= m.config.MaxAttempts; attempt++ { 70 | processes, err := m.getCursorProcesses() 71 | if err != nil { 72 | return fmt.Errorf("failed to get processes: %w", err) 73 | } 74 | 75 | if len(processes) == 0 { 76 | return nil 77 | } 78 | 79 | // Try graceful shutdown first on Windows 80 | if runtime.GOOS == "windows" { 81 | for _, pid := range processes { 82 | exec.Command("taskkill", "/PID", pid).Run() 83 | time.Sleep(500 * time.Millisecond) 84 | } 85 | } 86 | 87 | // Force kill remaining processes 88 | remainingProcesses, _ := m.getCursorProcesses() 89 | for _, pid := range remainingProcesses { 90 | m.killProcess(pid) 91 | } 92 | 93 | time.Sleep(m.config.RetryDelay) 94 | 95 | if processes, _ := m.getCursorProcesses(); len(processes) == 0 { 96 | return nil 97 | } 98 | } 99 | 100 | return nil 101 | } 102 | 103 | // getCursorProcesses returns PIDs of running Cursor processes 104 | func (m *Manager) getCursorProcesses() ([]string, error) { 105 | cmd := m.getProcessListCommand() 106 | if cmd == nil { 107 | return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) 108 | } 109 | 110 | output, err := cmd.Output() 111 | if err != nil { 112 | return nil, fmt.Errorf("failed to execute command: %w", err) 113 | } 114 | 115 | return m.parseProcessList(string(output)), nil 116 | } 117 | 118 | // getProcessListCommand returns the appropriate command to list processes based on OS 119 | func (m *Manager) getProcessListCommand() *exec.Cmd { 120 | switch runtime.GOOS { 121 | case "windows": 122 | return exec.Command("tasklist", "/FO", "CSV", "/NH") 123 | case "darwin": 124 | return exec.Command("ps", "-ax") 125 | case "linux": 126 | return exec.Command("ps", "-A") 127 | default: 128 | return nil 129 | } 130 | } 131 | 132 | // parseProcessList extracts Cursor process PIDs from process list output 133 | func (m *Manager) parseProcessList(output string) []string { 134 | var processes []string 135 | for _, line := range strings.Split(output, "\n") { 136 | lowerLine := strings.ToLower(line) 137 | 138 | if m.isOwnProcess(lowerLine) { 139 | continue 140 | } 141 | 142 | if pid := m.findCursorProcess(line, lowerLine); pid != "" { 143 | processes = append(processes, pid) 144 | } 145 | } 146 | return processes 147 | } 148 | 149 | // isOwnProcess checks if the process belongs to this application 150 | func (m *Manager) isOwnProcess(line string) bool { 151 | return strings.Contains(line, "cursor-id-modifier") || 152 | strings.Contains(line, "cursor-helper") 153 | } 154 | 155 | // findCursorProcess checks if a process line matches Cursor patterns and returns its PID 156 | func (m *Manager) findCursorProcess(line, lowerLine string) string { 157 | for _, pattern := range m.config.ProcessPatterns { 158 | if m.matchPattern(lowerLine, strings.ToLower(pattern)) { 159 | return m.extractPID(line) 160 | } 161 | } 162 | return "" 163 | } 164 | 165 | // matchPattern checks if a line matches a pattern, supporting wildcards 166 | func (m *Manager) matchPattern(line, pattern string) bool { 167 | switch { 168 | case strings.HasPrefix(pattern, "*") && strings.HasSuffix(pattern, "*"): 169 | search := pattern[1 : len(pattern)-1] 170 | return strings.Contains(line, search) 171 | case strings.HasPrefix(pattern, "*"): 172 | return strings.HasSuffix(line, pattern[1:]) 173 | case strings.HasSuffix(pattern, "*"): 174 | return strings.HasPrefix(line, pattern[:len(pattern)-1]) 175 | default: 176 | return line == pattern 177 | } 178 | } 179 | 180 | // extractPID extracts process ID from a process list line based on OS format 181 | func (m *Manager) extractPID(line string) string { 182 | switch runtime.GOOS { 183 | case "windows": 184 | parts := strings.Split(line, ",") 185 | if len(parts) >= 2 { 186 | return strings.Trim(parts[1], "\"") 187 | } 188 | case "darwin", "linux": 189 | parts := strings.Fields(line) 190 | if len(parts) >= 1 { 191 | return parts[0] 192 | } 193 | } 194 | return "" 195 | } 196 | 197 | // killProcess forcefully terminates a process by PID 198 | func (m *Manager) killProcess(pid string) error { 199 | cmd := m.getKillCommand(pid) 200 | if cmd == nil { 201 | return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) 202 | } 203 | return cmd.Run() 204 | } 205 | 206 | // getKillCommand returns the appropriate command to kill a process based on OS 207 | func (m *Manager) getKillCommand(pid string) *exec.Cmd { 208 | switch runtime.GOOS { 209 | case "windows": 210 | return exec.Command("taskkill", "/F", "/PID", pid) 211 | case "darwin", "linux": 212 | return exec.Command("kill", "-9", pid) 213 | default: 214 | return nil 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /internal/ui/display.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "runtime" 8 | "strings" 9 | 10 | "github.com/fatih/color" 11 | ) 12 | 13 | // Display handles UI operations for terminal output 14 | type Display struct { 15 | spinner *Spinner 16 | } 17 | 18 | // NewDisplay creates a new display instance with an optional spinner 19 | func NewDisplay(spinner *Spinner) *Display { 20 | if spinner == nil { 21 | spinner = NewSpinner(nil) 22 | } 23 | return &Display{spinner: spinner} 24 | } 25 | 26 | // Terminal Operations 27 | 28 | // ClearScreen clears the terminal screen based on OS 29 | func (d *Display) ClearScreen() error { 30 | var cmd *exec.Cmd 31 | switch runtime.GOOS { 32 | case "windows": 33 | cmd = exec.Command("cmd", "/c", "cls") 34 | default: 35 | cmd = exec.Command("clear") 36 | } 37 | cmd.Stdout = os.Stdout 38 | return cmd.Run() 39 | } 40 | 41 | // Progress Indicator 42 | 43 | // ShowProgress displays a progress message with a spinner 44 | func (d *Display) ShowProgress(message string) { 45 | d.spinner.SetMessage(message) 46 | d.spinner.Start() 47 | } 48 | 49 | // StopProgress stops the progress spinner 50 | func (d *Display) StopProgress() { 51 | d.spinner.Stop() 52 | } 53 | 54 | // Message Display 55 | 56 | // ShowSuccess displays success messages in green 57 | func (d *Display) ShowSuccess(messages ...string) { 58 | green := color.New(color.FgGreen) 59 | for _, msg := range messages { 60 | green.Println(msg) 61 | } 62 | } 63 | 64 | // ShowInfo displays an info message in cyan 65 | func (d *Display) ShowInfo(message string) { 66 | cyan := color.New(color.FgCyan) 67 | cyan.Println(message) 68 | } 69 | 70 | // ShowError displays an error message in red 71 | func (d *Display) ShowError(message string) { 72 | red := color.New(color.FgRed) 73 | red.Println(message) 74 | } 75 | 76 | // ShowPrivilegeError displays privilege error messages with instructions 77 | func (d *Display) ShowPrivilegeError(messages ...string) { 78 | red := color.New(color.FgRed, color.Bold) 79 | yellow := color.New(color.FgYellow) 80 | 81 | // Main error message 82 | red.Println(messages[0]) 83 | fmt.Println() 84 | 85 | // Additional instructions 86 | for _, msg := range messages[1:] { 87 | if strings.Contains(msg, "%s") { 88 | exe, _ := os.Executable() 89 | yellow.Printf(msg+"\n", exe) 90 | } else { 91 | yellow.Println(msg) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /internal/ui/logo.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | ) 6 | 7 | const cyberpunkLogo = ` 8 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ 9 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ 10 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ 11 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ 12 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ 13 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ 14 | ` 15 | 16 | // ShowLogo displays the application logo 17 | func (d *Display) ShowLogo() { 18 | cyan := color.New(color.FgCyan, color.Bold) 19 | cyan.Println(cyberpunkLogo) 20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/spinner.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/fatih/color" 9 | ) 10 | 11 | // SpinnerConfig defines spinner configuration 12 | type SpinnerConfig struct { 13 | Frames []string // Animation frames for the spinner 14 | Delay time.Duration // Delay between frame updates 15 | } 16 | 17 | // DefaultSpinnerConfig returns the default spinner configuration 18 | func DefaultSpinnerConfig() *SpinnerConfig { 19 | return &SpinnerConfig{ 20 | Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, 21 | Delay: 100 * time.Millisecond, 22 | } 23 | } 24 | 25 | // Spinner represents a progress spinner 26 | type Spinner struct { 27 | config *SpinnerConfig 28 | message string 29 | current int 30 | active bool 31 | stopCh chan struct{} 32 | mu sync.RWMutex 33 | } 34 | 35 | // NewSpinner creates a new spinner with the given configuration 36 | func NewSpinner(config *SpinnerConfig) *Spinner { 37 | if config == nil { 38 | config = DefaultSpinnerConfig() 39 | } 40 | return &Spinner{ 41 | config: config, 42 | stopCh: make(chan struct{}), 43 | } 44 | } 45 | 46 | // State management 47 | 48 | // SetMessage sets the spinner message 49 | func (s *Spinner) SetMessage(message string) { 50 | s.mu.Lock() 51 | defer s.mu.Unlock() 52 | s.message = message 53 | } 54 | 55 | // IsActive returns whether the spinner is currently active 56 | func (s *Spinner) IsActive() bool { 57 | s.mu.RLock() 58 | defer s.mu.RUnlock() 59 | return s.active 60 | } 61 | 62 | // Control methods 63 | 64 | // Start begins the spinner animation 65 | func (s *Spinner) Start() { 66 | s.mu.Lock() 67 | if s.active { 68 | s.mu.Unlock() 69 | return 70 | } 71 | s.active = true 72 | s.mu.Unlock() 73 | 74 | go s.run() 75 | } 76 | 77 | // Stop halts the spinner animation 78 | func (s *Spinner) Stop() { 79 | s.mu.Lock() 80 | defer s.mu.Unlock() 81 | 82 | if !s.active { 83 | return 84 | } 85 | 86 | s.active = false 87 | close(s.stopCh) 88 | s.stopCh = make(chan struct{}) 89 | fmt.Print("\r") // Clear the spinner line 90 | } 91 | 92 | // Internal methods 93 | 94 | func (s *Spinner) run() { 95 | ticker := time.NewTicker(s.config.Delay) 96 | defer ticker.Stop() 97 | 98 | cyan := color.New(color.FgCyan, color.Bold) 99 | message := s.message 100 | 101 | // Print initial state 102 | fmt.Printf("\r %s %s", cyan.Sprint(s.config.Frames[0]), message) 103 | 104 | for { 105 | select { 106 | case <-s.stopCh: 107 | return 108 | case <-ticker.C: 109 | s.mu.RLock() 110 | if !s.active { 111 | s.mu.RUnlock() 112 | return 113 | } 114 | frame := s.config.Frames[s.current%len(s.config.Frames)] 115 | s.current++ 116 | s.mu.RUnlock() 117 | 118 | fmt.Printf("\r %s", cyan.Sprint(frame)) 119 | fmt.Printf("\033[%dG%s", 4, message) // Move cursor and print message 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pkg/idgen/generator.go: -------------------------------------------------------------------------------- 1 | package idgen 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/hex" 6 | "fmt" 7 | "sync" 8 | ) 9 | 10 | // Generator handles secure ID generation for machines and devices 11 | type Generator struct { 12 | bufferPool sync.Pool 13 | } 14 | 15 | // NewGenerator creates a new ID generator 16 | func NewGenerator() *Generator { 17 | return &Generator{ 18 | bufferPool: sync.Pool{ 19 | New: func() interface{} { 20 | return make([]byte, 64) 21 | }, 22 | }, 23 | } 24 | } 25 | 26 | // Constants for ID generation 27 | const ( 28 | machineIDPrefix = "auth0|user_" 29 | uuidFormat = "%s-%s-%s-%s-%s" 30 | ) 31 | 32 | // generateRandomHex generates a random hex string of specified length 33 | func (g *Generator) generateRandomHex(length int) (string, error) { 34 | buffer := g.bufferPool.Get().([]byte) 35 | defer g.bufferPool.Put(buffer) 36 | 37 | if _, err := rand.Read(buffer[:length]); err != nil { 38 | return "", fmt.Errorf("failed to generate random bytes: %w", err) 39 | } 40 | return hex.EncodeToString(buffer[:length]), nil 41 | } 42 | 43 | // GenerateMachineID generates a new machine ID with auth0|user_ prefix 44 | func (g *Generator) GenerateMachineID() (string, error) { 45 | randomPart, err := g.generateRandomHex(32) // 生成64字符的十六进制 46 | if err != nil { 47 | return "", err 48 | } 49 | 50 | return fmt.Sprintf("%x%s", []byte(machineIDPrefix), randomPart), nil 51 | } 52 | 53 | // GenerateMacMachineID generates a new 64-byte MAC machine ID 54 | func (g *Generator) GenerateMacMachineID() (string, error) { 55 | return g.generateRandomHex(32) // 生成64字符的十六进制 56 | } 57 | 58 | // GenerateDeviceID generates a new device ID in UUID format 59 | func (g *Generator) GenerateDeviceID() (string, error) { 60 | id, err := g.generateRandomHex(16) 61 | if err != nil { 62 | return "", err 63 | } 64 | return fmt.Sprintf(uuidFormat, 65 | id[0:8], id[8:12], id[12:16], id[16:20], id[20:32]), nil 66 | } 67 | 68 | // GenerateSQMID generates a new SQM ID in UUID format (with braces) 69 | func (g *Generator) GenerateSQMID() (string, error) { 70 | id, err := g.GenerateDeviceID() 71 | if err != nil { 72 | return "", err 73 | } 74 | return fmt.Sprintf("{%s}", id), nil 75 | } 76 | 77 | // ValidateID validates the format of various ID types 78 | func (g *Generator) ValidateID(id string, idType string) bool { 79 | switch idType { 80 | case "machineID", "macMachineID": 81 | return len(id) == 64 && isHexString(id) 82 | case "deviceID": 83 | return isValidUUID(id) 84 | case "sqmID": 85 | if len(id) < 2 || id[0] != '{' || id[len(id)-1] != '}' { 86 | return false 87 | } 88 | return isValidUUID(id[1 : len(id)-1]) 89 | default: 90 | return false 91 | } 92 | } 93 | 94 | // Helper functions 95 | func isHexString(s string) bool { 96 | _, err := hex.DecodeString(s) 97 | return err == nil 98 | } 99 | 100 | func isValidUUID(uuid string) bool { 101 | if len(uuid) != 36 { 102 | return false 103 | } 104 | for i, r := range uuid { 105 | if i == 8 || i == 13 || i == 18 || i == 23 { 106 | if r != '-' { 107 | return false 108 | } 109 | continue 110 | } 111 | if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) { 112 | return false 113 | } 114 | } 115 | return true 116 | } 117 | -------------------------------------------------------------------------------- /process_cursor_links.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from dataclasses import dataclass 3 | from typing import List 4 | import json 5 | 6 | @dataclass 7 | class CursorVersion: 8 | version: str 9 | build_id: str 10 | 11 | def get_download_links(self) -> dict: 12 | base_url = f"https://downloader.cursor.sh/builds/{self.build_id}" 13 | return { 14 | "windows": { 15 | "x64": f"{base_url}/windows/nsis/x64", 16 | "arm64": f"{base_url}/windows/nsis/arm64" 17 | }, 18 | "mac": { 19 | "universal": f"{base_url}/mac/installer/universal", 20 | "arm64": f"{base_url}/mac/installer/arm64", 21 | "x64": f"{base_url}/mac/installer/x64" 22 | }, 23 | "linux": { 24 | "x64": f"{base_url}/linux/appImage/x64" 25 | } 26 | } 27 | 28 | def parse_versions(data: str) -> List[CursorVersion]: 29 | versions = [] 30 | for line in data.strip().split('\n'): 31 | if not line: 32 | continue 33 | version, build_id = line.strip().split(',') 34 | versions.append(CursorVersion(version, build_id)) 35 | return versions 36 | 37 | def generate_markdown(versions: List[CursorVersion]) -> str: 38 | md = """# 🖥️ Windows 39 | 40 | ## x64 41 |
42 | 📦 Windows x64 安装包 43 | 44 | | 版本 | 下载链接 | 45 | |------|----------| 46 | """ 47 | 48 | # Windows x64 49 | for version in versions: 50 | links = version.get_download_links() 51 | md += f"| {version.version} | [下载]({links['windows']['x64']}) |\n" 52 | 53 | md += """ 54 |
55 | 56 | ## ARM64 57 |
58 | 📱 Windows ARM64 安装包 59 | 60 | | 版本 | 下载链接 | 61 | |------|----------| 62 | """ 63 | 64 | # Windows ARM64 65 | for version in versions: 66 | links = version.get_download_links() 67 | md += f"| {version.version} | [下载]({links['windows']['arm64']}) |\n" 68 | 69 | md += """ 70 |
71 | 72 | # 🍎 macOS 73 | 74 | ## Universal 75 |
76 | 🎯 macOS Universal 安装包 77 | 78 | | 版本 | 下载链接 | 79 | |------|----------| 80 | """ 81 | 82 | # macOS Universal 83 | for version in versions: 84 | links = version.get_download_links() 85 | md += f"| {version.version} | [下载]({links['mac']['universal']}) |\n" 86 | 87 | md += """ 88 |
89 | 90 | ## ARM64 91 |
92 | 💪 macOS ARM64 安装包 93 | 94 | | 版本 | 下载链接 | 95 | |------|----------| 96 | """ 97 | 98 | # macOS ARM64 99 | for version in versions: 100 | links = version.get_download_links() 101 | md += f"| {version.version} | [下载]({links['mac']['arm64']}) |\n" 102 | 103 | md += """ 104 |
105 | 106 | ## Intel 107 |
108 | 💻 macOS Intel 安装包 109 | 110 | | 版本 | 下载链接 | 111 | |------|----------| 112 | """ 113 | 114 | # macOS Intel 115 | for version in versions: 116 | links = version.get_download_links() 117 | md += f"| {version.version} | [下载]({links['mac']['x64']}) |\n" 118 | 119 | md += """ 120 |
121 | 122 | # 🐧 Linux 123 | 124 | ## x64 125 |
126 | 🎮 Linux x64 AppImage 127 | 128 | | 版本 | 下载链接 | 129 | |------|----------| 130 | """ 131 | 132 | # Linux x64 133 | for version in versions: 134 | links = version.get_download_links() 135 | md += f"| {version.version} | [下载]({links['linux']['x64']}) |\n" 136 | 137 | md += """ 138 |
139 | 140 | 185 | """ 186 | return md 187 | 188 | def main(): 189 | # 示例数据 190 | data = """ 191 | 0.45.11,250207y6nbaw5qc 192 | 0.45.10,250205buadkzpea 193 | 0.45.9,250202tgstl42dt 194 | 0.45.8,250201b44xw1x2k 195 | 0.45.7,250130nr6eorv84 196 | 0.45.6,25013021lv9say3 197 | 0.45.5,250128loaeyulq8 198 | 0.45.4,250126vgr3vztvj 199 | 0.45.3,250124b0rcj0qql 200 | 0.45.2,250123mhituoa6o 201 | 0.45.1,2501213ljml5byg 202 | 0.45.0,250120dh9ezx9pg 203 | 0.44.11,250103fqxdt5u9z 204 | 0.44.10,250102ys80vtnud 205 | 0.44.9,2412268nc6pfzgo 206 | 0.44.8,241222ooktny8mh 207 | 0.44.7,2412219nhracv01 208 | 0.44.6,2412214pmryneua 209 | 0.44.5,241220s3ux0e1tv 210 | 0.44.4,241219117fcvexy 211 | 0.44.3,241218sybfbogmq 212 | 0.44.2,241218ntls52u8v 213 | 0.44.0,2412187f9v0nffu 214 | 0.43.6,241206z7j6me2e2 215 | 0.43.5,241127pdg4cnbu2 216 | 0.43.4,241126w13goyvrs 217 | 0.43.3,2411246yqzx1jmm 218 | 0.43.1,241124gsiwb66nc 219 | 0.42.5,24111460bf2loz1 220 | 0.42.4,2410291z3bdg1dy 221 | 0.42.3,241016kxu9umuir 222 | 0.42.2,2410127mj66lvaq 223 | 0.42.1,241011i66p9fuvm 224 | 0.42.0,241009fij7nohn5 225 | 0.41.3,240925fkhcqg263 226 | 0.41.2,240921llnho65ov 227 | 0.41.1,2409189xe3envg5 228 | 0.40.4,2409052yfcjagw2 229 | 0.40.3,240829epqamqp7h 230 | 0.40.2,240828c021k3aib 231 | 0.40.1,2408245thnycuzj 232 | 0.40.0,24082202sreugb2 233 | 0.39.6,240819ih4ta2fye 234 | 0.39.5,240814y9rhzmu7h 235 | 0.39.4,240810elmeg3seq 236 | 0.39.3,2408092hoyaxt9m 237 | 0.39.2,240808phaxh4b5r 238 | 0.39.1,240807g919tr4ly 239 | 0.39.0,240802cdixtv9a6 240 | 0.38.1,240725f0ti25os7 241 | 0.38.0,240723790oxe4a2 242 | 0.37.1,240714yrr3gmv3k 243 | 0.36.2,2407077n6pzboby 244 | 0.36.1,240706uekt2eaft 245 | 0.36.0,240703xqkjv5aqa 246 | 0.35.1,240621pc2f7rl8a 247 | 0.35.0,240608cv11mfsjl 248 | 0.34.6,240606kgzq24cfb 249 | 0.34.6,240605r495newcf 250 | 0.34.5,240602rq6xovt3a 251 | 0.34.4,2406014h0rgjghe 252 | 0.34.3,240529baisuyd2e 253 | 0.34.2,240528whh1qyo9h 254 | 0.34.1,24052838ygfselt 255 | 0.34.0,240527xus72jmkj 256 | 0.33.4,240511kb8wt1tms 257 | 0.33.3,2405103lx8342ta 258 | 0.33.2,240510dwmw395qe 259 | 0.33.1,2405039a9h2fqc9 260 | 0.33.0,240503hyjsnhazo 261 | 0.32.8,240428d499o6zja 262 | 0.32.7,240427w5guozr0l 263 | 0.32.2,240417ab4wag7sx 264 | 0.32.1,2404152czor73fk 265 | 0.32.0,240412ugli06ue0 266 | 0.31.3,240402rq154jw46 267 | 0.31.1,240402pkwfm2ps6 268 | 0.31.0,2404018j7z0xv2g 269 | 0.30.5,240327tmd2ozdc7 270 | 0.30.4,240325dezy8ziab 271 | 0.30.3,2403229gtuhto9g 272 | 0.30.2,240322gzqjm3p0d 273 | 0.30.1,2403212w1ejubt8 274 | 0.30.0,240320tpx86e7hk 275 | 0.29.1,2403027twmz0d1t 276 | 0.29.0,240301kpqvacw2h 277 | 0.28.1,240226tstim4evd 278 | 0.28.0,240224g2d7jazcq 279 | 0.27.4,240219qdbagglqz 280 | 0.27.3,240218dxhc6y8os 281 | 0.27.2,240216kkzl9nhxi 282 | 0.27.1,240215l4ooehnyl 283 | 0.27.0,240215at6ewkd59 284 | 0.26.2,240212o6r9qxtcg 285 | 0.26.1,2402107t904hing 286 | 0.26.0,240210k8is5xr6v 287 | 0.25.3,240207aacboj1k8 288 | 0.25.2,240206p3708uc9z 289 | 0.25.1,2402033t030rprh 290 | 0.25.0,240203kh86t91q8 291 | 0.24.4,240129iecm3e33w 292 | 0.24.3,2401289dx79qsc0 293 | 0.24.1,240127cad17436d 294 | 0.24.0,240126wp9irhmza 295 | 0.23.9,240124dsmraeml3 296 | 0.23.8,240123fnn1hj1fg 297 | 0.23.7,240123xsfe7ywcv 298 | 0.23.6,240121m1740elox 299 | 0.23.5,2401215utj6tx6q 300 | 0.23.4,240121f4qy6ba2y 301 | 0.23.3,2401201und3ytom 302 | 0.23.2,240120an2k2hf1i 303 | 0.23.1,240119fgzxwudn9 304 | 0.22.2,24011721vsch1l1 305 | 0.22.1,2401083eyk8kmzc 306 | 0.22.0,240107qk62kvva3 307 | 0.21.1,231230h0vi6srww 308 | 0.21.0,231229ezidnxiu3 309 | 0.20.2,231219aksf83aad 310 | 0.20.1,231218ywfaxax09 311 | 0.20.0,231216nsyfew5j1 312 | 0.19.1,2312156z2ric57n 313 | 0.19.0,231214per9qal2p 314 | 0.18.8,2312098ffjr3ign 315 | 0.18.7,23120880aolip2i 316 | 0.18.6,231207ueqazwde8 317 | 0.18.5,231206jzy2n2sbi 318 | 0.18.4,2312033zjv5fqai 319 | 0.18.3,231203k2vnkxq2m 320 | 0.18.1,23120176kaer07t 321 | 0.17.0,231127p7iyxn8rg 322 | 0.16.0,231116rek2xuq6a 323 | 0.15.5,231115a5mv63u9f 324 | 0.15.4,23111469e1i3xyi 325 | 0.15.3,231113b0yv3uqem 326 | 0.15.2,231113ah0kuf3pf 327 | 0.15.1,231111yanyyovap 328 | 0.15.0,231110mdkomczmw 329 | 0.14.1,231109xitrgihlk 330 | 0.14.0,231102m6tuamwbx 331 | 0.13.4,231029rso7pso8l 332 | 0.13.3,231025uihnjkh9v 333 | 0.13.2,231024w4iv7xlm6 334 | 0.13.1,231022f3j0ubckv 335 | 0.13.0,231022ptw6i4j42 336 | 0.12.3,231008c5ursm0oj""" 337 | 338 | versions = parse_versions(data) 339 | 340 | # 生成 Markdown 文件 341 | markdown_content = generate_markdown(versions) 342 | with open('Cursor历史.md', 'w', encoding='utf-8') as f: 343 | f.write(markdown_content) 344 | 345 | # 创建结果数据结构 346 | result = { 347 | "versions": [] 348 | } 349 | 350 | # 处理每个版本 351 | for version in versions: 352 | version_info = { 353 | "version": version.version, 354 | "build_id": version.build_id, 355 | "downloads": version.get_download_links() 356 | } 357 | result["versions"].append(version_info) 358 | 359 | # 保存为JSON文件 360 | with open('cursor_downloads.json', 'w', encoding='utf-8') as f: 361 | json.dump(result, f, indent=2, ensure_ascii=False) 362 | 363 | # 同时生成CSV格式的下载链接 364 | with open('cursor_downloads.csv', 'w', newline='', encoding='utf-8') as f: 365 | writer = csv.writer(f) 366 | writer.writerow(['Version', 'Platform', 'Architecture', 'Download URL']) 367 | 368 | for version in versions: 369 | links = version.get_download_links() 370 | for platform, archs in links.items(): 371 | for arch, url in archs.items(): 372 | writer.writerow([version.version, platform, arch, url]) 373 | 374 | if __name__ == "__main__": 375 | main() -------------------------------------------------------------------------------- /scripts/build_all.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal EnableDelayedExpansion 3 | 4 | :: Build optimization flags 5 | set "OPTIMIZATION_FLAGS=-trimpath -ldflags=\"-s -w\"" 6 | set "BUILD_JOBS=4" 7 | 8 | :: Messages / 消息 9 | set "EN_MESSAGES[0]=Starting build process for version" 10 | set "EN_MESSAGES[1]=Using optimization flags:" 11 | set "EN_MESSAGES[2]=Cleaning old builds..." 12 | set "EN_MESSAGES[3]=Cleanup completed" 13 | set "EN_MESSAGES[4]=Starting builds for all platforms..." 14 | set "EN_MESSAGES[5]=Building for" 15 | set "EN_MESSAGES[6]=Build successful:" 16 | set "EN_MESSAGES[7]=All builds completed!" 17 | 18 | :: Colors 19 | set "GREEN=[32m" 20 | set "RED=[31m" 21 | set "RESET=[0m" 22 | 23 | :: Cleanup function 24 | :cleanup 25 | if exist "..\bin" ( 26 | rd /s /q "..\bin" 27 | echo %GREEN%!EN_MESSAGES[3]!%RESET% 28 | ) 29 | mkdir "..\bin" 2>nul 30 | 31 | :: Build function with optimizations 32 | :build 33 | set "os=%~1" 34 | set "arch=%~2" 35 | set "ext=" 36 | if "%os%"=="windows" set "ext=.exe" 37 | 38 | echo %GREEN%!EN_MESSAGES[5]! %os%/%arch%%RESET% 39 | 40 | set "CGO_ENABLED=0" 41 | set "GOOS=%os%" 42 | set "GOARCH=%arch%" 43 | 44 | 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" 45 | exit /b 0 46 | 47 | :: Main execution 48 | echo %GREEN%!EN_MESSAGES[0]!%RESET% 49 | echo %GREEN%!EN_MESSAGES[1]! %OPTIMIZATION_FLAGS%%RESET% 50 | 51 | call :cleanup 52 | 53 | echo %GREEN%!EN_MESSAGES[4]!%RESET% 54 | 55 | :: Start builds in parallel 56 | set "pending=0" 57 | for %%o in (windows linux darwin) do ( 58 | for %%a in (amd64 386) do ( 59 | call :build %%o %%a 60 | set /a "pending+=1" 61 | if !pending! geq %BUILD_JOBS% ( 62 | timeout /t 1 /nobreak >nul 63 | set "pending=0" 64 | ) 65 | ) 66 | ) 67 | 68 | :: Wait for all builds to complete 69 | :wait_builds 70 | timeout /t 2 /nobreak >nul 71 | tasklist /fi "IMAGENAME eq go.exe" 2>nul | find "go.exe" >nul 72 | if not errorlevel 1 goto wait_builds 73 | 74 | echo %GREEN%!EN_MESSAGES[7]!%RESET% -------------------------------------------------------------------------------- /scripts/build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 设置颜色代码 / Set color codes 4 | GREEN='\033[0;32m' 5 | RED='\033[0;31m' 6 | NC='\033[0m' # No Color / 无颜色 7 | 8 | # Build optimization flags 9 | OPTIMIZATION_FLAGS="-trimpath -ldflags=\"-s -w\"" 10 | PARALLEL_JOBS=$(nproc || echo "4") # Get number of CPU cores or default to 4 11 | 12 | # Messages / 消息 13 | EN_MESSAGES=( 14 | "Starting build process for version" 15 | "Cleaning old builds..." 16 | "Creating bin directory..." 17 | "Failed to create bin directory" 18 | "Building for" 19 | "Successfully built:" 20 | "Failed to build for" 21 | "Build Summary:" 22 | "Successful builds:" 23 | "Failed builds:" 24 | "Generated files:" 25 | ) 26 | 27 | CN_MESSAGES=( 28 | "开始构建版本" 29 | "正在清理旧的构建文件..." 30 | "正在创建bin目录..." 31 | "创建bin目录失败" 32 | "正在构建" 33 | "构建成功:" 34 | "构建失败:" 35 | "构建摘要:" 36 | "成功构建数:" 37 | "失败构建数:" 38 | "生成的文件:" 39 | "构建过程被中断" 40 | "错误:" 41 | ) 42 | 43 | # 版本信息 / Version info 44 | VERSION="1.0.0" 45 | 46 | # Detect system language / 检测系统语言 47 | detect_language() { 48 | if [[ $(locale | grep "LANG=zh_CN") ]]; then 49 | echo "cn" 50 | else 51 | echo "en" 52 | fi 53 | } 54 | 55 | # Get message based on language / 根据语言获取消息 56 | get_message() { 57 | local index=$1 58 | local lang=$(detect_language) 59 | 60 | if [[ "$lang" == "cn" ]]; then 61 | echo "${CN_MESSAGES[$index]}" 62 | else 63 | echo "${EN_MESSAGES[$index]}" 64 | fi 65 | } 66 | 67 | # 错误处理函数 / Error handling function 68 | handle_error() { 69 | echo -e "${RED}$(get_message 12) $1${NC}" 70 | exit 1 71 | } 72 | 73 | # 清理函数 / Cleanup function 74 | cleanup() { 75 | if [ -d "../bin" ]; then 76 | rm -rf ../bin 77 | echo -e "${GREEN}$(get_message 1)${NC}" 78 | fi 79 | } 80 | 81 | # Build function with optimizations 82 | build() { 83 | local os=$1 84 | local arch=$2 85 | local ext="" 86 | [ "$os" = "windows" ] && ext=".exe" 87 | 88 | echo -e "${GREEN}$(get_message 4) $os/$arch${NC}" 89 | 90 | GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build \ 91 | -trimpath \ 92 | -ldflags="-s -w" \ 93 | -o "../bin/$os/$arch/cursor-id-modifier$ext" \ 94 | -a -installsuffix cgo \ 95 | -mod=readonly \ 96 | ../cmd/cursor-id-modifier & 97 | } 98 | 99 | # Parallel build execution 100 | build_all() { 101 | local builds=0 102 | local max_parallel=$PARALLEL_JOBS 103 | 104 | # Define build targets 105 | declare -A targets=( 106 | ["linux/amd64"]=1 107 | ["linux/386"]=1 108 | ["linux/arm64"]=1 109 | ["windows/amd64"]=1 110 | ["windows/386"]=1 111 | ["darwin/amd64"]=1 112 | ["darwin/arm64"]=1 113 | ) 114 | 115 | for target in "${!targets[@]}"; do 116 | IFS='/' read -r os arch <<< "$target" 117 | build "$os" "$arch" 118 | 119 | ((builds++)) 120 | 121 | if ((builds >= max_parallel)); then 122 | wait 123 | builds=0 124 | fi 125 | done 126 | 127 | # Wait for remaining builds 128 | wait 129 | } 130 | 131 | # Main execution 132 | main() { 133 | cleanup 134 | mkdir -p ../bin || { echo -e "${RED}$(get_message 3)${NC}"; exit 1; } 135 | build_all 136 | echo -e "${GREEN}Build completed successfully${NC}" 137 | } 138 | 139 | # 捕获错误信号 / Catch error signals 140 | trap 'echo -e "\n${RED}$(get_message 11)${NC}"; exit 1' INT TERM 141 | 142 | # 执行主函数 / Execute main function 143 | main -------------------------------------------------------------------------------- /scripts/install.ps1: -------------------------------------------------------------------------------- 1 | # Check for admin rights and handle elevation 2 | $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") 3 | if (-NOT $isAdmin) { 4 | # Detect PowerShell version and path 5 | $pwshPath = if (Get-Command "pwsh" -ErrorAction SilentlyContinue) { 6 | (Get-Command "pwsh").Source # PowerShell 7+ 7 | } elseif (Test-Path "$env:ProgramFiles\PowerShell\7\pwsh.exe") { 8 | "$env:ProgramFiles\PowerShell\7\pwsh.exe" 9 | } else { 10 | "powershell.exe" # Windows PowerShell 11 | } 12 | 13 | try { 14 | Write-Host "`nRequesting administrator privileges..." -ForegroundColor Cyan 15 | $scriptPath = $MyInvocation.MyCommand.Path 16 | $argList = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" 17 | Start-Process -FilePath $pwshPath -Verb RunAs -ArgumentList $argList -Wait 18 | exit 19 | } 20 | catch { 21 | Write-Host "`nError: Administrator privileges required" -ForegroundColor Red 22 | Write-Host "Please run this script from an Administrator PowerShell window" -ForegroundColor Yellow 23 | Write-Host "`nTo do this:" -ForegroundColor Cyan 24 | Write-Host "1. Press Win + X" -ForegroundColor White 25 | Write-Host "2. Click 'Windows Terminal (Admin)' or 'PowerShell (Admin)'" -ForegroundColor White 26 | Write-Host "3. Run the installation command again" -ForegroundColor White 27 | Write-Host "`nPress enter to exit..." 28 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') 29 | exit 1 30 | } 31 | } 32 | 33 | # Set TLS to 1.2 34 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 35 | 36 | # Create temporary directory 37 | $TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString()) 38 | New-Item -ItemType Directory -Path $TmpDir | Out-Null 39 | 40 | # Cleanup function 41 | function Cleanup { 42 | if (Test-Path $TmpDir) { 43 | Remove-Item -Recurse -Force $TmpDir 44 | } 45 | } 46 | 47 | # Error handler 48 | trap { 49 | Write-Host "Error: $_" -ForegroundColor Red 50 | Cleanup 51 | Write-Host "Press enter to exit..." 52 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') 53 | exit 1 54 | } 55 | 56 | # Detect system architecture 57 | function Get-SystemArch { 58 | if ([Environment]::Is64BitOperatingSystem) { 59 | return "x86_64" 60 | } else { 61 | return "i386" 62 | } 63 | } 64 | 65 | # Download with progress 66 | function Get-FileWithProgress { 67 | param ( 68 | [string]$Url, 69 | [string]$OutputFile 70 | ) 71 | 72 | try { 73 | $webClient = New-Object System.Net.WebClient 74 | $webClient.Headers.Add("User-Agent", "PowerShell Script") 75 | 76 | $webClient.DownloadFile($Url, $OutputFile) 77 | return $true 78 | } 79 | catch { 80 | Write-Host "Failed to download: $_" -ForegroundColor Red 81 | return $false 82 | } 83 | } 84 | 85 | # Main installation function 86 | function Install-CursorModifier { 87 | Write-Host "Starting installation..." -ForegroundColor Cyan 88 | 89 | # Detect architecture 90 | $arch = Get-SystemArch 91 | Write-Host "Detected architecture: $arch" -ForegroundColor Green 92 | 93 | # Set installation directory 94 | $InstallDir = "$env:ProgramFiles\CursorModifier" 95 | if (!(Test-Path $InstallDir)) { 96 | New-Item -ItemType Directory -Path $InstallDir | Out-Null 97 | } 98 | 99 | # Get latest release 100 | try { 101 | $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" 102 | Write-Host "Found latest release: $($latestRelease.tag_name)" -ForegroundColor Cyan 103 | 104 | # Look for Windows binary with our architecture 105 | $version = $latestRelease.tag_name.TrimStart('v') 106 | Write-Host "Version: $version" -ForegroundColor Cyan 107 | $possibleNames = @( 108 | "cursor-id-modifier_${version}_windows_x86_64.exe", 109 | "cursor-id-modifier_${version}_windows_$($arch).exe" 110 | ) 111 | 112 | $asset = $null 113 | foreach ($name in $possibleNames) { 114 | Write-Host "Checking for asset: $name" -ForegroundColor Cyan 115 | $asset = $latestRelease.assets | Where-Object { $_.name -eq $name } 116 | if ($asset) { 117 | Write-Host "Found matching asset: $($asset.name)" -ForegroundColor Green 118 | break 119 | } 120 | } 121 | 122 | if (!$asset) { 123 | Write-Host "`nAvailable assets:" -ForegroundColor Yellow 124 | $latestRelease.assets | ForEach-Object { Write-Host "- $($_.name)" } 125 | throw "Could not find appropriate Windows binary for $arch architecture" 126 | } 127 | 128 | $downloadUrl = $asset.browser_download_url 129 | } 130 | catch { 131 | Write-Host "Failed to get latest release: $_" -ForegroundColor Red 132 | exit 1 133 | } 134 | 135 | # Download binary 136 | Write-Host "`nDownloading latest release..." -ForegroundColor Cyan 137 | $binaryPath = Join-Path $TmpDir "cursor-id-modifier.exe" 138 | 139 | if (!(Get-FileWithProgress -Url $downloadUrl -OutputFile $binaryPath)) { 140 | exit 1 141 | } 142 | 143 | # Install binary 144 | Write-Host "Installing..." -ForegroundColor Cyan 145 | try { 146 | Copy-Item -Path $binaryPath -Destination "$InstallDir\cursor-id-modifier.exe" -Force 147 | 148 | # Add to PATH if not already present 149 | $currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine") 150 | if ($currentPath -notlike "*$InstallDir*") { 151 | [Environment]::SetEnvironmentVariable("Path", "$currentPath;$InstallDir", "Machine") 152 | } 153 | } 154 | catch { 155 | Write-Host "Failed to install: $_" -ForegroundColor Red 156 | exit 1 157 | } 158 | 159 | Write-Host "Installation completed successfully!" -ForegroundColor Green 160 | Write-Host "Running cursor-id-modifier..." -ForegroundColor Cyan 161 | 162 | # Run the program 163 | try { 164 | & "$InstallDir\cursor-id-modifier.exe" 165 | if ($LASTEXITCODE -ne 0) { 166 | Write-Host "Failed to run cursor-id-modifier" -ForegroundColor Red 167 | exit 1 168 | } 169 | } 170 | catch { 171 | Write-Host "Failed to run cursor-id-modifier: $_" -ForegroundColor Red 172 | exit 1 173 | } 174 | } 175 | 176 | # Run installation 177 | try { 178 | Install-CursorModifier 179 | } 180 | catch { 181 | Write-Host "Installation failed: $_" -ForegroundColor Red 182 | Cleanup 183 | Write-Host "Press enter to exit..." 184 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') 185 | exit 1 186 | } 187 | finally { 188 | Cleanup 189 | if ($LASTEXITCODE -ne 0) { 190 | Write-Host "Press enter to exit..." -ForegroundColor Green 191 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') 192 | } 193 | } -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Colors for output 6 | RED='\033[0;31m' 7 | GREEN='\033[0;32m' 8 | BLUE='\033[0;36m' 9 | YELLOW='\033[0;33m' 10 | NC='\033[0m' 11 | 12 | # Temporary directory for downloads 13 | TMP_DIR=$(mktemp -d) 14 | trap 'rm -rf "$TMP_DIR"' EXIT 15 | 16 | # Check for required commands 17 | check_requirements() { 18 | if ! command -v curl >/dev/null 2>&1; then 19 | echo -e "${RED}Error: curl is required${NC}" 20 | exit 1 21 | fi 22 | } 23 | 24 | # Detect system information 25 | detect_system() { 26 | local os arch suffix 27 | 28 | case "$(uname -s)" in 29 | Linux*) os="linux";; 30 | Darwin*) os="darwin";; 31 | *) echo -e "${RED}Unsupported OS${NC}"; exit 1;; 32 | esac 33 | 34 | case "$(uname -m)" in 35 | x86_64) 36 | arch="x86_64" 37 | ;; 38 | aarch64|arm64) 39 | arch="arm64" 40 | ;; 41 | i386|i686) 42 | arch="i386" 43 | ;; 44 | *) echo -e "${RED}Unsupported architecture${NC}"; exit 1;; 45 | esac 46 | 47 | echo "$os $arch" 48 | } 49 | 50 | # Download with progress 51 | download() { 52 | local url="$1" 53 | local output="$2" 54 | curl -#L "$url" -o "$output" 55 | } 56 | 57 | # Check and create installation directory 58 | setup_install_dir() { 59 | local install_dir="$1" 60 | 61 | if [ ! -d "$install_dir" ]; then 62 | mkdir -p "$install_dir" || { 63 | echo -e "${RED}Failed to create installation directory${NC}" 64 | exit 1 65 | } 66 | fi 67 | } 68 | 69 | # Main installation function 70 | main() { 71 | check_requirements 72 | 73 | echo -e "${BLUE}Starting installation...${NC}" 74 | 75 | # Detect system 76 | read -r OS ARCH SUFFIX <<< "$(detect_system)" 77 | echo -e "${GREEN}Detected: $OS $ARCH${NC}" 78 | 79 | # Set installation directory 80 | INSTALL_DIR="/usr/local/bin" 81 | 82 | # Setup installation directory 83 | setup_install_dir "$INSTALL_DIR" 84 | 85 | # Get latest release info 86 | echo -e "${BLUE}Fetching latest release information...${NC}" 87 | LATEST_URL="https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" 88 | 89 | # Get latest version and remove 'v' prefix 90 | VERSION=$(curl -s "$LATEST_URL" | grep "tag_name" | cut -d'"' -f4 | sed 's/^v//') 91 | 92 | # Construct binary name 93 | BINARY_NAME="cursor-id-modifier_${VERSION}_${OS}_${ARCH}" 94 | echo -e "${BLUE}Looking for asset: $BINARY_NAME${NC}" 95 | 96 | # Get download URL directly 97 | DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep -o "\"browser_download_url\": \"[^\"]*${BINARY_NAME}[^\"]*\"" | cut -d'"' -f4) 98 | 99 | if [ -z "$DOWNLOAD_URL" ]; then 100 | echo -e "${RED}Error: Could not find appropriate binary for $OS $ARCH${NC}" 101 | echo -e "${YELLOW}Available assets:${NC}" 102 | curl -s "$LATEST_URL" | grep "browser_download_url" | cut -d'"' -f4 103 | exit 1 104 | fi 105 | 106 | echo -e "${GREEN}Found matching asset: $BINARY_NAME${NC}" 107 | echo -e "${BLUE}Downloading from: $DOWNLOAD_URL${NC}" 108 | 109 | download "$DOWNLOAD_URL" "$TMP_DIR/cursor-id-modifier" 110 | 111 | # Install binary 112 | echo -e "${BLUE}Installing...${NC}" 113 | chmod +x "$TMP_DIR/cursor-id-modifier" 114 | sudo mv "$TMP_DIR/cursor-id-modifier" "$INSTALL_DIR/" 115 | 116 | echo -e "${GREEN}Installation completed successfully!${NC}" 117 | echo -e "${BLUE}Running cursor-id-modifier...${NC}" 118 | 119 | # Run the program with sudo, preserving environment variables 120 | export AUTOMATED_MODE=1 121 | if ! sudo -E cursor-id-modifier; then 122 | echo -e "${RED}Failed to run cursor-id-modifier${NC}" 123 | exit 1 124 | fi 125 | } 126 | 127 | main 128 | -------------------------------------------------------------------------------- /scripts/run/cursor_linux_id_modifier.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 设置错误处理 4 | set -e 5 | 6 | # 定义日志文件路径 7 | LOG_FILE="/tmp/cursor_linux_id_modifier.log" 8 | 9 | # 初始化日志文件 10 | initialize_log() { 11 | echo "========== Cursor ID 修改工具日志开始 $(date) ==========" > "$LOG_FILE" 12 | chmod 644 "$LOG_FILE" 13 | } 14 | 15 | # 颜色定义 16 | RED='\033[0;31m' 17 | GREEN='\033[0;32m' 18 | YELLOW='\033[1;33m' 19 | BLUE='\033[0;34m' 20 | NC='\033[0m' # No Color 21 | 22 | # 日志函数 - 同时输出到终端和日志文件 23 | log_info() { 24 | echo -e "${GREEN}[INFO]${NC} $1" 25 | echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" 26 | } 27 | 28 | log_warn() { 29 | echo -e "${YELLOW}[WARN]${NC} $1" 30 | echo "[WARN] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" 31 | } 32 | 33 | log_error() { 34 | echo -e "${RED}[ERROR]${NC} $1" 35 | echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" 36 | } 37 | 38 | log_debug() { 39 | echo -e "${BLUE}[DEBUG]${NC} $1" 40 | echo "[DEBUG] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" 41 | } 42 | 43 | # 记录命令输出到日志文件 44 | log_cmd_output() { 45 | local cmd="$1" 46 | local msg="$2" 47 | echo "[CMD] $(date '+%Y-%m-%d %H:%M:%S') 执行命令: $cmd" >> "$LOG_FILE" 48 | echo "[CMD] $msg:" >> "$LOG_FILE" 49 | eval "$cmd" 2>&1 | tee -a "$LOG_FILE" 50 | echo "" >> "$LOG_FILE" 51 | } 52 | 53 | # 获取当前用户 54 | get_current_user() { 55 | if [ "$EUID" -eq 0 ]; then 56 | echo "$SUDO_USER" 57 | else 58 | echo "$USER" 59 | fi 60 | } 61 | 62 | CURRENT_USER=$(get_current_user) 63 | if [ -z "$CURRENT_USER" ]; then 64 | log_error "无法获取用户名" 65 | exit 1 66 | fi 67 | 68 | # 定义Linux下的Cursor路径 69 | CURSOR_CONFIG_DIR="$HOME/.config/Cursor" 70 | STORAGE_FILE="$CURSOR_CONFIG_DIR/User/globalStorage/storage.json" 71 | BACKUP_DIR="$CURSOR_CONFIG_DIR/User/globalStorage/backups" 72 | 73 | # 可能的Cursor二进制路径 74 | CURSOR_BIN_PATHS=( 75 | "/usr/bin/cursor" 76 | "/usr/local/bin/cursor" 77 | "$HOME/.local/bin/cursor" 78 | "/opt/cursor/cursor" 79 | "/snap/bin/cursor" 80 | ) 81 | 82 | # 找到Cursor安装路径 83 | find_cursor_path() { 84 | log_info "查找Cursor安装路径..." 85 | 86 | for path in "${CURSOR_BIN_PATHS[@]}"; do 87 | if [ -f "$path" ]; then 88 | log_info "找到Cursor安装路径: $path" 89 | CURSOR_PATH="$path" 90 | return 0 91 | fi 92 | done 93 | 94 | # 尝试通过which命令定位 95 | if command -v cursor &> /dev/null; then 96 | CURSOR_PATH=$(which cursor) 97 | log_info "通过which找到Cursor: $CURSOR_PATH" 98 | return 0 99 | fi 100 | 101 | # 尝试查找可能的安装路径 102 | local cursor_paths=$(find /usr /opt $HOME/.local -name "cursor" -type f -executable 2>/dev/null) 103 | if [ -n "$cursor_paths" ]; then 104 | CURSOR_PATH=$(echo "$cursor_paths" | head -1) 105 | log_info "通过查找找到Cursor: $CURSOR_PATH" 106 | return 0 107 | fi 108 | 109 | log_warn "未找到Cursor可执行文件,将尝试使用配置目录" 110 | return 1 111 | } 112 | 113 | # 查找并定位Cursor资源文件目录 114 | find_cursor_resources() { 115 | log_info "查找Cursor资源目录..." 116 | 117 | # 可能的资源目录路径 118 | local resource_paths=( 119 | "/usr/lib/cursor" 120 | "/usr/share/cursor" 121 | "/opt/cursor" 122 | "$HOME/.local/share/cursor" 123 | ) 124 | 125 | for path in "${resource_paths[@]}"; do 126 | if [ -d "$path" ]; then 127 | log_info "找到Cursor资源目录: $path" 128 | CURSOR_RESOURCES="$path" 129 | return 0 130 | fi 131 | done 132 | 133 | # 如果有CURSOR_PATH,尝试从它推断 134 | if [ -n "$CURSOR_PATH" ]; then 135 | local base_dir=$(dirname "$CURSOR_PATH") 136 | if [ -d "$base_dir/resources" ]; then 137 | CURSOR_RESOURCES="$base_dir/resources" 138 | log_info "通过二进制路径找到资源目录: $CURSOR_RESOURCES" 139 | return 0 140 | fi 141 | fi 142 | 143 | log_warn "未找到Cursor资源目录" 144 | return 1 145 | } 146 | 147 | # 检查权限 148 | check_permissions() { 149 | if [ "$EUID" -ne 0 ]; then 150 | log_error "请使用 sudo 运行此脚本" 151 | echo "示例: sudo $0" 152 | exit 1 153 | fi 154 | } 155 | 156 | # 检查并关闭 Cursor 进程 157 | check_and_kill_cursor() { 158 | log_info "检查 Cursor 进程..." 159 | 160 | local attempt=1 161 | local max_attempts=5 162 | 163 | # 函数:获取进程详细信息 164 | get_process_details() { 165 | local process_name="$1" 166 | log_debug "正在获取 $process_name 进程详细信息:" 167 | ps aux | grep -i "cursor" | grep -v grep | grep -v "cursor_linux_id_modifier.sh" 168 | } 169 | 170 | while [ $attempt -le $max_attempts ]; do 171 | # 使用更精确的匹配来获取 Cursor 进程,排除当前脚本和grep进程 172 | CURSOR_PIDS=$(ps aux | grep -i "cursor" | grep -v "grep" | grep -v "cursor_linux_id_modifier.sh" | awk '{print $2}' || true) 173 | 174 | if [ -z "$CURSOR_PIDS" ]; then 175 | log_info "未发现运行中的 Cursor 进程" 176 | return 0 177 | fi 178 | 179 | log_warn "发现 Cursor 进程正在运行" 180 | get_process_details "cursor" 181 | 182 | log_warn "尝试关闭 Cursor 进程..." 183 | 184 | if [ $attempt -eq $max_attempts ]; then 185 | log_warn "尝试强制终止进程..." 186 | kill -9 $CURSOR_PIDS 2>/dev/null || true 187 | else 188 | kill $CURSOR_PIDS 2>/dev/null || true 189 | fi 190 | 191 | sleep 1 192 | 193 | # 再次检查进程是否还在运行,排除当前脚本和grep进程 194 | if ! ps aux | grep -i "cursor" | grep -v "grep" | grep -v "cursor_linux_id_modifier.sh" > /dev/null; then 195 | log_info "Cursor 进程已成功关闭" 196 | return 0 197 | fi 198 | 199 | log_warn "等待进程关闭,尝试 $attempt/$max_attempts..." 200 | ((attempt++)) 201 | done 202 | 203 | log_error "在 $max_attempts 次尝试后仍无法关闭 Cursor 进程" 204 | get_process_details "cursor" 205 | log_error "请手动关闭进程后重试" 206 | exit 1 207 | } 208 | 209 | # 备份配置文件 210 | backup_config() { 211 | if [ ! -f "$STORAGE_FILE" ]; then 212 | log_warn "配置文件不存在,跳过备份" 213 | return 0 214 | fi 215 | 216 | mkdir -p "$BACKUP_DIR" 217 | local backup_file="$BACKUP_DIR/storage.json.backup_$(date +%Y%m%d_%H%M%S)" 218 | 219 | if cp "$STORAGE_FILE" "$backup_file"; then 220 | chmod 644 "$backup_file" 221 | chown "$CURRENT_USER" "$backup_file" 222 | log_info "配置已备份到: $backup_file" 223 | else 224 | log_error "备份失败" 225 | exit 1 226 | fi 227 | } 228 | 229 | # 生成随机 ID 230 | generate_random_id() { 231 | # 生成32字节(64个十六进制字符)的随机数 232 | openssl rand -hex 32 233 | } 234 | 235 | # 生成随机 UUID 236 | generate_uuid() { 237 | # 在Linux上使用uuidgen生成UUID 238 | if command -v uuidgen &> /dev/null; then 239 | uuidgen | tr '[:upper:]' '[:lower:]' 240 | else 241 | # 备选方案:使用/proc/sys/kernel/random/uuid 242 | if [ -f /proc/sys/kernel/random/uuid ]; then 243 | cat /proc/sys/kernel/random/uuid 244 | else 245 | # 最后备选方案:使用openssl生成 246 | openssl rand -hex 16 | sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)/\1\2\3\4-\5\6-\7\8-\9\10-\11\12\13\14\15\16/' 247 | fi 248 | fi 249 | } 250 | 251 | # 修改现有文件 252 | modify_or_add_config() { 253 | local key="$1" 254 | local value="$2" 255 | local file="$3" 256 | 257 | if [ ! -f "$file" ]; then 258 | log_error "文件不存在: $file" 259 | return 1 260 | fi 261 | 262 | # 确保文件可写 263 | chmod 644 "$file" || { 264 | log_error "无法修改文件权限: $file" 265 | return 1 266 | } 267 | 268 | # 创建临时文件 269 | local temp_file=$(mktemp) 270 | 271 | # 检查key是否存在 272 | if grep -q "\"$key\":" "$file"; then 273 | # key存在,执行替换 274 | sed "s/\"$key\":[[:space:]]*\"[^\"]*\"/\"$key\": \"$value\"/" "$file" > "$temp_file" || { 275 | log_error "修改配置失败: $key" 276 | rm -f "$temp_file" 277 | return 1 278 | } 279 | else 280 | # key不存在,添加新的key-value对 281 | sed "s/}$/,\n \"$key\": \"$value\"\n}/" "$file" > "$temp_file" || { 282 | log_error "添加配置失败: $key" 283 | rm -f "$temp_file" 284 | return 1 285 | } 286 | fi 287 | 288 | # 检查临时文件是否为空 289 | if [ ! -s "$temp_file" ]; then 290 | log_error "生成的临时文件为空" 291 | rm -f "$temp_file" 292 | return 1 293 | fi 294 | 295 | # 使用 cat 替换原文件内容 296 | cat "$temp_file" > "$file" || { 297 | log_error "无法写入文件: $file" 298 | rm -f "$temp_file" 299 | return 1 300 | } 301 | 302 | rm -f "$temp_file" 303 | 304 | # 恢复文件权限 305 | chmod 444 "$file" 306 | 307 | return 0 308 | } 309 | 310 | # 生成新的配置 311 | generate_new_config() { 312 | echo 313 | log_warn "机器码重置选项" 314 | 315 | # 使用菜单选择函数询问用户是否重置机器码 316 | select_menu_option "是否需要重置机器码? (通常情况下,只修改js文件即可):" "不重置 - 仅修改js文件即可|重置 - 同时修改配置文件和机器码" 0 317 | reset_choice=$? 318 | 319 | # 记录日志以便调试 320 | echo "[INPUT_DEBUG] 机器码重置选项选择: $reset_choice" >> "$LOG_FILE" 321 | 322 | # 处理用户选择 - 索引0对应"不重置"选项,索引1对应"重置"选项 323 | if [ "$reset_choice" = "1" ]; then 324 | log_info "您选择了重置机器码" 325 | 326 | # 确保配置文件目录存在 327 | if [ -f "$STORAGE_FILE" ]; then 328 | log_info "发现已有配置文件: $STORAGE_FILE" 329 | 330 | # 备份现有配置(以防万一) 331 | backup_config 332 | 333 | # 生成并设置新的设备ID 334 | local new_device_id=$(generate_uuid) 335 | local new_machine_id="auth0|user_$(openssl rand -hex 16)" 336 | 337 | log_info "正在设置新的设备和机器ID..." 338 | log_debug "新设备ID: $new_device_id" 339 | log_debug "新机器ID: $new_machine_id" 340 | 341 | # 修改配置文件 342 | if modify_or_add_config "deviceId" "$new_device_id" "$STORAGE_FILE" && \ 343 | modify_or_add_config "machineId" "$new_machine_id" "$STORAGE_FILE"; then 344 | log_info "配置文件修改成功" 345 | else 346 | log_error "配置文件修改失败" 347 | fi 348 | else 349 | log_warn "未找到配置文件,这是正常的,脚本将跳过ID修改" 350 | fi 351 | else 352 | log_info "您选择了不重置机器码,将仅修改js文件" 353 | 354 | # 确保配置文件目录存在 355 | if [ -f "$STORAGE_FILE" ]; then 356 | log_info "发现已有配置文件: $STORAGE_FILE" 357 | 358 | # 备份现有配置(以防万一) 359 | backup_config 360 | else 361 | log_warn "未找到配置文件,这是正常的,脚本将跳过ID修改" 362 | fi 363 | fi 364 | 365 | echo 366 | log_info "配置处理完成" 367 | } 368 | 369 | # 查找Cursor的JS文件 370 | find_cursor_js_files() { 371 | log_info "查找Cursor的JS文件..." 372 | 373 | local js_files=() 374 | local found=false 375 | 376 | # 如果找到了资源目录,在资源目录中搜索 377 | if [ -n "$CURSOR_RESOURCES" ]; then 378 | log_debug "在资源目录中搜索JS文件: $CURSOR_RESOURCES" 379 | 380 | # 在资源目录中递归搜索特定JS文件 381 | local js_patterns=( 382 | "*/extensionHostProcess.js" 383 | "*/main.js" 384 | "*/cliProcessMain.js" 385 | "*/app/out/vs/workbench/api/node/extensionHostProcess.js" 386 | "*/app/out/main.js" 387 | "*/app/out/vs/code/node/cliProcessMain.js" 388 | ) 389 | 390 | for pattern in "${js_patterns[@]}"; do 391 | local files=$(find "$CURSOR_RESOURCES" -path "$pattern" -type f 2>/dev/null) 392 | if [ -n "$files" ]; then 393 | while read -r file; do 394 | log_info "找到JS文件: $file" 395 | js_files+=("$file") 396 | found=true 397 | done <<< "$files" 398 | fi 399 | done 400 | fi 401 | 402 | # 如果还没找到,尝试在/usr和$HOME目录下搜索 403 | if [ "$found" = false ]; then 404 | log_warn "在资源目录中未找到JS文件,尝试在其他目录中搜索..." 405 | 406 | # 在系统目录中搜索,限制深度以避免过长搜索 407 | local search_dirs=( 408 | "/usr/lib/cursor" 409 | "/usr/share/cursor" 410 | "/opt/cursor" 411 | "$HOME/.config/Cursor" 412 | "$HOME/.local/share/cursor" 413 | ) 414 | 415 | for dir in "${search_dirs[@]}"; do 416 | if [ -d "$dir" ]; then 417 | log_debug "搜索目录: $dir" 418 | local files=$(find "$dir" -name "*.js" -type f -exec grep -l "IOPlatformUUID\|x-cursor-checksum" {} \; 2>/dev/null) 419 | if [ -n "$files" ]; then 420 | while read -r file; do 421 | log_info "找到JS文件: $file" 422 | js_files+=("$file") 423 | found=true 424 | done <<< "$files" 425 | fi 426 | fi 427 | done 428 | fi 429 | 430 | if [ "$found" = false ]; then 431 | log_error "未找到任何可修改的JS文件" 432 | return 1 433 | fi 434 | 435 | # 保存找到的文件列表到全局变量 436 | CURSOR_JS_FILES=("${js_files[@]}") 437 | log_info "找到 ${#CURSOR_JS_FILES[@]} 个JS文件需要修改" 438 | return 0 439 | } 440 | 441 | # 修改Cursor的JS文件 442 | modify_cursor_js_files() { 443 | log_info "开始修改Cursor的JS文件..." 444 | 445 | # 先查找需要修改的JS文件 446 | if ! find_cursor_js_files; then 447 | log_error "无法找到可修改的JS文件" 448 | return 1 449 | fi 450 | 451 | local modified_count=0 452 | 453 | for file in "${CURSOR_JS_FILES[@]}"; do 454 | log_info "处理文件: $file" 455 | 456 | # 创建文件备份 457 | local backup_file="${file}.backup_$(date +%Y%m%d%H%M%S)" 458 | if ! cp "$file" "$backup_file"; then 459 | log_error "无法创建文件备份: $file" 460 | continue 461 | fi 462 | 463 | # 确保文件可写 464 | chmod 644 "$file" || { 465 | log_error "无法修改文件权限: $file" 466 | continue 467 | } 468 | 469 | # 检查文件内容并进行相应修改 470 | if grep -q 'i.header.set("x-cursor-checksum' "$file"; then 471 | log_debug "找到 x-cursor-checksum 设置代码" 472 | 473 | # 执行特定的替换 474 | if sed -i 's/i\.header\.set("x-cursor-checksum",e===void 0?`${p}${t}`:`${p}${t}\/${e}`)/i.header.set("x-cursor-checksum",e===void 0?`${p}${t}`:`${p}${t}\/${p}`)/' "$file"; then 475 | log_info "成功修改 x-cursor-checksum 设置代码" 476 | ((modified_count++)) 477 | else 478 | log_error "修改 x-cursor-checksum 设置代码失败" 479 | # 恢复备份 480 | cp "$backup_file" "$file" 481 | fi 482 | elif grep -q "IOPlatformUUID" "$file"; then 483 | log_debug "找到 IOPlatformUUID 关键字" 484 | 485 | # 尝试不同的替换模式 486 | if grep -q "function a\$" "$file" && ! grep -q "return crypto.randomUUID()" "$file"; then 487 | if sed -i 's/function a\$(t){switch/function a\$(t){return crypto.randomUUID(); switch/' "$file"; then 488 | log_debug "成功注入 randomUUID 调用到 a\$ 函数" 489 | ((modified_count++)) 490 | else 491 | log_error "修改 a\$ 函数失败" 492 | cp "$backup_file" "$file" 493 | fi 494 | elif grep -q "async function v5" "$file" && ! grep -q "return crypto.randomUUID()" "$file"; then 495 | if sed -i 's/async function v5(t){let e=/async function v5(t){return crypto.randomUUID(); let e=/' "$file"; then 496 | log_debug "成功注入 randomUUID 调用到 v5 函数" 497 | ((modified_count++)) 498 | else 499 | log_error "修改 v5 函数失败" 500 | cp "$backup_file" "$file" 501 | fi 502 | else 503 | # 通用注入方法 504 | if ! grep -q "// Cursor ID 修改工具注入" "$file"; then 505 | local inject_code=" 506 | // Cursor ID 修改工具注入 - $(date +%Y%m%d%H%M%S) 507 | // 随机设备ID生成器注入 - $(date +%s) 508 | const randomDeviceId_$(date +%s) = () => { 509 | try { 510 | return require('crypto').randomUUID(); 511 | } catch (e) { 512 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { 513 | const r = Math.random() * 16 | 0; 514 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 515 | }); 516 | } 517 | }; 518 | " 519 | # 将代码注入到文件开头 520 | echo "$inject_code" > "${file}.new" 521 | cat "$file" >> "${file}.new" 522 | mv "${file}.new" "$file" 523 | 524 | # 替换调用点 525 | sed -i 's/await v5(!1)/randomDeviceId_'"$(date +%s)"'()/g' "$file" 526 | sed -i 's/a\$(t)/randomDeviceId_'"$(date +%s)"'()/g' "$file" 527 | 528 | log_debug "完成通用修改" 529 | ((modified_count++)) 530 | else 531 | log_info "文件已经包含自定义注入代码,跳过修改" 532 | fi 533 | fi 534 | else 535 | # 未找到关键字,尝试通用方法 536 | if ! grep -q "return crypto.randomUUID()" "$file" && ! grep -q "// Cursor ID 修改工具注入" "$file"; then 537 | # 尝试找其他关键函数 538 | if grep -q "function t\$()" "$file" || grep -q "async function y5" "$file"; then 539 | # 修改 MAC 地址获取函数 540 | if grep -q "function t\$()" "$file"; then 541 | sed -i 's/function t\$(){/function t\$(){return "00:00:00:00:00:00";/' "$file" 542 | fi 543 | 544 | # 修改设备ID获取函数 545 | if grep -q "async function y5" "$file"; then 546 | sed -i 's/async function y5(t){/async function y5(t){return crypto.randomUUID();/' "$file" 547 | fi 548 | 549 | ((modified_count++)) 550 | else 551 | # 最通用的注入方法 552 | local new_uuid=$(generate_uuid) 553 | local machine_id="auth0|user_$(openssl rand -hex 16)" 554 | local device_id=$(generate_uuid) 555 | local mac_machine_id=$(openssl rand -hex 32) 556 | 557 | local inject_universal_code=" 558 | // Cursor ID 修改工具注入 - $(date +%Y%m%d%H%M%S) 559 | // 全局拦截设备标识符 - $(date +%s) 560 | const originalRequire_$(date +%s) = require; 561 | require = function(module) { 562 | const result = originalRequire_$(date +%s)(module); 563 | if (module === 'crypto' && result.randomUUID) { 564 | const originalRandomUUID_$(date +%s) = result.randomUUID; 565 | result.randomUUID = function() { 566 | return '$new_uuid'; 567 | }; 568 | } 569 | return result; 570 | }; 571 | 572 | // 覆盖所有可能的系统ID获取函数 573 | global.getMachineId = function() { return '$machine_id'; }; 574 | global.getDeviceId = function() { return '$device_id'; }; 575 | global.macMachineId = '$mac_machine_id'; 576 | " 577 | # 替换变量 578 | inject_universal_code=${inject_universal_code//\$new_uuid/$new_uuid} 579 | inject_universal_code=${inject_universal_code//\$machine_id/$machine_id} 580 | inject_universal_code=${inject_universal_code//\$device_id/$device_id} 581 | inject_universal_code=${inject_universal_code//\$mac_machine_id/$mac_machine_id} 582 | 583 | # 将代码注入到文件开头 584 | echo "$inject_universal_code" > "${file}.new" 585 | cat "$file" >> "${file}.new" 586 | mv "${file}.new" "$file" 587 | 588 | log_debug "完成最通用注入" 589 | ((modified_count++)) 590 | fi 591 | else 592 | log_info "文件已经被修改过,跳过修改" 593 | fi 594 | fi 595 | 596 | # 恢复文件权限 597 | chmod 444 "$file" 598 | done 599 | 600 | if [ "$modified_count" -eq 0 ]; then 601 | log_error "未能成功修改任何JS文件" 602 | return 1 603 | fi 604 | 605 | log_info "成功修改了 $modified_count 个JS文件" 606 | return 0 607 | } 608 | 609 | # 禁用自动更新 610 | disable_auto_update() { 611 | log_info "正在禁用 Cursor 自动更新..." 612 | 613 | # 查找可能的更新配置文件 614 | local update_configs=( 615 | "$CURSOR_CONFIG_DIR/update-config.json" 616 | "$HOME/.local/share/cursor/update-config.json" 617 | "/opt/cursor/resources/app-update.yml" 618 | ) 619 | 620 | local disabled=false 621 | 622 | for config in "${update_configs[@]}"; do 623 | if [ -f "$config" ]; then 624 | log_info "找到更新配置文件: $config" 625 | 626 | # 备份并清空配置文件 627 | cp "$config" "${config}.bak" 2>/dev/null 628 | echo '{"autoCheck": false, "autoDownload": false}' > "$config" 629 | chmod 444 "$config" 630 | 631 | log_info "已禁用更新配置文件: $config" 632 | disabled=true 633 | fi 634 | done 635 | 636 | # 尝试查找updater可执行文件并禁用 637 | local updater_paths=( 638 | "$HOME/.config/Cursor/updater" 639 | "/opt/cursor/updater" 640 | "/usr/lib/cursor/updater" 641 | ) 642 | 643 | for updater in "${updater_paths[@]}"; do 644 | if [ -f "$updater" ] || [ -d "$updater" ]; then 645 | log_info "找到更新程序: $updater" 646 | if [ -f "$updater" ]; then 647 | mv "$updater" "${updater}.bak" 2>/dev/null 648 | else 649 | touch "${updater}.disabled" 650 | fi 651 | 652 | log_info "已禁用更新程序: $updater" 653 | disabled=true 654 | fi 655 | done 656 | 657 | if [ "$disabled" = false ]; then 658 | log_warn "未找到任何更新配置文件或更新程序" 659 | else 660 | log_info "成功禁用了自动更新" 661 | fi 662 | } 663 | 664 | # 新增:通用菜单选择函数 665 | # 参数: 666 | # $1 - 提示信息 667 | # $2 - 选项数组,格式为 "选项1|选项2|选项3" 668 | # $3 - 默认选项索引 (从0开始) 669 | # 返回: 选中的选项索引 (从0开始) 670 | select_menu_option() { 671 | local prompt="$1" 672 | IFS='|' read -ra options <<< "$2" 673 | local default_index=${3:-0} 674 | local selected_index=$default_index 675 | local key_input 676 | local cursor_up='\033[A' 677 | local cursor_down='\033[B' 678 | local enter_key=$'\n' 679 | 680 | # 保存光标位置 681 | tput sc 682 | 683 | # 显示提示信息 684 | echo -e "$prompt" 685 | 686 | # 第一次显示菜单 687 | for i in "${!options[@]}"; do 688 | if [ $i -eq $selected_index ]; then 689 | echo -e " ${GREEN}►${NC} ${options[$i]}" 690 | else 691 | echo -e " ${options[$i]}" 692 | fi 693 | done 694 | 695 | # 循环处理键盘输入 696 | while true; do 697 | # 读取单个按键 698 | read -rsn3 key_input 699 | 700 | # 检测按键 701 | case "$key_input" in 702 | # 上箭头键 703 | $'\033[A') 704 | if [ $selected_index -gt 0 ]; then 705 | ((selected_index--)) 706 | fi 707 | ;; 708 | # 下箭头键 709 | $'\033[B') 710 | if [ $selected_index -lt $((${#options[@]}-1)) ]; then 711 | ((selected_index++)) 712 | fi 713 | ;; 714 | # Enter键 715 | "") 716 | echo # 换行 717 | log_info "您选择了: ${options[$selected_index]}" 718 | return $selected_index 719 | ;; 720 | esac 721 | 722 | # 恢复光标位置 723 | tput rc 724 | 725 | # 重新显示菜单 726 | for i in "${!options[@]}"; do 727 | if [ $i -eq $selected_index ]; then 728 | echo -e " ${GREEN}►${NC} ${options[$i]}" 729 | else 730 | echo -e " ${options[$i]}" 731 | fi 732 | done 733 | done 734 | } 735 | 736 | # 主函数 737 | main() { 738 | # 检查系统环境 739 | if [[ $(uname) != "Linux" ]]; then 740 | log_error "本脚本仅支持 Linux 系统" 741 | exit 1 742 | fi 743 | 744 | # 初始化日志文件 745 | initialize_log 746 | log_info "脚本启动..." 747 | 748 | 749 | # 记录系统信息 750 | log_info "系统信息: $(uname -a)" 751 | log_info "当前用户: $CURRENT_USER" 752 | log_cmd_output "lsb_release -a 2>/dev/null || cat /etc/*release 2>/dev/null || cat /etc/issue" "系统版本信息" 753 | 754 | clear 755 | # 显示 Logo 756 | echo -e " 757 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ 758 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ 759 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ 760 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ 761 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ 762 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ 763 | " 764 | echo -e "${BLUE}================================${NC}" 765 | echo -e "${GREEN} Cursor Linux 启动工具 ${NC}" 766 | echo -e "${BLUE}================================${NC}" 767 | echo 768 | echo -e "${YELLOW}[重要提示]${NC} 本工具优先修改js文件,更加安全可靠" 769 | echo 770 | 771 | # 执行主要功能 772 | check_permissions 773 | 774 | # 查找Cursor路径 775 | find_cursor_path 776 | find_cursor_resources 777 | 778 | # 检查并关闭Cursor进程 779 | check_and_kill_cursor 780 | 781 | # 备份配置文件 782 | backup_config 783 | 784 | # 询问用户是否需要重置机器码(默认不重置) 785 | generate_new_config 786 | 787 | # 修改JS文件 788 | log_info "正在修改Cursor JS文件..." 789 | if modify_cursor_js_files; then 790 | log_info "JS文件修改成功!" 791 | else 792 | log_warn "JS文件修改失败,但配置文件修改可能已成功" 793 | log_warn "如果重启后 Cursor 仍然提示设备被禁用,请重新运行此脚本" 794 | fi 795 | 796 | # 禁用自动更新 797 | disable_auto_update 798 | 799 | log_info "请重启 Cursor 以应用新的配置" 800 | 801 | # 显示最后的提示信息 802 | echo 803 | echo -e "${GREEN}================================${NC}" 804 | echo -e "${YELLOW} 关注公众号【煎饼果子卷AI】一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) ${NC}" 805 | echo -e "${GREEN}================================${NC}" 806 | echo 807 | 808 | # 记录脚本完成信息 809 | log_info "脚本执行完成" 810 | echo "========== Cursor ID 修改工具日志结束 $(date) ==========" >> "$LOG_FILE" 811 | 812 | # 显示日志文件位置 813 | echo 814 | log_info "详细日志已保存到: $LOG_FILE" 815 | echo "如遇问题请将此日志文件提供给开发者以协助排查" 816 | echo 817 | } 818 | 819 | # 执行主函数 820 | main 821 | -------------------------------------------------------------------------------- /scripts/run/cursor_mac_free_trial_reset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 设置错误处理 4 | set -e 5 | 6 | # 定义日志文件路径 7 | LOG_FILE="/tmp/cursor_free_trial_reset.log" 8 | 9 | # 初始化日志文件 10 | initialize_log() { 11 | echo "========== Cursor Free Trial Reset Tool Log Start $(date) ==========" > "$LOG_FILE" 12 | chmod 644 "$LOG_FILE" 13 | } 14 | 15 | # 颜色定义 16 | RED='\033[0;31m' 17 | GREEN='\033[0;32m' 18 | YELLOW='\033[1;33m' 19 | BLUE='\033[0;34m' 20 | NC='\033[0m' # No Color 21 | 22 | # 日志函数 - 同时输出到终端和日志文件 23 | log_info() { 24 | echo -e "${GREEN}[INFO]${NC} $1" 25 | echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" 26 | } 27 | 28 | log_warn() { 29 | echo -e "${YELLOW}[WARN]${NC} $1" 30 | echo "[WARN] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" 31 | } 32 | 33 | log_error() { 34 | echo -e "${RED}[ERROR]${NC} $1" 35 | echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" 36 | } 37 | 38 | log_debug() { 39 | echo -e "${BLUE}[DEBUG]${NC} $1" 40 | echo "[DEBUG] $(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" 41 | } 42 | 43 | # 记录命令输出到日志文件 44 | log_cmd_output() { 45 | local cmd="$1" 46 | local msg="$2" 47 | echo "[CMD] $(date '+%Y-%m-%d %H:%M:%S') 执行命令: $cmd" >> "$LOG_FILE" 48 | echo "[CMD] $msg:" >> "$LOG_FILE" 49 | eval "$cmd" 2>&1 | tee -a "$LOG_FILE" 50 | echo "" >> "$LOG_FILE" 51 | } 52 | 53 | # 获取当前用户 54 | get_current_user() { 55 | if [ "$EUID" -eq 0 ]; then 56 | echo "$SUDO_USER" 57 | else 58 | echo "$USER" 59 | fi 60 | } 61 | 62 | CURRENT_USER=$(get_current_user) 63 | if [ -z "$CURRENT_USER" ]; then 64 | log_error "无法获取用户名" 65 | exit 1 66 | fi 67 | 68 | # 定义配置文件路径 69 | STORAGE_FILE="$HOME/Library/Application Support/Cursor/User/globalStorage/storage.json" 70 | BACKUP_DIR="$HOME/Library/Application Support/Cursor/User/globalStorage/backups" 71 | 72 | # 定义 Cursor 应用程序路径 73 | CURSOR_APP_PATH="/Applications/Cursor.app" 74 | 75 | # 检查权限 76 | check_permissions() { 77 | if [ "$EUID" -ne 0 ]; then 78 | log_error "请使用 sudo 运行此脚本" 79 | echo "示例: sudo $0" 80 | exit 1 81 | fi 82 | } 83 | 84 | # 检查并关闭 Cursor 进程 85 | check_and_kill_cursor() { 86 | log_info "检查 Cursor 进程..." 87 | 88 | local attempt=1 89 | local max_attempts=5 90 | 91 | # 函数:获取进程详细信息 92 | get_process_details() { 93 | local process_name="$1" 94 | log_debug "正在获取 $process_name 进程详细信息:" 95 | ps aux | grep -i "/Applications/Cursor.app" | grep -v grep 96 | } 97 | 98 | while [ $attempt -le $max_attempts ]; do 99 | # 使用更精确的匹配来获取 Cursor 进程 100 | CURSOR_PIDS=$(ps aux | grep -i "/Applications/Cursor.app" | grep -v grep | awk '{print $2}') 101 | 102 | if [ -z "$CURSOR_PIDS" ]; then 103 | log_info "未发现运行中的 Cursor 进程" 104 | return 0 105 | fi 106 | 107 | log_warn "发现 Cursor 进程正在运行" 108 | get_process_details "cursor" 109 | 110 | log_warn "尝试关闭 Cursor 进程..." 111 | 112 | if [ $attempt -eq $max_attempts ]; then 113 | log_warn "尝试强制终止进程..." 114 | kill -9 $CURSOR_PIDS 2>/dev/null || true 115 | else 116 | kill $CURSOR_PIDS 2>/dev/null || true 117 | fi 118 | 119 | sleep 1 120 | 121 | # 同样使用更精确的匹配来检查进程是否还在运行 122 | if ! ps aux | grep -i "/Applications/Cursor.app" | grep -v grep > /dev/null; then 123 | log_info "Cursor 进程已成功关闭" 124 | return 0 125 | fi 126 | 127 | log_warn "等待进程关闭,尝试 $attempt/$max_attempts..." 128 | ((attempt++)) 129 | done 130 | 131 | log_error "在 $max_attempts 次尝试后仍无法关闭 Cursor 进程" 132 | get_process_details "cursor" 133 | log_error "请手动关闭进程后重试" 134 | exit 1 135 | } 136 | 137 | # 备份配置文件 138 | backup_config() { 139 | if [ ! -f "$STORAGE_FILE" ]; then 140 | log_warn "配置文件不存在,跳过备份" 141 | return 0 142 | fi 143 | 144 | mkdir -p "$BACKUP_DIR" 145 | local backup_file="$BACKUP_DIR/storage.json.backup_$(date +%Y%m%d_%H%M%S)" 146 | 147 | if cp "$STORAGE_FILE" "$backup_file"; then 148 | chmod 644 "$backup_file" 149 | chown "$CURRENT_USER" "$backup_file" 150 | log_info "配置已备份到: $backup_file" 151 | else 152 | log_error "备份失败" 153 | exit 1 154 | fi 155 | } 156 | 157 | # 生成随机 ID 158 | generate_random_id() { 159 | # 生成32字节(64个十六进制字符)的随机数 160 | openssl rand -hex 32 161 | } 162 | 163 | # 生成随机 UUID 164 | generate_uuid() { 165 | uuidgen | tr '[:upper:]' '[:lower:]' 166 | } 167 | 168 | # 修改现有文件 169 | modify_or_add_config() { 170 | local key="$1" 171 | local value="$2" 172 | local file="$3" 173 | 174 | if [ ! -f "$file" ]; then 175 | log_error "文件不存在: $file" 176 | return 1 177 | fi 178 | 179 | # 确保文件可写 180 | chmod 644 "$file" || { 181 | log_error "无法修改文件权限: $file" 182 | return 1 183 | } 184 | 185 | # 创建临时文件 186 | local temp_file=$(mktemp) 187 | 188 | # 检查key是否存在 189 | if grep -q "\"$key\":" "$file"; then 190 | # key存在,执行替换 191 | sed "s/\"$key\":[[:space:]]*\"[^\"]*\"/\"$key\": \"$value\"/" "$file" > "$temp_file" || { 192 | log_error "修改配置失败: $key" 193 | rm -f "$temp_file" 194 | return 1 195 | } 196 | else 197 | # key不存在,添加新的key-value对 198 | sed "s/}$/,\n \"$key\": \"$value\"\n}/" "$file" > "$temp_file" || { 199 | log_error "添加配置失败: $key" 200 | rm -f "$temp_file" 201 | return 1 202 | } 203 | fi 204 | 205 | # 检查临时文件是否为空 206 | if [ ! -s "$temp_file" ]; then 207 | log_error "生成的临时文件为空" 208 | rm -f "$temp_file" 209 | return 1 210 | fi 211 | 212 | # 使用 cat 替换原文件内容 213 | cat "$temp_file" > "$file" || { 214 | log_error "无法写入文件: $file" 215 | rm -f "$temp_file" 216 | return 1 217 | } 218 | 219 | rm -f "$temp_file" 220 | 221 | # 恢复文件权限 222 | chmod 444 "$file" 223 | 224 | return 0 225 | } 226 | 227 | # 生成新的配置 228 | generate_new_config() { 229 | echo 230 | log_warn "机器码处理" 231 | 232 | # 默认不重置机器码 233 | reset_choice=0 234 | 235 | # 记录日志以便调试 236 | echo "[INPUT_DEBUG] 机器码重置选项: 不重置 (默认)" >> "$LOG_FILE" 237 | 238 | # 处理 - 默认为不重置 239 | log_info "默认不重置机器码,将仅修改js文件" 240 | 241 | # 确保配置文件目录存在 242 | if [ -f "$STORAGE_FILE" ]; then 243 | log_info "发现已有配置文件: $STORAGE_FILE" 244 | 245 | # 备份现有配置(以防万一) 246 | backup_config 247 | else 248 | log_warn "未找到配置文件,这是正常的,脚本将跳过ID修改" 249 | fi 250 | 251 | echo 252 | log_info "配置处理完成" 253 | } 254 | 255 | # 清理 Cursor 之前的修改 256 | clean_cursor_app() { 257 | log_info "尝试清理 Cursor 之前的修改..." 258 | 259 | # 如果存在备份,直接恢复备份 260 | local latest_backup="" 261 | 262 | # 查找最新的备份 263 | latest_backup=$(find /tmp -name "Cursor.app.backup_*" -type d -print 2>/dev/null | sort -r | head -1) 264 | 265 | if [ -n "$latest_backup" ] && [ -d "$latest_backup" ]; then 266 | log_info "找到现有备份: $latest_backup" 267 | log_info "正在恢复原始版本..." 268 | 269 | # 停止 Cursor 进程 270 | check_and_kill_cursor 271 | 272 | # 恢复备份 273 | sudo rm -rf "$CURSOR_APP_PATH" 274 | sudo cp -R "$latest_backup" "$CURSOR_APP_PATH" 275 | sudo chown -R "$CURRENT_USER:staff" "$CURSOR_APP_PATH" 276 | sudo chmod -R 755 "$CURSOR_APP_PATH" 277 | 278 | log_info "已恢复原始版本" 279 | return 0 280 | else 281 | log_warn "未找到现有备份,尝试重新安装 Cursor..." 282 | echo "您可以从 https://cursor.sh 下载并重新安装 Cursor" 283 | echo "或者继续执行此脚本,将尝试修复现有安装" 284 | 285 | # 可以在这里添加重新下载和安装的逻辑 286 | return 1 287 | fi 288 | } 289 | 290 | # 修改 Cursor 主程序文件(安全模式) 291 | modify_cursor_app_files() { 292 | log_info "正在安全修改 Cursor 主程序文件..." 293 | log_info "详细日志将记录到: $LOG_FILE" 294 | 295 | # 先清理之前的修改 296 | clean_cursor_app 297 | 298 | # 验证应用是否存在 299 | if [ ! -d "$CURSOR_APP_PATH" ]; then 300 | log_error "未找到 Cursor.app,请确认安装路径: $CURSOR_APP_PATH" 301 | return 1 302 | fi 303 | 304 | # 定义目标文件 - 将extensionHostProcess.js放在最前面优先处理 305 | local target_files=( 306 | "${CURSOR_APP_PATH}/Contents/Resources/app/out/vs/workbench/api/node/extensionHostProcess.js" 307 | "${CURSOR_APP_PATH}/Contents/Resources/app/out/main.js" 308 | "${CURSOR_APP_PATH}/Contents/Resources/app/out/vs/code/node/cliProcessMain.js" 309 | ) 310 | 311 | # 检查文件是否存在并且是否已修改 312 | local need_modification=false 313 | local missing_files=false 314 | 315 | log_debug "检查目标文件..." 316 | for file in "${target_files[@]}"; do 317 | if [ ! -f "$file" ]; then 318 | log_warn "文件不存在: ${file/$CURSOR_APP_PATH\//}" 319 | echo "[FILE_CHECK] 文件不存在: $file" >> "$LOG_FILE" 320 | missing_files=true 321 | continue 322 | fi 323 | 324 | echo "[FILE_CHECK] 文件存在: $file ($(wc -c < "$file") 字节)" >> "$LOG_FILE" 325 | 326 | if ! grep -q "return crypto.randomUUID()" "$file" 2>/dev/null; then 327 | log_info "文件需要修改: ${file/$CURSOR_APP_PATH\//}" 328 | grep -n "IOPlatformUUID" "$file" | head -3 >> "$LOG_FILE" || echo "[FILE_CHECK] 未找到 IOPlatformUUID" >> "$LOG_FILE" 329 | need_modification=true 330 | break 331 | else 332 | log_info "文件已修改: ${file/$CURSOR_APP_PATH\//}" 333 | fi 334 | done 335 | 336 | # 如果所有文件都已修改或不存在,则退出 337 | if [ "$missing_files" = true ]; then 338 | log_error "部分目标文件不存在,请确认 Cursor 安装是否完整" 339 | return 1 340 | fi 341 | 342 | if [ "$need_modification" = false ]; then 343 | log_info "所有目标文件已经被修改过,无需重复操作" 344 | return 0 345 | fi 346 | 347 | # 创建临时工作目录 348 | local timestamp=$(date +%Y%m%d_%H%M%S) 349 | local temp_dir="/tmp/cursor_reset_${timestamp}" 350 | local temp_app="${temp_dir}/Cursor.app" 351 | local backup_app="/tmp/Cursor.app.backup_${timestamp}" 352 | 353 | log_debug "创建临时目录: $temp_dir" 354 | echo "[TEMP_DIR] 创建临时目录: $temp_dir" >> "$LOG_FILE" 355 | 356 | # 清理可能存在的旧临时目录 357 | if [ -d "$temp_dir" ]; then 358 | log_info "清理已存在的临时目录..." 359 | rm -rf "$temp_dir" 360 | fi 361 | 362 | # 创建新的临时目录 363 | mkdir -p "$temp_dir" || { 364 | log_error "无法创建临时目录: $temp_dir" 365 | echo "[ERROR] 无法创建临时目录: $temp_dir" >> "$LOG_FILE" 366 | return 1 367 | } 368 | 369 | # 备份原应用 370 | log_info "备份原应用..." 371 | echo "[BACKUP] 开始备份: $CURSOR_APP_PATH -> $backup_app" >> "$LOG_FILE" 372 | 373 | cp -R "$CURSOR_APP_PATH" "$backup_app" || { 374 | log_error "无法创建应用备份" 375 | echo "[ERROR] 备份失败: $CURSOR_APP_PATH -> $backup_app" >> "$LOG_FILE" 376 | rm -rf "$temp_dir" 377 | return 1 378 | } 379 | 380 | echo "[BACKUP] 备份完成" >> "$LOG_FILE" 381 | 382 | # 复制应用到临时目录 383 | log_info "创建临时工作副本..." 384 | echo "[COPY] 开始复制: $CURSOR_APP_PATH -> $temp_dir" >> "$LOG_FILE" 385 | 386 | cp -R "$CURSOR_APP_PATH" "$temp_dir" || { 387 | log_error "无法复制应用到临时目录" 388 | echo "[ERROR] 复制失败: $CURSOR_APP_PATH -> $temp_dir" >> "$LOG_FILE" 389 | rm -rf "$temp_dir" "$backup_app" 390 | return 1 391 | } 392 | 393 | echo "[COPY] 复制完成" >> "$LOG_FILE" 394 | 395 | # 确保临时目录的权限正确 396 | chown -R "$CURRENT_USER:staff" "$temp_dir" 397 | chmod -R 755 "$temp_dir" 398 | 399 | # 移除签名(增强兼容性) 400 | log_info "移除应用签名..." 401 | echo "[CODESIGN] 移除签名: $temp_app" >> "$LOG_FILE" 402 | 403 | codesign --remove-signature "$temp_app" 2>> "$LOG_FILE" || { 404 | log_warn "移除应用签名失败" 405 | echo "[WARN] 移除签名失败: $temp_app" >> "$LOG_FILE" 406 | } 407 | 408 | # 移除所有相关组件的签名 409 | local components=( 410 | "$temp_app/Contents/Frameworks/Cursor Helper.app" 411 | "$temp_app/Contents/Frameworks/Cursor Helper (GPU).app" 412 | "$temp_app/Contents/Frameworks/Cursor Helper (Plugin).app" 413 | "$temp_app/Contents/Frameworks/Cursor Helper (Renderer).app" 414 | ) 415 | 416 | for component in "${components[@]}"; do 417 | if [ -e "$component" ]; then 418 | log_info "正在移除签名: $component" 419 | codesign --remove-signature "$component" || { 420 | log_warn "移除组件签名失败: $component" 421 | } 422 | fi 423 | done 424 | 425 | # 修改目标文件 - 优先处理js文件 426 | local modified_count=0 427 | local files=( 428 | "${temp_app}/Contents/Resources/app/out/vs/workbench/api/node/extensionHostProcess.js" 429 | "${temp_app}/Contents/Resources/app/out/main.js" 430 | "${temp_app}/Contents/Resources/app/out/vs/code/node/cliProcessMain.js" 431 | ) 432 | 433 | for file in "${files[@]}"; do 434 | if [ ! -f "$file" ]; then 435 | log_warn "文件不存在: ${file/$temp_dir\//}" 436 | continue 437 | fi 438 | 439 | log_debug "处理文件: ${file/$temp_dir\//}" 440 | echo "[PROCESS] 开始处理文件: $file" >> "$LOG_FILE" 441 | echo "[PROCESS] 文件大小: $(wc -c < "$file") 字节" >> "$LOG_FILE" 442 | 443 | # 输出文件部分内容到日志 444 | echo "[FILE_CONTENT] 文件头部 100 行:" >> "$LOG_FILE" 445 | head -100 "$file" 2>/dev/null | grep -v "^$" | head -50 >> "$LOG_FILE" 446 | echo "[FILE_CONTENT] ..." >> "$LOG_FILE" 447 | 448 | # 创建文件备份 449 | cp "$file" "${file}.bak" || { 450 | log_error "无法创建文件备份: ${file/$temp_dir\//}" 451 | echo "[ERROR] 无法创建文件备份: $file" >> "$LOG_FILE" 452 | continue 453 | } 454 | 455 | # 使用 sed 替换而不是字符串操作 456 | if [[ "$file" == *"extensionHostProcess.js"* ]]; then 457 | log_debug "处理 extensionHostProcess.js 文件..." 458 | echo "[PROCESS_DETAIL] 开始处理 extensionHostProcess.js 文件" >> "$LOG_FILE" 459 | 460 | # 检查是否包含目标代码 461 | if grep -q 'i.header.set("x-cursor-checksum' "$file"; then 462 | log_debug "找到 x-cursor-checksum 设置代码" 463 | echo "[FOUND] 找到 x-cursor-checksum 设置代码" >> "$LOG_FILE" 464 | 465 | # 记录匹配的行到日志 466 | grep -n 'i.header.set("x-cursor-checksum' "$file" >> "$LOG_FILE" 467 | 468 | # 执行特定的替换 469 | if sed -i.tmp 's/i\.header\.set("x-cursor-checksum",e===void 0?`${p}${t}`:`${p}${t}\/${e}`)/i.header.set("x-cursor-checksum",e===void 0?`${p}${t}`:`${p}${t}\/${p}`)/' "$file"; then 470 | log_info "成功修改 x-cursor-checksum 设置代码" 471 | echo "[SUCCESS] 成功完成 x-cursor-checksum 设置代码替换" >> "$LOG_FILE" 472 | # 记录修改后的行 473 | grep -n 'i.header.set("x-cursor-checksum' "$file" >> "$LOG_FILE" 474 | ((modified_count++)) 475 | log_info "成功修改文件: ${file/$temp_dir\//}" 476 | else 477 | log_error "修改 x-cursor-checksum 设置代码失败" 478 | echo "[ERROR] 替换 x-cursor-checksum 设置代码失败" >> "$LOG_FILE" 479 | cp "${file}.bak" "$file" 480 | fi 481 | else 482 | log_warn "未找到 x-cursor-checksum 设置代码" 483 | echo "[FILE_CHECK] 未找到 x-cursor-checksum 设置代码" >> "$LOG_FILE" 484 | 485 | # 记录文件部分内容到日志以便排查 486 | echo "[FILE_CONTENT] 文件中包含 'header.set' 的行:" >> "$LOG_FILE" 487 | grep -n "header.set" "$file" | head -20 >> "$LOG_FILE" 488 | 489 | echo "[FILE_CONTENT] 文件中包含 'checksum' 的行:" >> "$LOG_FILE" 490 | grep -n "checksum" "$file" | head -20 >> "$LOG_FILE" 491 | fi 492 | 493 | echo "[PROCESS_DETAIL] 完成处理 extensionHostProcess.js 文件" >> "$LOG_FILE" 494 | elif grep -q "IOPlatformUUID" "$file"; then 495 | log_debug "找到 IOPlatformUUID 关键字" 496 | echo "[FOUND] 找到 IOPlatformUUID 关键字" >> "$LOG_FILE" 497 | grep -n "IOPlatformUUID" "$file" | head -5 >> "$LOG_FILE" 498 | 499 | # 定位 IOPlatformUUID 相关函数 500 | if grep -q "function a\$" "$file"; then 501 | # 检查是否已经修改过 502 | if grep -q "return crypto.randomUUID()" "$file"; then 503 | log_info "文件已经包含 randomUUID 调用,跳过修改" 504 | ((modified_count++)) 505 | continue 506 | fi 507 | 508 | # 针对 main.js 中发现的代码结构进行修改 509 | if sed -i.tmp 's/function a\$(t){switch/function a\$(t){return crypto.randomUUID(); switch/' "$file"; then 510 | log_debug "成功注入 randomUUID 调用到 a\$ 函数" 511 | ((modified_count++)) 512 | log_info "成功修改文件: ${file/$temp_dir\//}" 513 | else 514 | log_error "修改 a\$ 函数失败" 515 | cp "${file}.bak" "$file" 516 | fi 517 | elif grep -q "async function v5" "$file"; then 518 | # 检查是否已经修改过 519 | if grep -q "return crypto.randomUUID()" "$file"; then 520 | log_info "文件已经包含 randomUUID 调用,跳过修改" 521 | ((modified_count++)) 522 | continue 523 | fi 524 | 525 | # 替代方法 - 修改 v5 函数 526 | if sed -i.tmp 's/async function v5(t){let e=/async function v5(t){return crypto.randomUUID(); let e=/' "$file"; then 527 | log_debug "成功注入 randomUUID 调用到 v5 函数" 528 | ((modified_count++)) 529 | log_info "成功修改文件: ${file/$temp_dir\//}" 530 | else 531 | log_error "修改 v5 函数失败" 532 | cp "${file}.bak" "$file" 533 | fi 534 | else 535 | # 检查是否已经注入了自定义代码 536 | if grep -q "// Cursor ID 修改工具注入" "$file"; then 537 | log_info "文件已经包含自定义注入代码,跳过修改" 538 | ((modified_count++)) 539 | continue 540 | fi 541 | 542 | # 使用更通用的注入方法 543 | log_warn "未找到具体函数,尝试使用通用修改方法" 544 | inject_code=" 545 | // Cursor ID 修改工具注入 - $(date +%Y%m%d%H%M%S) 546 | // 随机设备ID生成器注入 - $(date +%s) 547 | const randomDeviceId_$(date +%s) = () => { 548 | try { 549 | return require('crypto').randomUUID(); 550 | } catch (e) { 551 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { 552 | const r = Math.random() * 16 | 0; 553 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 554 | }); 555 | } 556 | }; 557 | " 558 | # 将代码注入到文件开头 559 | echo "$inject_code" > "${file}.new" 560 | cat "$file" >> "${file}.new" 561 | mv "${file}.new" "$file" 562 | 563 | # 替换调用点 564 | sed -i.tmp 's/await v5(!1)/randomDeviceId_'"$(date +%s)"'()/g' "$file" 565 | sed -i.tmp 's/a\$(t)/randomDeviceId_'"$(date +%s)"'()/g' "$file" 566 | 567 | log_debug "完成通用修改" 568 | ((modified_count++)) 569 | log_info "使用通用方法成功修改文件: ${file/$temp_dir\//}" 570 | fi 571 | else 572 | # 未找到 IOPlatformUUID,可能是文件结构变化 573 | log_warn "未找到 IOPlatformUUID,尝试替代方法" 574 | 575 | # 检查是否已经注入或修改过 576 | if grep -q "return crypto.randomUUID()" "$file" || grep -q "// Cursor ID 修改工具注入" "$file"; then 577 | log_info "文件已经被修改过,跳过修改" 578 | ((modified_count++)) 579 | continue 580 | fi 581 | 582 | # 尝试找其他关键函数如 getMachineId 或 getDeviceId 583 | if grep -q "function t\$()" "$file" || grep -q "async function y5" "$file"; then 584 | log_debug "找到设备ID相关函数" 585 | 586 | # 修改 MAC 地址获取函数 587 | if grep -q "function t\$()" "$file"; then 588 | sed -i.tmp 's/function t\$(){/function t\$(){return "00:00:00:00:00:00";/' "$file" 589 | log_debug "修改 MAC 地址获取函数成功" 590 | fi 591 | 592 | # 修改设备ID获取函数 593 | if grep -q "async function y5" "$file"; then 594 | sed -i.tmp 's/async function y5(t){/async function y5(t){return crypto.randomUUID();/' "$file" 595 | log_debug "修改设备ID获取函数成功" 596 | fi 597 | 598 | ((modified_count++)) 599 | log_info "使用替代方法成功修改文件: ${file/$temp_dir\//}" 600 | else 601 | # 最后尝试的通用方法 - 在文件顶部插入重写函数定义 602 | log_warn "未找到任何已知函数,使用最通用的方法" 603 | 604 | inject_universal_code=" 605 | // Cursor ID 修改工具注入 - $(date +%Y%m%d%H%M%S) 606 | // 全局拦截设备标识符 - $(date +%s) 607 | const originalRequire_$(date +%s) = require; 608 | require = function(module) { 609 | const result = originalRequire_$(date +%s)(module); 610 | if (module === 'crypto' && result.randomUUID) { 611 | const originalRandomUUID_$(date +%s) = result.randomUUID; 612 | result.randomUUID = function() { 613 | return '${new_uuid}'; 614 | }; 615 | } 616 | return result; 617 | }; 618 | 619 | // 覆盖所有可能的系统ID获取函数 620 | global.getMachineId = function() { return '${machine_id}'; }; 621 | global.getDeviceId = function() { return '${device_id}'; }; 622 | global.macMachineId = '${mac_machine_id}'; 623 | " 624 | # 将代码注入到文件开头 625 | local new_uuid=$(uuidgen | tr '[:upper:]' '[:lower:]') 626 | local machine_id="auth0|user_$(openssl rand -hex 16)" 627 | local device_id=$(uuidgen | tr '[:upper:]' '[:lower:]') 628 | local mac_machine_id=$(openssl rand -hex 32) 629 | 630 | inject_universal_code=${inject_universal_code//\$\{new_uuid\}/$new_uuid} 631 | inject_universal_code=${inject_universal_code//\$\{machine_id\}/$machine_id} 632 | inject_universal_code=${inject_universal_code//\$\{device_id\}/$device_id} 633 | inject_universal_code=${inject_universal_code//\$\{mac_machine_id\}/$mac_machine_id} 634 | 635 | echo "$inject_universal_code" > "${file}.new" 636 | cat "$file" >> "${file}.new" 637 | mv "${file}.new" "$file" 638 | 639 | log_debug "完成通用覆盖" 640 | ((modified_count++)) 641 | log_info "使用最通用方法成功修改文件: ${file/$temp_dir\//}" 642 | fi 643 | fi 644 | 645 | # 添加在关键操作后记录日志 646 | echo "[MODIFIED] 文件修改后内容:" >> "$LOG_FILE" 647 | grep -n "return crypto.randomUUID()" "$file" | head -3 >> "$LOG_FILE" 648 | 649 | # 清理临时文件 650 | rm -f "${file}.tmp" "${file}.bak" 651 | echo "[PROCESS] 文件处理完成: $file" >> "$LOG_FILE" 652 | done 653 | 654 | if [ "$modified_count" -eq 0 ]; then 655 | log_error "未能成功修改任何文件" 656 | rm -rf "$temp_dir" 657 | return 1 658 | fi 659 | 660 | # 重新签名应用(增加重试机制) 661 | local max_retry=3 662 | local retry_count=0 663 | local sign_success=false 664 | 665 | while [ $retry_count -lt $max_retry ]; do 666 | ((retry_count++)) 667 | log_info "尝试签名 (第 $retry_count 次)..." 668 | 669 | # 使用更详细的签名参数 670 | if codesign --sign - --force --deep --preserve-metadata=entitlements,identifier,flags "$temp_app" 2>&1 | tee /tmp/codesign.log; then 671 | # 验证签名 672 | if codesign --verify -vvvv "$temp_app" 2>/dev/null; then 673 | sign_success=true 674 | log_info "应用签名验证通过" 675 | break 676 | else 677 | log_warn "签名验证失败,错误日志:" 678 | cat /tmp/codesign.log 679 | fi 680 | else 681 | log_warn "签名失败,错误日志:" 682 | cat /tmp/codesign.log 683 | fi 684 | 685 | sleep 1 686 | done 687 | 688 | if ! $sign_success; then 689 | log_error "经过 $max_retry 次尝试仍无法完成签名" 690 | log_error "请手动执行以下命令完成签名:" 691 | echo -e "${BLUE}sudo codesign --sign - --force --deep '${temp_app}'${NC}" 692 | echo -e "${YELLOW}操作完成后,请手动将应用复制到原路径:${NC}" 693 | echo -e "${BLUE}sudo cp -R '${temp_app}' '/Applications/'${NC}" 694 | log_info "临时文件保留在:${temp_dir}" 695 | return 1 696 | fi 697 | 698 | # 替换原应用 699 | log_info "安装修改版应用..." 700 | if ! sudo rm -rf "$CURSOR_APP_PATH" || ! sudo cp -R "$temp_app" "/Applications/"; then 701 | log_error "应用替换失败,正在恢复..." 702 | sudo rm -rf "$CURSOR_APP_PATH" 703 | sudo cp -R "$backup_app" "$CURSOR_APP_PATH" 704 | rm -rf "$temp_dir" "$backup_app" 705 | return 1 706 | fi 707 | 708 | # 清理临时文件 709 | rm -rf "$temp_dir" "$backup_app" 710 | 711 | # 设置权限 712 | sudo chown -R "$CURRENT_USER:staff" "$CURSOR_APP_PATH" 713 | sudo chmod -R 755 "$CURSOR_APP_PATH" 714 | 715 | log_info "Cursor 主程序文件修改完成!原版备份在: ${backup_app/$HOME/\~}" 716 | return 0 717 | } 718 | 719 | # 显示文件树结构 720 | show_file_tree() { 721 | local base_dir=$(dirname "$STORAGE_FILE") 722 | echo 723 | log_info "文件结构:" 724 | echo -e "${BLUE}$base_dir${NC}" 725 | echo "├── globalStorage" 726 | echo "│ ├── storage.json (已修改)" 727 | echo "│ └── backups" 728 | 729 | # 列出备份文件 730 | if [ -d "$BACKUP_DIR" ]; then 731 | local backup_files=("$BACKUP_DIR"/*) 732 | if [ ${#backup_files[@]} -gt 0 ]; then 733 | for file in "${backup_files[@]}"; do 734 | if [ -f "$file" ]; then 735 | echo "│ └── $(basename "$file")" 736 | fi 737 | done 738 | else 739 | echo "│ └── (空)" 740 | fi 741 | fi 742 | echo 743 | } 744 | 745 | # 显示公众号信息 746 | show_follow_info() { 747 | echo 748 | echo -e "${GREEN}================================${NC}" 749 | echo -e "${YELLOW} 关注公众号【煎饼果子卷AI】一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) ${NC}" 750 | echo -e "${GREEN}================================${NC}" 751 | echo 752 | } 753 | 754 | # 禁用自动更新 755 | disable_auto_update() { 756 | local updater_path="$HOME/Library/Application Support/Caches/cursor-updater" 757 | local app_update_yml="/Applications/Cursor.app/Contents/Resources/app-update.yml" 758 | 759 | echo 760 | log_info "正在禁用 Cursor 自动更新..." 761 | 762 | # 备份并清空 app-update.yml 763 | if [ -f "$app_update_yml" ]; then 764 | log_info "备份并修改 app-update.yml..." 765 | if ! sudo cp "$app_update_yml" "${app_update_yml}.bak" 2>/dev/null; then 766 | log_warn "备份 app-update.yml 失败,继续执行..." 767 | fi 768 | 769 | if sudo bash -c "echo '' > \"$app_update_yml\"" && \ 770 | sudo chmod 444 "$app_update_yml"; then 771 | log_info "成功禁用 app-update.yml" 772 | else 773 | log_error "修改 app-update.yml 失败,请手动执行以下命令:" 774 | echo -e "${BLUE}sudo cp \"$app_update_yml\" \"${app_update_yml}.bak\"${NC}" 775 | echo -e "${BLUE}sudo bash -c 'echo \"\" > \"$app_update_yml\"'${NC}" 776 | echo -e "${BLUE}sudo chmod 444 \"$app_update_yml\"${NC}" 777 | fi 778 | else 779 | log_warn "未找到 app-update.yml 文件" 780 | fi 781 | 782 | # 同时也处理 cursor-updater 783 | log_info "处理 cursor-updater..." 784 | if sudo rm -rf "$updater_path" && \ 785 | sudo touch "$updater_path" && \ 786 | sudo chmod 444 "$updater_path"; then 787 | log_info "成功禁用 cursor-updater" 788 | else 789 | log_error "禁用 cursor-updater 失败,请手动执行以下命令:" 790 | echo -e "${BLUE}sudo rm -rf \"$updater_path\" && sudo touch \"$updater_path\" && sudo chmod 444 \"$updater_path\"${NC}" 791 | fi 792 | 793 | echo 794 | log_info "验证方法:" 795 | echo "1. 运行命令:ls -l \"$updater_path\"" 796 | echo " 确认文件权限显示为:r--r--r--" 797 | echo "2. 运行命令:ls -l \"$app_update_yml\"" 798 | echo " 确认文件权限显示为:r--r--r--" 799 | echo 800 | log_info "完成后请重启 Cursor" 801 | } 802 | 803 | # 新增恢复功能选项 804 | restore_feature() { 805 | # 检查备份目录是否存在 806 | if [ ! -d "$BACKUP_DIR" ]; then 807 | log_warn "备份目录不存在" 808 | return 1 809 | fi 810 | 811 | # 使用 find 命令获取备份文件列表并存储到数组 812 | backup_files=() 813 | while IFS= read -r file; do 814 | [ -f "$file" ] && backup_files+=("$file") 815 | done < <(find "$BACKUP_DIR" -name "*.backup_*" -type f 2>/dev/null | sort) 816 | 817 | # 检查是否找到备份文件 818 | if [ ${#backup_files[@]} -eq 0 ]; then 819 | log_warn "未找到任何备份文件" 820 | return 1 821 | fi 822 | 823 | echo 824 | log_info "可用的备份文件:" 825 | 826 | # 构建菜单选项字符串 827 | menu_options="退出 - 不恢复任何文件" 828 | for i in "${!backup_files[@]}"; do 829 | menu_options="$menu_options|$(basename "${backup_files[$i]}")" 830 | done 831 | 832 | # 使用菜单选择函数 833 | select_menu_option "请使用上下箭头选择要恢复的备份文件,按Enter确认:" "$menu_options" 0 834 | choice=$? 835 | 836 | # 处理用户输入 837 | if [ "$choice" = "0" ]; then 838 | log_info "跳过恢复操作" 839 | return 0 840 | fi 841 | 842 | # 获取选择的备份文件 (减1是因为第一个选项是"退出") 843 | local selected_backup="${backup_files[$((choice-1))]}" 844 | 845 | # 验证文件存在性和可读性 846 | if [ ! -f "$selected_backup" ] || [ ! -r "$selected_backup" ]; then 847 | log_error "无法访问选择的备份文件" 848 | return 1 849 | fi 850 | 851 | # 尝试恢复配置 852 | if cp "$selected_backup" "$STORAGE_FILE"; then 853 | chmod 644 "$STORAGE_FILE" 854 | chown "$CURRENT_USER" "$STORAGE_FILE" 855 | log_info "已从备份文件恢复配置: $(basename "$selected_backup")" 856 | return 0 857 | else 858 | log_error "恢复配置失败" 859 | return 1 860 | fi 861 | } 862 | 863 | # 解决"应用已损坏,无法打开"问题 864 | fix_damaged_app() { 865 | log_info "正在修复"应用已损坏"问题..." 866 | 867 | # 检查Cursor应用是否存在 868 | if [ ! -d "$CURSOR_APP_PATH" ]; then 869 | log_error "未找到Cursor应用: $CURSOR_APP_PATH" 870 | return 1 871 | fi 872 | 873 | log_info "尝试移除隔离属性..." 874 | if sudo xattr -rd com.apple.quarantine "$CURSOR_APP_PATH" 2>/dev/null; then 875 | log_info "成功移除隔离属性" 876 | else 877 | log_warn "移除隔离属性失败,尝试其他方法..." 878 | fi 879 | 880 | log_info "尝试重新签名应用..." 881 | if sudo codesign --force --deep --sign - "$CURSOR_APP_PATH" 2>/dev/null; then 882 | log_info "应用重新签名成功" 883 | else 884 | log_warn "应用重新签名失败" 885 | fi 886 | 887 | echo 888 | log_info "修复完成!请尝试重新打开Cursor应用" 889 | echo 890 | echo -e "${YELLOW}如果仍然无法打开,您可以尝试以下方法:${NC}" 891 | echo "1. 在系统偏好设置->安全性与隐私中,点击"仍要打开"按钮" 892 | echo "2. 暂时关闭Gatekeeper(不建议): sudo spctl --master-disable" 893 | echo "3. 重新下载安装Cursor应用" 894 | echo 895 | echo -e "${BLUE}参考链接: https://sysin.org/blog/macos-if-crashes-when-opening/${NC}" 896 | 897 | return 0 898 | } 899 | 900 | # 新增:通用菜单选择函数 901 | # 参数: 902 | # $1 - 提示信息 903 | # $2 - 选项数组,格式为 "选项1|选项2|选项3" 904 | # $3 - 默认选项索引 (从0开始) 905 | # 返回: 选中的选项索引 (从0开始) 906 | select_menu_option() { 907 | local prompt="$1" 908 | IFS='|' read -ra options <<< "$2" 909 | local default_index=${3:-0} 910 | local selected_index=$default_index 911 | local key_input 912 | local cursor_up='\033[A' 913 | local cursor_down='\033[B' 914 | local enter_key=$'\n' 915 | 916 | # 保存光标位置 917 | tput sc 918 | 919 | # 显示提示信息 920 | echo -e "$prompt" 921 | 922 | # 第一次显示菜单 923 | for i in "${!options[@]}"; do 924 | if [ $i -eq $selected_index ]; then 925 | echo -e " ${GREEN}►${NC} ${options[$i]}" 926 | else 927 | echo -e " ${options[$i]}" 928 | fi 929 | done 930 | 931 | # 循环处理键盘输入 932 | while true; do 933 | # 读取单个按键 934 | read -rsn3 key_input 935 | 936 | # 检测按键 937 | case "$key_input" in 938 | # 上箭头键 939 | $'\033[A') 940 | if [ $selected_index -gt 0 ]; then 941 | ((selected_index--)) 942 | fi 943 | ;; 944 | # 下箭头键 945 | $'\033[B') 946 | if [ $selected_index -lt $((${#options[@]}-1)) ]; then 947 | ((selected_index++)) 948 | fi 949 | ;; 950 | # Enter键 951 | "") 952 | echo # 换行 953 | log_info "您选择了: ${options[$selected_index]}" 954 | return $selected_index 955 | ;; 956 | esac 957 | 958 | # 恢复光标位置 959 | tput rc 960 | 961 | # 重新显示菜单 962 | for i in "${!options[@]}"; do 963 | if [ $i -eq $selected_index ]; then 964 | echo -e " ${GREEN}►${NC} ${options[$i]}" 965 | else 966 | echo -e " ${options[$i]}" 967 | fi 968 | done 969 | done 970 | } 971 | 972 | # 主函数 973 | main() { 974 | 975 | # 初始化日志文件 976 | initialize_log 977 | log_info "脚本启动..." 978 | 979 | # 记录系统信息 980 | log_info "系统信息: $(uname -a)" 981 | log_info "当前用户: $CURRENT_USER" 982 | log_cmd_output "sw_vers" "macOS 版本信息" 983 | log_cmd_output "which codesign" "codesign 路径" 984 | log_cmd_output "ls -la \"$CURSOR_APP_PATH\"" "Cursor 应用信息" 985 | 986 | # 新增环境检查 987 | if [[ $(uname) != "Darwin" ]]; then 988 | log_error "本脚本仅支持 macOS 系统" 989 | exit 1 990 | fi 991 | 992 | clear 993 | # 显示 Logo 994 | echo -e " 995 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ 996 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ 997 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ 998 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ 999 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ 1000 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ 1001 | " 1002 | echo -e "${BLUE}================================${NC}" 1003 | echo -e "${GREEN} Cursor 启动工具 ${NC}" 1004 | echo -e "${YELLOW} 关注公众号【煎饼果子卷AI】 ${NC}" 1005 | echo -e "${YELLOW} 一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) ${NC}" 1006 | echo -e "${BLUE}================================${NC}" 1007 | echo 1008 | echo -e "${YELLOW}[重要提示]${NC} 本工具优先修改js文件,更加安全可靠" 1009 | echo -e "${YELLOW}[重要提示]${NC} 本工具免费,如果对您有帮助,请关注公众号【煎饼果子卷AI】" 1010 | echo 1011 | 1012 | # 执行主要功能 1013 | check_permissions 1014 | check_and_kill_cursor 1015 | backup_config 1016 | 1017 | # 处理配置文件,默认不重置机器码 1018 | generate_new_config 1019 | 1020 | # 执行主程序文件修改 1021 | log_info "正在执行主程序文件修改..." 1022 | 1023 | # 使用子shell执行修改,避免错误导致整个脚本退出 1024 | ( 1025 | if modify_cursor_app_files; then 1026 | log_info "主程序文件修改成功!" 1027 | else 1028 | log_warn "主程序文件修改失败,但配置文件修改可能已成功" 1029 | log_warn "如果重启后 Cursor 仍然提示设备被禁用,请重新运行此脚本" 1030 | fi 1031 | ) 1032 | 1033 | # 恢复错误处理 1034 | set -e 1035 | 1036 | show_file_tree 1037 | show_follow_info 1038 | 1039 | # 直接执行禁用自动更新 1040 | disable_auto_update 1041 | 1042 | log_info "请重启 Cursor 以应用新的配置" 1043 | 1044 | # 显示最后的提示信息 1045 | show_follow_info 1046 | 1047 | # 提供修复选项(移到最后) 1048 | echo 1049 | log_warn "Cursor 修复选项" 1050 | 1051 | # 使用新的菜单选择函数 1052 | select_menu_option "请使用上下箭头选择,按Enter确认:" "忽略 - 不执行修复操作|修复模式 - 恢复原始的 Cursor 安装" 0 1053 | fix_choice=$? 1054 | 1055 | # 记录日志以便调试 1056 | echo "[INPUT_DEBUG] 修复选项选择: $fix_choice" >> "$LOG_FILE" 1057 | 1058 | # 确保脚本不会因为输入问题而终止 1059 | set +e 1060 | 1061 | # 处理用户选择 - 索引1对应"修复模式"选项 1062 | if [ "$fix_choice" = "1" ]; then 1063 | log_info "您选择了修复模式" 1064 | # 使用子shell执行清理,避免错误导致整个脚本退出 1065 | ( 1066 | if clean_cursor_app; then 1067 | log_info "Cursor 已恢复到原始状态" 1068 | log_info "如果您需要应用ID修改,请重新运行此脚本" 1069 | else 1070 | log_warn "未能找到备份,无法自动恢复" 1071 | log_warn "建议重新安装 Cursor" 1072 | fi 1073 | ) 1074 | else 1075 | log_info "已跳过修复操作" 1076 | fi 1077 | 1078 | # 恢复错误处理 1079 | set -e 1080 | 1081 | # 记录脚本完成信息 1082 | log_info "脚本执行完成" 1083 | echo "========== Cursor ID 修改工具日志结束 $(date) ==========" >> "$LOG_FILE" 1084 | 1085 | # 显示日志文件位置 1086 | echo 1087 | log_info "详细日志已保存到: $LOG_FILE" 1088 | echo "如遇问题请将此日志文件提供给开发者以协助排查" 1089 | echo 1090 | 1091 | # 添加修复"应用已损坏"选项 1092 | echo 1093 | log_warn "应用修复选项" 1094 | 1095 | # 使用新的菜单选择函数 1096 | select_menu_option "请使用上下箭头选择,按Enter确认:" "忽略 - 不执行修复操作|修复"应用已损坏"问题 - 解决macOS提示应用已损坏无法打开的问题" 0 1097 | damaged_choice=$? 1098 | 1099 | echo "[INPUT_DEBUG] 应用修复选项选择: $damaged_choice" >> "$LOG_FILE" 1100 | 1101 | set +e 1102 | 1103 | # 处理用户选择 - 索引1对应"修复应用已损坏"选项 1104 | if [ "$damaged_choice" = "1" ]; then 1105 | log_info "您选择了修复"应用已损坏"问题" 1106 | ( 1107 | if fix_damaged_app; then 1108 | log_info "修复"应用已损坏"问题完成" 1109 | else 1110 | log_warn "修复"应用已损坏"问题失败" 1111 | fi 1112 | ) 1113 | else 1114 | log_info "已跳过应用修复操作" 1115 | fi 1116 | 1117 | set -e 1118 | } 1119 | 1120 | # 执行主函数 1121 | main 1122 | 1123 | -------------------------------------------------------------------------------- /scripts/run/cursor_win_id_modifier.ps1: -------------------------------------------------------------------------------- 1 | # 设置输出编码为 UTF-8 2 | $OutputEncoding = [System.Text.Encoding]::UTF8 3 | [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 4 | 5 | # 颜色定义 6 | $RED = "`e[31m" 7 | $GREEN = "`e[32m" 8 | $YELLOW = "`e[33m" 9 | $BLUE = "`e[34m" 10 | $NC = "`e[0m" 11 | 12 | # 配置文件路径 13 | $STORAGE_FILE = "$env:APPDATA\Cursor\User\globalStorage\storage.json" 14 | $BACKUP_DIR = "$env:APPDATA\Cursor\User\globalStorage\backups" 15 | 16 | # 检查管理员权限 17 | function Test-Administrator { 18 | $user = [Security.Principal.WindowsIdentity]::GetCurrent() 19 | $principal = New-Object Security.Principal.WindowsPrincipal($user) 20 | return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 21 | } 22 | 23 | if (-not (Test-Administrator)) { 24 | Write-Host "$RED[错误]$NC 请以管理员身份运行此脚本" 25 | Write-Host "请右键点击脚本,选择'以管理员身份运行'" 26 | Read-Host "按回车键退出" 27 | exit 1 28 | } 29 | 30 | # 显示 Logo 31 | Clear-Host 32 | Write-Host @" 33 | 34 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ 35 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ 36 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ 37 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ 38 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ 39 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ 40 | 41 | "@ 42 | Write-Host "$BLUE================================$NC" 43 | Write-Host "$GREEN Cursor 设备ID 修改工具 $NC" 44 | Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】 $NC" 45 | Write-Host "$YELLOW 一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC" 46 | Write-Host "$YELLOW [重要提示] 本工具免费,如果对您有帮助,请关注公众号【煎饼果子卷AI】 $NC" 47 | Write-Host "$BLUE================================$NC" 48 | Write-Host "" 49 | 50 | # 获取并显示 Cursor 版本 51 | function Get-CursorVersion { 52 | try { 53 | # 主要检测路径 54 | $packagePath = "$env:LOCALAPPDATA\Programs\cursor\resources\app\package.json" 55 | 56 | if (Test-Path $packagePath) { 57 | $packageJson = Get-Content $packagePath -Raw | ConvertFrom-Json 58 | if ($packageJson.version) { 59 | Write-Host "$GREEN[信息]$NC 当前安装的 Cursor 版本: v$($packageJson.version)" 60 | return $packageJson.version 61 | } 62 | } 63 | 64 | # 备用路径检测 65 | $altPath = "$env:LOCALAPPDATA\cursor\resources\app\package.json" 66 | if (Test-Path $altPath) { 67 | $packageJson = Get-Content $altPath -Raw | ConvertFrom-Json 68 | if ($packageJson.version) { 69 | Write-Host "$GREEN[信息]$NC 当前安装的 Cursor 版本: v$($packageJson.version)" 70 | return $packageJson.version 71 | } 72 | } 73 | 74 | Write-Host "$YELLOW[警告]$NC 无法检测到 Cursor 版本" 75 | Write-Host "$YELLOW[提示]$NC 请确保 Cursor 已正确安装" 76 | return $null 77 | } 78 | catch { 79 | Write-Host "$RED[错误]$NC 获取 Cursor 版本失败: $_" 80 | return $null 81 | } 82 | } 83 | 84 | # 获取并显示版本信息 85 | $cursorVersion = Get-CursorVersion 86 | Write-Host "" 87 | 88 | Write-Host "$YELLOW[重要提示]$NC 最新的 0.49.x (以支持)" 89 | Write-Host "" 90 | 91 | # 检查并关闭 Cursor 进程 92 | Write-Host "$GREEN[信息]$NC 检查 Cursor 进程..." 93 | 94 | function Get-ProcessDetails { 95 | param($processName) 96 | Write-Host "$BLUE[调试]$NC 正在获取 $processName 进程详细信息:" 97 | Get-WmiObject Win32_Process -Filter "name='$processName'" | 98 | Select-Object ProcessId, ExecutablePath, CommandLine | 99 | Format-List 100 | } 101 | 102 | # 定义最大重试次数和等待时间 103 | $MAX_RETRIES = 5 104 | $WAIT_TIME = 1 105 | 106 | # 处理进程关闭 107 | function Close-CursorProcess { 108 | param($processName) 109 | 110 | $process = Get-Process -Name $processName -ErrorAction SilentlyContinue 111 | if ($process) { 112 | Write-Host "$YELLOW[警告]$NC 发现 $processName 正在运行" 113 | Get-ProcessDetails $processName 114 | 115 | Write-Host "$YELLOW[警告]$NC 尝试关闭 $processName..." 116 | Stop-Process -Name $processName -Force 117 | 118 | $retryCount = 0 119 | while ($retryCount -lt $MAX_RETRIES) { 120 | $process = Get-Process -Name $processName -ErrorAction SilentlyContinue 121 | if (-not $process) { break } 122 | 123 | $retryCount++ 124 | if ($retryCount -ge $MAX_RETRIES) { 125 | Write-Host "$RED[错误]$NC 在 $MAX_RETRIES 次尝试后仍无法关闭 $processName" 126 | Get-ProcessDetails $processName 127 | Write-Host "$RED[错误]$NC 请手动关闭进程后重试" 128 | Read-Host "按回车键退出" 129 | exit 1 130 | } 131 | Write-Host "$YELLOW[警告]$NC 等待进程关闭,尝试 $retryCount/$MAX_RETRIES..." 132 | Start-Sleep -Seconds $WAIT_TIME 133 | } 134 | Write-Host "$GREEN[信息]$NC $processName 已成功关闭" 135 | } 136 | } 137 | 138 | # 关闭所有 Cursor 进程 139 | Close-CursorProcess "Cursor" 140 | Close-CursorProcess "cursor" 141 | 142 | # 创建备份目录 143 | if (-not (Test-Path $BACKUP_DIR)) { 144 | New-Item -ItemType Directory -Path $BACKUP_DIR | Out-Null 145 | } 146 | 147 | # 备份现有配置 148 | if (Test-Path $STORAGE_FILE) { 149 | Write-Host "$GREEN[信息]$NC 正在备份配置文件..." 150 | $backupName = "storage.json.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" 151 | Copy-Item $STORAGE_FILE "$BACKUP_DIR\$backupName" 152 | } 153 | 154 | # 生成新的 ID 155 | Write-Host "$GREEN[信息]$NC 正在生成新的 ID..." 156 | 157 | # 在颜色定义后添加此函数 158 | function Get-RandomHex { 159 | param ( 160 | [int]$length 161 | ) 162 | 163 | $bytes = New-Object byte[] ($length) 164 | $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::new() 165 | $rng.GetBytes($bytes) 166 | $hexString = [System.BitConverter]::ToString($bytes) -replace '-','' 167 | $rng.Dispose() 168 | return $hexString 169 | } 170 | 171 | # 改进 ID 生成函数 172 | function New-StandardMachineId { 173 | $template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" 174 | $result = $template -replace '[xy]', { 175 | param($match) 176 | $r = [Random]::new().Next(16) 177 | $v = if ($match.Value -eq "x") { $r } else { ($r -band 0x3) -bor 0x8 } 178 | return $v.ToString("x") 179 | } 180 | return $result 181 | } 182 | 183 | # 在生成 ID 时使用新函数 184 | $MAC_MACHINE_ID = New-StandardMachineId 185 | $UUID = [System.Guid]::NewGuid().ToString() 186 | # 将 auth0|user_ 转换为字节数组的十六进制 187 | $prefixBytes = [System.Text.Encoding]::UTF8.GetBytes("auth0|user_") 188 | $prefixHex = -join ($prefixBytes | ForEach-Object { '{0:x2}' -f $_ }) 189 | # 生成32字节(64个十六进制字符)的随机数作为 machineId 的随机部分 190 | $randomPart = Get-RandomHex -length 32 191 | $MACHINE_ID = "$prefixHex$randomPart" 192 | $SQM_ID = "{$([System.Guid]::NewGuid().ToString().ToUpper())}" 193 | 194 | # 在Update-MachineGuid函数前添加权限检查 195 | if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { 196 | Write-Host "$RED[错误]$NC 请使用管理员权限运行此脚本" 197 | Start-Process powershell "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs 198 | exit 199 | } 200 | 201 | function Update-MachineGuid { 202 | try { 203 | # 检查注册表路径是否存在,不存在则创建 204 | $registryPath = "HKLM:\SOFTWARE\Microsoft\Cryptography" 205 | if (-not (Test-Path $registryPath)) { 206 | Write-Host "$YELLOW[警告]$NC 注册表路径不存在: $registryPath,正在创建..." 207 | New-Item -Path $registryPath -Force | Out-Null 208 | Write-Host "$GREEN[信息]$NC 注册表路径创建成功" 209 | } 210 | 211 | # 获取当前的 MachineGuid,如果不存在则使用空字符串作为默认值 212 | $originalGuid = "" 213 | try { 214 | $currentGuid = Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction SilentlyContinue 215 | if ($currentGuid) { 216 | $originalGuid = $currentGuid.MachineGuid 217 | Write-Host "$GREEN[信息]$NC 当前注册表值:" 218 | Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" 219 | Write-Host " MachineGuid REG_SZ $originalGuid" 220 | } else { 221 | Write-Host "$YELLOW[警告]$NC MachineGuid 值不存在,将创建新值" 222 | } 223 | } catch { 224 | Write-Host "$YELLOW[警告]$NC 获取 MachineGuid 失败: $($_.Exception.Message)" 225 | } 226 | 227 | # 创建备份目录(如果不存在) 228 | if (-not (Test-Path $BACKUP_DIR)) { 229 | New-Item -ItemType Directory -Path $BACKUP_DIR -Force | Out-Null 230 | } 231 | 232 | # 创建备份文件(仅当原始值存在时) 233 | if ($originalGuid) { 234 | $backupFile = "$BACKUP_DIR\MachineGuid_$(Get-Date -Format 'yyyyMMdd_HHmmss').reg" 235 | $backupResult = Start-Process "reg.exe" -ArgumentList "export", "`"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography`"", "`"$backupFile`"" -NoNewWindow -Wait -PassThru 236 | 237 | if ($backupResult.ExitCode -eq 0) { 238 | Write-Host "$GREEN[信息]$NC 注册表项已备份到:$backupFile" 239 | } else { 240 | Write-Host "$YELLOW[警告]$NC 备份创建失败,继续执行..." 241 | } 242 | } 243 | 244 | # 生成新GUID 245 | $newGuid = [System.Guid]::NewGuid().ToString() 246 | 247 | # 更新或创建注册表值 248 | Set-ItemProperty -Path $registryPath -Name MachineGuid -Value $newGuid -Force -ErrorAction Stop 249 | 250 | # 验证更新 251 | $verifyGuid = (Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction Stop).MachineGuid 252 | if ($verifyGuid -ne $newGuid) { 253 | throw "注册表验证失败:更新后的值 ($verifyGuid) 与预期值 ($newGuid) 不匹配" 254 | } 255 | 256 | Write-Host "$GREEN[信息]$NC 注册表更新成功:" 257 | Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" 258 | Write-Host " MachineGuid REG_SZ $newGuid" 259 | return $true 260 | } 261 | catch { 262 | Write-Host "$RED[错误]$NC 注册表操作失败:$($_.Exception.Message)" 263 | 264 | # 尝试恢复备份(如果存在) 265 | if (($backupFile -ne $null) -and (Test-Path $backupFile)) { 266 | Write-Host "$YELLOW[恢复]$NC 正在从备份恢复..." 267 | $restoreResult = Start-Process "reg.exe" -ArgumentList "import", "`"$backupFile`"" -NoNewWindow -Wait -PassThru 268 | 269 | if ($restoreResult.ExitCode -eq 0) { 270 | Write-Host "$GREEN[恢复成功]$NC 已还原原始注册表值" 271 | } else { 272 | Write-Host "$RED[错误]$NC 恢复失败,请手动导入备份文件:$backupFile" 273 | } 274 | } else { 275 | Write-Host "$YELLOW[警告]$NC 未找到备份文件或备份创建失败,无法自动恢复" 276 | } 277 | return $false 278 | } 279 | } 280 | 281 | # 创建或更新配置文件 282 | Write-Host "$GREEN[信息]$NC 正在更新配置..." 283 | 284 | try { 285 | # 检查配置文件是否存在 286 | if (-not (Test-Path $STORAGE_FILE)) { 287 | Write-Host "$RED[错误]$NC 未找到配置文件: $STORAGE_FILE" 288 | Write-Host "$YELLOW[提示]$NC 请先安装并运行一次 Cursor 后再使用此脚本" 289 | Read-Host "按回车键退出" 290 | exit 1 291 | } 292 | 293 | # 读取现有配置文件 294 | try { 295 | $originalContent = Get-Content $STORAGE_FILE -Raw -Encoding UTF8 296 | 297 | # 将 JSON 字符串转换为 PowerShell 对象 298 | $config = $originalContent | ConvertFrom-Json 299 | 300 | # 备份当前值 301 | $oldValues = @{ 302 | 'machineId' = $config.'telemetry.machineId' 303 | 'macMachineId' = $config.'telemetry.macMachineId' 304 | 'devDeviceId' = $config.'telemetry.devDeviceId' 305 | 'sqmId' = $config.'telemetry.sqmId' 306 | } 307 | 308 | # 更新特定的值 309 | $config.'telemetry.machineId' = $MACHINE_ID 310 | $config.'telemetry.macMachineId' = $MAC_MACHINE_ID 311 | $config.'telemetry.devDeviceId' = $UUID 312 | $config.'telemetry.sqmId' = $SQM_ID 313 | 314 | # 将更新后的对象转换回 JSON 并保存 315 | $updatedJson = $config | ConvertTo-Json -Depth 10 316 | [System.IO.File]::WriteAllText( 317 | [System.IO.Path]::GetFullPath($STORAGE_FILE), 318 | $updatedJson, 319 | [System.Text.Encoding]::UTF8 320 | ) 321 | Write-Host "$GREEN[信息]$NC 成功更新配置文件" 322 | } catch { 323 | # 如果出错,尝试恢复原始内容 324 | if ($originalContent) { 325 | [System.IO.File]::WriteAllText( 326 | [System.IO.Path]::GetFullPath($STORAGE_FILE), 327 | $originalContent, 328 | [System.Text.Encoding]::UTF8 329 | ) 330 | } 331 | throw "处理 JSON 失败: $_" 332 | } 333 | # 直接执行更新 MachineGuid,不再询问 334 | Update-MachineGuid 335 | # 显示结果 336 | Write-Host "" 337 | Write-Host "$GREEN[信息]$NC 已更新配置:" 338 | Write-Host "$BLUE[调试]$NC machineId: $MACHINE_ID" 339 | Write-Host "$BLUE[调试]$NC macMachineId: $MAC_MACHINE_ID" 340 | Write-Host "$BLUE[调试]$NC devDeviceId: $UUID" 341 | Write-Host "$BLUE[调试]$NC sqmId: $SQM_ID" 342 | 343 | # 显示文件树结构 344 | Write-Host "" 345 | Write-Host "$GREEN[信息]$NC 文件结构:" 346 | Write-Host "$BLUE$env:APPDATA\Cursor\User$NC" 347 | Write-Host "├── globalStorage" 348 | Write-Host "│ ├── storage.json (已修改)" 349 | Write-Host "│ └── backups" 350 | 351 | # 列出备份文件 352 | $backupFiles = Get-ChildItem "$BACKUP_DIR\*" -ErrorAction SilentlyContinue 353 | if ($backupFiles) { 354 | foreach ($file in $backupFiles) { 355 | Write-Host "│ └── $($file.Name)" 356 | } 357 | } else { 358 | Write-Host "│ └── (空)" 359 | } 360 | 361 | # 显示公众号信息 362 | Write-Host "" 363 | Write-Host "$GREEN================================$NC" 364 | Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC" 365 | Write-Host "$GREEN================================$NC" 366 | Write-Host "" 367 | Write-Host "$GREEN[信息]$NC 请重启 Cursor 以应用新的配置" 368 | Write-Host "" 369 | 370 | # 询问是否要禁用自动更新 371 | Write-Host "" 372 | Write-Host "$YELLOW[询问]$NC 是否要禁用 Cursor 自动更新功能?" 373 | Write-Host "0) 否 - 保持默认设置 (按回车键)" 374 | Write-Host "1) 是 - 禁用自动更新" 375 | $choice = Read-Host "请输入选项 (0)" 376 | 377 | if ($choice -eq "1") { 378 | Write-Host "" 379 | Write-Host "$GREEN[信息]$NC 正在处理自动更新..." 380 | $updaterPath = "$env:LOCALAPPDATA\cursor-updater" 381 | 382 | # 定义手动设置教程 383 | function Show-ManualGuide { 384 | Write-Host "" 385 | Write-Host "$YELLOW[警告]$NC 自动设置失败,请尝试手动操作:" 386 | Write-Host "$YELLOW手动禁用更新步骤:$NC" 387 | Write-Host "1. 以管理员身份打开 PowerShell" 388 | Write-Host "2. 复制粘贴以下命令:" 389 | Write-Host "$BLUE命令1 - 删除现有目录(如果存在):$NC" 390 | Write-Host "Remove-Item -Path `"$updaterPath`" -Force -Recurse -ErrorAction SilentlyContinue" 391 | Write-Host "" 392 | Write-Host "$BLUE命令2 - 创建阻止文件:$NC" 393 | Write-Host "New-Item -Path `"$updaterPath`" -ItemType File -Force | Out-Null" 394 | Write-Host "" 395 | Write-Host "$BLUE命令3 - 设置只读属性:$NC" 396 | Write-Host "Set-ItemProperty -Path `"$updaterPath`" -Name IsReadOnly -Value `$true" 397 | Write-Host "" 398 | Write-Host "$BLUE命令4 - 设置权限(可选):$NC" 399 | Write-Host "icacls `"$updaterPath`" /inheritance:r /grant:r `"`$($env:USERNAME):(R)`"" 400 | Write-Host "" 401 | Write-Host "$YELLOW验证方法:$NC" 402 | Write-Host "1. 运行命令:Get-ItemProperty `"$updaterPath`"" 403 | Write-Host "2. 确认 IsReadOnly 属性为 True" 404 | Write-Host "3. 运行命令:icacls `"$updaterPath`"" 405 | Write-Host "4. 确认只有读取权限" 406 | Write-Host "" 407 | Write-Host "$YELLOW[提示]$NC 完成后请重启 Cursor" 408 | } 409 | 410 | try { 411 | # 检查cursor-updater是否存在 412 | if (Test-Path $updaterPath) { 413 | # 如果是文件,说明已经创建了阻止更新 414 | if ((Get-Item $updaterPath) -is [System.IO.FileInfo]) { 415 | Write-Host "$GREEN[信息]$NC 已创建阻止更新文件,无需再次阻止" 416 | return 417 | } 418 | # 如果是目录,尝试删除 419 | else { 420 | try { 421 | Remove-Item -Path $updaterPath -Force -Recurse -ErrorAction Stop 422 | Write-Host "$GREEN[信息]$NC 成功删除 cursor-updater 目录" 423 | } 424 | catch { 425 | Write-Host "$RED[错误]$NC 删除 cursor-updater 目录失败" 426 | Show-ManualGuide 427 | return 428 | } 429 | } 430 | } 431 | 432 | # 创建阻止文件 433 | try { 434 | New-Item -Path $updaterPath -ItemType File -Force -ErrorAction Stop | Out-Null 435 | Write-Host "$GREEN[信息]$NC 成功创建阻止文件" 436 | } 437 | catch { 438 | Write-Host "$RED[错误]$NC 创建阻止文件失败" 439 | Show-ManualGuide 440 | return 441 | } 442 | 443 | # 设置文件权限 444 | try { 445 | # 设置只读属性 446 | Set-ItemProperty -Path $updaterPath -Name IsReadOnly -Value $true -ErrorAction Stop 447 | 448 | # 使用 icacls 设置权限 449 | $result = Start-Process "icacls.exe" -ArgumentList "`"$updaterPath`" /inheritance:r /grant:r `"$($env:USERNAME):(R)`"" -Wait -NoNewWindow -PassThru 450 | if ($result.ExitCode -ne 0) { 451 | throw "icacls 命令失败" 452 | } 453 | 454 | Write-Host "$GREEN[信息]$NC 成功设置文件权限" 455 | } 456 | catch { 457 | Write-Host "$RED[错误]$NC 设置文件权限失败" 458 | Show-ManualGuide 459 | return 460 | } 461 | 462 | # 验证设置 463 | try { 464 | $fileInfo = Get-ItemProperty $updaterPath 465 | if (-not $fileInfo.IsReadOnly) { 466 | Write-Host "$RED[错误]$NC 验证失败:文件权限设置可能未生效" 467 | Show-ManualGuide 468 | return 469 | } 470 | } 471 | catch { 472 | Write-Host "$RED[错误]$NC 验证设置失败" 473 | Show-ManualGuide 474 | return 475 | } 476 | 477 | Write-Host "$GREEN[信息]$NC 成功禁用自动更新" 478 | } 479 | catch { 480 | Write-Host "$RED[错误]$NC 发生未知错误: $_" 481 | Show-ManualGuide 482 | } 483 | } 484 | else { 485 | Write-Host "$GREEN[信息]$NC 保持默认设置,不进行更改" 486 | } 487 | 488 | # 保留有效的注册表更新 489 | Update-MachineGuid 490 | 491 | } catch { 492 | Write-Host "$RED[错误]$NC 主要操作失败: $_" 493 | Write-Host "$YELLOW[尝试]$NC 使用备选方法..." 494 | 495 | try { 496 | # 备选方法:使用 Add-Content 497 | $tempFile = [System.IO.Path]::GetTempFileName() 498 | $config | ConvertTo-Json | Set-Content -Path $tempFile -Encoding UTF8 499 | Copy-Item -Path $tempFile -Destination $STORAGE_FILE -Force 500 | Remove-Item -Path $tempFile 501 | Write-Host "$GREEN[信息]$NC 使用备选方法成功写入配置" 502 | } catch { 503 | Write-Host "$RED[错误]$NC 所有尝试都失败了" 504 | Write-Host "错误详情: $_" 505 | Write-Host "目标文件: $STORAGE_FILE" 506 | Write-Host "请确保您有足够的权限访问该文件" 507 | Read-Host "按回车键退出" 508 | exit 1 509 | } 510 | } 511 | 512 | Write-Host "" 513 | Read-Host "按回车键退出" 514 | exit 0 515 | 516 | # 在文件写入部分修改 517 | function Write-ConfigFile { 518 | param($config, $filePath) 519 | 520 | try { 521 | # 使用 UTF8 无 BOM 编码 522 | $utf8NoBom = New-Object System.Text.UTF8Encoding $false 523 | $jsonContent = $config | ConvertTo-Json -Depth 10 524 | 525 | # 统一使用 LF 换行符 526 | $jsonContent = $jsonContent.Replace("`r`n", "`n") 527 | 528 | [System.IO.File]::WriteAllText( 529 | [System.IO.Path]::GetFullPath($filePath), 530 | $jsonContent, 531 | $utf8NoBom 532 | ) 533 | 534 | Write-Host "$GREEN[信息]$NC 成功写入配置文件(UTF8 无 BOM)" 535 | } 536 | catch { 537 | throw "写入配置文件失败: $_" 538 | } 539 | } 540 | 541 | # 获取并显示版本信息 542 | $cursorVersion = Get-CursorVersion 543 | Write-Host "" 544 | if ($cursorVersion) { 545 | Write-Host "$GREEN[信息]$NC 检测到 Cursor 版本: $cursorVersion,继续执行..." 546 | } else { 547 | Write-Host "$YELLOW[警告]$NC 无法检测版本,将继续执行..." 548 | } --------------------------------------------------------------------------------